《OGL dev》Etay Meiri Tutorial 04 - Shaders 笔记

OpenGL的可编程管线

vertex processor对应vertex shader(顶点着色器)。

  1. 顶点的数量取决于draw call的参数,即glDrawArrays(GL_TRIANGLES, 0, 3);的最后一个参数。
  2. 不知道图元的拓扑。
  3. 无法丢弃顶点。

geometry processor对应geometry shader(几何着色器)。

  1. 接收图元的所有顶点和邻近的顶点。
  2. 能够切换到非draw call选择的拓扑。
  3. 一个顶点能够输出多个拓扑。(例如,广告牌:1个顶点生成2个三角形)

clipper(裁剪)

  1. 按近Z和远Z的平面进行裁剪,用户自定义裁剪平面。
  2. 裁剪至normalized box。

clipper留下的顶点,先进行屏幕映射,之后rasterizer(光栅化)依据它们的拓扑渲染到屏幕。
rasterizer会找出依据拓扑的所有顶点。

fragment processor对应fragment shader(片段着色器)。

  1. 每一个顶点,rasterizer都调用fragment processor。
  2. 决定像素的颜色。
  3. 可以丢弃像素。
  4. 可以改变Z值,影响后续的Z测试。

vertex、geometry和fragment processors是可选、可编程的。不绑定shader会执行默认的功能。
clipper是固定的。

调用shader

  1. 创建shader对象:GLuint ShaderObj = glCreateShader(ShaderType);。ShaderType可选 GL_VERTEX_SHADER和GL_FRAGMENT_SHADER。
  2. 为shader对象指定源代码:glShaderSource(ShaderObj, 1, p, Lengths);。OpenGL允许shader源代码分散在多个char *中。因此p是char * [],Lengths是GLint[]对应每个p的元素的长度(不包含'\0'),第二个参数1表示p的元素个数。
  3. 编译shader:glCompileShader(ShaderObj);
  4. 检查编译错误并打印:glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success); glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);。GL_COMPILE_STATUS表示编译,编译失败时success为0。InfoLog是char *用于存储错误信息。
  5. 创建program对象:GLuint ShaderProgram = glCreateProgram();。program对象用于附加shader。
  6. 将编译好的shader对象附加到program对象:glAttachShader(ShaderProgram, ShaderObj);
  7. 链接shader:glLinkProgram(ShaderProgram);。链接shader会对shader进行优化。例如:将输出法线的vertex shader和忽略法线的fragment shader配对,driver中的GLSL编译器会删除法线。因此,相同的shader链接后可能生成不同的shader。
  8. 检查链接错误并打印:glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success); glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);。GL_LINK_STATUS表示链接,失败时success为0。ErrorLog是char *用于存储错误信息。
  9. 验证program:glValidateProgram(ShaderProgram);。检查program是否能在当前状态下的管线中执行。
  10. 检查验证错误并打印:与检查链接错误相同,仅将glGetProgramiv的第二个参数改为GL_VALIDATE_STATUS,表示验证。
  11. 将propram设置到管线状态中:glUseProgram(ShaderProgram);。这个propram一直生效,直到glUseProgram调用另一个propram替代它。glUseProgram(NULL)表示使用固定功能管线。如果propram没有包含所有类型的shader,其余的都将使用固定功能。

shader链接到program对象之后,依旧可以被删除。

  1. 调用void glDeleteShader(GLuint shader)
    1. 如果一个shader没有附加到任何program,它将被删除,与glCreateShader相反。
    2. 如果shader已附加到program,将shader标记为删除。OpenGL使用引用计数管理shader,因此不会直接删除。
  2. 调用void glDetachShader(GLuint program, GLuint shader);,将shader从program中分离,与glAttachShader相反。如果一个shader被标记为删除,分离后没有附加到任何program,即引用计数归0,glDetachShader将删除这个shader。

编写shader

#version 330,表示GLSL版本为3.3,编译不支持则报错。

layout (location = 0) in vec3 Position;

  1. 声明了一个vertex specific attribute,名称为Position,它的值来自于buffer。
  2. vec3表示Position是一个3 float的向量。
  3. layout (location = 0)将buffer中的attribute name与shader中的attribute绑定。0是glVertexAttributePointer的第一个参数,表示坐标。
  • 0是硬编码的值,复杂的程序会仅在shader中声明in vec3 Position,然后运行时调用glGetAttribLocation查询它的位置。但教程没有举例说明具体用法。

void main()是shader的入口,一个shader必须有且仅有一个。

gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);,gl_Position是一个内置变量,它包含齐次顶点坐标(X、Y、Z和W)。因为屏幕映射过程中会使用透视除法,将gl_Position的所有坐标都除以W,所以这里将W设为1。

out vec4 FragColor;

  1. 声明变量FragColor,FragColor的值将被光栅器接收写入到帧缓冲中。
  2. vec4表示4 float,分别表示R、G、B和A。