1. 程式人生 > 實用技巧 >【Unity】Geometry Shader實現

【Unity】Geometry Shader實現

https://www.cnblogs.com/jaffhan/p/7565178.html 

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 }

GS Billboard

  

設定著色器編譯目標級別為5.0。不過根據Unity的文件,4.0的著色器編譯目標級別就已經支援Geometry Shader了。

#pragma target 5.0

設定幾何體著色器函式名稱為GS_Main【可自定義】。

#pragma geometry GS_Main

設定頂點著色器向幾何體著色器輸出的最大頂點數量為4。

[maxvertexcount(4)]

幾何體著色器輸入的圖元是點,數量為1。輸出的圖元是三角形流。

voidGS_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對貼圖進行取樣,返回顏色。