跳轉到內容

Cg 程式設計/Unity/紋理球體

來自華夏公益教科書,開放世界的開放書籍
從阿波羅 17 號上看到的地球。地球的形狀接近一個相當光滑的球體。

本教程介紹了紋理對映

這是關於 Unity 中 Cg 著色器紋理的一系列教程中的第一個。在本教程中,我們從球體上的單個紋理貼圖開始。更具體地說,我們將地球表面的影像對映到球體上。基於此,後續教程將涵蓋諸如紋理表面的照明、透明紋理、多重紋理、光澤對映等主題。

逼近球體的三角形網格。
地球表面的影像。水平座標代表經度,垂直座標代表緯度。

紋理對映

[編輯 | 編輯原始碼]

“紋理對映”(或“紋理化”)的基本思想是將影像(即“紋理”或“紋理貼圖”)對映到三角形網格上;換句話說,將平面圖像放到三維形狀的表面上。

為此,定義了“紋理座標”,它們只是指定紋理(即影像)中的位置。在 OpenGL 中,水平座標稱為S,垂直座標稱為T。然而,通常將它們稱為xy。在動畫和建模工具中,紋理座標通常稱為UV

為了將紋理影像對映到網格,網格的每個頂點都給定一對紋理座標。(這個過程(以及結果)有時被稱為“UV 對映”,因為每個頂點都對映到 UV 空間中的一個點。)因此,每個頂點都對映到紋理影像中的一個點。然後可以為三個頂點之間的任何三角形的每個點插值頂點的紋理座標,因此網格的所有三角形的每個點都可以有一對(插值的)紋理座標。這些紋理座標將網格的每個點對映到紋理貼圖中的特定位置,因此對映到該位置的顏色。因此,渲染紋理對映的網格包括對所有可見點執行兩個步驟:紋理座標的插值和紋理影像在由插值紋理座標指定的位置的顏色查詢。

通常,任何有效的浮點數都是有效的紋理座標。但是,當 GPU 被要求查詢紋理影像的畫素(或“紋素”(例如使用下面描述的“tex2D”指令))時,它將在內部將紋理座標對映到 0 到 1 之間的範圍內,方式取決於匯入紋理時指定的“包裹模式”:包裹模式“重複”基本上使用紋理座標的小數部分來確定 0 到 1 之間的紋理座標。另一方面,包裹模式“鉗位”將紋理座標鉗位到此範圍。然後,這些 0 到 1 之間的內部紋理座標用於確定紋理影像中的位置: 指定紋理影像的左下角; 右下角; 左上角;等等。

在 Unity 中紋理化球體

[編輯 | 編輯原始碼]

要在 Unity 中將地球表面的影像對映到球體上,您首先必須將影像匯入 Unity。單擊影像,直到您獲得較大的版本,並將其儲存到您的計算機上(通常使用右鍵單擊)(記住您儲存的位置)。然後切換到 Unity 並從主選單中選擇資產 > 匯入新資產...。選擇影像檔案,然後在檔案選擇框中單擊匯入。匯入的紋理影像應出現在專案視窗中。(或者,您也可以簡單地將影像檔案拖放到專案視窗中。)透過在其中選擇它,關於匯入方式的詳細資訊將出現在檢查器視窗中(並且可以更改)。

現在建立一個球體、一個材質和一個著色器,並將著色器附加到材質,並將材質附加到球體,如“最小著色器”部分中所述。著色器程式碼應該是

Shader "Cg shader with single texture" {
   Properties {
      _MainTex ("Texture Image", 2D) = "white" {} 
         // a 2D texture property that we call "_MainTex", which should
         // be labeled "Texture Image" in Unity's user interface.
         // By default we use the built-in texture "white"  
         // (alternatives: "black", "gray" and "bump").
   }
   SubShader {
      Pass {	
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;	
            // a uniform variable referring to the property above
            // (in fact, this is just a small integer specifying a 
            // "texture unit", which has the texture image "bound" 
            // to it; Unity takes care of this).
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
               // Unity provides default longitude-latitude-like 
               // texture coordinates at all vertices of a 
               // sphere mesh as the input parameter 
               // "input.texcoord" with semantic "TEXCOORD0".
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
         float4 frag(vertexOutput input) : COLOR
         {
            return tex2D(_MainTex, input.tex.xy);	
               // look up the color of the texture image specified by 
               // the uniform "_MainTex" at the position specified by 
               // "input.tex.x" and "input.tex.y" and return it
 
         }
 
         ENDCG
      }
   }
   Fallback "Unlit/Texture"
}

請注意,選擇名稱_MainTex是為了確保回退著色器Unlit/Texture可以訪問它(參見“漫反射”部分中關於回退著色器的討論)。

球體現在應該是白色的。如果它是灰色的,您應該檢查著色器是否附加到材質,以及材質是否附加到球體。如果球體是洋紅色的,您應該檢查著色器程式碼。特別是,您應該在專案視窗中選擇著色器,並在檢查器視窗中讀取錯誤訊息。

