几何着色器
在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader),几何着色器的输入是一个图元(如点或三角形)的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而,几何着色器最有趣的地方在于,它能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点。
几何着色器
#version 330 core
//对应glDrawArrays一种绘制方式: points、lines、lines_adjacency、triangles、triangles_adjacency
layout (points) in;
//线的方式输出,并限制最大顶点数为2
layout (line_strip, max_vertices = 2) out;
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关键字前声明一个布局修饰符(Layout Qualifier)。这个输入布局修饰符可以从顶点着色器接收下列任何一个图元值, 对应了不同的绘制方式下的图元值: points:绘制GL_POINTS图元时(1)。 lines:绘制GL_LINES或GL_LINE_STRIP时(2) lines_adjacency:GL_LINES_ADJACENCY或GL_LINE_STRIP_ADJACENCY(4) triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN(3) triangles_adjacency:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY(6)
以上是能提供给glDrawArrays渲染函数的几乎所有图元了。如果我们想要将顶点绘制为GL_TRIANGLES,我们就要将输入修饰符设置为triangles。括号内的数字表示的是一个图元所包含的最小顶点数。
输出: 接下来,我们还需要指定几何着色器输出的图元类型,这需要在out关键字前面加一个布局修饰符。和输入布局修饰符一样,输出布局修饰符也可以接受几个图元值:点线三角形 points line_strip triangle_strip 几何着色器同时希望我们设置一个它最大能够输出的顶点数量(如果你超过了这个值,OpenGL将不会绘制多出的顶点),这个也可以在out关键字的布局修饰符中设置
为了生成更有意义的结果,我们需要某种方式来获取前一着色器阶段的输出。GLSL提供给我们一个内建(Built-in)变量,在内部看起来(可能)是这样的:
in gl_Vertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[];要注意的是,它被声明为一个数组,因为大多数的渲染图元包含多于1个的顶点,而几何着色器的输入是一个图元的所有顶点。
有了之前顶点着色器阶段的顶点数据,我们就可以使用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被调用时,所有发射出的(Emitted)顶点都会合成为指定的输出渲染图元。在一个或多个EmitVertex调用之后重复调用EndPrimitive能够生成多个图元。
实践一,创建更多顶点
- 顶点
// 2d顶点 + 顶点颜色
float points[] = {
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // top-left
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // top-right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // bottom-right
-0.5f, -0.5f, 1.0f, 1.0f, 0.0f // bottom-left
};
unsigned int VBO, VAO;
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), &points, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);- 绘制
// 使用 GL_POINTS 绘制
shader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_POINTS, 0, 4);- 着色器
- 几何着色器
#version 330 core //1. 对应glDrawArrays(GL_POINTS, layout (points) in; //2. triangle_strip输出, 限制最多5个顶点 layout (triangle_strip, max_vertices = 5) out; //3. 接受顶点的颜色值 in VS_OUT { vec3 color; } gs_in[]; //4. 传递颜色值 out vec3 fColor; //5. `EmitVertex`创建顶点,`EndPrimitive`创建图元。原本是gl_in[0].gl_Position, 输出创建的顶点 void build_house(vec4 position) { //5.1 接收的颜色值 fColor = gs_in[0].color; // gs_in[0] since there's only one input vertex //1. 顶点1 gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:bottom-left EmitVertex(); //2. 顶点2 gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:bottom-right EmitVertex(); //3. 顶点3 gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:top-left EmitVertex(); //4. 顶点4 gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:top-right EmitVertex(); //5. 顶点5 gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:top //这里改变颜色,将会影响后面的顶点颜色。之前的颜色为上次的改变。 //比如在第一个顶点结束, 第二个顶点开始设置颜色。则在下次改变之前都使用这个颜色,作为顶点颜色传递。 //也可以这么理解: 一个 EmitVertex调用对应一次顶点着色器执行,虽然不准确,但是相对比较好理解。 fColor = vec3(0.0, 1.0, 0.0); EmitVertex(); EndPrimitive(); } void main() { build_house(gl_in[0].gl_Position); }- 顶点着色器
// 传递顶点和颜色,2d顶点z=0 #version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec3 aColor; out VS_OUT { vec3 color; } vs_out; void main() { vs_out.color = aColor; gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); }- 片元着色器
#version 330 core out vec4 FragColor; in vec3 fColor; void main() { FragColor = vec4(fColor, 1.0); }
实践二,爆破物体
当我们说爆破一个物体时,我们并不是指要将宝贵的顶点集给炸掉,我们是要将每个三角形沿着法向量的方向移动一小段时间。效果就是,整个物体看起来像是沿着每个三角形的法线向量爆炸一样。爆炸三角形的效果在纳米装模型上看起来像是这样的:
- 重要点
- 法向量, 这里使用垂直于三顶点组成的平面(顶点不在一条线)
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); // cross 作为法向量, 所以顺序很重要 return normalize(cross(a, b)); }- 移动的爆破函数
vec4 explode(vec4 position, vec3 normal) { float magnitude = 2.0; // time 作为变量,使用shader传递参数 // 法向量 * magnitude 的 0-1 的sin值,作为移动向量 vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude; return position + vec4(direction, 0.0); } - 模型和参数准备
Shader shader("./res/shaders/09geometry/02geometry_shader.vs", "./res/shaders/09geometry/02geometry_shader.gs", "./res/shaders/09geometry/02geometry_shader.fs");
Model nanosuit("./res/models/nanosuit/nanosuit.obj");- 绘制
//。。。
// 设置shader 的time变量
shader.setFloat("time", glfwGetTime());
//。。。
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);- 几何着色器, 顶点着色器使用mvp和uv的顶点着色器, 片元着色器使用贴图 uv的片元着色器
#version 330 core
//1. 对应glDrawElements(GL_TRIANGLES
layout (triangles) in;
//2. 限制为最大3个顶点
layout (triangle_strip, max_vertices = 3) out;
//3. 接受传递uv
in VS_OUT {
vec2 texCoords;
} gs_in[];
//4. 传递uv
out vec2 TexCoords;
//接受 time 输入
uniform float time;
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);
}
vec3 GetNormal()
{
//GL_TRIANGLES 模式下, gl_in 为三个大小的数据, 三个顶点
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));
}
void main() {
//计算三角形的垂直向量
vec3 normal = GetNormal();
//重新设置位置
gl_Position = explode(gl_in[0].gl_Position, normal);
//对应顶点的uv 不变
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();
}实践三,法线可视化
- 几何着色器
#version 330 core
//1. 对应glDrawElements(GL_TRIANGLES
layout (triangles) in;
//2. 使用line_strip, 因为这里只绘制法线的颜色线段
//max_vertices 限制为6, 是因为需要另外多创建一组。
layout (line_strip, max_vertices = 6) out;
//3. 接收normal 输入
in VS_OUT {
vec3 normal;
} gs_in[];
const float MAGNITUDE = 0.4;
//4. 为法线生成对应线段的另一个顶点, index 为三角形的顶点index,储存于gl_in[index]中
void GenerateLine(int index)
{
//4.1 第一个顶点不变
gl_Position = gl_in[index].gl_Position;
EmitVertex();
//2. 第二个顶点 偏移法线 *MAGNITUDE 的距离。
gl_Position = gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE;
EmitVertex();
EndPrimitive();
}
void main()
{
GenerateLine(0); // 第一个顶点法线
GenerateLine(1); // 第二个顶点法线
GenerateLine(2); // 第三个顶点法线
}