卐 3-管线

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了卐 3-管线相关的知识,希望对你有一定的参考价值。

Vertex Fetch stage(顶点获取阶段)

在顶点着色器运行之前,有一个固定管线被称为vertex fetching(或vertex pulling)在运行。它自动为顶点着色器提供输入。

Vertex Attribute(顶点属性)

在不同的管线阶段,inout限定符用于在着色器之间形成管道,并在它们之间传递数据。

如下代码定义了一个变量offset,它作为输入属性(input attribute):

#version 450 core
layout(location = 0) in vec4 offset;// 'offset' is an input vertex attribute
void main() {
	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));
	gl_Position = vertices[gl_VertexID] + offset;
}

由于变量offset是输入给管线中的第一个着色器的,它将通过vertex fetch阶段自动获取。我们通过glVertexAttrib*()函数告诉这个阶段用什么值去填充这个变量。

void glVertexAttrib4fv(GLuint index, const GLfloat* v);
index - 用来引用属性的索引值,示例中location=0,所以要传递给offset值,此值就应该等于0
v - 放入到属性中的数据指针,示例中即是传递给offset变量的值

一个延圆形轨道匀速移动的三角形:

virtual 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);
	glUseProgram(rendering_program);
	GLfloat attrib[] = {(float)sin(currentTime)*0.5f,
						(float)cos(currentTime)*0.5f,
						0.0, 0.0 };
	glVertexAttrib4fv(0, attrib);
	glDrawArrays(GL_TRIANGLES, 0, 3);
}
Passing Data from Stage to Stage(在阶段间传递数据)

例如,如果你vertex shader声明一个变量,名叫“vs_color”,并使用out关键字,那么它将匹配fragment shader中一个使用in关键字的名为“vs_color”的变量。

#version 450 core
layout(location = 0) in vec4 offset;// input vertex attribute
layout(location = 1) in vec4 color;// input vertex attribute
out vec4 vs_color;
void main() {
	const vec4 vertices[3] = ...(同上);
	gl_Position = vertices[gl_VertexID] + offset;
	vs_color = color;
}

// 片段着色器
#version 450 core
in vec4 vs_color;// 从vertex shader输入
out vec4 color;// 输出到framebuffer
void main() {
	color = vs_color;
}

以上程序就允许我们选择一种颜色从一个vertex attribute通过设置glVertexAttrib*()一路经过vertex shader到fragment shader,并输出到framebuffer。因此,我们可以绘制不同颜色的三角形。

Interface Blocks(接口块)

我们可能想在不同阶段交流更多类型的数据,如数组、结构或其他组织复杂形式的数据,我们把一系列变量组织在一起,称之为接口块(interface block)

#version 450 core
layout(location = 0) in vec4 offset;
layout(location = 1) in vec4 color;
out VS_OUT {// 声明VS_OUT为一个输出接口块
	vec4 color;// 发送color到下一个阶段
} vs_out;// 实例名

void main() {
	const vec4 vertices[3] = ...(同上);
	gl_Position = vertices[gl_VertexID] + offset;
	vs_out.color = color;
}

// 片段着色器
#version 450 core
in VS_OUT {
	vec4 color;
} fs_in;
out vec4 color;
void main() {
	color = fs_in.color;
}

注意:示例中接口块有一个名称VS_OUT和一个实例名称vs_out。顶点着色器被out关键字修饰的变量名是VS_OUT,那么在片段着色器中要想获取顶点着色器传递出来的数据,被in关键字修饰的变量名就也得为VS_OUT,即接口块在不同阶段的匹配是使用接口块的名称。但是接口块在着色器中的具体使用是使用它们的实例名。

Tessellation(细分)

细分(Tessellation)是将高阶基本体(在OpenGL中称为“补丁(patch)”)分解为许多更小、更简单的基本体(如用于渲染的三角形)的过程。

OpenGL包含一个固定功能、可配置的细分引擎,该引擎能够将四边形、三角形和线分解为大量较小的点、线或三角形,这些点、线或三角形可以直接由后续的常规光栅化硬件使用。