如果球體是白色的,則在層次結構視窗場景檢視中選擇球體,並檢視檢查器視窗中的資訊。您的材質應出現在網格渲染器下,並且在它下面應該有一個標籤紋理影像。(否則,單擊材質欄以使其顯示。)標籤“紋理影像”與我們在著色器程式碼中為我們的著色器屬性_MainTex指定的標籤相同。在該標籤的右側有一個空框。要麼單擊框中的小選擇按鈕並選擇匯入的紋理影像,要麼將紋理影像從專案視窗拖放到此空框中。

如果一切順利,紋理影像現在應該出現在球體上。恭喜!

它是如何工作的

[編輯 | 編輯原始碼]

由於許多技術使用紋理對映,因此理解這裡發生的事情非常有意義。因此,讓我們回顧一下著色器程式碼

Unity 的球體物件的頂點帶有每個頂點的紋理座標,這些座標位於語義為TEXCOORD0的頂點輸入引數texcoord中。這些座標類似於經度和緯度(但範圍從 0 到 1)。這類似於語義為POSITION的頂點輸入引數vertex,它指定了物件空間中的位置,只是texcoord指定了紋理影像空間中的紋理座標。

然後,頂點著色器將每個頂點的紋理座標寫入頂點輸出引數output.tex。對於三角形的每個片段(即每個覆蓋的畫素),對三個三角形頂點處的此輸出引數的值進行插值(參見“光柵化”部分中的描述),並且將插值的紋理座標作為輸入引數提供給片段著色器。然後,片段著色器使用它們在由均勻變數_MainTex指定的紋理影像中查詢紋理空間中插值位置的顏色,並將此顏色作為片段輸出引數返回,然後將其寫入幀緩衝區並在螢幕上顯示。

為了理解其他教程中介紹的更復雜的紋理對映技術,您必須對這些步驟有一個很好的瞭解。

重複和移動紋理

[編輯 | 編輯原始碼]

在上面的著色器的 Unity 介面中,您可能已經注意到引數平鋪偏移,每個引數都有一個x和一個y分量。在內建著色器中,這些引數允許您重複紋理(透過在紋理座標空間中縮小紋理影像)並在表面上移動紋理影像(透過在紋理座標空間中偏移它)。為了與這種行為保持一致,必須定義另一個均勻變數

         uniform float4 _MainTex_ST; 
            // tiling and offset parameters of property "_MainTex"

對於每個紋理屬性,Unity 提供了一個以“_ST”結尾的 float4 統一變數。(記住:“S”和“T”是紋理座標的官方名稱,通常稱為“U”和“V”,或“x”和“y”。)這個統一變數儲存了 _MainTex_ST.x_MainTex_ST.yTiling 引數的 xy 元件,而 Offset 引數的 xy 元件則儲存在 _MainTex_ST.z_MainTex_ST.w 中。這個統一變數應該像這樣使用

            return tex2D(_MainTex, 
               _MainTex_ST.xy * input.tex.xy + _MainTex_ST.zw);

這使得著色器表現得像內建著色器一樣。在其他教程中,為了使著色器程式碼更簡潔,通常不會實現此功能。

為了完整起見,以下是包含此功能的完整著色器程式碼

Shader "Cg shader with single texture" {
   Properties {
      _MainTex ("Texture Image", 2D) = "white" {} 
         // a 2D texture property that we call "_MainTex", which should
         // be labeled "Texture Image" in Unity's user interface.
         // By default we use the built-in texture "white"  
         // (alternatives: "black", "gray" and "bump").
   }
   SubShader {
      Pass {	
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
                   
         uniform sampler2D _MainTex;	
         uniform float4 _MainTex_ST; 
            // tiling and offset parameters of property

         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            return tex2D(_MainTex, 
               _MainTex_ST.xy * input.tex.xy + _MainTex_ST.zw);	
               // texture coordinates are multiplied with the tiling 
               // parameters and the offset parameters are added
         }

         ENDCG
      }
   }
   Fallback "Unlit/Texture"
}

Unity 在 UnityCG.cginc 中為這種紋理座標變換提供了一個宏,即你必須在 Pass 中包含這一行

         #include "UnityCG.cginc"

有了它,你可以使用宏 TRANSFORM_TEX() 來改寫上面的 return 語句

         return tex2D(_MainTex, TRANSFORM_TEX(input.tex, _MainTex));

你已經到達了最重要的教程之一的結尾。我們已經瞭解了

  • 如何匯入紋理影像,以及如何將其附加到著色器的紋理屬性。
  • 頂點著色器和片段著色器如何協同工作,將紋理影像對映到網格。
  • Unity 的紋理平鋪和偏移引數的工作原理,以及如何實現它們。

進一步閱讀

[編輯 | 編輯原始碼]

如果你想知道更多

  • 關於頂點著色器和片段著色器的資料流進出(即頂點輸入和輸出引數等),你應該閱讀 “可程式設計圖形管道” 部分的描述。
  • 關於片段著色器的頂點輸出引數的插值,你應該閱讀 “光柵化” 部分的討論。

< Cg Programming/Unity

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