跳轉到內容

GLSL程式設計/Unity/世界空間著色

來自Wikibooks,開放世界中的開放書籍
一些變色龍能夠根據周圍的世界改變它們的顏色。

本教程介紹了**uniform變數**。它基於“最小著色器”部分“RGB立方體”部分“著色器除錯”部分

在本教程中,我們將瞭解一個根據片段在世界中的位置改變片段顏色的著色器。這個概念並不複雜;然而,它有著極其重要的應用,例如使用燈光和環境貼圖進行著色。我們還將瞭解現實世界中的著色器;即,如何讓非程式設計師能夠使用你的著色器?

從物件空間變換到世界空間

[編輯 | 編輯原始碼]

“著色器除錯”部分所述,屬性gl_Vertex指定了物件座標,即網格區域性物件(或模型)空間中的座標。物件空間(或物件座標系)對每個遊戲物件都是特定的;但是,所有遊戲物件都變換到一個共同的座標系——世界空間。

如果一個遊戲物件直接放置到世界空間中,則物件到世界的變換由遊戲物件的Transform元件指定。要檢視它,請在**場景檢視**或**層次結構檢視**中選擇物件,然後在**檢查器檢視**中找到Transform元件。Transform元件中包含“位置”、“旋轉”和“縮放”引數,它們指定了頂點如何從物件座標變換到世界座標。(如果一個遊戲物件是某個物件組的一部分,在層次結構檢視中透過縮排顯示,則Transform元件僅指定從遊戲物件的物件座標到父物件的物件座標的變換。在這種情況下,實際的物件到世界變換由物件變換與其父級、祖父母等的變換組合給出。)頂點透過平移、旋轉和縮放的變換,以及變換的組合及其作為4×4矩陣的表示,在“頂點變換”部分中討論。

回到我們的示例:從物件空間到世界空間的變換被放入一個4×4矩陣中,該矩陣也被稱為“模型矩陣”(因為該變換也被稱為“模型變換”)。該矩陣在uniform變數unity_ObjectToWorld中可用(在Unity 5中,在舊版本中可能是_Object2World),它在以下著色器中定義和使用