从逻辑上讲,细分阶段直接位于顶点着色阶段之后,细分阶段由三部分组成:细分控制着色器、(固定管线的)细分引擎、细分评估着色器。

Tessellation Control Shaders(TCS)(细分控制着色器)

细分控制着色器从vertex shader获取输入,主要负责两件事:➀决定细分级别并发送到细分引擎;➁将产生的数据发送到细分评估着色器。

OpenGL中的细分工作原理是将高阶曲面(称为补丁)分解为点、线或三角形。每个补丁都由一系列控制点(control points)形成。每个补丁的控制点数量可通过glPatchParameteri()配置。

void glPatchParameteri(GLenum pname, GLint value);
pname - GL_PATCH_VERTICES
value - 构建每个补丁的控制点的数量,默认值为3

如果默认的3个控制点足够的话,就不用去调用glPatchParameteri()函数了。
每个补丁的控制点的最大数量值依实现而定,但至少确保为32个。

当细分处于活动状态时,顶点着色器在每个控制点上运行一次,而细分控制着色器在控制点组上分批运行(runs in batches on groups),其中每个批次的大小与每个补丁的顶点数相同。也就是说,顶点着色器的结果用作控制点并作为输入批量传递给细分控制着色器。可以更改每个补丁的控制点数量,以便细分控制着色器输出的控制点数量可以与其使用的控制点数量不同。控制着色器生成的控制点数量是使用控制着色器源代码中的输出布局限定符设置的。这样的layout限定符像这样:layout(vertices = N) out; 其中N就是每个补丁中控制点的数量。控制着色器负责计算输出控制点的值,并为将发送到固定管线细分引擎的结果补丁设置细分因子。输出细分因子被写入到内置输出变量gl_TessLevelInnergl_TessLevelOuter,然而管线中传递的任何其他数据将像往常一样被写入到用户自定义的输出变量(那些使用out关键字的声明,或者内置的gl_out[]数组)。

// 细分控制着色器
#version 450 core
layout(vertices = 3) out;
void main() {
	if (gl_InvocationID == 0) {// Only if I am invocation 0 ...
		gl_TessLevelInner[0] = 5.0;
		gl_TessLevelOuter[0] = 5.0;
		gl_TessLevelOuter[1] = 5.0;
		gl_TessLevelOuter[2] = 5.0;
	}
	// Everybody copies their input to their output
	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}

通过layout(vertices = 3) out;将输出控制点设置为3,通过内置变量gl_in[]gl_out[]将它的输入复制到输出,并设置内部和外部细分级别都为5。更大的数值将产生更密集的细分输出,更小的数值将产生更粗糙的细分输出。将细分因子设置为0将导致丢弃整个补丁。

内置输入变量gl_InvocationID包含当前调用细分控制着色器处理的补丁内控制点的从零开始的索引。

The Tessellation Engine(细分引擎)

细分引擎是OpenGL管线的一个固定功能部分,它将高阶曲面表示为补丁,并将其分解为更简单的基本体,如点、线或三角形。在细分引擎接收到一个补丁之前,细分控制着色器处理传入的控制点并设置用来分解补丁的细分因子。在细分引擎生成输出基本体后,表示它们的顶点将由细分评估着色器拾取。细分引擎负责生成提供给细分评估着色器调用的参数,细分评估着色器使用这些参数变换生成的基本体并使它们准备好进行光栅化。

Tessellation Evaluation Shaders(TES)(细分评估着色器)

一旦固定管线细分引擎运行,它将生成表示其生成的基本体的多个输出顶点。这些将传递给细分评估着色器。细分评估着色器为细分器生成的每个顶点都运行一次。当细分级别较高时,细分评估着色器可能会运行非常多次。因此,应小心使用复杂的评估着色器和高细分级别。

