1.简介
曲面细分(Tessellation)是现代图形管线中的一个重要阶段,它允许在运行时动态地增加几何细节。以下是曲面细分的基本流程图和每个步骤的简要介绍:
- 输入装配(Input Assembly)
输入数据:包括顶点数据、索引(如果使用的话),以及定义patch的控制点。Patch可以是一条线、一个三角形或四边形等。
设置:需要指定如何解释输入数据(例如,作为一个点列表、线段、三角形等)。 - 细分控制着色器(Tessellation Control Shader, TCS)
功能:TCS为每个控制点执行一次。它的主要任务是计算并输出细分等级(tessellation levels),这些等级决定了最终生成的几何细节程度。此外,TCS还可以对控制点的数据进行处理或传递其他属性给下一个阶段。
输出:除了细分等级外,还包括控制点的位置和其他可能需要传递给TES的属性。 - 原始生成器(Primitive Generator)
功能:基于TCS提供的细分等级,原始生成器负责将输入的patch细分为更小的图元(如点、线段、三角形)。这个过程根据所选的细分模式(如isolines, triangles, 或 quads)来确定如何细分。
输出:产生新的顶点位置(但不直接包含顶点的具体属性),这些位置将用于接下来的评估。 - 细分评估着色器(Tessellation Evaluation Shader, TES)
功能:对于每个新生成的顶点,TES都会执行一次。它使用gl_TessCoord(表示当前顶点在其所属图元中的位置)和来自TCS的控制点信息来计算每个顶点的确切属性(如位置、颜色、法线等)。
输出:经过计算后的顶点属性,这些属性随后会被送入后续的图形管线阶段(如几何着色器或光栅化)。 - 几何着色器(可选)
功能:如果存在,几何着色器可以在细分之后进一步操作或生成更多的几何体。 - 光栅化(Rasterization)
功能:将几何形状转换为像素,并准备进入片段着色器阶段。 - 片段着色器(Fragment Shader)
功能:为每个像素计算颜色值,考虑光照、材质属性等。
执行流程图
以下是细分着色的整体工作流程:
- 顶点数据通过顶点着色器处理后,作为图元的控制点传递给细分控制着色器(TCS)。
- 细分控制着色器计算细分因子,并将控制点信息传递给原始生成器。
- 原始生成器根据细分因子将几何图元细分为更小的图元。
- 细分评估着色器(TES)根据细分后的每个顶点的位置,计算出最终几何形状。
2.例子
给定两个点绘制一条正弦曲线,两点 :A(10, 200, 0),B(310, 200, 0)。
顶点着色器(VS)
顶点着色器为细分阶段提供输入顶点数据。
#version 400 core
layout(location = 0) in vec3 in_Vertex;
void main()
{
gl_Position = vec4(in_Vertex, 1.0); // 顶点位置
}
细分控制着色器(TCS)
细分控制着色器用于控制细分因子,并传递控制点数据。
#version 400 core
layout(vertices = 2) out;
void main()
{
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
gl_TessLevelOuter[0] = float(1);
gl_TessLevelOuter[1] = float(32);
}
内置变量说明
细分评估着色器(TES)
细分评估着色器用于计算细分后顶点的位置。
#version 400 core
layout(isolines) in;
uniform mat4 mvp;
void main()
{
// 计算当前顶点的位置(Barycentric 插值)
float u= gl_TessCoord.x;
vec3 p0 = gl_in[0].gl_Position.xyz;
vec3 p1 = gl_in[1].gl_Position.xyz;
float = len = length(p1 - p2) / 2.0;
vec3 p;
p.x = p0.x * u + p1.x * (1 - u);
p.y = p0.y + len * sin(u * 2 * 3.1415);
gl_Position = mvp * vec4(p, 1);
}
片段着色器
片段着色器对细分后的顶点数据进行光栅化后的每个片段进行着色。
#version 400 core
in vec3 tesColor; // 从细分评估着色器输入的颜色
void main()
{
gl_FragColor= vec4(tesColor, 1.0); // 输出片段颜色
}
示意图
最终效果图
整体工作流程:
-
执行顶点着色器(VS),并且执行完后会将每两个顶点打包成一个patch,传给下一步。会被执行2次
-
细分控制着色器(TCS),将每个patch中的顶点作为输入进行执行,将分割设置和patch传给原始生成器。会被执行1个patch,每个patch含两个顶点,因此也会被执行2次
gl_InvocationID
layout(vertices = 2) out;
这表示你正在处理包含 2 个控制点的 patch。因此,在这个情况下,gl_InvocationID 的值将分别为 0 和 1,每次 TCS 被调用以处理新的 patch 时,这个 ID 都会重新从 0 开始计数。换句话说,对于每一个新的 patch,gl_InvocationID 会再次从 0 开始,并非每两次重新置 0,而是每次处理一个新的 patch 时都会重置为 0。
因此,如果你连续处理多个 patches,比如第一个 patch 处理完后接着处理下一个,那么对于每个新 patch,gl_InvocationID 总是从 0 开始递增,而不是跨 patch 累加或周期性地每隔两次重置。每个独立的 patch 处理过程都是独立开始的。
设置将每个patch分割数
gl_TessLevelOuter[0] = float(1);
gl_TessLevelOuter[1] = float(32);
在OpenGL的细分着色器(Tessellation Control Shader, TCS)中,gl_out[]数组和输出变量如tcsColor[]是按每个控制点(即输入顶点)执行一次来处理的。这意味着对于你定义的每一个输入顶点,TCS都会执行一次,并且在这个执行过程中,你可以访问和修改对应于该顶点的数据。
不过,有几个问题需要注意:
重复设置 gl_TessLevelOuter: 在你的代码示例中有重复的代码行设置了gl_TessLevelOuter[0]和gl_TessLevelOuter[1]。实际上,这些行只需要设置一次即可,重复设置不会带来额外的效果,并且从代码整洁性角度来看,应该避免这样做。
gl_TessLevelOuter 设置值:
gl_TessLevelOuter[0] = 0; 这一行可能不是你想要的结果。细分等级通常应该是正数,0可能会导致未定义行为或被驱动程序修正为默认值。
gl_TessLevelOuter[1] = 1; 这个设置意味着将对应的边细分为1段,即不进行细分。
关于 layout(vertices = 2) out;: 你指定了一个包含2个顶点的patch,这适用于线段的细分,而不是三角形。如果你要处理三角形,则需要指定3个顶点。
颜色传递: 你在TCS中直接将输入的颜色vColor[]复制到了输出tcsColor[],这也是针对每个顶点执行的操作。
总结来说,在TCS中,对每个输入顶点(控制点),都会执行一次着色器代码,从而允许你修改每个顶点相关的数据(如位置、颜色等)以及设置细分等级。但是,注意不要多次设置相同的全局属性(如gl_TessLevelOuter[]),除非这是你的意图并且理解这样做的后果。
#version 400 core
layout(vertices = 2) out;
void main()
{
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
gl_TessLevelOuter[0] = float(1);
gl_TessLevelOuter[1] = float(32);
}
优化后,减少重复执行
优化后的代码:
#version 400 core
layout(vertices = 2) out;
void main()
{
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
if(gl_InvocationID == 0){
gl_TessLevelOuter[0] = float(1);
gl_TessLevelOuter[1] = float(32);
}
}
3.原始生成器(primitive generator)基于TCS提供的细分等级和细分模式(通过layout指定,比如isolines, triangles, 或 quads),原始生成器会创建新的顶点。这些顶点是细分后的结果,它们定义了如何将输入的patch细分为更小的图元。将线段分为32份,即生成33个点,这里输出的并不是33个点的直角坐标而是uv坐标。并传给细分评估着色器(TES)。每个patch包执行一次,会被执行1次
4.细分评估着色器(TES)对细分后产生的每个新顶点执行一次。TES使用gl_TessCoord(表示当前顶点在其所属图元中的位置)以及来自TCS的原始控制点数据来计算新顶点的实际属性(如位置、颜色等)。会被执行33次
5.最后运行片元着色器(像素着色器)生成颜色和其他与像素相关的数据。会被执行33次