Cg 程式設計/Unity/物體輪廓
本教程介紹一種頂點變換,它將每個頂點沿著其表面法線向量移動,以建立物體的更大輪廓。雖然這種頂點變換非常簡單,但要建立合理的輪廓渲染需要使用模板緩衝區,本文也會對此進行討論。
建立物體周圍輪廓的一種方法是透過沿著其表面法線向量移動頂點的來擴大物體。給定物體座標中的位置和法線向量,這在頂點著色器中相當簡單
uniform float _Thickness;
float4 vert(float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
return mul(UNITY_MATRIX_MVP, float4(normal, 0.0) * _Thickness + vertex);
}
這個頂點著色器將表面法線向量 normal 轉換為 float4 向量,然後將其乘以使用者指定的統一 _Thickness(以允許調整輪廓的厚度),並將其新增到 vertex 中頂點的座標。所有這些操作都在物體座標中進行;因此,我們必須透過乘以 UNITY_MATRIX_MVP 轉換為裁剪座標,然後返回座標。
關於這種特定頂點變換,沒有太多可說的 - 除了它最適合光滑表面。具有硬邊(即表面法線向量不連續)的表面效果不佳。有時使用 Cull Off 來渲染正面和背面會有所幫助,但總的來說,這種方法在硬邊上效果不佳。
為了使輪廓呈現統一的綠色,我們可以使用類似這樣的簡單片段著色器
float4 frag(void) : COLOR {
return float4(0.0, 1.0, 0.0, 1.0);
}
渲染這種輪廓更具挑戰性的部分是避免輪廓物體被輪廓遮擋:由於我們已經擴大了物體來建立輪廓,因此更大的輪廓通常會遮擋我們要輪廓的物體。另一方面,輪廓應該遮擋背景中的其他物體,並且應該被前景中的物體遮擋,即它應該像任何其他不透明物體一樣,除了它在我們要輪廓的物體前面時。
如果我們假設我們首先渲染一個要輪廓的物體的普通版本,然後渲染輪廓,我們可以這樣描述挑戰:輪廓只應該為不被輪廓物體覆蓋的畫素進行光柵化。這樣,挑戰聽起來像可以用模板測試解決,因為模板測試用於將光柵化限制到幀緩衝區的特定部分;在本例中,限制到不被我們要輪廓的物體覆蓋的幀緩衝區部分。
我們的策略是在模板緩衝區中用 1 標記所有被我們要輪廓的物體覆蓋的畫素。之後,在渲染輪廓時,我們可以使用模板測試來僅在未標記的畫素中光柵化輪廓。
為了用 1 在模板緩衝區中標記所有被物體覆蓋的畫素,我們可以在 Pass 塊中使用此 ShaderLab 語法,該塊位於 CGPROGRAM 之前
Stencil {
Ref 1
Comp Always
Pass Replace
}
Stencil 關鍵字指定我們希望啟用模板測試,這很必要,因為寫入模板緩衝區在技術上是模板測試的一部分。Ref 1 設定模板測試的參考值;在本例中,是我們想要寫入模板緩衝區的的值。
Comp Always 指定我們希望與所有片段一起工作,無論模板緩衝區中畫素的值是什麼。一般來說,Comp 指定對模板緩衝區中畫素值的比較。如果比較失敗,則丟棄片段,並且不會光柵化畫素。Always 指定始終透過的比較,即沒有片段被丟棄。
Pass Replace 指定如果比較透過,則應將 Replace "操作" 應用於模板緩衝區,即應將模板緩衝區中畫素的值替換為使用 Ref 指定的參考值。
一個完整的通道來渲染黑色物體,同時用 1 在所有被物體覆蓋的畫素中標記模板緩衝區,可以看起來像這樣
Pass {
Stencil {
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 vertex : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertex);
}
float4 frag(void) : COLOR {
return float4(0.0, 0.0, 0.0, 1.0);
}
ENDCG
}
我們方法的第二部分是僅在模板緩衝區未用 1 標記的地方渲染輪廓。這種模板測試可以這樣指定
Stencil {
Ref 1
Comp NotEqual
Pass Keep
}
同樣,Ref 1 設定模板測試的參考值,但在此情況下,該值用於比較。
Comp NotEqual 指定對模板緩衝區中畫素值的比較,該比較僅在值不等於參考值時才透過。如果比較失敗(即,如果模板緩衝區中的值為 1),則丟棄片段,並且不會光柵化畫素,這就是我們希望輪廓做到的,這樣它就不會遮擋我們要輪廓的物體。
Pass Keep 指定如果比較透過,則應將 Keep "操作" 應用於模板緩衝區,即我們不更改模板緩衝區,而只是保留模板緩衝區中已有的任何值。(這很重要,因為網格的多個三角形可能會覆蓋同一個畫素。)
此模板測試應用於上述沿著表面法線向量移動了頂點的輪廓的著色器。
將所有內容放在一起併為 _Thickness 統一變數定義屬性塊,我們有以下著色器
Shader "OutlinedObject" {
Properties {
_Thickness ("Thickness", Float) = 0.1
}
SubShader {
Pass {
Stencil {
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 vertex : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertex);
}
float4 frag(void) : COLOR {
return float4(0.0, 0.0, 0.0, 1.0);
}
ENDCG
}
Pass {
Cull Off
Stencil {
Ref 1
Comp NotEqual
Pass Keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform float _Thickness;
float4 vert(float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
return mul(UNITY_MATRIX_MVP, vertex + normal * _Thickness);
}
float4 frag(void) : COLOR {
return float4(0.0, 1.0, 0.0, 1.0);
}
ENDCG
}
}
}
此著色器為統一黑色物體渲染不透明的綠色輪廓。但是,您可以輕鬆地替換頂點和片段著色器以渲染其他顏色的透明輪廓或陰影物體 - 只要您保留模板測試,並且第二個通道中的輪廓大於第一個通道中的物體即可。
如果您仍想了解更多資訊
- 關於模板測試如何在 OpenGL 管道中發揮作用,您應該閱讀 “每片段操作”部分.
- 關於渲染輪廓的其他方法,您應該閱讀 “卡通著色”部分.
- 關於模板測試的規範,您應該閱讀 Unity 關於 “ShaderLab:模板” 的文件。