一、几何着色器
输入:一个图元的一组顶点
输出:变换后新的图元的一组顶点
如下:
#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out; //设置输出的最大顶点数为2
void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
在几何着色器的顶部,我们使用in
关键字声明一个布局修饰符,修饰几何着色器输入的图元类型。
- points:绘制GL_POINTS图元时
- lines:绘制GL_LINES或GL_LINE_STRIP时
- lines_adjacency:GL_LINES_ADJACENCY或GL_LINE_STRIP_ADJACENCY
- triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN
- triangles_adjacency:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY
同样,我们也可以使用out
关键字声明几何着色器的输出图元类型。 - points
- line_strip
- triangle_strip
同样的,当数据很多的时候我们可以定义一个接口块。
in gl_Vertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[];
然后,有了顶点着色器阶段的顶点数据后,我们可以使用2个几何着色器中的函数EmitVertex
和EndPrimitive
来生成新的数据。如下,我们生成一个线条图元。
void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
调用EmitVertex
,将gl_Position
中的向量添加到图元中。
当调用EndPrimitive
时,所有输入的顶点会被合成为指定的输出渲染图元。当一个或多个EmitVertex
之后重复调用EndPrimitive
时,这两个顶点会合成为多个包含两个顶点的线条。
二、使用几何着色器
- 定义顶点坐标
float points[] = {
-0.5f, 0.5f, // 左上
0.5f, 0.5f, // 右上
0.5f, -0.5f, // 右下
-0.5f, -0.5f // 左下
};
- 配置顶点着色器和片段着色器
//顶点
#version 330 core
layout (location = 0) in vec2 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
//片段
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
这时直接绘制的话,会得到黑暗场景中的四个点。
shader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_POINTS, 0, 4);
- 配置几何着色器,接受一个点图元,直接传递到下一个着色器
//几何
#version 330 core
layout (points) in;
layout (points, max_vertices = 1) out;
void main() {
gl_Position = gl_in[0].gl_Position;
EmitVertex();
EndPrimitive();
}
编译和链接着色器源码
geometryShader = glCreateShader(GL_GEOMETRY_SHADER); //几何着色器类型
glShaderSource(geometryShader, 1, &gShaderCode, NULL);
glCompileShader(geometryShader);
...
glAttachShader(program, geometryShader);
glLinkProgram(program);
然而现在效果还是没变,所以我们需要再进行一些处理。
三、造几个房子
OpenGL中,三角形带(Triangle Strip)是绘制三角形更高效的方式,它使用顶点更少。在第一个三角形绘制完之后,每个后续顶点将会在上一个三角形边上生成另一个三角形:每3个临近的顶点将会形成一个三角形。如下:
代码如下:
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;
void build_house(vec4 position)
{
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:顶部
EmitVertex();
EndPrimitive();
}
void main() {
build_house(gl_in[0].gl_Position);
}
四、爆破物体
像这种效果:
实现的原理是将每一个三角形沿着法线方向移动一小段距离。
- 获取法向量
利用平行三角的两个不平行的向量a和b,再利用叉乘就可以获取法向量了。
vec3 GetNormal()
{
vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);
vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
return normalize(cross(a, b));
}
- 设置偏移函数
vec4 explode(vec4 position, vec3 normal)
{
float magnitude = 2.0;
vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude;
return position + vec4(direction, 0.0);
}
完整的代码如下:
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT {
vec2 texCoords;
} gs_in[];
out vec2 TexCoords;
uniform float time;
vec4 explode(vec4 position, vec3 normal) { ... }
vec3 GetNormal() { ... }
void main() {
vec3 normal = GetNormal();
gl_Position = explode(gl_in[0].gl_Position, normal);
TexCoords = gs_in[0].texCoords;
EmitVertex();
gl_Position = explode(gl_in[1].gl_Position, normal);
TexCoords = gs_in[1].texCoords;
EmitVertex();
gl_Position = explode(gl_in[2].gl_Position, normal);
TexCoords = gs_in[2].texCoords;
EmitVertex();
EndPrimitive();
}