跳轉到內容

GLSL 程式設計/Unity/反射表面

來自華夏公益教科書
檔案:Cloud Gate Chicago.jpg
反射表面的例子:芝加哥的“雲門”雕塑。

本教程介紹了反射對映(以及用於實現它的立方體貼圖)。

這是關於在 Unity 中使用立方體貼圖進行環境對映的一系列教程中的第一個。本教程基於 “平滑鏡面高光”部分 中描述的逐畫素光照,以及在 “紋理球體”部分 中介紹的紋理對映概念。

天空盒是一個包圍整個場景的(無限大的)大盒子。這裡,反射的攝像機射線(即視點射線)擊中天空盒的紋理面之一。

使用天空盒的反射對映

[編輯 | 編輯原始碼]

左側的插圖描繪了使用靜態天空盒的反射對映的概念:視點射線在物體表面的一個點反射,反射射線與天空盒相交,以確定相應畫素的顏色。天空盒只是一個帶有紋理面的包圍整個場景的大立方體。需要注意的是,天空盒通常是靜態的,不包含場景中的任何動態物體。但是,用於反射對映的“天空盒”通常被渲染以包含從特定視角看到的場景。然而,這超出了本教程的範圍。

此外,本教程只涵蓋了反射的計算,不涵蓋天空盒的渲染,這在 “天空盒”部分 中討論。對於物體中天空盒的反射,我們必須渲染物體,並將攝像機射線從攝像機反射到表面法線向量上的表面點。這種反射的數學原理與光線在表面法線向量上的反射相同,這在 “鏡面高光”部分 中討論過。

一旦我們有了反射射線,就必須計算它與大天空盒的交點。如果天空盒是無限大的,這種計算實際上就變得更容易了:在這種情況下,表面點的座標位置根本無關緊要,因為其距離座標系原點的距離與天空盒的大小相比是無限小的;因此,只有反射射線的方向很重要,而不是它的位置。因此,我們實際上也可以認為是一條從一個小天空盒的中心發出的射線,而不是從無限大的天空盒中的某個地方發出的射線。(如果您不熟悉這個概念,您可能需要一些時間來接受它。)根據反射射線的方向,它將與紋理天空盒的六個面之一相交。我們可以計算出哪個面被相交,以及面被相交的位置,然後在特定面的紋理影像中進行紋理查詢(參見 “紋理球體”部分)。但是,GLSL 提供了立方體貼圖,它完全支援使用方向向量在立方體的六個面上進行這種紋理查詢。因此,我們只需要提供一個環境立方體貼圖作為著色器屬性,並使用textureCube指令和反射方向來獲得立方體貼圖中相應位置的顏色。

立方體貼圖

[編輯 | 編輯原始碼]

名為_Cube的立方體貼圖著色器屬性可以在 Unity 著色器中這樣定義

   Properties {
      _Cube ("Reflection Map", Cube) = "" {}
   }

相應的 uniform 變數在 GLSL 著色器中這樣定義

            uniform samplerCube _Cube;

要建立立方體貼圖,請在專案檢視中選擇建立 > 立方體貼圖。然後,您必須在檢查器檢視中為立方體的面指定六個紋理影像。此類紋理的示例可以在標準資源 > 天空盒 > 紋理中找到。此外,您應該為立方體貼圖在檢查器檢視中選中MipMaps;這應該為反射對映產生明顯更好的視覺效果。

頂點著色器必須計算視點方向viewDirection和法線方向normalDirection,如 “鏡面高光”部分 中所述。為了在片段著色器中反射視點方向,我們可以使用 GLSL 函式reflect,這也如 “鏡面高光”部分 中所述

            vec3 reflectedDirection = 
               reflect(viewDirection, normalize(normalDirection));

要執行立方體貼圖中的紋理查詢並將結果顏色儲存在片段顏色中,我們只需使用

            gl_FragColor = textureCube(_Cube, reflectedDirection);

就是這樣。

完整的著色器程式碼

[編輯 | 編輯原始碼]

然後著色器程式碼變為

Shader "GLSL shader with reflection map" {
   Properties {
      _Cube("Reflection Map", Cube) = "" {}
   }
   SubShader {
      Pass {   
         GLSLPROGRAM

         // User-specified uniforms
         uniform samplerCube _Cube;   

         // The following built-in uniforms
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix

         // Varyings         
         varying vec3 normalDirection;
         varying vec3 viewDirection;
         
         #ifdef VERTEX
         
         void main()
         {            
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            normalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));
            viewDirection = vec3(modelMatrix * gl_Vertex 
               - vec4(_WorldSpaceCameraPos, 1.0));
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif


         #ifdef FRAGMENT
         
         void main()
         {
            vec3 reflectedDirection = 
               reflect(viewDirection, normalize(normalDirection));
            gl_FragColor = textureCube(_Cube, reflectedDirection);
         }

         #endif

         ENDGLSL
      }
   }
}


恭喜!您已經完成了第一個關於環境貼圖的教程。我們已經看到了

  • 如何在物體中計算天空盒的反射。
  • 如何在 Unity 中生成立方體貼圖,以及如何將它們用於反射對映。

進一步閱讀

[編輯 | 編輯原始碼]

如果您仍然想了解更多


< GLSL 程式設計/Unity

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