学习例子:https://github.com/JackieLong/OpenGL/tree/main/project_shader_test
OpenGL 2.1开始,要求必须设置顶点着色器和片段着色器。
这里就展示一个简单的包含shader的OpenGL程序做示范。

一、顶点着色器:VertexShader

  1. // ************************************************
  2. // ********** VertexShader.vert,顶点着色器扩展名一般为vert
  3. // ************************************************
  4. #version 330 core // 开头必须声明版本,和OpenGL版本一样
  5. layout (location = 0) in vec2 aPos; // 顶点数据中的顶点坐标,
  6. // 简单起见,例子中我们直接传入的NDC坐标,无需矩阵变换
  7. uniform vec3 appColor; // 从应用程序中动态设置一个颜色值
  8. out vec3 outColor; // 向片段着色器输出一个颜色
  9. void main()
  10. {
  11. outColor = appColor;
  12. // gl_Position解释见https://www.yuque.com/tvvhealth/cs/qgs2z1#34NPT
  13. // GLSL内置变量,是顶点进行投影矩阵变换之后输出的裁剪坐标,供下一个阶段进行高效裁剪(Clipping)
  14. // 必须赋值,否则报错。
  15. // 本来这里需要进行模型、视图、投影矩阵变换,简单起见,我们直接使用NDC坐标,也就是变换后的坐标。
  16. gl_Position = vec4(aPos, 0.0, 1.0);
  17. };

二、片段着色器:FragmentShader

  1. // ************************************************
  2. // ********** FragmentShader.frag,片段着色器扩展名一般为frag
  3. // ************************************************
  4. #version 330 core
  5. in vec4 outColor; // 从顶点着色器传入
  6. out vec4 FragColor; // 需要输出一个vec4类型数值,表示生成的片段的颜色值。
  7. void main()
  8. {
  9. FragColor = outColor;
  10. };

三、编译链接GLProgram

  1. const char* vShaderSrc = "......";
  2. const char* fShaderSrc = "......";
  3. GLuint vShaderID = -1; // handle of vertex shader object
  4. GLuint fShaderID = -1; // handle of fragment shader object
  5. GLuint programID = -1; // handle of gl program object
  6. // **********************************************************************
  7. // 一、创建并编译一个Vertex/Fragment Shader Object
  8. // **********************************************************************
  9. vShaderID = glCreateShader(GL_VERTEX_SHADER); // 创建一个shader对象
  10. glShaderSource(vShaderID, 1, &vShaderSrc, NULL); // 导入源码
  11. glCompileShader(vShader); // 编译shader
  12. GLint success; // 编译成功与否
  13. const GLsizei lenLog = 512; // 日志长度
  14. GLchar infoLog[lenLog]; // 编译输出日志
  15. glGetShaderiv( shader, GL_COMPILE_STATUS, &success ); // 获取编译状态数据
  16. if( !success ) { // 编译没有成功,输出日志
  17. glGetShaderInfoLog( shader, lenLog, NULL, infoLog ); // 保存日志
  18. cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << endl;
  19. }
  20. // **********************************************************************
  21. // 二、创建GLProgram,链接vertex/fragment shader
  22. // **********************************************************************
  23. programID = glCreateProgram(); // 创建一个gl program object
  24. glAttachShader(GL_VERTEX_SHADER, vShaderID); // 至少要绑定顶点/片段着色器
  25. glAttachShader(GL_FRAGMENT_SHADER, vShaderID);
  26. glLinkProgram(programID); // 链接,会做一些优化工作,去掉没被使用的uniform/attribute
  27. GLint success; // 链接是否成功
  28. const GLsizei lenLog = 512; // 链接日志长度
  29. GLchar infoLog[lenLog]; // 链接输出日志
  30. glGetProgramiv( id, GL_LINK_STATUS, &success ); // check for linking errors
  31. if( !success ) { // link失败,输出日志
  32. glGetProgramInfoLog( id, lenLog, NULL, infoLog ); // 获取日志
  33. cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << endl;
  34. }
  35. // **********************************************************************
  36. // 三、link finished,可以删除shader object了
  37. // **********************************************************************
  38. // 在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了,只需要programID即可了。
  39. glDeleteShader( vShaderID );
  40. glDeleteShader( fShaderID );

四、准备顶点数据

