文章目录
由于时间关系,没能详细的编写几何着色器相关的内容
几何着色器的学习也是早在差不多一个月前完整了,现在才有空补上
示例1
加载网格部分,顶点数据有:位置、颜色信息
GLfloat temp_vertices[4 * 3] = {
-0.5f, +0.5f, 0.0f,
+0.5f, +0.5f, 0.0f,
+0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
};
GLfloat temp_colors[4 * 4] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
};
GLuint temp_indices[4] = {
0, 1, 2, 3
};
mesh->pos_copy_from(temp_vertices, sizeof(temp_vertices) / sizeof(temp_vertices[0]));
mesh->color_copy_from(temp_colors, sizeof(temp_colors) / sizeof(temp_colors[0]));
mesh->indices_copy_from(temp_indices, sizeof(temp_indices) / sizeof(temp_indices[0]));
ReferMgr<Mesh>::setObj(mesh_ref_name.c_str(), mesh);
memcpy(mesh->name, mesh_ref_name.c_str(), mesh_ref_name.size() + 1);
mesh->primiveType = DrawState_PrimitiveType::POINTS;
加载着色器
// material - shader
std::string shader_ref_name = "TestingGeometryShader\\testing_geometry_shader";
shader = ReferMgr<ShaderProgram>::getObj(shader_ref_name.c_str());
if (shader == NULL) {
shader = new ShaderProgram(shader_ref_name.c_str());
char vs_path[MAX_PATH], fs_path[MAX_PATH], gs_path[MAX_PATH];
g_GetShaderPathCallback(vs_path, "TestingGeometryShader\\testing_geometry_shader.vert");
g_GetShaderPathCallback(fs_path, "TestingGeometryShader\\testing_geometry_shader.frag");
g_GetShaderPathCallback(gs_path, "TestingGeometryShader\\testing_geometry_shader.geom");
if (!shader->initByPath(vs_path, fs_path, "", "", gs_path)) {
std::cout << shader->name << ", ShaderProgram init Error: " << shader->errorLog() << std::endl; // 输出shader program错误
exit(EXIT_FAILURE);
}
ReferMgr<ShaderProgram>::setObj(shader_ref_name.c_str(), shader);
}
Vertex Shader
// jave.lin - testing_geometry_shader.vert
#version 450 compatibility
in vec3 vPos;
in vec4 vCol;
out VS_OUT {
vec4 col;
};
void main() {
col = vCol;
gl_Position = vec4(vPos, 1.0);
}
Geometry Shader
// jave.lin - testing_geometry_shader.geos
#version 450 compatibility
layout (points) in;
layout (line_strip, max_vertices = 2) out;
in VS_OUT {
vec4 col;
} gs_in[];
out GS_OUT {
vec4 col;
};
void main() {
col = gs_in[0].col;
gl_Position = gl_in[0].gl_Position + vec4(-0.2, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4(0.2, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive(); // 如果只有一个图元数量的输出,当 EmitVertex 的数次 大于或等于 max_vertices时,EndPrimitive() 可有可无
// col = vec4(1);
// gl_Position = gl_in[0].gl_Position + vec4(-0.2, -0.1, 0.0, 0.0);
// EmitVertex();
// col = vec4(1);
// gl_Position = gl_in[0].gl_Position + vec4(0.2, -0.1, 0.0, 0.0);
// EmitVertex();
}
Fragment Shader
// jave.lin - testing_geometry_shader.frag
#version 450 compatibility
in GS_OUT {
vec4 col;
};
void main() {
gl_FragColor = col;
}
运行效果
几何着色器在管线的位置
可以参考我之前写的一篇:Summary: Rendering Pipeline简单总结一下渲染管线流程图
在管线中,可以看到 几何着色器 (Geometry Shader) 是在 图元装配 (Primitive Assembly) 前处理的
而几何着色器是可以将输入的图元类型,再改变图元类型后,再输出到下一阶段的:Primitive Assembly
从上面的示例就可以知道,我们输入的图元类型是 points(点),输出改变成了:line_strip(线,条带) 了
Geometry Shader 中主要部分
就上面提供的示例的代码而言
...
layout (points) in;
layout (line_strip, max_vertices = 2) out;
...
in VS_OUT {
vec4 col;
} gs_in[];
...
col = gs_in[0].col;
gl_Position = gl_in[0].gl_Position + vec4(-0.2, 0.0, 0.0, 0.0);
...
EmitVertex();
...
EndPrimitive();
layout (points) in;
中的points
是指定原始输入的图元类型layout (line_strip, max_vertices = 2) out;
中的line_strip
是几何着色器输出的图元类型,max_vertices
是指定图元要输出的顶点最多多少个顶点的数量,超出max_vertices
指定的EmitVertex()
过的顶点,都作废,或是如果输出的图元类型中,在不满足图元需要的数量的顶点,也是作废的图元输出in VS_OUT { ... } gs_in[]
中的gs_in[]
数组,这里的数组长度是根据layout (input_primitive_type) in
中的input_primitive_type
来决定的,而input_primitive-type
可以接受的图元类型有好几个,参考下面的表格col = gs_in[0].col;
中的gs_in[0]
取上游着色器输出的顶点数据中的,属于指定图元类型中第0
个索引的顶点输出的信息gl_Position = gl_in[0].gl_Position + vec4(-0.2, 0.0, 0.0, 0.0);
中的gl_in[0]
注意这里的是gl_in
不是gs_in
,gl_in
没有看到定义,因为它是内置声明的数据类型,内置的gl_in
可以查看下面对应的gl_in
声明内容EmitVertex()
几何着色器 发射/生成 顶点(这里的 发射可以理解为像是粒子系统中的 发射 粒子的意思,其实就是 生成 的意思)EndPrimitive()
结束该次对几何体图元的处理,后续的EmitVertex()
将作为新的下一个图元的输出,但如果EmitVertex()
后的次数已达到图元需要的数量,或是达到max_vertices
的次数,也是可以不同EndPritimive()
的
内置的 gl_in
声明内容:
in gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
float gl_CullDistance[];
} gl_in[];
在几何着色器代码中也可以通过 gl_in.length()
获得对应的图元类型的顶点数组的长度
接受的输入图元类型
上面将的 layout (points) in
中的 points
指定的就是输入图元只接受为 点 的图元类型的输入
如果应用层数绘制的图元类型与几何着色器的输入类型不对应,将会在运行时发生错误
layout (input_primitive_type) in
中的 input_primitive_type
输入图元类型可以是以下几种:
表10-2 几何着色器的图元类型与顶点数目关系
图元类型 | 输入数组大小 |
---|---|
points | 1 |
lines | 2 |
triangles | 3 |
lines_adjacency | 4 |
triangles_adjacency | 6 |
接受的输出图元类型
目前几何着色器能有效输出的图元类型分别有:
- points
- line_strip
- triangle_strip
示例2
之前应用层(C++)的顶点数据都是有:位置、颜色信息
我们可以将顶点数据中的颜色信息去掉,改而在几何着色器中来动态处理颜色
加载网格部分,顶点数据有:位置信息,颜色信息删除了
GLfloat temp_vertices[4 * 3] = {
-0.5f, +0.5f, 0.0f,
+0.5f, +0.5f, 0.0f,
+0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
};
GLuint temp_indices[4] = {
0, 1, 2, 3
};
mesh->pos_copy_from(temp_vertices, sizeof(temp_vertices) / sizeof(temp_vertices[0]));
mesh->indices_copy_from(temp_indices, sizeof(temp_indices) / sizeof(temp_indices[0]));
ReferMgr<Mesh>::setObj(mesh_ref_name.c_str(), mesh);
memcpy(mesh->name, mesh_ref_name.c_str(), mesh_ref_name.size() + 1);
mesh->primiveType = DrawState_PrimitiveType::POINTS;
Vertex Shader
添加了 vertex_id
的输出
// jave.lin - testing_geometry_shader.vert
#version 450 compatibility
in vec3 vPos;
// in vec4 vCol;
out VS_OUT {
// vec4 col;
int vertex_id;
};
void main() {
// col = vCol;
vertex_id = gl_VertexID;
gl_Position = vec4(vPos, 1.0);
}
Geomestry Shader
添加了 SetColor
方法,根据顶点索引来设置图元颜色
// jave.lin - testing_geometry_shader.geos
#version 450 compatibility
layout (points) in;
layout (line_strip, max_vertices = 2) out;
in VS_OUT {
// vec4 col;
int vertex_id;
} gs_in[];
out GS_OUT {
vec4 col;
};
void SetColor(int id) {
if (id == 0) {
col = vec4(1,0,0,1);
} else if (id == 1) {
col = vec4(0,1,0,1);
} else if (id == 2) {
col = vec4(0,0,1,1);
} else if (id == 3) {
col = vec4(1,1,0,1);
} else {
col = vec4(1);
}
}
void main() {
SetColor(gs_in[0].vertex_id);
gl_Position = gl_in[0].gl_Position + vec4(-0.2, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4(0.2, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive(); // 如果只有一个图元数量的输出,当 EmitVertex 的数次 大于或等于 max_vertices时,EndPrimitive() 可有可无
}
Fragment Shader
片元着色器的话没有变化
运行效果
可以看到还是与之前的 示例1 是一样的
所以我们将 SetColor
函数调整一下:
void SetColor(int id) {
if (id == 0) {
col = vec4(0,1,0,1);
// col = vec4(1,0,0,1);
} else if (id == 1) {
col = vec4(1,0,0,1);
// col = vec4(0,1,0,1);
} else if (id == 2) {
col = vec4(0,0,1,1);
} else if (id == 3) {
col = vec4(1,1,0,1);
} else {
col = vec4(1);
}
}
将第 0
个顶点 与 第 1
个顶点的颜色互换
运行效果如下:
OK,在知道几何着色器大概是如何工作之后,我们就可以用到实现其他功能
如下面的功能:显示可便于调式用的 顶点法线
示例3 - 显示顶点法线
在运行时,为了调试、查看顶点法线是否有误
实现思路
- 第一个 pass 绘制本体
- 第二个 pass 绘制几何着色器来显示法线
(前提:增加了材质多 pass 的功能,其实就是:给绘制对象指定多个 shader 来绘制,设计思路按照类似 UnityShader 的方式来设置,也与我之前的 C# 软光栅渲染器中的那篇 的 Pass 设计类似)
应用层代码
绘制对象的测试组件
// material - component
mat = new Material();
getOwner()->addComp(mat);
// pass0
{
// material - shader0
std::string shader_ref_name = "TestingRefraction\\testing_refraction";
shader0 = ReferMgr<ShaderProgram>::getObj(shader_ref_name.c_str());
if (shader0 == NULL) {
shader0 = new ShaderProgram(shader_ref_name.c_str());
char vs_path[MAX_PATH], fs_path[MAX_PATH];
g_GetShaderPathCallback(vs_path, "TestingRefraction\\testing_refraction.vert");
g_GetShaderPathCallback(fs_path, "TestingRefraction\\testing_refraction.frag");
if (!shader0->initByPath(vs_path, fs_path)) {
std::cout << shader0->name << ", ShaderProgram init Error: " << shader0->errorLog() << std::endl; // 输出shader program错误
exit(EXIT_FAILURE);
}
ReferMgr<ShaderProgram>::setObj(shader_ref_name.c_str(), shader0);
}
Pass* pass = new Pass(shader_ref_name.c_str());
mat->addPass(pass);
pass->ref_name = shader_ref_name;
pass->setShader(shader0);
// material : texture
// 纹理一般是外部设置的
{
std::string ref_name = "default";
main_tex = ReferMgr<Texture>::getObj(ref_name.c_str());
if (main_tex == NULL) {
main_tex = new Texture();
main_tex->loadDefault();
ReferMgr<Texture>::setObj(ref_name.c_str(), main_tex);
}
TexSetting* setting = pass->newTex();
setting->ref_name = ref_name;
setting->set_texture_obj(main_tex);
setting->name = "main_tex";
}
pass->enabledDepthWrite = false;
pass->enabledBlend = true;
pass->blendSrcFactor = DrawState_BlendFactorType::SRC_ALPHA;
pass->blendDstFactor = DrawState_BlendFactorType::ONE_MINUS_SRC_ALPHA;
pass->SrcRefractionK = 1.00f;
pass->DstRefractionK = 1.52f;
pass->UseTexAlpha = false;
pass->Alpha = 0.80f;
pass->ReflectionK = 1.0f;
pass->RefractionK = 0.5f;
pass->glossy = 100.0f;
}
// pass1
{
// material - shader1
std::string shader_ref_name = "TestingGeometryShaderDisplayNormals\\testing_geometry_shader_display_normals";
shader1 = ReferMgr<ShaderProgram>::getObj(shader_ref_name.c_str());
if (shader1 == NULL) {
shader1 = new ShaderProgram(shader_ref_name.c_str());
char vs_path[MAX_PATH], fs_path[MAX_PATH], gs_path[MAX_PATH];
g_GetShaderPathCallback(vs_path, "TestingGeometryShaderDisplayNormals\\testing_geometry_shader_display_normals.vert");
g_GetShaderPathCallback(fs_path, "TestingGeometryShaderDisplayNormals\\testing_geometry_shader_display_normals.frag");
g_GetShaderPathCallback(gs_path, "TestingGeometryShaderDisplayNormals\\testing_geometry_shader_display_normals.geom");
if (!shader1->initByPath(vs_path, fs_path, "", "", gs_path)) {
std::cout << shader1->name << ", ShaderProgram init Error: " << shader1->errorLog() << std::endl; // 输出shader1 program错误
exit(EXIT_FAILURE);
}
ReferMgr<ShaderProgram>::setObj(shader_ref_name.c_str(), shader1);
}
Pass* pass = new Pass(shader_ref_name.c_str());
mat->addPass(pass);
pass->ref_name = shader_ref_name;
pass->setShader(shader1);
}
实例化测试的组件
// geometry shader testing
{
// cube
GameObject* temp_go = new GameObject();
sprintf_s(name, "%s##%d", "GeemetryShaderTestingDisplayNormals", temp_go->id());
temp_go->set_name_copy_from(name);
//temp_go->getTrans()->local_position = vec3(1.2f, 0, i);
temp_go->getTrans()->local_position = vec3(0.0f, 0.0f, 0.0f);
temp_go->getTrans()->local_scale = vec3(1, 1, 1);
MyGeometryShader_Testing_Display_Normals* temp_comp = new MyGeometryShader_Testing_Display_Normals();
temp_comp->mesh_name = "Testing_Cube.m";
temp_go->addComp(temp_comp);
SceneMgr::inst()->getRoot()->add(temp_go);
}
{
// Sphere
GameObject* temp_go = new GameObject();
sprintf_s(name, "%s##%d", "GeemetryShaderTestingDisplayNormals", temp_go->id());
temp_go->set_name_copy_from(name);
//temp_go->getTrans()->local_position = vec3(1.2f, 0, i);
temp_go->getTrans()->local_position = vec3(1.0f, 0.0f, 0.0f);
temp_go->getTrans()->local_scale = vec3(1, 1, 1);
MyGeometryShader_Testing_Display_Normals* temp_comp = new MyGeometryShader_Testing_Display_Normals();
temp_comp->mesh_name = "Sphere_637003627289014299.m";
temp_go->addComp(temp_comp);
SceneMgr::inst()->getRoot()->add(temp_go);
}
{
// cube
GameObject* temp_go = new GameObject();
sprintf_s(name, "%s##%d", "GeemetryShaderTestingDisplayNormals", temp_go->id());
temp_go->set_name_copy_from(name);
//temp_go->getTrans()->local_position = vec3(1.2f, 0, i);
temp_go->getTrans()->local_position = vec3(2.0f, 0.0f, 0.0f);
temp_go->getTrans()->local_scale = vec3(1, 1, 1);
MyGeometryShader_Testing_Display_Normals* temp_comp = new MyGeometryShader_Testing_Display_Normals();
temp_comp->mesh_name = "BalloonStupidCat_637003750150312129.m";
temp_go->addComp(temp_comp);
SceneMgr::inst()->getRoot()->add(temp_go);
}
Vertex Shader
// jave.lin - testing_geometry_shader.vert
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
in vec3 vPos;
in vec3 vNormal;
out VS_OUT {
vec3 normal;
};
void main() {
normal = ObjectToWorldNormal(vNormal); // 将模型空间的法线 转换到 世界坐标顶点法线
gl_Position = mMat * vec4(vPos, 1.0); // 世界坐标
}
Geometry Shader
// jave.lin - testing_geometry_shader.geos
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
layout (triangles) in;
layout (line_strip, max_vertices = 2 * 3) out;
// 下面 in/out 是用 interface block 接口块来传输数据
in VS_OUT { // Vertex Shader 的输出
vec3 normal;
} gs_in[];
out GS_OUT { // Geometry Shader 的输出
vec4 col;
};
// 外部可控的 uniform
uniform float normal_line_len = 1; // 法线的长度
uniform int normal_type = 1; // 法线类型,0:三角面中心显示,1:面个顶点都显示
void center_point_normal_line() {
vec3 vec_1 = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz;
vec3 vec_2 = (gl_in[2].gl_Position - gl_in[0].gl_Position).xyz;
vec3 normal_vec = normalize(cross(vec_1, vec_2));
vec4 centerPos = (
gl_in[0].gl_Position +
gl_in[1].gl_Position +
gl_in[2].gl_Position) / 3.0;
gl_Position = pMat * vMat * centerPos;
EmitVertex();
gl_Position = pMat * vMat * vec4(centerPos.xyz + (normal_vec * normal_line_len), centerPos.w);
EmitVertex();
EndPrimitive();
}
void point_normal_line(int idx) {
gl_Position = pMat * vMat * gl_in[idx].gl_Position;
col = vec4(0, 0.5, 1, 1); // 法线头部颜色,浅蓝色
EmitVertex();
gl_Position = pMat * vMat * vec4(gl_in[idx].gl_Position.xyz + (gs_in[idx].normal * normal_line_len), 1);
col = vec4(1); // 法线尾部颜色,白色,便于查看法线是否正确
EmitVertex();
EndPrimitive();
}
void per_point_normal_line() {
point_normal_line(0);
point_normal_line(1);
point_normal_line(2);
}
void main() {
if (normal_type == 0) {
// 三角面中心
center_point_normal_line();
} else {
// 每个顶点
per_point_normal_line();
}
}
Fragment Shader
// jave.lin - testing_geometry_shader.frag
#version 450 compatibility
in GS_OUT {
vec4 col;
};
void main() {
gl_FragColor = col;
}
运行效果
Fur Shading
在 OpenGL 第9版本的红宝书 中,还有使用 Geometry Shader 来使用 fur shading(毛发着色)
后面如果有空,我也会去尝试一下
References
- OpenGL 编程指南 - 红宝书 - 第9版 - 第10章 - 393 页
- 几何着色器