LearnGL - 17 - Geometry Shader - 几何着色器


LearnGL - 学习笔记目录

由于时间关系,没能详细的编写几何着色器相关的内容

几何着色器的学习也是早在差不多一个月前完整了,现在才有空补上


示例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_ingl_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 几何着色器的图元类型与顶点数目关系

图元类型输入数组大小
points1
lines2
triangles3
lines_adjacency4
triangles_adjacency6

接受的输出图元类型

目前几何着色器能有效输出的图元类型分别有:

  • 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 页
  • 几何着色器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值