学习例子:https://github.com/JackieLong/OpenGL/tree/main/project_shader_test
OpenGL 2.1开始,要求必须设置顶点着色器和片段着色器。
这里就展示一个简单的包含shader的OpenGL程序做示范。
一、顶点着色器:VertexShader
// ************************************************// ********** VertexShader.vert,顶点着色器扩展名一般为vert// ************************************************#version 330 core // 开头必须声明版本,和OpenGL版本一样layout (location = 0) in vec2 aPos; // 顶点数据中的顶点坐标,// 简单起见,例子中我们直接传入的NDC坐标,无需矩阵变换uniform vec3 appColor; // 从应用程序中动态设置一个颜色值out vec3 outColor; // 向片段着色器输出一个颜色void main(){outColor = appColor;// gl_Position解释见https://www.yuque.com/tvvhealth/cs/qgs2z1#34NPT// GLSL内置变量,是顶点进行投影矩阵变换之后输出的裁剪坐标,供下一个阶段进行高效裁剪(Clipping)// 必须赋值,否则报错。// 本来这里需要进行模型、视图、投影矩阵变换,简单起见,我们直接使用NDC坐标,也就是变换后的坐标。gl_Position = vec4(aPos, 0.0, 1.0);};
二、片段着色器:FragmentShader
// ************************************************// ********** FragmentShader.frag,片段着色器扩展名一般为frag// ************************************************#version 330 corein vec4 outColor; // 从顶点着色器传入out vec4 FragColor; // 需要输出一个vec4类型数值,表示生成的片段的颜色值。void main(){FragColor = outColor;};
三、编译链接GLProgram
const char* vShaderSrc = "......";const char* fShaderSrc = "......";GLuint vShaderID = -1; // handle of vertex shader objectGLuint fShaderID = -1; // handle of fragment shader objectGLuint programID = -1; // handle of gl program object// **********************************************************************// 一、创建并编译一个Vertex/Fragment Shader Object// **********************************************************************vShaderID = glCreateShader(GL_VERTEX_SHADER); // 创建一个shader对象glShaderSource(vShaderID, 1, &vShaderSrc, NULL); // 导入源码glCompileShader(vShader); // 编译shaderGLint success; // 编译成功与否const GLsizei lenLog = 512; // 日志长度GLchar infoLog[lenLog]; // 编译输出日志glGetShaderiv( shader, GL_COMPILE_STATUS, &success ); // 获取编译状态数据if( !success ) { // 编译没有成功,输出日志glGetShaderInfoLog( shader, lenLog, NULL, infoLog ); // 保存日志cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << endl;}// **********************************************************************// 二、创建GLProgram,链接vertex/fragment shader// **********************************************************************programID = glCreateProgram(); // 创建一个gl program objectglAttachShader(GL_VERTEX_SHADER, vShaderID); // 至少要绑定顶点/片段着色器glAttachShader(GL_FRAGMENT_SHADER, vShaderID);glLinkProgram(programID); // 链接,会做一些优化工作,去掉没被使用的uniform/attributeGLint success; // 链接是否成功const GLsizei lenLog = 512; // 链接日志长度GLchar infoLog[lenLog]; // 链接输出日志glGetProgramiv( id, GL_LINK_STATUS, &success ); // check for linking errorsif( !success ) { // link失败,输出日志glGetProgramInfoLog( id, lenLog, NULL, infoLog ); // 获取日志cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << endl;}// **********************************************************************// 三、link finished,可以删除shader object了// **********************************************************************// 在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了,只需要programID即可了。glDeleteShader( vShaderID );glDeleteShader( fShaderID );
四、准备顶点数据
准备一批供着色器处理的顶点数据。
const GLfloat vertices[] ={// 顶点坐标(NDC坐标),屏幕左下角(-1.0f, -1.0f),右上角(1.0f, 1.0f)-1.0f, -1.0f, // 左下角,index = 01.0f, -1.0f, // 右下角,index = 11.0f, 1.0f, // 右上角,index = 2-1.0f,-1.0f, // 左下角,index = 31.0f, 1.0f, // 右上角,index = 4-1.0f,1.0f, // 左上角,index = 5};// 按索引顺序,绘制的三角形如下,构成一个矩形:// 5 -- 4 2// | / / |// | / / |// | / / |// 3 0 -- 1GLuint VAO; // handle of VAOGLuint VBO; // handle of VBOglGenBuffers(1, &VBO); // 创建一个Buffer Object,在显存中glBindBuffers(GL_ARRAY_BUFFER, VBO); // 绑定为VBO,只有在绑定时才最终决定这个是干什么用的buffer// 开辟一块内存(显存),由VBO管理,并将CPU内存中的数据vertices,一次性大批传递到这块显存中// 此时顶点数据就缓存在了GPU内存中。glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glGenVertexArrays(1, &VAO); // 创建一个VAO对象,这个就不是Buffer缓冲类型的了。glBindVertexArray(VAO); // 绑定为当前VAO,接下来的设置的相关状态都会记录到VAO中。// 告诉OpenGL,顶点着色器中的attribute变量如何从当前的VBO管理的内存中读取数据。// 下面glVertexAttribPointer这句是告诉OpenGL,attribute变量:layout (location = index) in vec2 aPos;// 它的数据从当前VBO中内存中的第一个地址偏移pointer个字节开始每次执行读取size*type个字节的数据,// 两次读取的首地址之间的间隔是stride个字节GLuint index = 0;GLint size = 2; // 分量数量GLenum type = GL_FLOAT; // 分量类型 size和type就组成了数据类型,也就是vec2GLboolean normalized = GL_FALSE; // 数据是否需要归一化。GLsizei stride = 2 * sizeof(GLfloat);const GLvoid* pointer = (GLvoid*)(0 * sizeof(GLfloat));glVertexAttribPointer(index, size, type, normalized, stride, pointer);glEnableVertexAttribArray(index); // 这个是开关,千万别忘记调用,不然就不会生效了。
五、渲染绘制
GLuint programID = -1; // handle of gl program objectGLuint VAO; // handle of VAOGLuint VBO; // handle of VBO......; // 执行前面的代码,获取上面这些数据 。while(......){ // 渲染循环glClearColor(0.0f, 0.0f, 0.0f, 0.0f);glClear(GL_COLOR_BUFFER_BIT);// 准备好顶点数据glBindBuffer(GL_ARRAY_BUFFER, VBO); // 使用VBOglBindVertexArray(VAO); // 使用VAO// 准备好shader programglUseProgram(programID); // 使用GLProgram// 设置好shader program中的uniform变量的值// 获得GLProgram(programID)中名称叫appColor的uniform变量的locationGLint uniformLocation = glGetUniformLocation(programID, "appColor" );glUniform3f(uniformLocation, 0.0f, 0.0f, 1.0f ); // appColor赋值// 所有数据准备完备,可以开始绘制了。// GLProgram的数据全部准备完毕,可以执行绘制(glDrawArrays/glDrawElements)来启动管线了GLenum mode = GL_TRIANGLES;GLint first = 0; // 第一个顶点数据,在VBO中的索引GLsizei count = 6; // 一次绘制6个顶点,索引顺序偏移// 绘制顶点构成的三角形,按顺序拼成。// 0,1,2构成了第一个三角形。// 3,4,5构成了第二个三角形。glDrawArrays(GL_TRIANGLES, 0, 6);}
