几何着色器 Geometry Shader
参考:
【Unity Shader入门】4、几何着色器Geometry Shaders之结构解析_shader编译目标级别-CSDN博客
Geometry Shader(Unity几何着色器)_unity geometry shader-CSDN博客
概述
在顶点着色器和片元着色器之间有一个可选的几何着色器(geometry shader) 阶段。 几何着色器可以将输入图元转换为其他图元,这是曲面细分等阶段无法左到的。
几何着色器在输入的几何图元(如点、线段、三角形)级别上进行操作,并根据需要生成零个、一个或多个输出的新的几何图元。
这使得在几何着色器阶段可以实现诸如几何图元的细分、几何的放大缩小、草地生成、粒子系统等各种复杂的效果。
几何着色器的输入是完整的图元,输出是新的图元。
顶点着色器以顶点数据作为输入数据,而几何着色器的输入是上一个阶段生成的顶点组成的图元。与顶点着色器不同,几何着色器可以创建或销毁几何图元。然后,几何着色器将新的几何图元传递给光栅化阶段。经过光栅化处理后,最终会生成像素片段,供像素着色器阶段进行处理。
需要图形API,Vulkan 或 DX11以上 或 OpenglES3.2以上 才支持,不过目前来看,低端的红米手机都能跑
/* [maxvertexcount(N)]:用来指定几何着色器单次调用所输出的顶点数量最大值。 其中,N是几何着色器单次调用所输出的顶点数量最大值。几何着色器每次输出的顶点个数都可能不同,但是这个数量不能超过之前定义的最大值。 出于对性能方面的考虑,我们应当令maxvertexcount的值尽可能小。线管资料显示,GS着色器每次输出的标量数量在1-20时,它将发挥出最佳的性能;而当27-40时,它的性能将下降到峰值性能的50%。 每次调用几何着色器所输出的标量个数为:maxvertexcount与输出顶点类型结构体中标量个数的乘积。 例如,如果顶点结构体定义了float3 pos : POSITION与float2 tex : COORD0,即顶点元素中含有5个标量。 假设此时将maxvertexcount设置为4,则几何着色器每次输出20个标量,以峰值性能执行。 */ [maxvertexcount(3)] /* * Input: PrimitiveType InputVertexType InputName[NumElements] PrimitiveType:表示输入的图元类型,我们一般都是处理基于3角形的3D模型,一般选三角形,可以有以下几种: point:输入图元为点,顶点数量1 line:输入图元为线,顶点数量2 triangle:输入图元为三角形,顶点数量3 lineadj:输入图元为带有邻接信息的直线,由4个顶点构成3条线,顶点数量4 triangleadj:输入图元为带有邻接信息的三角形,由6个顶点构成,顶点数量4 InputVertexType:表示输入的结构体,例如v2g,在代码里定义 NumElements:表示输入的顶点数据元素数量,根据上面的输入图元类型填对应的顶点数量 * Inout: StreamOutputObjectVertexType<OutputVertexType> OutputName StreamOutputObjectVertexType:表示输出的顶点类型,可以有: PointStream:输出图元为点,即最终模型只显示顶点 LineStream:输出图元为线,即最终模型只显示线框 TriangleStream:输出图元为三角形,普通的模型面显示方式 OutputVertexType:表示输出的顶点类型,例如g2f,在代码里定义 */ void geom(triangle v2g p[3], inout TriangleStream<g2f> tStream) { for (int i = 0; i < 3; i++) { // 定义一个g2f输出结构体,并将参数都初始化为0 g2f o = (g2f)0; o.pos = p[i].pos; o.normal = p[i].normal; o.uv = p[i].uv ; // 将每个顶点添加到TriangleStream流里 tStream.Append(o); } // 如果是添加三角面,每输出点足够对应相应的图元后,都要RestartStrip()一下再继续构成下一图元,其他两种输出模式不需要这步骤 tStream.RestartStrip(); }
一个例子,在每个三角形的每个顶点周围均匀分布生成10个裂变顶点,并且随时间变化在原位置上移动:
[maxvertexcount(30)] void geom(triangle v2g p[3], inout PointStream<g2f> pStream) { float time = _Time.y; // 获取当前时间 // 对每个三角形的每个顶点进行裂变 for (int i = 0; i < 3; ++i) { for (int j = 0; j < 10; ++j) { g2f o; // 在三角形顶点周围均匀分布生成新顶点 float phi = 2 * 3.14159265359 * float(j) / 10.0f; float r = 0.1f; // 裂变顶点的半径,可以根据需求调整 // 计算新顶点位置,同时考虑时间变化 float3 offset = float3(cos(phi), sin(phi), 0) * r; float movement = sin(time + phi * 2) * 0.02f; // 时间因子,用于随时间变化 o.pos = p[i].pos + float4(offset * (1.0 + movement), 0); // 使用原始顶点的法线和纹理坐标 o.normal = p[i].normal; o.uv = p[i].uv; // 添加到输出流 pStream.Append(o); } } }
一个完整的Shader例子,接受输入为三角形面的三个顶点,分别输出为点、线、面三个函数:
Shader "Custom/GeometryShader" { Properties { _MainTex("MainTex", 2D) = "white" {} _Color ("Main Color", Color) = (1,1,1,1) } SubShader { Pass { Tags{ "RenderType" = "Opaque" } LOD 200 CGPROGRAM #pragma vertex vert // 这里指明要使用一个名为geom的几何着色器 #pragma geometry geom #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; fixed4 _Color; // 定义输入结构体,用于顶点着色器输出,几何着色器输入 struct v2g { float4 pos : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; // 定义输出结构体,用于几何着色器输出,片段着色器输入 struct g2f { float4 pos : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; // 顶点着色器 v2g vert(appdata_base v) { v2g o = (v2g)0; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; } // 几何着色器 // 将模型输出为点 [maxvertexcount(3)] void geom(triangle v2g p[3], inout PointStream<g2f> pStream) { for (int i = 0; i < 3; i++) { g2f o = (g2f)0; o.pos = p[i].pos; o.normal = p[i].normal; o.uv = p[i].uv ; //将每个顶点添加到PointStream流里 pStream.Append(o); } } // [maxvertexcount(3)] // 将模型输出为线框 // void geom(triangle v2g p[3], inout LineStream<g2f> lStream) // { // for (int i = 0; i < 3; i++) // { // g2f o = (g2f)0; // o.pos = p[i].pos; // o.normal = p[i].normal; // o.uv = p[i].uv ; // //将每个顶点添加到LineStream流里 // lStream.Append(o); // } // } // [maxvertexcount(3)] // 将模型输出为三角面 // void geom(triangle v2g p[3], inout TriangleStream<g2f> tStream) // { // for (int i = 0; i < 3; i++) // { // g2f o = (g2f)0; // o.pos = p[i].pos; // o.normal = p[i].normal; // o.uv = p[i].uv ; // //将每个顶点添加到TriangleStream流里 // tStream.Append(o); // } // //添加三角面 // //每输出点足够对应相应的图元后 // //都要RestartStrip()一下再继续构成下一图元 // tStream.RestartStrip(); // } // 片段着色器 fixed4 frag(g2f i) : COLOR { return tex2D(_MainTex, i.uv) * _Color; } ENDCG } } FallBack "Diffuse" }