Shader "GLSL shading in world space" {
   SubShader {
      Pass {
         GLSLPROGRAM

         uniform mat4 unity_ObjectToWorld; 
            // definition of a Unity-specific uniform variable 

         

         #ifdef VERTEX
         
         varying vec4 position_in_world_space;

         void main()
         {
            position_in_world_space = unity_ObjectToWorld * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to world coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         varying vec4 position_in_world_space;
                  
         void main()
         {
            float dist = distance(position_in_world_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (the 4th coordinate should always be 
               // 1 for points).
            
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

請注意,此著色器確保uniform的定義包含在頂點著色器和片段著色器中(儘管此特定的片段著色器不需要它)。這類似於“RGB立方體”部分中討論的varying變數的定義。

通常,OpenGL應用程式必須設定uniform變數的值;但是,Unity負責始終設定預定義uniform變數(如unity_ObjectToWorld)的正確值;因此,我們不必擔心它。

此著色器將頂點位置變換到世界空間,並將其作為varying傳遞給片段著色器。對於片段著色器,varying變數包含世界座標中片段的插值位置。根據此位置到世界座標系原點的距離,設定兩種顏色之一。因此,如果你在編輯器中移動帶有此著色器的物件,它將在世界座標系原點附近變成綠色。遠離原點,它將變成深灰色。

更多Unity特定的uniform變數

[編輯 | 編輯原始碼]

事實上,有幾個類似於unity_ObjectToWorld的預定義uniform變數。以下是一個簡短的列表(包括unity_ObjectToWorld),它出現在幾個教程的著色器程式碼中

      // The following built-in uniforms (except _LightColor0 and 
      // _LightMatrix0) are also defined in "UnityCG.glslinc", 
      // i.e. one could #include "UnityCG.glslinc" 
      uniform vec4 _Time, _SinTime, _CosTime; // time values from Unity
      uniform vec4 _ProjectionParams;
         // x = 1 or -1 (-1 if projection is flipped)
         // y = near plane; z = far plane; w = 1/far plane
      uniform vec4 _ScreenParams; 
         // x = width; y = height; z = 1 + 1/width; w = 1 + 1/height
      uniform vec4 unity_Scale; // w = 1/scale; see _World2Object
      uniform vec3 _WorldSpaceCameraPos;
      uniform mat4 unity_ObjectToWorld; // model matrix
      uniform mat4 unity_WorldToObject; // inverse model matrix 
         // (all but the bottom-right element have to be scaled 
         // with unity_Scale.w if scaling is important) 
      uniform vec4 _LightPositionRange; // xyz = pos, w = 1/range
      uniform vec4 _WorldSpaceLightPos0; 
         // position or direction of light source
      uniform vec4 _LightColor0; // color of light source 
      uniform mat4 _LightMatrix0; // matrix to light space

如註釋所示,除了_LightColor0_LightMatrix0之外,你還可以包含檔案UnityCG.glslinc來代替定義所有這些uniform變數。但是,由於某種未知的原因,_LightColor0_LightMatrix0沒有包含在這個檔案中;因此,我們必須單獨定義它們

      #include "UnityCG.glslinc"
      uniform vec4 _LightColor0; 
      uniform mat4 _LightMatrix0;

Unity並不總是更新所有這些uniform變數。特別是,_WorldSpaceLightPos0_LightColor0_LightMatrix0僅針對適當標記的著色器傳遞正確設定,例如在Pass {...} 塊的第一行使用Tags {"LightMode" = "ForwardBase"};另請參閱“漫反射”部分

更多OpenGL特定的uniform變數

[編輯 | 編輯原始碼]

另一類內建uniform變數是為OpenGL相容性配置檔案定義的,例如mat4矩陣gl_ModelViewProjectionMatrix,它等效於其他兩個內建uniform變數的矩陣乘積gl_ProjectionMatrix * gl_ModelViewMatrix。相應的變換在“頂點變換”部分中詳細描述。

如你在上面的著色器中看到的,這些uniform變數不必定義;它們在Unity中的GLSL著色器中始終可用。如果你必須定義它們,定義將如下所示

      uniform mat4 gl_ModelViewMatrix;
      uniform mat4 gl_ProjectionMatrix;
      uniform mat4 gl_ModelViewProjectionMatrix;
      uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
      uniform mat3 gl_NormalMatrix; 
         // transpose of the inverse of gl_ModelViewMatrix
      uniform mat4 gl_ModelViewMatrixInverse;
      uniform mat4 gl_ProjectionMatrixInverse;
      uniform mat4 gl_ModelViewProjectionMatrixInverse;
      uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixTranspose;
      uniform mat4 gl_ProjectionMatrixTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixTranspose;
      uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixInverseTranspose;
      uniform mat4 gl_ProjectionMatrixInverseTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
      uniform mat4 gl_TextureMatrixInverseTranspose[gl_MaxTextureCoords];

      struct gl_LightModelParameters { vec4 ambient; };
      uniform gl_LightModelParameters gl_LightModel;
      ...

事實上,OpenGL的相容性配置檔案定義了更多uniform變數;請參閱可在Khronos的OpenGL頁面上獲得的“OpenGL著色語言4.10.6規範”的第7章。Unity支援其中許多,但並非全部。

其中一些uniform變數是陣列,例如gl_TextureMatrix。實際上,可以使用一個矩陣陣列gl_TextureMatrix[0]gl_TextureMatrix[1]、…、gl_TextureMatrix[gl_MaxTextureCoords - 1],其中gl_MaxTextureCoords是一個內建整數。

計算檢視矩陣

[編輯 | 編輯原始碼]

傳統上,習慣於在視空間中進行許多計算,視空間只是世界空間的旋轉和平移版本(有關詳細資訊,請參見“頂點變換”部分)。因此,OpenGL 只提供模型矩陣和檢視矩陣的乘積,即模型檢視矩陣,它在統一變數gl_ModelViewMatrix中可用。檢視矩陣不可用。Unity 也不提供它。

但是,unity_ObjectToWorld只是模型矩陣,而unity_WorldToObject是模型矩陣的逆矩陣。(除了右下角元素之外,所有元素都必須按untiy_Scale.w縮放。)因此,我們可以很容易地計算出檢視矩陣。數學表示式如下所示

  

換句話說,檢視矩陣是模型檢視矩陣和模型矩陣的逆矩陣的乘積(除了右下角元素為 1 外,它是unity_WorldToObject * unity_Scale.w)。假設我們已經定義了統一變數unity_WorldToObjectunity_Scale,我們可以在 GLSL 中以這種方式計算檢視矩陣

      mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
      modelMatrixInverse[3][3] = 1.0; 
      mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;

使用者指定的統一變數:著色器屬性

[編輯 | 編輯原始碼]

還有一種重要的統一變數型別:使用者可以設定的統一變數。實際上,在 Unity 中這些被稱為著色器屬性。您可以將它們視為著色器的引數。沒有引數的著色器通常只由其程式設計師使用,因為即使是最小的必要更改也需要一些程式設計。另一方面,使用具有描述性名稱的引數的著色器可以被其他人使用,即使是非程式設計師,例如 CG 藝術家。假設您在一個遊戲開發團隊中,並且一位 CG 藝術家要求您為其 100 次設計迭代中的每一次都調整您的著色器。顯而易見的是,一些引數(即使 CG 藝術家也可以使用)可以為您節省大量時間。此外,假設您想出售您的著色器:引數通常會極大地提高著色器的價值。

由於 Unity 的 ShaderLab 參考中對著色器屬性的描述已經相當不錯,這裡只舉一個如何在我們的示例中使用著色器屬性的例子。我們首先宣告屬性,然後定義相同名稱和相應型別的統一變數。

Shader "GLSL shading in world space" {
   Properties {
      _Point ("a point in world space", Vector) = (0., 0., 0., 1.0)
      _DistanceNear ("threshold distance", Float) = 5.0
      _ColorNear ("color near to point", Color) = (0.0, 1.0, 0.0, 1.0)
      _ColorFar ("color far from point", Color) = (0.3, 0.3, 0.3, 1.0)
   }
   
   SubShader {
      Pass {
         GLSLPROGRAM

         // uniforms corresponding to properties
         uniform vec4 _Point;
         uniform float _DistanceNear;
         uniform vec4 _ColorNear;
         uniform vec4 _ColorFar;

         #include "UnityCG.glslinc" 
            // defines _Object2World and _World2Object
         
         varying vec4 position_in_world_space;

         #ifdef VERTEX
         
         void main()
         {
            mat4 modelMatrix = _Object2World;

            position_in_world_space = modelMatrix * gl_Vertex;
                        
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
         }
         
         #endif

         #ifdef FRAGMENT
                  
         void main()
         {
            float dist= distance(position_in_world_space, _Point);
            
            if (dist < _DistanceNear)
            {
               gl_FragColor = _ColorNear;
            }
            else
            {
               gl_FragColor = _ColorFar;
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

使用這些引數,非程式設計師可以修改我們著色器的效果。這很好;但是,著色器的屬性(實際上,統一變數通常)也可以透過指令碼設定!例如,附加到正在使用著色器的遊戲物件的 JavaScript 可以使用以下幾行程式碼設定屬性

   renderer.sharedMaterial.SetVector("_Point", 
      Vector4(1.0, 0.0, 0.0, 1.0));
   renderer.sharedMaterial.SetFloat("_DistanceNear", 
      10.0);
   renderer.sharedMaterial.SetColor("_ColorNear", 
      Color(1.0, 0.0, 0.0));
   renderer.sharedMaterial.SetColor("_ColorFar", 
      Color(1.0, 1.0, 1.0));

如果您想更改使用此材質的所有物件的引數,請使用sharedMaterial;如果您只想更改一個物件的引數,請使用material。透過指令碼,您可以例如將_Point設定為另一個物件的位置(即其 Transform 元件的position)。透過這種方式,您可以只通過在編輯器中移動另一個物件來指定一個點。為了編寫這樣的指令碼,請在**專案檢視**中選擇**建立 > JavaScript**,然後複製並貼上此程式碼

@script ExecuteInEditMode() // make sure to run in edit mode

var other : GameObject; // another user-specified object

function Update () // this function is called for every frame
{
   if (null != other) // has the user specified an object?
   {
      renderer.sharedMaterial.SetVector("_Point", 
         other.transform.position); // set the shader property 
         // _Point to the position of the other object
   }
}

然後,您應該將指令碼附加到具有著色器的物件,並在**檢查器檢視**中將另一個物件拖放到指令碼的other變數中。

恭喜你,你做到了!(如果你想知道:是的,我在這裡也在自言自語。;))我們討論了

  • 如何將頂點變換到世界座標。
  • Unity 支援的最重要的特定於 Unity 的統一變數。
  • Unity 支援的最重要的特定於 OpenGL 的統一變數。
  • 如何透過新增著色器屬性使著色器更有用和更有價值。

進一步閱讀

[編輯 | 編輯原始碼]

如果你想了解更多


< GLSL 程式設計/Unity

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