Cg 程式設計/Unity/輪廓增強

本教程涵蓋了**表面法線向量變換**。它假設你熟悉“透明度”部分中討論的alpha混合以及“世界空間中的著色”部分中討論的著色器屬性。
本教程的目標是實現左側照片中可見的效果:半透明物體的輪廓往往比物體其他部分更不透明。這在沒有照明的情況下,也增加了三維形狀的印象。事實證明,變換後的法線對於獲得這種效果至關重要。

在平滑表面情況下,輪廓上表面的點以平行於觀察平面的法線向量為特徵,因此垂直於觀察者方向。在左側的圖形中,圖形頂部輪廓處的藍色法線向量平行於觀察平面,而其他法線向量更多地指向觀察者(或相機)方向。透過計算觀察者方向和法線向量,並測試它們是否(幾乎)相互正交,我們因此可以測試一個點是否(幾乎)在輪廓上。
更具體地說,如果V是觀察者方向的歸一化(即長度為 1)向量,而N是歸一化的表面法線向量,則當點積為 0 時,這兩個向量是正交的:V·N = 0。在實踐中,這種情況很少發生。但是,如果點積V·N接近 0,我們可以假設該點接近輪廓。
對於我們的效果,因此應該增加透明度,如果點積V·N接近 0。有各種方法可以增加觀察者方向和法線向量之間點積較小時的透明度。以下是一種方法(它實際上有一個物理模型作為其基礎,這在該出版物的第 5.1 節中進行了描述)來計算從材料的常規透明度 中增加的透明度
檢查像這樣的方程式的極端情況總是很有意義。考慮接近輪廓的點的案例:V·N ≈ 0。在這種情況下,常規透明度 將除以一個小的正數。(請注意,GPU 通常會優雅地處理除以零的情況;因此,我們不必擔心它。)因此,無論 是多少, 和一個小正數的比率將更大。該 函式將確保生成的透明度 永遠不會大於 1。
另一方面,對於遠離輪廓的點,我們有V·N ≈ 1。在這種情況下,α' ≈ min(1, α) ≈ α;也就是說,這些點的透明度不會發生太大變化。這正是我們想要的。因此,我們剛剛檢查了該方程至少是合理的。
為了在著色器中實現像 這樣的公式,第一個問題應該是:應該在頂點著色器還是片段著色器中實現?在某些情況下,答案很明顯,因為實現需要紋理對映,而紋理對映通常只能在片段著色器中使用。然而,在許多情況下,沒有普遍的答案。在頂點著色器中實現往往更快(因為通常頂點的數量比片段少),但影像質量較低(因為法線和其他頂點屬性在頂點之間可能會發生突然變化)。因此,如果您最關心的是效能,那麼在頂點著色器中實現可能是一個更好的選擇。另一方面,如果您最關心的是影像質量,那麼在片段著色器中實現可能是一個更好的選擇。在每頂點光照(即 Gouraud 著色,在 “鏡面高光”部分 中討論)和每片段光照(即 Phong 著色,在 “平滑鏡面高光”部分 中討論)之間也存在同樣的權衡。
下一個問題是:應該在哪個座標系中實現公式?(請參閱 “頂點變換”部分,以瞭解標準座標系的描述。)同樣,也沒有普遍的答案。但是,在世界座標系中實現通常是 Unity 中一個不錯的選擇,因為許多統一變數是在世界座標系中指定的。(在其他環境中,在檢視座標系中實現非常常見。)
在實現公式之前,最後一個問題是:我們從哪裡獲取公式的引數?常規的不透明度 是由著色器屬性指定的(在一個 RGBA 顏色中)(請參閱 “在世界空間中著色”部分)。法線向量 normal 是一個標準的頂點輸入引數(請參閱 “著色器的除錯”部分)。指向觀察者的方向可以在頂點著色器中計算出來,作為從世界空間中的頂點位置到世界空間中的相機位置 _WorldSpaceCameraPos 的向量,該向量由 Unity 提供。
因此,我們只需要在實現公式之前將頂點位置和法線向量變換到世界空間。從物體空間到世界空間的變換矩陣 unity_ObjectToWorld 及其逆矩陣 unity_WorldToObject 由 Unity 提供,如 “在世界空間中著色”部分 所述。將變換矩陣應用於點和法線向量將在 “應用矩陣變換”部分 中詳細討論。基本結果是,點和方向只需透過將它們與變換矩陣相乘即可進行變換,例如,將 modelMatrix 設定為 unity_ObjectToWorld
output.viewDir = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
另一方面,法線向量透過將它們與轉置逆變換矩陣相乘來進行變換。由於 Unity 為我們提供了逆變換矩陣(即 unity_WorldToObject),一個更好的替代方法是將法線向量從左邊乘以逆矩陣,這等效於將它從右邊乘以轉置逆矩陣,如 “應用矩陣變換”部分 所述。
output.normal = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
現在我們擁有了編寫著色器所需的所有部分。
著色器程式碼
[edit | edit source]Shader "Cg silhouette enhancement" {
Properties {
_Color ("Color", Color) = (1, 1, 1, 0.5)
// user-specified RGBA color including opacity
}
SubShader {
Tags { "Queue" = "Transparent" }
// draw after all opaque geometry has been drawn
Pass {
ZWrite Off // don't occlude other objects
Blend SrcAlpha OneMinusSrcAlpha // standard alpha blending
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color; // define shader property for shaders
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float3 normal : TEXCOORD0;
float3 viewDir : TEXCOORD1;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = unity_ObjectToWorld;
float4x4 modelMatrixInverse = unity_WorldToObject;
output.normal = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.viewDir = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float3 normalDirection = normalize(input.normal);
float3 viewDirection = normalize(input.viewDir);
float newOpacity = min(1.0, _Color.a
/ abs(dot(viewDirection, normalDirection)));
return float4(_Color.rgb, newOpacity);
}
ENDCG
}
}
}
對 newOpacity 的賦值是對該公式的幾乎直接翻譯
請注意,我們在頂點著色器中對頂點輸出引數 output.normal 和 output.viewDir 進行了歸一化(因為我們希望在方向之間進行插值,而不會對任何方向賦予更多或更少的權重),並在片段著色器開始時進行歸一化(因為插值會在一定程度上扭曲我們的歸一化)。但是,在許多情況下,在頂點著色器中對 output.normal 進行歸一化並不是必需的。類似地,在大多數情況下,在片段著色器中對 output.viewDir 進行歸一化也是沒有必要的。
更多藝術控制
[edit | edit source]雖然描述的輪廓增強基於物理模型,但它缺乏藝術控制;也就是說,CG 藝術家無法輕鬆地建立比物理模型建議的更細或更粗的輪廓。為了允許更多藝術控制,您可以引入另一個(正)浮點數字屬性,並在使用上面的公式之前,將點積 |V·N| 乘以該數字的冪(使用內建的 Cg 函式 pow(float x, float y))。這將允許 CG 藝術家獨立於基本顏色的不透明度建立更細或更粗的輪廓。
總結
[edit | edit source]恭喜,您已經完成了本教程。我們討論了
- 如何找到平滑表面的輪廓(使用法線向量和視角方向的點積)。
- 如何在這些輪廓處增強不透明度。
- 如何在著色器中實現公式。
- 如何將點和法線向量從物體空間變換到世界空間(對法線向量使用轉置逆模型矩陣)。
- 如何計算視角方向(作為從相機位置到頂點位置的差)。
- 如何插值歸一化方向(即歸一化兩次:在頂點著色器和片段著色器中)。
- 如何提供對輪廓粗細的更多藝術控制。
進一步閱讀
[edit | edit source]如果您仍然想了解更多
- 關於物體空間和世界空間,您應該閱讀 “頂點變換”部分 中的描述。
- 關於如何將變換矩陣應用於點、方向和法線向量,您應該閱讀 “應用矩陣變換”部分。
- 關於渲染透明物體的基礎知識,您應該閱讀 “透明度”部分。
- 關於 Unity 提供的統一變數和著色器屬性,您應該閱讀 “在世界空間中著色”部分。
- 關於輪廓增強的數學原理,您可以閱讀 Martin Kraus 在 2005 年 IEEE 視覺化大會上發表的論文“Scale-Invariant Volume Rendering”的第 5.1 節,該論文可在 網上 獲取。