使用几何着色器显示多边形法线
译者注:示例代码点击此处
渲染几何体时,我们通常为每个顶点提供多个属性 - 绘制模型的位置,纹理的纹理坐标和照明计算的法线向量。 检查所有这些数据是否正确可能并不容易,但有时,当我们的渲染技术无法按预期工作时,可能是必要的。
在图形编程中,有一些常用的调试方法。 显示为二维的纹理坐标而不是颜色。 我们可以对法线向量做同样的事情,但由于它们是三维的,我们也可以用线的形式显示它们。 为此,可以使用几何着色器。
怎么做...
1.创建一个名为normals.vert的顶点着色器(请参阅编写顶点着色器内容)。
2.定义一个输入变量,其中顶点位置将提供给顶点着色器:
layout( location = 0 ) in vec4 app_position;
3.定义第二个输入变量,其中将提供顶点法线向量:
layout( location = 1 ) in vec3 app_normal;
4.使用两个矩阵定义一个uniform的块 - 一个用于模型视图转换,另一个用于投影矩阵
layout( set = 0, binding = 0 ) uniform UniformBuffer {
mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
};
5.定义一个输出变量,通过该输出变量,我们将向几何着色器提供从局部空间转换为视图空间的法线向量:
layout( location = 0 ) out vec4 vert_normal;
6.通过将ModelViewMatrix变量相乘并将结果存储在gl_Position内置变量中,将顶点位置转换为视图空间:
gl_Position = ModelViewMatrix * app_position;
7.以类似的方式,将顶点法线转换为视图空间,使用选定的值缩放结果,并将结果存储在vert_normal输出变量中:
vert_normal = vec4( mat3( ModelViewMatrix ) * app_normal * <scale>, 0.0 );
8.创建一个名为normal.geom的几何着色器(请参阅编编写几何着色器内容)。
9.定义triangle输入基元类型:
layout( triangles ) in;
10.定义一个输入变量,通过该变量从顶点着色器提供视图空间顶点法线:
layout( location = 0 ) in vec4 vert_normal[];
11. 使用两个矩阵定义一个uniform的块 - 一个用于模型视图转换,另一个用于投影矩阵:
layout( set = 0, binding = 0 ) uniform UniformBuffer {
mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
};
12.通过输出布局(output layout)限定符,将line_strip指定为最多包含六个顶点的基元类型。
layout( line_strip, max_vertices = 6 ) out;
13.定义一个输出变量,通过该变量将颜色从几何着色器提供给片段着色器:
layout( location = 0 ) out vec4 geom_color;
14.在void main()函数内部,使用名为vertex的int类型的变量来遍历所有输入顶点。 对每个输入顶点执行以下操作:
1.通过输入顶点位置乘以ProjectionMatrix并将结果存储在gl_Position内置变量中:
gl_Position = ProjectionMatrix * gl_in[vertex].gl_Position;
2.geom_color输出变量中,在几何体(顶点)和顶点法线之间的接触点处存储顶点法线的所需颜色:
geom_color = vec4( <chosen color> );
3.通过调用EmitVertex()函数生成新的顶点。
4.通过vert_normal变量偏移输入顶点位置乘以ProjectionMatrix。 将结果存储在gl_Position内置变量中:
gl_Position = ProjectionMatrix * (gl_in[vertex].gl_Position + vert_normal[vertex]);
5.将顶点法线的最终颜色存储在geom_color输出变量中:
geom_color = vec4( <chosen color> );
6.通过调用EmitVertex()函数生成新的顶点。
7.通过调用EndPrimitive()函数生成基元(具有两个点的线)。
15.创建名为normals.frag的片段着色器(请参阅编写片段着色器内容)。
16.定义一个输入变量,通过该变量,将在几何着色器生成的线的两个顶点之间插入的颜色将提供给片段着色器:
layout( location = 0 ) in vec4 geom_color;
17. 为片段的颜色定义输出变量:
layout( location = 0 ) out vec4 frag_color
18.在void main()函数内部,将geom_color输入变量的值存储在frag_color输出变量中:
frag_color = geom_color;
这个怎么运作...
从应用程序显示顶点法线向量分两步执行:首先,我们使用着色器组以正常方式绘制几何体。 第二步是绘制相同的模型,但使用管线对象,该对象使用此内容中指定的顶点,几何和片段着色器。
顶点着色器只需将顶点位置和法线向量传递给几何着色器。 它可以将两者都转换为视图空间,可以在几何着色器中执行相同的操作。 通过uniform缓冲区提供转换的顶点着色器的示例源代码是:
#version 450
layout( location = 0 ) in vec4 app_position;
layout( location = 1 ) in vec3 app_normal;
layout( set = 0, binding = 0 ) uniform UniformBuffer {
mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
};
layout( location = 0 ) out vec4 vert_normal;
void main() {
gl_Position = ModelViewMatrix * app_position;
vert_normal = vec4( mat3( ModelViewMatrix ) * app_normal * 0.2, 0.0 );
}
在前面的代码中,位置和法线向量都使用模型视图矩阵转换到视图空间。 如果我们打算非均匀地缩放模型(对于所有维度不是相同的比例),则必须使用模型 - 视图矩阵的逆转置来转换法向量。
代码中最重要的部分是在几何体内部执行。 它采用形成原始基本类型(通常是三角形)的顶点,但输出形成线段的顶点。 它需要一个输入顶点,将其转换为裁剪空间并进一步传递。 第二次使用相同的顶点,但这次它被顶点法线偏移。 转换后,它将转换为裁剪空间并传递给输出。 对形成原始图元的所有顶点执行这些操作。 整个几何着色器的源代码可能如下所示:
#version 450
layout( triangles ) in;
layout( location = 0 ) in vec4 vert_normal[];
layout( set = 0, binding = 0 ) uniform UniformBuffer {
mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
};
layout( line_strip, max_vertices = 6 ) out;
layout( location = 0 ) out vec4 geom_color;
void main() {
for( int vertex = 0; vertex < 3; ++vertex ) {
gl_Position = ProjectionMatrix * gl_in[vertex].gl_Position;
geom_color = vec4( 0.2 );
EmitVertex();
gl_Position = ProjectionMatrix * (gl_in[vertex].gl_Position + vert_normal[vertex]);
geom_color = vec4( 0.6 );
EmitVertex();
EndPrimitive();
}
}
几何着色器将顶点着色器转换的顶点移到视图空间,并将它们进一步转换为裁剪空间。 这是通过与顶点着色器中使用的相同的uniform缓冲区提供的投影矩阵来完成的。 为什么我们在一个uniform缓冲区中定义两个矩阵变量,如果我们在顶点着色器中只使用其中一个,而在几何着色器中只使用第二个? 这种方法更方便,因为我们只需要创建一个缓冲区,我们只需要将一个描述符集绑定到命令缓冲区。 通常,我们在命令缓冲区中执行或记录的操作越少,我们实现的性能就越高。 所以这种方法也应该更快。
片段着色器很简单,因为它只传递几何着色器存储的插值颜色:
#version 450
layout( location = 0 ) in vec4 geom_color;
layout( location = 0 ) out vec4 frag_color;
void main() {
frag_color = geom_color;
}
使用前面的着色器绘制几何图形的结果可以在下图中看到: