【Unity】Geometry Shader实现
Unity官方文档关于Geometry Shader的内容较少。不过也是因为Unity的开发者大多数面向的是移动平台开发,所以Geometry Shader作为DirectX 10的特性并没有被开发者广泛使用。
首先要知道,Geometry Shader和Vertex Shader以及Fragment Shader的区别。
在DirectX 9的渲染管线中,可编程的Shader只有顶点着色器和片段着色器两类。但是在DirectX 10开始,渲染管线增加了一个【可选】的几何体着色器。这是与顶点着色器和片段着色器【理论可选】作为渲染管线必需的两个着色器的主要区别。
几何着色器在渲染管线中的位置是在顶点着色器和片段着色器之间,准确的说是和顶点着色器相邻。所以在功能方面,几何体着色器的操作和顶点着色器有相似性。一些原来位于顶点着色器中的计算,理论上完全可以转移到几何体着色器中进行。但实际操作中,并不建议那么做。因为几何体着色器并行调用硬件困难,并行程度低,效率和顶点着色器有很大的差距,这是第二个区别。
顶点着色器是逐顶点操作,可以进行坐标变换等计算。
片段着色器是逐片段/像素操作,进行最终输出颜色的计算。
几何着色器同理,是逐图元的操作。它的输入是图元,输出也是图元。
图元是渲染对象在顶点着色器之后,光栅化之前的一种状态。简单的来说,就是包含点【点Point而不是顶点Vertex】或者线段或者三角形的集合。
经过这些简单的了解,大概能猜到几何着色器可以用来做什么了。比如细分【DirectX 11新增了更优的可选的细分着色器实现】,比如Low Poly,比如线框。Billboard也可以,不过Billboard可以在脚本和顶点Shader中同样可以实现。
通过简单的一个Billboard例子来讲解几何体着色器:
给一个Quad使用后的运行结果是产生Y轴约束,面向摄像机的Quad。
1 Shader "Custom/GS Billboard" 2 { 3 Properties 4 { 5 _SpriteTex ("Base (RGB)", 2D) = "white" {} 6 _Size ("Size", Range(0, 3)) = 0.5 7 } 8 9 SubShader 10 { 11 Pass 12 { 13 Tags { "RenderType"="Opaque" } 14 LOD 200 15 16 CGPROGRAM 17 #pragma target 5.0 18 #pragma vertex VS_Main 19 #pragma fragment FS_Main 20 #pragma geometry GS_Main 21 #include "UnityCG.cginc" 22 23 // ************************************************************** 24 // Data structures * 25 // ************************************************************** 26 struct GS_INPUT 27 { 28 float4 pos : POSITION; 29 float3 normal : NORMAL; 30 float2 tex0 : TEXCOORD0; 31 }; 32 33 struct FS_INPUT 34 { 35 float4 pos : POSITION; 36 float2 tex0 : TEXCOORD0; 37 }; 38 39 40 // ************************************************************** 41 // Vars * 42 // ************************************************************** 43 44 float _Size; 45 float4x4 _VP; 46 Texture2D _SpriteTex; 47 SamplerState sampler_SpriteTex; 48 49 // ************************************************************** 50 // Shader Programs * 51 // ************************************************************** 52 53 // Vertex Shader ------------------------------------------------ 54 GS_INPUT VS_Main(appdata_base v) 55 { 56 GS_INPUT output = (GS_INPUT)0; 57 58 output.pos = mul(_Object2World, v.vertex); 59 output.normal = v.normal; 60 output.tex0 = float2(0, 0); 61 62 return output; 63 } 64 65 66 67 // Geometry Shader ----------------------------------------------------- 68 [maxvertexcount(4)] 69 void GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream) 70 { 71 float3 up = float3(0, 1, 0); 72 float3 look = _WorldSpaceCameraPos - p[0].pos; 73 look.y = 0; 74 look = normalize(look); 75 float3 right = cross(up, look); 76 77 float halfS = 0.5f * _Size; 78 79 float4 v[4]; 80 v[0] = float4(p[0].pos + halfS * right - halfS * up, 1.0f); 81 v[1] = float4(p[0].pos + halfS * right + halfS * up, 1.0f); 82 v[2] = float4(p[0].pos - halfS * right - halfS * up, 1.0f); 83 v[3] = float4(p[0].pos - halfS * right + halfS * up, 1.0f); 84 85 float4x4 vp = mul(UNITY_MATRIX_MVP, _World2Object); 86 FS_INPUT pIn; 87 pIn.pos = mul(vp, v[0]); 88 pIn.tex0 = float2(1.0f, 0.0f); 89 triStream.Append(pIn); 90 91 pIn.pos = mul(vp, v[1]); 92 pIn.tex0 = float2(1.0f, 1.0f); 93 triStream.Append(pIn); 94 95 pIn.pos = mul(vp, v[2]); 96 pIn.tex0 = float2(0.0f, 0.0f); 97 triStream.Append(pIn); 98 99 pIn.pos = mul(vp, v[3]); 100 pIn.tex0 = float2(0.0f, 1.0f); 101 triStream.Append(pIn); 102 } 103 104 105 106 // Fragment Shader ----------------------------------------------- 107 float4 FS_Main(FS_INPUT input) : COLOR 108 { 109 return _SpriteTex.Sample(sampler_SpriteTex, input.tex0); 110 } 111 112 ENDCG 113 } 114 } 115 }
设定着色器编译目标级别为5.0。不过根据Unity的文档,4.0的着色器编译目标级别就已经支持Geometry Shader了。
#pragma target 5.0
设定几何体着色器函数名称为GS_Main【可自定义】。
#pragma geometry GS_Main
设置顶点着色器向几何体着色器输出的最大顶点数量为4。
[maxvertexcount(4)]
几何体着色器输入的图元是点,数量为1。输出的图元是三角形流。
void GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
Append()是几何着色器内置的向输出流附加顶点的函数,因为允许的最大输出顶点数量就是4,所以append四次,输出四个顶点。
triStream.Append(pIn);
其他没有什么新东西要讲,简单的描述一下这个Shader的流程。
顶点着色器:
将顶点变换到世界空间,同时传递顶点法线和填充纹理坐标。
几何着色器:
1.定义一个垂直向上的向量up。
2.计算观察向量look。
3.将look向量的Y轴设为0并规格化,使得look向量成为平行于XZ平面指向摄像机的单位向量。
4.计算up向量和look向量的叉乘,得到一个垂直于二者的新向量right。
5.计算Size的一半halfS。
6.将输入的点,以该点为中心点,分别向right向量正负轴向,up向量正负轴向移动halfS的距离,四个顶点的四个顶点的坐标。
7.将顶点依次转换到投影空间,分配UV并Append到输出流上,最后输出。
片段着色器:
根据UV对贴图进行采样,返回颜色。