Unity Shader 之 Geometry Shader 簡單實現物體線框呈現的效果
目錄
Unity Shader 之 Geometry Shader 簡單實現物體線框呈現的效果
一、簡單介紹
Shader Language的發展方向是設計出在便攜性方面可以和C++、Java等相比的高級語言,“賦予程序員靈活而方便的編程方式”,並“儘可能的控制渲染過程”同時“利用圖形硬件的並行性,提高算法效率”。
幾何着色器在渲染管線中的位置是在頂點着色器和片段着色器之間,準確的說是和頂點着色器相鄰。所以在功能方面,幾何體着色器的操作和頂點着色器有相似性。一些原來位於頂點着色器中的計算,理論上完全可以轉移到幾何體着色器中進行。但實際操作中,並不建議那麼做。因爲幾何體着色器並行調用硬件困難,並行程度低,效率和頂點着色器有很大的差距。
-
頂點着色器是逐頂點操作,可以進行座標變換等計算。
-
片段着色器是逐片段/像素操作,進行最終輸出顏色的計算。
-
幾何着色器同理,是逐圖元的操作。它的輸入是圖元,輸出也是圖元。
圖元是渲染對象在頂點着色器之後,光柵化之前的一種狀態。簡單的來說,就是包含點【點Point而不是頂點Vertex】或者線段或者三角形的集合。
頂點着色器:
將頂點變換到世界空間,同時傳遞頂點法線和填充紋理座標。
幾何着色器:
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對貼圖進行採樣,返回顏色。
幾何着色器在啓用後,它將獲得頂點着色器以組成一個基礎圖元爲一組的頂點輸入,通過對輸入的頂點進行處理,幾何着色器將決定輸出的圖元類型和個數。當輸出的圖元減少或者不輸出時,實際上起到了裁剪圖形的作用,當輸出的圖元類型改變或者輸出更多圖元時起到了產生和改變圖元的作用。
要啓用幾何着色器,我們需要在之前的頂點和片元着色器基礎上,在 Unity shader 腳本上添加 #pragma geometry geom_名稱,即可使用幾何着色器了。
Unity官方文檔關於Geometry Shader的內容較少。不過也是因爲Unity的開發者大多數面向的是移動平臺開發,所以Geometry Shader作爲DirectX 10的特性並沒有被開發者廣泛使用。
不過,驍龍835處理器的Areno 540開始就支持OpenGL ES3.2了,而這個版本的一個新特性就是支持Geometry Shader!!!所以隨着硬件的發展,在移動端以及非旗艦性能的PC端大量使用GS應該也不是一個久遠的事情了。
二、關鍵參數
1、設定着色器編譯目標級別爲5.0。不過根據Unity的文檔,4.0的着色器編譯目標級別就已經支持Geometry Shader了
#pragma target 5.0
2、設定幾何體着色器函數名稱爲GS_Main【可自定義】
#pragma geometry GS_Main
3、設置頂點着色器向幾何體着色器輸出的最大頂點數量爲4
[maxvertexcount(4)]
4、幾何體着色器輸入的圖元是點,數量爲1。輸出的圖元是三角形流
void
GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
5、Append()是幾何着色器內置的向輸出流附加頂點的函數,因爲允許的最大輸出頂點數量就是4,所以append四次,輸出四個頂點。
triStream.Append(pIn);
三、效果預覽
四、實現步驟
1、打開Unity,新建一個工程
2、編寫shader,並且對應的構建 Material
3、在場景中添加一個 Capsual ,賦值上材質
4、適當調整 shader 參數
5、效果如上
五、關鍵代碼
Shader "Unlit/GS_Wireframe"
{
Properties
{
_WireColor("WireColor", Color) = (1, 0, 0, 1)
_FillColor("FillColor", Color) = (1, 1, 1, 1)
_WireWidth("WireWidth", Range(0, 0.005)) = 1
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex: POSITION;
float2 uv: TEXCOORD0;
};
struct v2g
{
float3 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
};
struct g2f
{
float3 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
float3 dist: TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _FillColor, _WireColor;
float _WireWidth, _Clip, _Lerp, _WireLerpWidth;
v2g vert(appdata v)
{
v2g o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
//o.uv.z = _Clip + v.vertex.y;
o.uv.z = v.vertex.y;
return o;
}
[maxvertexcount(3)]
void geom(triangle v2g IN[3], inout TriangleStream < g2f > triStream)
{
float2 p0 = IN[0].vertex.xy / IN[0].vertex.w;
float2 p1 = IN[1].vertex.xy / IN[1].vertex.w;
float2 p2 = IN[2].vertex.xy / IN[2].vertex.w;
float2 v0 = p2 - p1;
float2 v1 = p2 - p0;
float2 v2 = p1 - p0;
//triangles area
float area = abs(v1.x * v2.y - v1.y * v2.x);
// //到三條邊的最短距離
g2f OUT;
OUT.vertex = IN[0].vertex;
OUT.uv = IN[0].uv;
OUT.dist = float3(area / length(v0), 0, 0);
triStream.Append(OUT);
OUT.vertex = IN[1].vertex;
OUT.uv = IN[1].uv;
OUT.dist = float3(0, area / length(v1), 0);
triStream.Append(OUT);
OUT.vertex = IN[2].vertex;
OUT.uv = IN[2].uv;
OUT.dist = float3(0, 0, area / length(v2));
triStream.Append(OUT);
}
fixed4 frag(g2f i) : SV_Target
{
fixed4 col_Wire;
float d = min(i.dist.x, min(i.dist.y, i.dist.z));
col_Wire = d < _WireWidth ? _WireColor : _FillColor;
return col_Wire;
}
ENDCG
}
}
}
六、參考博客
1、https://www.cnblogs.com/Esfog/p/UnityGeometryShader_WireFrame.html
2、https://blog.csdn.net/u012722551/article/details/104281175
3、Unity Shader:用幾何着色器實現復聯3滅霸的終極大招灰飛煙滅
4、Unity Shader之幾何着色器(Geometry Shader)實現面片飛散的爆炸效果