// 细分评估着色器
#version 450 core
layout(triangles, equal_spacing, cw) in;
void main() {
	gl_Position = gl_TessCoord.x * gl_in[0].gl_Position +
				  gl_TessCoord.y * gl_in[1].gl_Position +
				  gl_TessCoord.z * gl_in[2].gl_Position;
}

细分评估着色器接收从细分控制着色器传入的顶点。着色器最开头是一个layout限定符,它设置了细分模式。本例中,我们选择的模式是triangles;equal_spacing表示新顶点是沿着细分多边形边缘等距离产生的;cw表示生成的三角形应使用顺时针顶点缠绕顺序。
更多可选项,见Chapter 8-“Tessellation”。

gl_TessCoord是细分器产生的顶点的质心坐标(barycentric coordinate)。gl_in[]是一个结构体数组(结构体有成员gl_Position),它与通过控制着色器的gl_out[]输出相匹配。该着色器基本上实现了传递细分(pass-through tessellation)。也就是说,细分输出补丁的形状与原始传入三角形补丁的形状完全相同。

为了看到细分的效果,我们需要告诉OpenGL只绘制产生的三角形线框,我们需要调用glPolygonMode()

void glPolygonMode(GLenum face, GLenum mode);
face - 指定要影响的多边形类型。本例设置为GL_FRONT_AND_BACK,因为我们想影响everything。
mode - 指定我们的多边形的渲染模式。本例为GL_LINE,即线模式。

Geometry Shaders(几何着色器)

几何(体)着色器在逻辑上是前端中(in the front end)的最后一个着色器阶段,位于顶点着色器和细分着色器之后,在光栅着色器之前。几何着色器对每个基本体运行一次,并且可以访问构成正在处理的基本体的所有顶点的所有输入顶点数据。几何着色器在着色器阶段中也是独一无二的,因为它能够以编程方式增加或减少流经管线的数据量(the amount of data)。细分着色器也可以增加或减少管线中的工作量(the amount of work),但只能通过设置补丁的细分级别来隐式增加或减少。相反,几何着色器包含两个函数EmitVertex()EndPrimitive(),它们显式生成发送到基本体装配和光栅化的顶点。

几何着色器的另一个独特功能是,它们可以在管道中间(mid-pipeline)更改基本体模式。例如,他们可以将三角形作为输入并生成一组点或线作为输出,甚至可以从独立点创建三角形。

// 几何体着色器
#version 450 core
layout(triangles) in;
layout(points, max_vertices = 3) out;
void main() {
	for (int i = 0; i < gl_in.length(); i++) {
		gl_Position = gl_in[i].gl_Position;
		EmitVertex();
	}
}

示例中的着色器充当另一个简单的穿透着色器(pass-through shader),它将三角形转换为点,以便我们可以看到它们的顶点。第一个layout限定符表示几何着色器期望将三角形(triangles)视作输入。第二个layout限定符告诉OpenGL几何着色器将产生点(points),并且每个着色器将生成的最大顶点数量为3。

实际上我们知道gl_in.length()等于3,因为我们处理的是三角形并且每个三角形有3个顶点。几何着色器的输出和顶点着色器相似。特别是,我们写入gl_Position以设置结果顶点的位置。接下来,我们调用EmitVertex(),它在几何着色器的输出处生成一个顶点。在几何着色器的最后会自动调用EndPrimitive(),因此在示例中没必要显示地调用EndPrimitive()。作为几何着色器运行的结果是,将生成3个顶点,并作为点(points)被渲染。

下图中的输出,我们通过glPointSize()把点设置为5.0让点更大更可见。

Primitive Assembly,Clipping,Rasterization(图元组装、剪裁、栅格化)

管线前端包括:顶点着色器,细分,几何着色器。当管线前面运行后,管道的固定功能部分执行一系列任务,将场景的顶点表示转换为一系列像素,这些像素需要着色并写入屏幕。这里面的第一步是图元组装,将顶点分组为直线和三角形。对于点,仍然会发生图元组装,但在这种情况下,它是微不足道的。

