跳轉到內容

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

來自華夏公益教科書,開放世界開放書籍
阿波羅 17 號拍攝的地球照片。地球的形狀非常接近一個光滑的球體。

本教程介紹了 **紋理對映**。

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

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

紋理對映

[編輯 | 編輯原始碼]

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

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

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

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

在 Unity 中紋理化球體

[編輯 | 編輯原始碼]

要在 Unity 中將地球表面的影像對映到球體,首先必須將影像匯入 Unity。點選 影像,直到出現更大的版本,然後將其儲存(通常右鍵單擊)到您的計算機上(記住您儲存的位置)。然後切換到 Unity,從主選單中選擇 **Assets > Import New Asset...**。選擇影像檔案,並在檔案選擇框中點選 **Import**。匯入的紋理影像應該出現在 **Project View** 中。透過在其中選擇它,關於其匯入方式的詳細資訊將顯示在 **Inspector View** 中(並且可以更改)。

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

Shader "GLSL 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 {	
         GLSLPROGRAM
                  
         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).

         varying vec4 textureCoordinates; 
            // the texture coordinates at the vertices,
            // which are interpolated for each fragment

         #ifdef VERTEX
                  
         void main()
         {
            textureCoordinates = gl_MultiTexCoord0;
               // Unity provides default longitude-latitude-like 
               // texture coordinates at all vertices of a 
               // sphere mesh as the attribute "gl_MultiTexCoord0".
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = 
               texture2D(_MainTex, vec2(textureCoordinates));	
               // look up the color of the texture image specified by 
               // the uniform "_MainTex" at the position specified by 
               // "textureCoordinates.x" and "textureCoordinates.y" 
               // and return it in "gl_FragColor"             
         }
         
         #endif

         ENDGLSL
      }
   }
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Unlit/Texture"
}

請注意,_MainTex 的名稱是經過選擇的,以確保回退著色器 Unlit/Texture 可以訪問它(參見 “漫反射”章節 中對回退著色器的討論)。

現在球體應該變為白色。如果它是灰色的,您應該檢查著色器是否附加到材質,以及材質是否附加到球體。如果球體是洋紅色的,您應該檢查著色器程式碼。特別是,您應該在 **Project View** 中選擇著色器,並閱讀 **Inspector View** 中的錯誤訊息。

如果球體是白色的,請在 **Hierarchy View** 或 **Scene View** 中選擇球體,並檢視 **Inspector View** 中的資訊。您的材質應該出現在 **Mesh Renderer** 下面,在其下方應該有一個 **Texture Image** 標籤。(否則,點選材質欄以使其顯示。)“Texture Image” 標籤與我們在著色器程式碼中為我們的著色器屬性 _MainTex 指定的標籤相同。該標籤右側有一個空框。點選框中的小 **Select** 按鈕,並選擇匯入的紋理影像,或者將紋理影像從 **Project View** 拖放到此空框中。

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

工作原理

[編輯 | 編輯原始碼]

由於許多技術使用紋理對映,因此瞭解這裡發生了什麼非常值得。因此,讓我們回顧一下著色器程式碼

Unity 球體物件的頂點帶有 gl_MultiTexCoord0 中的屬性資料(每個頂點),它指定了類似於經度和緯度的紋理座標(但範圍從 0 到 1)。這類似於指定物件空間中位置的屬性 gl_Vertex,不同之處在於 gl_MultiTexCoord0 指定紋理影像空間中的紋理座標。

然後頂點著色器將每個頂點的紋理座標寫入變化變數 textureCoordinates。對於三角形的每個片段(即每個覆蓋的畫素),三角形的三個頂點處此變化變數的值都會被插值(參見 “光柵化”章節 中的描述),並且插值的紋理座標將被傳遞給片段著色器。然後,片段著色器使用它們來查詢紋理影像(由統一變數 _MainTex 指定)中紋理空間中插值位置的顏色,並在 gl_FragColor 中返回該顏色,該顏色隨後被寫入幀緩衝區並在螢幕上顯示。

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

重複和移動紋理

[編輯 | 編輯原始碼]

在上面著色器的 Unity 介面中,您可能已經注意到 **Tiling** 和 **Offset** 引數,它們分別具有 **x** 和 **y** 分量。在內建著色器中,這些引數允許您透過在紋理座標空間中縮小紋理影像來重複紋理,以及透過在紋理座標空間中偏移紋理影像來移動紋理影像。為了與這種行為保持一致,必須定義另一個統一變數

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

對於每個紋理屬性,Unity 都提供了一個帶有結尾“_ST”的 vec4 統一變數。(記住:“S”和“T”是紋理座標的官方名稱,通常被稱為“U”和“V”,或者“x”和“y”。)這個統一變數在 _MainTex_ST.x_MainTex_ST.y 中儲存 **Tiling** 引數的 **x** 和 **y** 分量,而在 _MainTex_ST.w_MainTex_ST.z 中儲存 **Offset** 引數的 **x** 和 **y** 分量。統一變數應該像這樣使用

            gl_FragColor = texture2D(_MainTex, 
               _MainTex_ST.xy * textureCoordinates.xy 
               + _MainTex_ST.zw);

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

為了完整性,這裡附帶此功能的完整著色器程式碼

Shader "GLSL shader with single texture" {
   Properties {
      _MainTex ("Texture Image", 2D) = "white" {} 
   }
   SubShader {
      Pass {	
         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	
         uniform vec4 _MainTex_ST; 
            // tiling and offset parameters of property             

         varying vec4 textureCoordinates; 

         #ifdef VERTEX
                  
         void main()
         {
            textureCoordinates = gl_MultiTexCoord0;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = texture2D(_MainTex, 
               _MainTex_ST.xy * textureCoordinates.xy 
               + _MainTex_ST.zw);	
               // textureCoordinates are multiplied with the tiling 
               // parameters and the offset parameters are added
         }
         
         #endif

         ENDGLSL
      }
   }
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Unlit/Texture"
}

您已完成最重要的教程之一。我們已經瞭解了

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

進一步閱讀

[編輯 | 編輯原始碼]

如果你想了解更多

  • 關於頂點著色器和片段著色器輸入輸出的資料流(例如,頂點屬性、變元等),您應該閱讀 “OpenGL ES 2.0 管線” 部分的描述。
  • 關於片段著色器中變元變數的插值,您應該閱讀 “光柵化” 部分的討論。


< GLSL 程式設計/Unity

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