Cg 程式設計/Unity/著色器除錯

本教程討論了頂點輸入引數。假設你熟悉“最小著色器”部分和“RGB 立方體”部分.
本教程還介紹了在 Unity 中除錯著色器的主要技術:假彩色影像,即透過將片段顏色的一組分量設定為特定值來視覺化該值。然後,在生成的影像中,該顏色分量的強度使你能夠對著色器中的值做出推斷。這可能看起來是一種非常原始的除錯技術,因為它確實是一種非常原始的除錯技術。不幸的是,在 Unity 中沒有其他選擇。
在“RGB 立方體”部分中,你已經看到了片段著色器如何透過頂點輸出引數的輸出結構從頂點著色器獲取資料。這裡的問題是:頂點著色器從哪裡獲取資料?在 Unity 中,答案是遊戲物件的網格渲染器元件在每一幀都會將遊戲物件網格的所有資料傳送到 GPU。(這通常被稱為“繪製呼叫”。請注意,每個繪製呼叫都有一定的效能開銷;因此,將一個大型網格傳送到 GPU 進行一次繪製呼叫比傳送多個小型網格進行多次繪製呼叫效率更高。)這些資料通常由一個三角形列表組成,每個三角形由三個頂點定義,每個頂點具有某些屬性,包括位置。這些屬性透過頂點輸入引數在頂點著色器中可用。在 Cg 中,透過語義來實現將不同屬性對映到不同頂點輸入引數的過程,即每個頂點輸入引數都必須指定一個特定的語義,例如 POSITION、NORMAL、TEXCOORD0、TEXCOORD1、TANGENT、COLOR 等。(在舊版本的 Unity 中,內建頂點輸入引數也必須具有特定的名稱,即本示例中使用的名稱。)
將所有輸入頂點引數包含在一個結構中通常很方便,例如
struct vertexInput {
float4 vertex : POSITION; // position (in object coordinates,
// i.e. local or model coordinates)
float4 tangent : TANGENT;
// vector orthogonal to the surface normal
float3 normal : NORMAL; // surface normal vector (in object
// coordinates; usually normalized to unit length)
float4 texcoord : TEXCOORD0; // 0th set of texture
// coordinates (a.k.a. “UV”; between 0 and 1)
float4 texcoord1 : TEXCOORD1; // 1st set of tex. coors.
float4 texcoord2 : TEXCOORD2; // 2nd set of tex. coors.
float4 texcoord3 : TEXCOORD3; // 3rd set of tex. coors.
fixed4 color : COLOR; // color (usually constant)
};
該結構可以這樣使用
Shader "Cg shader with all built-in vertex input parameters" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct vertexInput {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
output.col = input.texcoord; // set the output color
// other possibilities to play with:
// output.col = input.vertex;
// output.col = input.tangent;
// output.col = float4(input.normal, 1.0);
// output.col = input.texcoord;
// output.col = input.texcoord1;
// output.col = input.texcoord2;
// output.col = input.texcoord3;
// output.col = input.color;
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
}
}
在“RGB 立方體”部分中,我們已經看到了如何透過將片段顏色設定為這些值來視覺化頂點座標。在本示例中,片段顏色被設定為紋理座標,這樣我們就可以看到 Unity 提供了什麼樣的紋理座標。
請注意,只有 tangent 的前三個分量代表切線方向。縮放和第四個分量以特定方式設定,這主要用於視差貼圖(參見“凹凸表面的投影”部分)。
通常,透過只指定你實際需要的頂點輸入引數(例如位置、法線和一組紋理座標;有時還有切線向量)可以實現更高的效能。Unity 為最常見的情況提供了預定義輸入結構 appdata_base、appdata_tan、appdata_full 和 appdata_img。這些結構在檔案 UnityCG.cginc 中定義(位於 Unity > Editor > Data > CGIncludes 目錄中)。
struct appdata_base {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_tan {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
// and additional texture coordinates only on XBOX360
};
struct appdata_img {
float4 vertex : POSITION;
half2 texcoord : TEXCOORD0;
};
檔案 UnityCG.cginc 透過以下程式碼行包含:#include "UnityCG.cginc"。因此,上面的著色器可以這樣重寫
Shader "Cg shader with all built-in vertex input parameters" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
vertexOutput vert(appdata_full input)
{
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
output.col = input.texcoord;
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
}
}
在嘗試理解假彩色影像中的資訊時,重要的是隻關注一個顏色分量。例如,如果將語義為 TEXCOORD0 的輸入頂點引數 texcoord 寫入片段顏色,那麼片段的紅色分量會視覺化 texcoord 的 x 座標,即輸出顏色是最大純紅色還是最大黃色或最大洋紅色都沒有關係,在所有情況下紅色分量都是 1。另一方面,對於紅色分量來說,顏色是藍色、綠色還是青色以及任何強度也無關緊要,因為在所有情況下紅色分量都是 0。如果你從未學會只關注一個顏色分量,這可能相當具有挑戰性;因此,你可能需要考慮一次只檢視一個顏色分量。例如,使用以下程式碼行在頂點著色器中設定輸出引數
output.col = float4(input.texcoord.x, 0.0, 0.0, 1.0);
這將輸出引數的紅色分量設定為 texcoord 的 x 分量,但將綠色和藍色分量設定為 0(以及 alpha 或不透明度分量設定為 1,但這在該著色器中無關緊要)。
如果你只關注紅色分量或只可視化紅色分量,你應該會看到它在你繞球體移動時從 0 增加到 1,並且在 360° 後又降到 0。實際上,它的行為類似於行星表面上的經度座標。(在球面座標系中,它對應於方位角。)
如果 texcoord 的 x 分量對應於經度,那麼可以預期 y 分量會對應於緯度(或球面座標系中的傾角)。但是,請注意,紋理座標始終介於 0 和 1 之間;因此,該值在底部(南極)為 0,在頂部(北極)為 1。你可以將 y 分量作為綠色獨立視覺化
output.col = float4(0.0, input.texcoord.y, 0.0, 1.0);
紋理座標特別容易視覺化,因為它們就像顏色分量一樣,介於 0 和 1 之間。幾乎同樣容易視覺化的是歸一化向量的座標(即長度為 1 的向量;例如,normal 輸入引數通常是歸一化的),因為它們始終介於 -1 和 +1 之間。要將此範圍對映到 0 到 1 的範圍,你需要向每個分量加 1 並將所有分量除以 2,例如
output.col = float4(
(input.normal + float3(1.0, 1.0, 1.0)) / 2.0, 1.0);
請注意,normal 是一個三維向量。黑色對應於座標 -1,一個分量的全強度對應於座標 +1。
如果你要視覺化的值不在 0 到 1 或 -1 到 +1 的範圍內,你需要將其對映到 0 到 1 的範圍,這是顏色分量的範圍。如果你不知道應該期望什麼值,你只需要嘗試一下。這方面有幫助的是,如果你指定了超出 0 到 1 範圍的顏色分量,它們會自動被限制到此範圍內。即,小於 0 的值被設定為 0,大於 1 的值被設定為 1。因此,當顏色分量為 0 或 1 時,你至少知道該值小於或大於你假設的值,然後你可以迭代地調整對映,直到顏色分量介於 0 和 1 之間。
為了練習著色器的除錯,本節包含一些程式碼行,當用它們替換頂點著色器中對 col 的賦值時,會產生黑色。你的任務是找出每行程式碼為什麼導致結果為黑色。為此,你應該嘗試視覺化你沒有完全確定的任何值,並將小於 0 或大於 1 的值對映到其他範圍,這樣這些值就可以顯示出來,並且你至少可以瞭解它們所在的範圍。請注意,大多數函式和運算子在“向量和矩陣運算”部分中有介紹。
output.col = input.texcoord - float4(1.5, 2.3, 1.1, 0.0);
output.col = input.texcoord.zzzz;
output.col = input.texcoord / tan(0.0);
以下程式碼行需要對點積和叉積有一定的瞭解
output.col = dot(input.normal, input.tangent.xyz) *
input.texcoord;
output.col = dot(cross(input.normal, input.tangent.xyz),
input.normal) * input.texcoord;
output.col = float4(cross(input.normal, input.normal), 1.0);
output.col = float4(cross(input.normal,
input.vertex.xyz), 1.0);
// only for a sphere!
函式 radians() 總是返回黑色嗎?它有什麼用?
output.col = radians(input.texcoord);
恭喜你已經學完了本教程!我們已經學習了
- Unity 中內建頂點輸入引數的列表。
- 如何透過設定片段輸出顏色的元件來視覺化這些引數(或任何其他值)。
如果你還想了解更多
- 關於頂點和片段著色器中的資料流,請閱讀“可程式設計圖形管道”部分。
- 關於向量操作和函式,請閱讀“向量和矩陣操作”部分。
- 關於 Unity 中頂點輸入引數的額外語義,請閱讀Unity 的著色器語義文件。