从单独的顶点构造图元后,它们将根据可显示区域进行剪裁(clipped),该区域通常指窗口或屏幕,但也可以是称为视口(viewport)的较小区域。最后,被确定为潜在可见的基本体部分被发送到一个名为光栅器(rasterizer)的固定功能子系统(fixed-function subsystem)。此块确定基本体(点、线或三角形)覆盖哪些像素,并将像素列表发送到下一阶段,即片段着色器。

Clipping(剪裁)

当顶点退出管线前端时,它们的位置被称为在裁剪空间(clip space)中。这是许多可以用来表示位置的坐标系之一。你可能已经注意到,我们在顶点、细分和几何着色器中写入的gl_Position变量具有vec4类型,并且通过写入它生成的位置都是四个分量的向量(four-compoent vectors)。这就是所谓的齐次坐标(homogeneous coordinate)。齐次坐标系用于投影几何(projective geometry)中,因为齐次坐标空间中的许多数学运算最终比常规笛卡尔空间中的更简单。齐次坐标比其等效笛卡尔坐标多了一个分量,这就是为什么我们的三维位置向量表示为四分量变量。

虽然前端的输出是一个四分量齐次坐标,但裁剪发生在笛卡尔空间中。因此,为了将齐次坐标转换为笛卡尔坐标,OpenGL执行透视分割(perspective division),其中包括将位置的所有四个分量除以最后一个w分量。这具有将顶点从齐次空间投影到笛卡尔空间的效果,即让w分量为1.0了。在迄今为止的所有示例中,我们已将gl_Position的w分量设置为1.0,因此透视分割没有任何效果。当我们在一会儿后探索投影几何时,我们将讨论将w设置为1.0以外的值的效果。

透视分割后,得到的位置在归一化设备空间(normalized device space)中。在OpenGL中,规一化设备空间的可见区域x、y范围[-1…+1],z范围[0…1]。此区域中包含的任何几何图形都可能对用户可见,并且应丢弃该区域以外的任何几何图形。该体积的六个面由三维空间中的平面构成。当平面将坐标空间一分为二时,平面每一侧的体积称为半空间(half-spaces)。

在将基本体传递到下一阶段之前,OpenGL通过确定每个基本体的顶点位于这些平面的哪一侧来执行剪裁。每个平面实际上都有一个“外部(outside)”和一个“内部(inside)”。如果一个基本体的顶点都位于任何一个平面的“外部”,那么整个物体都会被丢弃。如果基本体的所有顶点都位于所有平面的“内部”(因此也位于视体内),则它将原封不动地通过。必须特别处理部分可见的基本体(这意味着它们穿过其中一个平面)。
第7章中的“剪裁”部分给出了有关此操作的更多详细信息。

Viewport Transformation(视口变换)

剪裁后,几何体的所有顶点都具有x和y,范围为[-1…+1]。z范围为[0…1],这些坐标称为归一化设备坐标(normalized device coordinates)。但是,要绘制的窗口的坐标通常从左下角的(0, 0)开始,范围为(w-1, h-1),其中w和h分别是窗口的宽度和高度(以像素为单位)。要将几何体放置到窗口中,OpenGL将应用视口变换(viewport transform),该变换将缩放和偏移应用到顶点的标准化设备坐标,以将它们移动到窗口坐标(window coordinates)中。要应用的比例和偏移由视口边界决定,你可以通过调用glViewport()和glDepthRange()来设置视口边界。

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
void glDepthRange(GLdouble nearVal, GLdouble farVal);

这个变换使用下面的形式:

xw, yw, zw ——是窗口空间的坐标。
 xd, yd, zd ——是归一化设备空间的坐标。
      px, py ——分别是视口(viewport)的宽度和高度。
          n, f ——分别是在z坐标轴上的近剪裁平面的距离和远剪裁平面的距离。
ox, oy, oz ——是视口的原点。

Culling(剔除)

