跳轉到內容

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

來自華夏公益教科書,開放世界的開放書籍
一張偽彩色衛星影像。

本教程介紹了屬性變數。它基於“最小著色器”部分“RGB 立方體”部分.

本教程還介紹了在 Unity 中除錯著色器的主要技術:偽彩色影像,即透過將片段顏色的某個分量設定為某個值來視覺化該值。然後,所得影像中該顏色分量的強度允許您對著色器中的值做出推斷。這似乎是一種非常原始的除錯技術,因為它確實是一種非常原始的除錯技術。不幸的是,Unity 中沒有其他選擇。

頂點資料從哪裡來?

[編輯 | 編輯原始碼]

“RGB 立方體”部分中,您已經瞭解了片段著色器如何透過 varying 變數從頂點著色器獲取資料。這裡的問題是:頂點著色器從哪裡獲取資料?在 Unity 中,答案是遊戲物件的 Mesh Renderer 元件在每一幀都將遊戲物件的網格的所有資料傳送到 OpenGL。(這通常稱為“繪製呼叫”。請注意,每個繪製呼叫都有一定的效能開銷;因此,將一個大型網格傳送到 OpenGL 以執行一次繪製呼叫比傳送多個小型網格以執行多次繪製呼叫要高效得多。)這些資料通常包含一個長長的三角形列表,其中每個三角形由三個頂點定義,每個頂點都具有某些屬性,包括位置。這些屬性透過屬性變數在頂點著色器中可用。

內建屬性變數及其視覺化方法

[編輯 | 編輯原始碼]

在 Unity 中,大多數標準屬性(位置、顏色、表面法線和紋理座標)都是內建的,也就是說,您不需要(實際上也不應該)定義它們。這些內建屬性的名稱實際上是由 OpenGL 的“相容性配置檔案”定義的,因為如果您將為固定功能管道編寫的 OpenGL 應用程式與(可程式設計的)頂點著色器混合,則需要這些內建名稱。如果您必須定義它們,則定義(僅在頂點著色器中)看起來像這樣

   attribute vec4 gl_Vertex; // position (in object coordinates, 
      // i.e. local or model coordinates)
   attribute vec4 gl_Color; // color (usually constant)
   attribute vec3 gl_Normal; // surface normal vector 
      // (in object coordinates; usually normalized to unit length)
   attribute vec4 gl_MultiTexCoord0; //0th set of texture coordinates 
      // (a.k.a. “UV”; between 0 and 1) 
   attribute vec4 gl_MultiTexCoord1; //1st set of texture coordinates 
      // (a.k.a. “UV”; between 0 and 1)
   ...

Unity 提供了一個屬性變數,但在 OpenGL 中沒有標準名稱,即切線向量,它是一個與表面法線正交的向量。您應該將此變數自己定義為型別為 vec4 的屬性變數,並使用 Tangent 這個特定名稱,如以下著色器所示

