学习目标
创建和编译Shader代码
通过OpenGL画图
利用这本书的应用程序架构初始化程序并自行clean up
准备工作(转自配置环境&第一个程序-CSDN博客)
下载安装CMake -> download -> Binary distributions -> Windows x64 Installer
下载sb7code,运行CMake编译(第三步Configure选择win32)


然后关闭Visual Studio,打开文件夹。
把sb7code-master\extern\glfw-3.0.4\build\src\Debug文件夹下的glfw3.lib复制到sb7code-master\lib,并改名为glfw3_d.lib
把sb7code-master\extern\glfw-3.0.4\build\src\Release文件夹下的glfw3.lib复制到sb7code-master\lib,这次不改名
再次运行CMake,这次是生成蓝宝书的示例程序。

成功打开项目后,编译整个工程(直接编译,不用改东西),Debug和Release都要。
这时准备工作已经做好了。
可以开始新建自己的工程了
打开Visual Studio,新建一个新解决方案,复制文件夹

新建项目,配置以下属性
C/C++附加包含目录 – $(SolutionDir)include
附加库目录 – $(SolutionDir)lib
附加依赖项 – sb7_d.lib glfw3_d.lib opengl32.lib glu32.lib,
预处理器 – WIN32 _WINDOWS,
系统-子系统 – 窗口 (/SUBSYSTEM:WINDOWS)
大功告成!复制书上代码至新建的源文件,成功!
第一个程序
// Include the "sb7.h" header file #include "sb7.h" // Derive my_application from sb7::application class my_application : public sb7::application { public: // Our rendering function void render(double currentTime) { // Simply clear the window with red static const GLfloat red[] = { 1.0f, 0.0f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, red); } }; // Our one and only instance of DECLARE_MAIN DECLARE_MAIN(my_application);
函数命名规则
所有OpenGL函数都以gl开头,并遵循许多命名约定,例如把参数类型加入尾缀,上述的glClearBufferfv中的f是float,v是vector,组合起来意思就是这个函数使用浮点型的向量。
glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat * value)
这个函数用于清除缓冲区为value值,buffer指定缓冲区,drawbuffer表示你要清除的缓冲区的索引。
第二个程序
void render(double currentTime) { const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f, (float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, color); }
使用Shader
如果想要在屏幕显示出像素,最少使用一个vertex shader和一个fragment shader
一个简单的vertex shader
#version 450 core void main(void) { gl_Position = vec4(0.0, 0.0, 0.5, 1.0); }
#version 450 core 表面我们使用4.5的core profile版本的OpenGL。
在顶点着色器中,gl_Position表示顶点的输出位置。
一个简单的fragment shader
#version 450 core out vec4 color; void main(void) { color = vec4(0.0, 0.8, 1.0, 1.0); }
out vec4 color中out关键字声明输出变量,输出的变量将被输出到窗口或屏幕上
编译链接shader
GLuint compile_shaders(void) { GLuint vertex_shader; GLuint fragment_shader; GLuint program; // Source code for vertex shader static const GLchar* vertex_shader_source[] = { "#version 450 core \n" " \n" "void main(void) \n" "{ \n" " gl_Position = vec4(0.0, 0.0, 0.5, 1.0); \n" "} \n" }; // Source code for fragment shader static const GLchar* fragment_shader_source[] = { "#version 450 core \n" " \n" "out vec4 color; \n" " \n" "void main(void) \n" "{ \n" " color = vec4(0.0, 0.8, 1.0, 1.0); \n" "} \n" }; // Create and compile vertex shader vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, vertex_shader_source, NULL); glCompileShader(vertex_shader); // Create and compile fragment shader fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, fragment_shader_source, NULL); glCompileShader(fragment_shader); // Create program, attach shaders to it, and link it program = glCreateProgram(); glAttachShader(program, vertex_shader); glAttachShader(program, fragment_shader); glLinkProgram(program); // Delete the shaders as the program has them now glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); return program; }
glCreateShader() 创建一个空的着色器对象,准备接受源代码并进行编译。
glShaderSource() 将着色器源代码交给着色器对象,以便它可以保留它的副本。
glCompileShader() 编译着色器对象中包含的任何源代码。
glCreateProgram() 创建一个程序对象,您可以将着色器对象附加到该对象。
glAttachShader() 将着色器对象附加到程序对象。
glLinkProgram() 将附加到程序对象的所有着色器对象链接在一起。
glDeleteShader() 删除着色器对象。将着色器链接到程序对象后,程序将包含二进制代码,不再需要着色器。
shader的使用
class my_application : public sb7::application { public: // <snip> void startup() { rendering_program = compile_shaders(); glCreateVertexArrays(1, &vertex_array_object); glBindVertexArray(vertex_array_object); } void shutdown() { glDeleteVertexArrays(1, &vertex_array_object); glDeleteProgram(rendering_program); glDeleteVertexArrays(1, &vertex_array_object); glPointSize(40.0f); } // Our rendering function void render(double currentTime) { const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f, (float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, color); // Use the program object we created earlier for rendering glUseProgram(rendering_program); // Draw one point glDrawArrays(GL_POINTS, 0, 1); } private: GLuint rendering_program; GLuint vertex_array_object; };
由于shader编译好了就不用变了,所以我们只需要在程序一开始的时候(startup)的时候调用这个编译程序并保留编译所得program的句柄即可。
glDrawArrays(GL_POINTS, 0, 1)中,GL_POINTS指定绘制基元(primitive)的类型,0暂时不用管,1是点的数量。其实虽然说是draw array,但其实这里的array我们并没有分配具体的点,因为我们的shader里面,并没有接受这个数据而是写死的,而这里要创建这个vertex array是因为core profile强制必须创建。
顺带一提,glPointSize()用来设置点的像素大小
画第一个三角形
之前我们的shader代码只有一个点,没办法构成线更或是三角形,所以这里我们修改shader代码(居然也是写死的!)
#shader vertex #version 450 core void main(void) { // Declare a hard-coded array of positions const vec4 vertices[3] = vec4[3](vec4(0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4(0.25, 0.25, 0.5, 1.0)); // Index into our array using gl_VertexID gl_Position = vertices[gl_VertexID]; } void render(double currentTime) { const GLfloat color[] = { 1.0f, 0.0f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, color); // Use the program object we created earlier for rendering glUseProgram(rendering_program); // Draw one triangle glDrawArrays(GL_TRIANGLES, 0, 3); }
这里使用了gl_VertexID,这个变量指的是当前正在渲染第N个点,因为我们的shader是对所有的点进行着色的,这个变量可以标注是顺序中的第几个点,glDrawArrays(GL_TRIANGLES, 0, 3);中,第三个变量3指的是点的个数,就代表着这个绘制会调用shader三次,第一次gl_VertexID为0,第二次为1,第三次为2。
总结
本节我们写了第一个OpenGL程序,但是这一节中,所有的数据都是固定在shader代码里面,并没有从我们的程序中获取数据,后面我们会学习如何从程序传递数据给shader,如何在shader之间传递数据等等。