在进一步处理三角形之前,可以选择将其通过一个称为剔除(culling)的阶段,该阶段确定三角形是否朝向或远离观察者,并且可以根据计算结果决定是否实际进行并绘制三角形。如果三角形朝向观察者,则视为前向面(front-facing);否则,它被称为后向面(back-facing)。丢弃后向三角形非常常见,因为当对象闭合时,任何后向三角形都将被另一个前向三角形隐藏。

为了确定三角形是正面还是背面,OpenGL将确定其在窗口空间中的有符号面积(signed area)。确定三角形面积的一种方法是取其两条边的叉积。这个方程是:

xiw和yiw是窗口空间中三角形的第i个顶点。i⊕1表示(i+1)%3。

如果面积为正,则三角形被视为前向面;如果为负数,则视为后向面。通过调用glFrontFace(),将参数设置为GL_CW或GL_CCW(其中CW和CCW分别表示顺时针和逆时针),可以反转此计算的意义。这称为三角形的缠绕顺序,顺时针或逆时针术语指的是顶点在窗口空间中出现的顺序。默认情况下,此状态设置为GL_CCW,表示顶点按逆时针顺序排列的三角形视为正面三角形,顶点按顺时针顺序排列的三角形视为背向三角形。如果状态为GL_CW,则在剔除过程中使用之前,a将被简单地变为无效。

一旦确定了三角形面对的方向,OpenGL就能够丢弃正面、背面甚至两种类型的三角形。默认情况下,OpenGL将渲染所有三角形,无论它们面向哪个方向。要启用剔除,请调用glEnable(),并将参数设置为GL_CULL_FACE。启用赐除后,OpenGL将默认赐除后向三角形。要更改要剔除的三角形类型,请调用glCullFace(),并将面设置为GL_FRONT、GL_BACK或GL_FRONT_AND_BACK。

由于点和线没有任何几何面积,此面计算不适用于它们,因此在此阶段无法剔除它们。

Obviously, once they are rendered to the screen, points and lines have area; otherwise, we wouldn’t be able to see them. However, this area is artificial and can’t be calculated directly from their vertices.

Rasterization(光栅化)

光栅化是确定哪些片段可能被基本体(如直线或三角形)覆盖的过程。有无数的算法可以实现这一点,但大多数OpenGL系统将采用基于半空间(half-space-based)的三角形方法,因为它非常适合并行实现。本质上,OpenGL将在窗口坐标中确定三角形的边界框,并测试其中的每个片段,以确定它是在三角形内部还是外部。为此,它将三角形的三条边视为将窗口一分为二的半空间。位于所有三条边内部的碎片被视为位于三角形内部,位于任何三条边外部的碎片被视为位于三角形外部。由于确定点位于直线哪一侧的算法相对简单,并且与直线端点和被测点的位置无关,因此可以同时执行许多测试,从而提供大规模并行的机会。

Fragment Shaders(片段着色器)

片段着色器是OpenGL图形管线中最后一个可编程的阶段。此阶段负责在将每个片段发送到帧缓冲区以便在窗口中合成之前确定其颜色。光栅器处理基本体后,会生成需要着色的片段列表,并将该列表传递给片段着色器。在这里,管线中的工作量发生爆炸,因为每个三角形可能产生数百、数千甚至数百万个片段。

The term fragment is used to describe an element that may ultimately contribute to the final color of a pixel. The pixel may not end up being the color produced by any particular invocation of the fragment shader due to a number of other effects such as depth or stencil tests, blending, and multi-sampling, all of which will be covered later in the book.

在实际应用程序中,片段着色器通常会非常复杂,并负责执行与照明相关的计算、应用材质,甚至确定片段的深度。片段着色器的输入有几个内置变量,如gl_FragCoord,其中包含窗口中片段的位置。可以使用这些变量为每个片段生成唯一的颜色。

