跳轉到內容

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

來自華夏公益教科書,開放的書籍,開放的世界
肥皂泡中的反射。

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

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

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

使用天空盒的反射貼圖

[編輯 | 編輯原始碼]

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

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

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

立方體貼圖

[編輯 | 編輯原始碼]

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

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

相應的統一變數在 Cg 著色器中以這種方式定義

            uniform samplerCUBE _Cube;

要建立立方體貼圖,請在專案視窗中選擇建立 > Legacy > 立方體貼圖。然後,您必須在檢查器視窗中為立方體的面指定六個紋理影像。此類紋理的免費示例可以在 Unity 的 Asset Store 中找到(選擇Windows > Asset Store並搜尋“天空盒”)。此外,您應該為立方體貼圖在檢查器視窗中選中MipMaps;這應該為反射貼圖產生明顯更好的視覺效果。或者,您可以透過在檢查器視窗中將紋理型別設定為立方體貼圖來匯入立方體貼圖紋理(其中包括六個面的影像在一個影像檔案中)。

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

            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));

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

            return texCUBE(_Cube, reflectedDir);

就是這樣。

完整著色器程式碼

[編輯 | 編輯原始碼]

然後著色器程式碼變為

Shader "Cg shader with reflection map" {
   Properties {
      _Cube("Reflection Map", Cube) = "" {}
   }
   SubShader {
      Pass {   
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag

         #include "UnityCG.cginc"

         // User-specified uniforms
         uniform samplerCUBE _Cube;

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float3 normalDir : TEXCOORD0;
            float3 viewDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            output.viewDir = mul(modelMatrix, input.vertex).xyz 
               - _WorldSpaceCameraPos;
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));
            return texCUBE(_Cube, reflectedDir);
         }
 
         ENDCG
      }
   }
}

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

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

進一步閱讀

[編輯 | 編輯原始碼]

如果您仍然想知道更多

< Cg 程式設計/Unity

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