准备一批供着色器处理的顶点数据。

  1. const GLfloat vertices[] =
  2. {
  3. // 顶点坐标(NDC坐标),屏幕左下角(-1.0f, -1.0f),右上角(1.0f, 1.0f)
  4. -1.0f, -1.0f, // 左下角,index = 0
  5. 1.0f, -1.0f, // 右下角,index = 1
  6. 1.0f, 1.0f, // 右上角,index = 2
  7. -1.0f,-1.0f, // 左下角,index = 3
  8. 1.0f, 1.0f, // 右上角,index = 4
  9. -1.0f,1.0f, // 左上角,index = 5
  10. };
  11. // 按索引顺序,绘制的三角形如下,构成一个矩形:
  12. // 5 -- 4 2
  13. // | / / |
  14. // | / / |
  15. // | / / |
  16. // 3 0 -- 1
  17. GLuint VAO; // handle of VAO
  18. GLuint VBO; // handle of VBO
  19. glGenBuffers(1, &VBO); // 创建一个Buffer Object,在显存中
  20. glBindBuffers(GL_ARRAY_BUFFER, VBO); // 绑定为VBO,只有在绑定时才最终决定这个是干什么用的buffer
  21. // 开辟一块内存(显存),由VBO管理,并将CPU内存中的数据vertices,一次性大批传递到这块显存中
  22. // 此时顶点数据就缓存在了GPU内存中。
  23. glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  24. glGenVertexArrays(1, &VAO); // 创建一个VAO对象,这个就不是Buffer缓冲类型的了。
  25. glBindVertexArray(VAO); // 绑定为当前VAO,接下来的设置的相关状态都会记录到VAO中。
  26. // 告诉OpenGL,顶点着色器中的attribute变量如何从当前的VBO管理的内存中读取数据。
  27. // 下面glVertexAttribPointer这句是告诉OpenGL,attribute变量:layout (location = index) in vec2 aPos;
  28. // 它的数据从当前VBO中内存中的第一个地址偏移pointer个字节开始每次执行读取size*type个字节的数据,
  29. // 两次读取的首地址之间的间隔是stride个字节
  30. GLuint index = 0;
  31. GLint size = 2; // 分量数量
  32. GLenum type = GL_FLOAT; // 分量类型 size和type就组成了数据类型,也就是vec2
  33. GLboolean normalized = GL_FALSE; // 数据是否需要归一化。
  34. GLsizei stride = 2 * sizeof(GLfloat);
  35. const GLvoid* pointer = (GLvoid*)(0 * sizeof(GLfloat));
  36. glVertexAttribPointer(index, size, type, normalized, stride, pointer);
  37. glEnableVertexAttribArray(index); // 这个是开关,千万别忘记调用,不然就不会生效了。

五、渲染绘制

  1. GLuint programID = -1; // handle of gl program object
  2. GLuint VAO; // handle of VAO
  3. GLuint VBO; // handle of VBO
  4. ......; // 执行前面的代码,获取上面这些数据 。
  5. while(......){ // 渲染循环
  6. glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  7. glClear(GL_COLOR_BUFFER_BIT);
  8. // 准备好顶点数据
  9. glBindBuffer(GL_ARRAY_BUFFER, VBO); // 使用VBO
  10. glBindVertexArray(VAO); // 使用VAO
  11. // 准备好shader program
  12. glUseProgram(programID); // 使用GLProgram
  13. // 设置好shader program中的uniform变量的值
  14. // 获得GLProgram(programID)中名称叫appColor的uniform变量的location
  15. GLint uniformLocation = glGetUniformLocation(programID, "appColor" );
  16. glUniform3f(uniformLocation, 0.0f, 0.0f, 1.0f ); // appColor赋值
  17. // 所有数据准备完备,可以开始绘制了。
  18. // GLProgram的数据全部准备完毕,可以执行绘制(glDrawArrays/glDrawElements)来启动管线了
  19. GLenum mode = GL_TRIANGLES;
  20. GLint first = 0; // 第一个顶点数据,在VBO中的索引
  21. GLsizei count = 6; // 一次绘制6个顶点,索引顺序偏移
  22. // 绘制顶点构成的三角形,按顺序拼成。
  23. // 0,1,2构成了第一个三角形。
  24. // 3,4,5构成了第二个三角形。
  25. glDrawArrays(GL_TRIANGLES, 0, 6);
  26. }