#version 450 core
out vec4 color;
void main() {
	color = vec4(sin(gl_FragCoord.x*0.25)*0.5 + 0.5,
				 cos(gl_FragCoord.x*0.25)*0.5 + 0.5,
				 sin(gl_FragCoord.x*0.15)*sin(gl_FragCoord.x*0.15),
				 1.0);
}

示例根据片段的位置给片段着色,效果如下:

gl_FragCoord变量是片段着色器可用的内置变量之一。但是,与其他着色器阶段一样,我们可以定义自己对片段着色器的输入,这些输入将基于光栅化之前最后一个阶段的输出进行填充。例如,如果我们有一个只包含顶点着色器和片段着色器的简单程序,我们可以将数据从片段着色器传递到顶点着色器。

片段着色器的输入与其他着色器阶段的输入有些不同,因为OpenGL会在渲染的基本体上插值它们的值。为了演示,我们使用一个顶点着色器,并对其进行修改,为每个顶点指定不同的固定颜色,代码如下:

// 顶点着色器
#version 450 core
// 'vs_color' is an output that will be sent to the next shader stage
out vec4 vs_color;
void main() {
	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));
	const vec4 colors[] = vec4[3](vec4(1.0, 0.0, 0.0, 1.0),
								  vec4(0.0, 1.0, 0.0, 1.0),
								  vec4(0.0, 0.0, 1.0, 1.0));
	// Add 'offset' to our hard-coded vertex position
	gl_Position = vertices[gl_VertexID] + offset;
	// Output a fixed value for vs_color
	vs_color = color[gl_VertexID];
}

// 片段着色器
#version 450 core
// 'vs_color' is the color produced by the vertex shader
in vec4 vs_color;
out vec4 color;
void main() {
	color = vs_color;
}


正如你所看到的颜色在三角形上平滑变化。

Framebuffer Operations(帧缓冲区操作)

帧缓冲区是OpenGL图形管线的最后一个阶段。它可以表示屏幕的可见内容和用于存储除颜色以外的每像素值的许多附加内存区域。在大多数平台上,这意味着你在桌面上看到的窗口(或者如果你的应用程序覆盖了整个屏幕),它属于操作系统(或者更准确地说是窗口系统)。窗口系统提供的帧缓冲区称为默认帧缓冲区,但如果您希望执行诸如渲染到屏幕外区域之类的操作,则可以提供自己的帧缓冲区。帧缓冲区持有的状态包括诸如片段着色器生成的数据应写入何处、该数据的格式等信息。此状态存储在帧缓冲区对象(framebuffer object)中。像素操作状态也是帧缓冲区的一部分,但不是存储在每个帧缓冲区对象中。

Pixel Operations(像素操作)

片段着色器生成输出后,在将片段写入窗口之前,可能会发生一些事情,例如确定它是否属于窗口。应用程序可以打开或关闭这些功能。可能发生的第一件事是裁剪测试(scissor test),它根据你可以定义的矩形来测试片段。如果它在矩形内,那么它将被进一步处理;如果它在外面,它就会被扔掉。

接下来是模板测试。这会将应用程序提供的参考值与模板缓冲区的内容进行比较,模板缓冲区每像素存储一个单独的值。模板缓冲区的内容没有特定的语义含义,可以用于任何目的。

It’s possible for a framebuffer to store multiple depth, stencil, or color values per pixel when a technique called multi-sampling is employed. We’ll dig into this later in the book.

模板测试完成后,进行深度测试。深度测试是将片段的z坐标与深度缓冲区的内容进行比较的操作。深度缓冲区是一个内存区域,与模板缓冲区一样,是帧缓冲区的一部分,具有足够的空间为每个像素提供单个值;它包含每个像素的深度(与距离观察者的距离有关)。

通常,深度缓冲区中的值范围为0到1,其中0是深度缓冲区中可能最近的点,1是深度缓冲区中可能最远的点。为了确定片段是否比已在同一位置渲染的其他片段更接近,OpenGL可以将片段窗口空间坐标的z分量与深度缓冲区中已有的值进行比较。如果该值小于已经存在的值,则片段可见。这个测试的意义也可以改变。例如,您可以要求OpenGL允许z坐标大于、等于或不等于深度缓冲区内容的片段通过。深度测试的结果也会影响OpenGL对模板缓冲区的操作。