Shader "GLSL shader with all built-in attributes" {
   SubShader {
      Pass {
         GLSLPROGRAM

         #ifdef VERTEX

         varying vec4 color;

         attribute vec4 Tangent; // this attribute is specific to Unity 
         
         void main()
         {
            color = gl_MultiTexCoord0; // set the varying variable

            // other possibilities to play with:

            // color = gl_Vertex;
            // color = gl_Color;
            // color = vec4(gl_Normal, 1.0);
            // color = gl_MultiTexCoord0;
            // color = gl_MultiTexCoord1;
            // color = Tangent;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
               
         varying vec4 color;

         void main()
         {
            gl_FragColor = color; // set the output fragment color
         }
         
         #endif

         ENDGLSL
      }
   }
}

“RGB 立方體”部分中,我們已經看到了如何透過將片段顏色設定為這些值來視覺化 gl_Vertex 座標。在本例中,片段顏色被設定為 gl_MultiTexCoord0,這樣我們就可以看到 Unity 提供了什麼樣的紋理座標。

請注意,只有 Tangent 的前三個分量表示切線方向。縮放比例和第四個分量以特定方式設定,這主要對視差貼圖有用(參見“凸起表面的投影”部分)。

如何解讀偽彩色影像

[編輯 | 編輯原始碼]

在嘗試理解偽彩色影像中的資訊時,重點關注一個顏色分量。例如,如果將球體的標準屬性 gl_MultiTexCoord0 寫入片段顏色,那麼片段的紅色分量將視覺化 gl_MultiTexCoord0x 座標,也就是說,輸出顏色是最大純紅色還是最大黃色或最大洋紅色並不重要,在所有情況下,紅色分量都是 1。另一方面,對於紅色分量來說,顏色是藍色還是綠色或青色,或者任何強度也不重要,因為在所有情況下,紅色分量都是 0。如果您從未學會只關注一個顏色分量,這可能很具挑戰性;因此,您可能需要考慮一次只檢視一個顏色分量。例如,透過在頂點著色器中使用以下行設定 varying 變數

            color = vec4(gl_MultiTexCoord0.x, 0.0, 0.0, 1.0);

這將 varying 變數的紅色分量設定為 gl_MultiTexCoord0x 分量,但將綠色和藍色分量設定為 0(以及 alpha 或不透明度分量設定為 1,但在本著色器中無關緊要)。

如果您專注於紅色分量,或者僅視覺化紅色分量,您應該會看到它隨著您繞球體移動從 0 增加到 1,並在 360° 後再次下降到 0。它實際上類似於行星表面上的經度座標。(在球座標系中,它對應於方位角。)

如果 gl_MultiTexCoord0x 分量對應於經度,那麼人們會期望 y 分量對應於緯度(或球座標系中的傾角)。但是,請注意紋理座標始終介於 0 和 1 之間;因此,該值在底部(南極)為 0,在頂部(北極)為 1。您可以將 y 分量視覺化為它本身的綠色,使用

            color = vec4(0.0, gl_MultiTexCoord0.y, 0.0, 1.0);

紋理座標特別容易視覺化,因為它們介於 0 和 1 之間,就像顏色分量一樣。幾乎與之一樣好的是歸一化向量(即長度為 1 的向量;例如,gl_Normal 通常是歸一化的)的座標,因為它們始終介於 -1 和 +1 之間。要將此範圍對映到 0 到 1 的範圍,您需要在每個分量中新增 1,並將所有分量除以 2,例如

            color = vec4((gl_Normal + vec3(1.0, 1.0, 1.0)) / 2.0, 1.0);

請注意,gl_Normal 是一個三維向量。黑色對應於座標 -1,一個分量的完全強度對應於座標 +1。

如果您要視覺化的值不在 0 到 1 或 -1 到 +1 的範圍內,您需要將其對映到 0 到 1 的範圍,這是顏色分量的範圍。如果您不知道要期望什麼值,您只需進行嘗試。這裡有幫助的是,如果您指定了超出 0 到 1 範圍的顏色分量,它們會自動限制在這個範圍內。也就是說,小於 0 的值將被設定為 0,大於 1 的值將被設定為 1。因此,當顏色分量為 0 或 1 時,您至少知道該值小於或大於您假設的值,然後您可以迭代地調整對映,直到顏色分量介於 0 和 1 之間。

除錯實踐

[編輯 | 編輯原始碼]

為了練習著色器的除錯,本節包含一些程式碼行,當頂點著色器中對 color 的賦值被每一行替換時,這些程式碼行會產生黑色。您的任務是找出每行程式碼為什麼導致結果為黑色。為此,您應該嘗試視覺化任何您不完全確定的值,並將小於 0 或大於 1 的值對映到其他範圍,以便這些值可見,並且您至少知道它們在哪個範圍內。請注意,大多數函式和運算子都在“向量和矩陣運算”部分中進行了說明。

            color = gl_MultiTexCoord0 - vec4(1.5, 2.3, 1.1, 0.0);


            color = vec4(gl_MultiTexCoord0.z);


            color = gl_MultiTexCoord0 / tan(0.0);

以下程式碼行需要一些關於點積和叉積的知識

            color = dot(gl_Normal, vec3(Tangent)) * gl_MultiTexCoord0;


            color = dot(cross(gl_Normal, vec3(Tangent)), gl_Normal) * 
               gl_MultiTexCoord0;


            color = vec4(cross(gl_Normal, gl_Normal), 1.0);


            color = vec4(cross(gl_Normal, vec3(gl_Vertex)), 1.0); 
               // only for a sphere!

radians()noise() 函式總是返回黑色嗎?這對什麼有用?

            color = radians(gl_MultiTexCoord0);


            color = noise4(gl_MultiTexCoord0);

請參閱“OpenGL ES 著色語言 1.0.17 規範”中的文件(可從“Khronos OpenGL ES API 登錄檔”獲得),以瞭解 radians() 的用途以及 noise4() 的問題。

片段著色器中的特殊變數

[編輯 | 編輯原始碼]

屬性是頂點特有的,也就是說,它們通常在不同的頂點具有不同的值。片段著色器也有類似的變數,即對每個片段具有不同值的變數。但是,它們與屬性不同,因為它們不是由網格(即三角形列表)指定的。它們也與變體不同,因為它們不是由頂點著色器顯式設定的。

具體來說,一個四維向量gl_FragCoord可用,它包含正在處理的片段的螢幕(或視窗)座標;有關螢幕座標系的描述,請參見“頂點變換”部分

此外,還提供了一個布林變數gl_FrontFacing,它指定正在渲染三角形的正面還是背面。正面通常朝向模型的“外部”,背面朝向模型的“內部”;但是,如果模型不是封閉的曲面,則沒有明顯的外部或內部。通常,表面法向量指向正面方向,但這不是必需的。實際上,正面和背面由頂點三角形的順序指定:如果頂點以逆時針順序出現,則正面可見;如果它們以順時針順序出現,則背面可見。一個應用顯示在“截面”部分

恭喜,您已經完成了本教程!我們已經看到了

  • Unity 中的內建屬性列表:gl_Vertexgl_Colorgl_Normalgl_MultiTexCoord0gl_MultiTexCoord1,以及特殊的Tangent
  • 如何透過設定輸出片段顏色的分量來視覺化這些屬性(或任何其他值)。
  • 片段程式中可用的另外兩個特殊變數:gl_FragCoordgl_FrontFacing

進一步閱讀

[編輯 | 編輯原始碼]

如果您仍然想要了解更多


< GLSL 程式設計/Unity

除非另有說明,否則本頁面上的所有示例原始碼均歸屬公共領域。
華夏公益教科書