【OpenGL】LearnOpenGL学习笔记19 - 几何着色器 Geometry Shader
上接:https://blog.csdn.net/weixin_44506615/article/details/151043459?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL
几何着色器 (Geometry Shader)
接下来认识一种新的着色器 几何着色器,它在顶点和片段着色器之间执行,其输入是一个图元 (点或三角形),它可以对其进行随意的变换
#version 330 corelayout (points) in;
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();// 结束line_strip图元EndPrimitive();
}
第二行 layout (points) 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 |
第三行 layout (line_strip, max_vertices = 2) out;
表示输出的图元类型
这里的 gl_in
是一个GLSL的内建变量
in gl_Vertex
{vec4 gl_Position;float gl_PointSize;float gl_ClipDistance[];
} gl_in[];
接下来尝试几个简单的几何着色器应用
在这之前我们先修改一下Shader类以加载几何着色器
Shader.h
// 增加可选的geoPath
Shader(const char* vertexPath, const char* fragmentPath, const char* geoPath = nullptr);
Shader.cpp
增加几何着色器
Shader::Shader(const char* vertexPath, const char* fragmentPath, const char* geoPath)
{std::string vertexCode;std::string fragmentCode;std::string geoCode;try{if (vertexPath != nullptr){std::ifstream vertexShaderFile;vertexShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);vertexShaderFile.open(vertexPath);std::stringstream vShaderStream;vShaderStream << vertexShaderFile.rdbuf();vertexShaderFile.close();vertexCode = vShaderStream.str();}if (fragmentPath != nullptr){std::ifstream fragmentShaderFile;fragmentShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);fragmentShaderFile.open(fragmentPath);std::stringstream fShaderStream;fShaderStream << fragmentShaderFile.rdbuf();fragmentShaderFile.close();fragmentCode = fShaderStream.str();}if (geoPath != nullptr){std::ifstream geoShaderFile;geoShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);geoShaderFile.open(geoPath);std::stringstream gShaderStream;gShaderStream << geoShaderFile.rdbuf();geoShaderFile.close();geoCode = gShaderStream.str();}}catch (std::ifstream::failure e){std::cout << "[Error] Failed to read shader file" << std::endl;}ProgramID = glCreateProgram();int success;char infoLog[512];// 顶点着色器unsigned int vertexShader = 0;if (!vertexCode.empty()){const char* vertexShaderCode = vertexCode.c_str();vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderCode, NULL);glCompileShader(vertexShader);glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "[Error] Vertex shader compile failed!\n" << infoLog << std::endl;};glAttachShader(ProgramID, vertexShader);}// 片段着色器unsigned int fragmentShader = 0;if (!fragmentCode.empty()){const char* fragmentShaderCode = fragmentCode.c_str();fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderCode, NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "[Error] Fragment shader compile failed!\n" << infoLog << std::endl;}glAttachShader(ProgramID, fragmentShader);}// 几何着色器unsigned int geoShader = 0;if (!geoCode.empty()){const char* geoShaderCode = geoCode.c_str();geoShader = glCreateShader(GL_GEOMETRY_SHADER);glShaderSource(geoShader, 1, &geoShaderCode, NULL);glCompileShader(geoShader);glGetShaderiv(geoShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(geoShader, 512, NULL, infoLog);std::cout << "[Error] Geo shader compile failed!\n" << infoLog << std::endl;}glAttachShader(ProgramID, geoShader);}glLinkProgram(ProgramID);glGetProgramiv(ProgramID, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(ProgramID, 512, NULL, infoLog);std::cout << "[Error] Shader program link failed!\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);glDeleteShader(geoShader);
}
1、绘制小房子
首先我们需要一个点
// 几何着色器小房子顶点
float housePoints[] = {-0.5f, 0.5f,
};unsigned int houseVAO, houseVBO;
glGenVertexArrays(1, &houseVAO);
glGenBuffers(1, &houseVBO);
glBindVertexArray(houseVAO);
glBindBuffer(GL_ARRAY_BUFFER, houseVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(housePoints), &housePoints, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
接着创建小房子的顶点、片段和几何着色器
HouseVertex.glsl 新建
#version 330 corelayout (location = 0) in vec2 aPos;void main()
{gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
HouseFragment.glsl 新建
#version 330 coreout vec4 FragColor;void main()
{FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
HouseGeo.glsl 新建
依次绘制组成小房子的三个三角形
#version 330 corelayout (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);
}
最后创建着色器并绘制
Shader houseShader("Shader/HouseVertex.glsl", "Shader/HouseFragment.glsl", "Shader/HouseGeo.glsl");
//...
houseShader.Use();
glBindVertexArray(houseVAO);
glDrawArrays(GL_POINTS, 0, 1);
编译运行,顺利的话可以看见以下图像
线框模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
houseShader.Use();
glBindVertexArray(houseVAO);
glDrawArrays(GL_POINTS, 0, 1);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2、“爆破”
接下来我们实现一个背包爆破的效果
首先通过叉乘计算图元法线,接着将顶点沿图元法线方向偏移
BagExplodeGeo.glsl 新增
#version 330 corelayout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;in VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} gs_in[];out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} gs_out;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()
{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);gs_out.FragPos = gs_in[0].FragPos;gs_out.Normal = gs_in[0].Normal;gs_out.TexCoords = gs_in[0].TexCoords;gs_out.Position = gs_in[0].Position;EmitVertex();gl_Position = Explode(gl_in[1].gl_Position, normal);gs_out.FragPos = gs_in[1].FragPos;gs_out.Normal = gs_in[1].Normal;gs_out.TexCoords = gs_in[1].TexCoords;gs_out.Position = gs_in[1].Position;EmitVertex();gl_Position = Explode(gl_in[2].gl_Position, normal);gs_out.FragPos = gs_in[2].FragPos;gs_out.Normal = gs_in[2].Normal;gs_out.TexCoords = gs_in[2].TexCoords;gs_out.Position = gs_in[2].Position;EmitVertex();EndPrimitive();
}
顶点和片段着色器需要一并换成接口块
VertexShader.glsl
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;// 先保留输出
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out vec3 Position;uniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} vs_out;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;Position = vec3(model * vec4(aPos, 1.0));vs_out.Position = Position;
}
FragmentShader.glsl
// ...
in VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} fs_in;
// ...
Main.cpp
// 加上几何着色器
Shader shader("Shader/VertexShader.glsl", "Shader/FragmentShader.glsl", "Shader/BagExplodeGeo.glsl");// 主循环
// 设置时间
shader.SetFloat("time", glfwGetTime());
编译运行,顺利的话可以看见背包在爆破又恢复接着又爆破
3、法线可视化
最后来做一个很使用的功能 法线可视化
我们从顶点着色器中传递法向量到几何着色器,然后在几何着色器中沿着法向量画一条线
NormalVisualVertex.glsl 新增
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out VS_OUT {vec3 normal;
} vs_out;layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};
uniform mat4 model;void main()
{gl_Position = view * model * vec4(aPos, 1.0); mat3 normalMatrix = mat3(transpose(inverse(view * model)));vs_out.normal = normalize(vec3(vec4(normalMatrix * aNormal, 0.0)));
}
NormalVisualGeo.glsl 新增
#version 330 corelayout (triangles) in;
layout (line_strip, max_vertices = 6) out;in VS_OUT {vec3 normal;
} gs_in[];const float MAGNITUDE = 0.2;layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};void GenerateLine(int index)
{gl_Position = projection * gl_in[index].gl_Position;EmitVertex();gl_Position = projection * (gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE);EmitVertex();EndPrimitive();
}void main()
{GenerateLine(0); GenerateLine(1); GenerateLine(2);
}
GreenFragment.glsl 由HouseFragment.glsl改名
#version 330 coreout vec4 FragColor;void main()
{FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
Main.cpp
// 法线可视化Shader
Shader normalVisualShader("Shader/NormalVisualVertex.glsl", "Shader/GreenFragment.glsl", "Shader/NormalVisualGeo.glsl");
// 绑定UBO槽
BindMatericesBlock(normalVisual, normalVisualShader, 0);// 主循环
// 绘制背包之后
// 法线可视化
normalVisualShader.Use();
normalVisualShader.SetMat4("model", modelMatrix);
model.Draw(normalVisualShader);
编译运行,顺利的话可以看见以下图像
当出现光照问题时,我们可以通过法向量可视化来排查
完整代码可在顶部git仓库中找到