接下来,片段的颜色会发送到混合或逻辑操作阶段,具体取决于帧缓冲区是否被视为存储浮点值、规格化值或整数值。如果帧缓冲区的内容是浮点值或规范化整数值,则应用混合。混合在OpenGL中是一个高度可配置的阶段,将在其自己的章节中详细介绍。

简言之,OpenGL能够使用范围广泛的函数,这些函数获取片段着色器输出和帧缓冲区当前内容的组件,并计算写回帧缓冲区的新值。如果帧缓冲区包含非规范化整数值,则可以将逻辑操作(如逻辑AND、OR和XOR)应用于着色器的输出和帧缓冲区中当前的值,以生成将写回帧缓冲区的新值。

Compute Shaders(计算着色器)

本章的第一节介绍OpenGL中的图形管线(graphics pipeline)。然而,OpenGL还包括计算着色器(compute shader)阶段,它几乎可以被认为是一个独立的管线,独立于其他面向图形的阶段运行。计算着色器是获取系统中图形处理器所拥有的计算能力的一种方法。与以图形为中心的顶点、细分、几何体和片段着色器不同,计算着色器可以单独视为一个特殊的单级管线。每个计算着色器在称为工作项的单个工作单元上运行;反过来,这些项目被收集到一起,组成称为本地工作组的小组。这些工作组的集合可以发送到OpenGL的计算管线中进行处理。计算着色器没有任何固定的输入或输出,只有一些内置变量可以告诉着色器它正在处理哪个项目。计算着色器执行的所有处理都由着色器本身显式写入内存,而不是由后续管道阶段使用。

// 计算着色器
#version 450 core
layout(local_size_x = 32, local_size_y = 32) in;
void main() {
	// Do nothing
}

计算着色器在其他方面与OpenGL中的任何其他着色器阶段一样。要编译一个着色器,请创建一个类型为GL_COMPUTE_SHADER的着色器对象,使用glShaderSource()将GLSL源代码附加到该着色器对象,使用glCompileShader()对其进行编译,然后使用glAttachShader()和glLinkProgram()将其链接到一个程序中。结果是一个程序对象,其中包含一个已编译的计算着色器,可以启动该着色器为你工作。示例中的着色器告诉OpenGL,本地工作组的大小将是32乘32个工作项,但随后什么也不做。要创建一个真正有用的计算着色器,您需要更多地了解OpenGL,所以我们将在本书后面的部分重新讨论这个主题。

UsingExtensions in OpenGL(使用OpenGL扩展)…待补充
Enhancing OpenGL with Extensions(用扩展增强OpenGL)…待补充
Summary

In this chapter, you have taken a whirlwind trip down OpenGL’s graphics pipeline. You have been (very) briefly introduced to each major stage and have created a program that uses each one of them, if only to do nothing impressive. We’ve glossed over or even neglected to mention several useful features of OpenGL with the intention of getting you from zero to rendering in as few pages as possible. You’ve also seen how OpenGL’s pipeline and functionality can be enhanced by using extensions, which some of the examples later in the book will rely on. Over the next few chapters, you’ll learn more fundamentals of computer graphics and of OpenGL, and then we’ll take a second trip down the pipeline, dig deeper into the topics from this chapter, and get into some of the things we skipped in this preview of what OpenGL can do.

以上是关于卐 3-管线的主要内容,如果未能解决你的问题,请参考以下文章

✠OpenGL-2-图像管线

我的OpenGL学习进阶之旅OpenGL ES 3.0实现了具有可编程着色功能的图形管线

我的OpenGL学习进阶之旅OpenGL ES 3.0实现了具有可编程着色功能的图形管线

✠OpenGL-2-图像管线

[Unity Shader] 渲染管线流程

卐 4-3D图形的数学