计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 13.几何着色器(一)修改顶点
几何着色器
以下是OpenGL图像管线的主要阶段:
几何着色器(Geometry Shader)
几何着色器是OpenGL管线中的一个可选阶段,位于顶点着色器和片段着色器之间。它能够动态地生成或修改图元(primitives)。
主要特点
-
图元操作能力
- 可以创建新的顶点
- 可以修改现有顶点
- 可以删除顶点
- 可以改变图元类型
-
输入/输出图元类型
- 输入:points、lines、triangles等
- 输出:points、line_strip、triangle_strip等
基本语法
#version 430// 定义输入图元类型
layout (triangles) in;// 定义输出图元类型和最大顶点数
layout (triangle_strip, max_vertices = 3) out;// 输入变量(必须声明为数组)
in vec3 varyingNormal[];
in vec3 varyingColor[]; // 输出变量
out vec3 gNormal;
out vec3 gColor;void main() {// 处理每个顶点for(int i = 0; i < 3; i++) {// 发射一个顶点gl_Position = gl_in[i].gl_Position;gNormal = varyingNormal[i];gColor = varyingColor[i];EmitVertex();}// 结束图元EndPrimitive();
}
常见应用
-
几何细分
- 增加模型细节
- 动态LOD(Level of Detail)
- 曲面细分
-
粒子效果
- 粒子系统
- 特效生成
- 动态粒子发射
-
阴影体生成
- 体积阴影
- 轮廓线提取
- 阴影体挤出
-
实例化渲染
- 批量复制几何体
- 程序化生成几何体
- 植被渲染
性能考虑
- 几何着色器会增加GPU负载
- 应该限制输出顶点的数量
- 复杂的几何操作可能影响性能
- 建议在必要时才使用几何着色器
修改顶点
这是我们在前面的例子中渲染出来的图形
如果我们想要将其充气膨胀,我们可以使用几何着色器来修改顶点。
即将顶点沿法线方向移动一定的距离。这样外面的顶点向外膨胀,里面的顶点向内收缩。
上图是做出来的效果,有些细节没有处理好,但是基本效果已经出来了。
几何着色器中图元输入类型的选项有:
输入布局限定符 | 描述 | 顶点数 | 典型用途 |
---|---|---|---|
points | 点图元 | 1 | 粒子效果 |
lines | 独立线段 | 2 | 线条渲染 |
lines_adjacency | 带邻接信息的线段 | 4 | 轮廓检测 |
line_strip | 线带 | 2 | 连续线段 |
line_strip_adjacency | 带邻接信息的线带 | 4 | 曲线平滑 |
triangles | 独立三角形 | 3 | 基本3D渲染 |
triangles_adjacency | 带邻接信息的三角形 | 6 | 边缘检测 |
triangle_strip | 三角形带 | 3 | 连续曲面 |
triangle_strip_adjacency | 带邻接信息的三角形带 | 6 | 细分曲面 |
重点
- 几何着色器中,输入图元类型和输出图元类型必须相同
- 需要使用
layout
限定符来指定输入和输出图元类型 - 需要使用
EmitVertex()
函数来发射顶点 - 需要使用
EndPrimitive()
函数来结束图元 - 从顶点着色器接收的输入变量必须声明为数组
- 从顶点着色器中输出的gl_Position值不能有投影矩阵,投影矩阵会在几何着色器中应用
gl_in 内置变量说明
gl_in
是几何着色器中的一个内置变量数组,用于访问来自顶点着色器的内置变量值。
主要特点
- 数据结构
in gl_PerVertex {vec4 gl_Position;float gl_PointSize;float gl_ClipDistance[];
} gl_in[];
- 数组大小
- 数组大小取决于输入图元类型:
points
: 1个顶点lines
: 2个顶点triangles
: 3个顶点lines_adjacency
: 4个顶点triangles_adjacency
: 6个顶点
- 常用成员
gl_in[i].gl_Position
: 顶点位置gl_in[i].gl_PointSize
: 点精灵大小(仅用于点图元)gl_in[i].gl_ClipDistance[]
: 裁剪距离数组
使用示例
// 在几何着色器中访问顶点位置
void main() {for(int i = 0; i < 3; i++) {// 获取顶点位置vec4 position = gl_in[i].gl_Position;// 对位置进行处理position = position + vec4(offset, 0.0);// 设置新的顶点位置gl_Position = position;EmitVertex();}EndPrimitive();
}
注意事项
gl_in
数组的大小是固定的,由输入图元类型决定- 只能在几何着色器中访问
- 是只读变量,不能修改其值
修改顶点的几何着色器代码
#version 430// 定义输入图元类型为三角形
layout (triangles) in;// 从顶点着色器接收的输入变量(必须声明为数组)
in vec3 varyingNormal[]; // 法线向量数组
in vec3 varyingLightDir[]; // 光照方向数组
in vec3 varyingVertPos[]; // 顶点位置数组// 传递给片段着色器的输出变量
out vec3 gNormal; // 法线向量
out vec3 gLightDir; // 光照方向
out vec3 gVertPos; // 顶点位置// uniform变量声明
uniform mat4 proj_matrix; // 投影矩阵
uniform mat4 norm_matrix; // 法线变换矩阵// 定义输出图元类型为三角形带,每个图元最多输出3个顶点
layout (triangle_strip, max_vertices = 3) out;void main(void)
{// 处理三角形的每个顶点for (int i = 0; i < 3; i++){// 计算膨胀效果vec3 normal = normalize(varyingNormal[i]); // 归一化法线向量// 将顶点沿法线方向移动(膨胀效果)// 注释掉的是带投影矩阵的版本//gl_Position = proj_matrix * gl_in[i].gl_Position + normalize(vec4(normal, 1.0));gl_Position = gl_in[i].gl_Position + normalize(vec4(normal, 1.0)) * 0.5;// 将变量传递给片段着色器gNormal = varyingNormal[i]; // 传递法线gLightDir = varyingLightDir[i]; // 传递光照方向gVertPos = varyingVertPos[i]; // 传递顶点位置// 发射顶点EmitVertex();}// 结束当前图元的构建EndPrimitive();
}
上述代码中,几何着色器将每个顶点沿法线方向移动0.5个单位,从而实现膨胀效果。
但该代码有一个问题,就是如果gl_Position已经是在投影空间后,是不能直接去进行修改顶点的,我们需要在摄像机空间中来修改顶点,然后再使用投影矩阵
以下是修改后的代码的效果图:
代码修改
顶点着色器中修改
顶点着色器中,我们使用模型视图矩阵来计算顶点位置
// 计算裁剪空间中的顶点位置 ,由于要在几何着色器中进行膨胀处理,所以这里不使用投影矩阵//gl_Position = proj_matrix * mv_matrix * vec4(vertPos,1.0);gl_Position = mv_matrix * vec4(vertPos,1.0);
几何着色器中修改
最后,我们需要在几何着色器中修改gl_Position,将顶点位置乘以投影矩阵,
// 计算膨胀效果gl_Position =proj_matrix*( gl_in[i].gl_Position + normalize(vec4(normal, 1.0)) * 0.5);