本篇在讲什么 OpenGL蓝宝书第八章学习笔记之几何着色器 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C++语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 |
★提高阅读体验★ 👉 ♠ 一级标题 👈👉 ♥ 二级标题 👈👉 ♣ 三级标题 👈👉 ♦ 四级标题 👈 |
目录
- ♠ 几何着色器
- ♥ 传递几何着色器
- ♥ 在应用程序中使用几何着色器
- ♥ 删除几何着色器中的几何
- ♥ 在几何着色器中生成几何体
- ♥ 修改几何着色器中的基元类型
- ♥ 多视口转换
- ♠ 推送
- ♠ 结语
♠ 几何着色器
几何着色器作为可选择的可编程部分,其位于曲面细分和片段着色器之间,如果不设置几何着色器,则曲面细分的输出直接到片段着色器,以下阐述有关几何着色器的一些特点
-
可以一次性处理整个基元(三角形、线条或点)
-
可以通过编程方式实际改变OpenGL管线中的数据量
-
可以访问基元中的所有顶点,可更改基元类型,甚至可以创建和销毁基元
♥ 传递几何着色器
下面是一个最简单的一个着色器例子,这种传递几何着色器将其输入直接发送给输出而不经任何修改
#version 410 core
layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;
void main(void)
{
int i;
for (i = 0; i < gl_in.length(); i++)
{
gl_Position = gl_in[i].gl_Position;
EmitVertex();
}
EndPrimitive();
}
我们简单分析一下这部分着色器代码,首先第一部分我们使用布局限定符设置输入输出类型和最大顶点数量
- 使用布局限定符
triangles
作为输入 - 使用布局限定符
triangle_strip
作为输出 - 使用布局限定符
max_vertices=3
指定着色器应生成的最大顶点数量为三
接下来是main()函数
该着色器含一个循环,且该循环根据内置数组gl_in
的长度运行很多次
gl_in是几何着色器的另一个特有变量,包含顶点着色器入的所有内置变量
gl_in[]数组的长度由输入基元模式确定。在该着色器中,三角形为输入基元模式,因此gl_in[]的尺寸为3
Emitvertex()
该内置函数专对几何着色器,告知着色器我们已经完成对该顶点的操作
EndPrimitive()
该内置函数告知着色器我们已经生成当前基元的各个顶点,应继续下一个基元的操作
♥ 在应用程序中使用几何着色器
下面代码创建了一个几何着色器
glCreateShader(GL_GEOMETRY_SHADER);
与其他着色器类型相同,通过调用glcreateShader()函数创建几何着色器,并使用GL_GEOMETRY_SHADER作为着色器类型
和其他着色器相同,通过调用glCompileshader()函数编译着色器,并通过调用glAttachshader()函数将其附加到程序对象。然后使用glLnkProgram()函数将程序正常链接
几何着色器接收的基元必须与其自身所预期的输入基元模式匹配
当曲面细分未激活时,绘图命令中使用的基元模式必须与几何着色器输入基元模式匹配。例如,如果几何着色器的输入基元模式为点,则可在调用glDrawArrays()时只使用GL POINTS。如果几何着色器的输入基元模式为三角形,则可在调用glDrawArrays()时使用GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN
几何着色器输入基元模式和可用几何类型如下表所示
几何着色器输入模式 | 可用绘图模式 |
---|---|
points | GL_POINTS |
lines | GL_LINES,GL_LINE_LOOP,GL_LINE_STRIP |
triangles | GL_TRIANGLES,GL_TRIANGLE_FAN,GL_TRIANGLE_STRIP |
lines_adjacency | GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY |
triangles_adjacency | GL_TRIANGLES_ADJACENCY,GL_TRIANGLE_STRIP_ADJACENCY |
当曲面细分被激活时,绘图命令中使用的模式应始终为GL_PATCHES
,几何着色器的输入基元模式应与曲面细分基元模式匹配。输入基元类型使用布局限定符在几何着色器主体内指定。输入布局限定符的一般形式为
layout (primitive_type) in;
几何着色器预定义输入存储在名为gL_in[]的内置数组中,其结构如下所示
in gl_PerVertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
}gl_in[];
几何着色器的输入数组长度取决于所处理的基元类型,上文我们已知三角形的长度是3,下表列出了对应基元类型输入数组大小
输入基元类型 | 输入数组大小 |
---|---|
points | 1 |
lines | 2 |
triangles | 3 |
lines_adjacency | 4 |
triangles_adjacency | 6 |
♥ 删除几何着色器中的几何
如果正在运行几何着色器,但从未针对该特定基元调EmitVertex(),则不会绘制任何东西。因此,我们可以实现一种剔除几何图形的自定义背面剔除程序,在蓝宝书中提供了详细示例gsculling
以供参考,我们这里截取示例中的几何着色器的main
代码
void main(void)
{
int n;
vec3 ab = gl_in[1].gl_Position.xyz - gl_in[0].gl_Position.xyz;
vec3 ac = gl_in[2].gl_Position.xyz - gl_in[0].gl_Position.xyz;
vec3 normal = normalize(cross(ab, ac));
vec3 transformed_normal = (mat3(mvMatrix) * normal);
vec4 worldspace = /* mvMatrix * */ gl_in[0].gl_Position;
vec3 vt = normalize(viewpoint - worldspace.xyz);
if (dot(normal, vt) > 0.0) {
for (n = 0; n < 3; n++) {
gl_Position = mvpMatrix * gl_in[n].gl_Position;
color = vertex[n].color;
EmitVertex();
}
EndPrimitive();
}
}
注意在满足(dot(normal, vt) > 0.0)
条件下EmitVertex()
和EndPrimitive()
才会被调用,运行效果如下图所示
如果去除掉限制条件,则不会有剔除效果,运行效果如下图所示
♥ 在几何着色器中生成几何体
可以根据需要多次调用 EmitVertex()和 EndPrimitive()以生成新几何体,以下代码摘自蓝宝书示例gstessellate
#version 410 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 12) out;
uniform float stretch = 0.7;
flat out vec4 color;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
void make_face(vec3 a, vec3 b, vec3 c)
{
vec3 face_normal = normalize(cross(c - a, c - b));
vec4 face_color = vec4(1.0, 0.4, 0.7, 1.0) * (mat3(mvMatrix) * face_normal).z;
gl_Position = mvpMatrix * vec4(a, 1.0);
color = face_color;
EmitVertex();
gl_Position = mvpMatrix * vec4(b, 1.0);
color = face_color;
EmitVertex();
gl_Position = mvpMatrix * vec4(c, 1.0);
color = face_color;
EmitVertex();
EndPrimitive();
}
void main(void)
{
int n;
vec3 a = gl_in[0].gl_Position.xyz;
vec3 b = gl_in[1].gl_Position.xyz;
vec3 c = gl_in[2].gl_Position.xyz;
vec3 d = (a + b) * stretch;
vec3 e = (b + c) * stretch;
vec3 f = (c + a) * stretch;
a *= (2.0 - stretch);
b *= (2.0 - stretch);
c *= (2.0 - stretch);
make_face(a, d, f);
make_face(d, b, e);
make_face(e, c, f);
make_face(d, e, f);
EndPrimitive();
}
我们可以从中看出我们通过一个矩阵变换mvpMatrix
重新设置了每个顶点的xyz,还给每个顶点重新设置了颜色face_color
,执行效果如下图
♥ 修改几何着色器中的基元类型
下面示例中我们将把几何类型从三角形改为线,该示例节选字蓝宝书示例normalviewer
static const char * gs_source[] =
{
"#version 410 core \n"
" \n"
"layout (triangles) in; \n"
"layout (line_strip, max_vertices = 4) out; \n"
" \n"
"uniform mat4 mv_matrix; \n"
"uniform mat4 proj_matrix; \n"
" \n"
"in VS_OUT \n"
"{ \n"
" vec3 normal; \n"
" vec4 color; \n"
"} gs_in[]; \n"
" \n"
"out GS_OUT \n"
"{ \n"
" vec3 normal; \n"
" vec4 color; \n"
"} gs_out; \n"
" \n"
"uniform float normal_length = 0.2; \n"
" \n"
"void main(void) \n"
"{ \n"
" mat4 mvp = proj_matrix * mv_matrix; \n"
" vec3 ab = gl_in[1].gl_Position.xyz - gl_in[0].gl_Position.xyz; \n"
" vec3 ac = gl_in[2].gl_Position.xyz - gl_in[0].gl_Position.xyz; \n"
" vec3 face_normal = normalize(cross(ab, ac)); \n"
" \n"
" vec4 tri_centroid = (gl_in[0].gl_Position + \n"
" gl_in[1].gl_Position + \n"
" gl_in[2].gl_Position) / 3.0; \n"
" \n"
" gl_Position = mvp * tri_centroid; \n"
" gs_out.normal = gs_in[0].normal; \n"
" gs_out.color = gs_in[0].color; \n"
" EmitVertex(); \n"
" \n"
" gl_Position = mvp * (tri_centroid + \n"
" vec4(face_normal * normal_length, 0.0)); \n"
" gs_out.normal = gs_in[0].normal; \n"
" gs_out.color = gs_in[0].color; \n"
" EmitVertex(); \n"
" EndPrimitive(); \n"
" \n"
" gl_Position = mvp * gl_in[0].gl_Position; \n"
" gs_out.normal = gs_in[0].normal; \n"
" gs_out.color = gs_in[0].color; \n"
" EmitVertex(); \n"
" \n"
" gl_Position = mvp * (gl_in[0].gl_Position + \n"
" vec4(gs_in[0].normal * normal_length, 0.0)); \n"
" gs_out.normal = gs_in[0].normal; \n"
" gs_out.color = gs_in[0].color; \n"
" EmitVertex(); \n"
" EndPrimitive(); \n"
"} \n"
};
最终展示效果如下图所示
♥ 多视口转换
OpenGL有助于你同时使用多个视口,这是一种称为视口数组的功能
为使用视口数组,首先需要指定想要使用的视口边界。为此,可以调用glviewportIndexedf()
或glViewportIndexedfv()
void glviewportIndexedf(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h);
void glviewportIndexedfv(GLuint index, const GLfloatW * v);
void glDepthRangeIndexed(GLuint index, GLdouble n, GLdouble f);
index是要修改的视口索引。注意,有索引的视口命令的视口参数为浮点值,而不是 glviewport()所用的整数
OpenGL至少支持 16 个视口,因此 index 的范围为0~15
每个视口都有自己的深度范围,可通过调用 glDepthRangeIndexed()指定,index的值可能为0~15
如果要一次性设置多个视口,则可以考虑使用glviewportArrayv()和gDepthRangeArrayv()
void glViewportArrayv(GLuint first, GLsizei count, const GLfloat * v);
void glDepthRangeArrayv(GLuint first, GLsizei count, const GLdouble * v);
一旦指定视口,你需要将几何图形定向到这些视口。这可以通过使用几何着色器完成。写入内置变量gl_viewportIndex 选择要染到哪个视口
♠ 推送
- Github
https://github.com/KingSun5
♠ 结语
若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。