跳轉到內容

GLSL 程式設計/Unity/投影儀

來自華夏公益教科書
一個 overhead projector。

本教程涵蓋投影儀的投影紋理貼圖,它們是 Unity 的特定渲染元件。

它基於“Cookies”部分。如果你還沒有閱讀過本教程,建議你先閱讀。

Unity 的投影儀

[編輯 | 編輯原始碼]

Unity 的投影儀有點類似於聚光燈。事實上,它們可以用於類似的應用。然而,存在著重要的技術差異:對於聚光燈,所有被照亮物件的著色器必須計算聚光燈的光照,如“Cookies”部分所述。如果物件的著色器忽略了聚光燈,它就不會被聚光燈照亮。對於投影儀來說,情況不同:每個投影儀都與一個材質相關聯,該材質具有一個應用於投影儀範圍內的任何物件的著色器。因此,物件的著色器不需要處理投影儀;相反,投影儀會將它的著色器應用於它範圍內的所有物件,作為額外的渲染通道,以實現某些效果,例如新增投影影像的光照或衰減物件的顏色以模擬陰影。事實上,透過使用投影儀著色器的不同混合方程,可以實現各種效果。(混合方程在“透明度”部分中討論。)

人們甚至可以認為投影儀是實現光照的更“自然”的方式。然而,光照和材質之間的互動通常是特定於每種材質的,而投影儀的單個著色器無法處理所有這些差異。這限制了投影儀的可能性,只有三種基本行為:向物體新增光照,調製物體的顏色,或者兩者兼而有之,新增光照和調製物體的顏色。我們將以向物體新增光照和衰減物體的顏色為例,它們是調製顏色的一種示例。

用於新增光照的投影儀

[編輯 | 編輯原始碼]

為了建立一個投影儀,從主選單中選擇GameObject > Create Empty,然後(在仍然選中新物件的情況下)從主選單中選擇Component > Effects > Projector。現在你擁有了一個投影儀,可以像聚光燈一樣進行操作。Inspector View中投影儀的設定在Unity 的參考手冊中進行了說明。這裡,唯一的設定是投影儀的材質,它將應用於它範圍內的所有物件。因此,我們必須建立另一個材質,並將合適的著色器分配給它。這個著色器通常無法訪問它應用到的遊戲物件的材質;因此,它無法訪問它們的紋理等。它也無法訪問任何有關光源的資訊。然而,它可以訪問遊戲物件頂點的屬性及其自身的著色器屬性。

一個向物體新增光照的著色器可以用來將任何影像投影到其他物體上,類似於 overhead projector 或電影放映機。因此,它應該使用類似於聚光燈 cookie 的紋理影像(參見“Cookies”部分),但紋理影像的 RGB 顏色應該被新增以允許彩色投影。我們可以透過將片段顏色設定為紋理影像的 RGBA 顏色,並使用混合方程來實現這一點

Blend One One

它只是將片段顏色新增到幀緩衝區中的顏色。(取決於紋理影像,最好使用Blend SrcAlpha One以去除任何不透明度為零的顏色。)

與聚光燈的 cookie 不同,我們應該使用 Unity 特定的統一矩陣_Projector 將位置從物件空間轉換為投影儀空間,而不是矩陣_LightMatrix0。然而,投影儀空間中的座標與光空間中的座標非常相似——除了結果 座標處於正確範圍內;因此,我們不必擔心新增 0.5。儘管如此,我們必須對 座標進行除法運算(如投影紋理貼圖中始終如此);可以透過顯式地將 除以,或者使用texture2DProj來實現。

Shader "GLSL projector shader for adding light" {
   Properties {
      _ShadowTex ("Projected Image", 2D) = "white" {}
   }
   SubShader {
      Pass {      
         Blend One One 
            // add color of _ShadowTex to the color in the framebuffer 
 
         GLSLPROGRAM

         // User-specified properties
         uniform sampler2D _ShadowTex; 

         // Projector-specific uniforms
         uniform mat4 _Projector; // transformation matrix 
            // from object space to projector space 
                  
         varying vec4 positionInProjSpace; 
            // position of the vertex (and fragment) in projector space

         #ifdef VERTEX

         void main()
         {                                         
            positionInProjSpace = _Projector * gl_Vertex;            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

         #endif

         #ifdef FRAGMENT

         void main()
         {
            if (positionInProjSpace.w > 0.0) // in front of projector?
            {
               gl_FragColor = texture2D(_ShadowTex , 
                  vec2(positionInProjSpace) / positionInProjSpace.w); 
               // alternatively: gl_FragColor = texture2DProj(  
               //    _ShadowTex, vec3(positionInProjSpace));
            }
            else // behind projector
            {
               gl_FragColor = vec4(0.0);
            }
         }

         #endif
         
         ENDGLSL
      }
   }  
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Projector/Light"
}

請注意,我們必須測試 是否為正數(即片段位於投影儀前面,而不是後面)。如果沒有這個測試,投影儀也會向它後面的物體新增光照。此外,紋理影像必須是正方形,並且通常建議使用將包裝模式設定為鉗位的紋理。

如果你想知道:紋理的著色器屬性叫做_ShadowTex,以便與投影儀的內建著色器相容。

一個帶有投影陰影的卡通角色。

用於調製顏色的投影儀

[編輯 | 編輯原始碼]

建立調色投影儀的基本步驟與上面相同。唯一的區別在於著色器程式碼。以下示例透過衰減顏色(特別是地板的顏色)新增陰影。請注意,在實際應用中,陰影投射者的顏色不應該被衰減。這可以透過將陰影投射者分配到特定的 **層**(在遊戲物件的 **Inspector View** 中)並在投影儀的 **Inspector View** 中的 **Ignore Layers** 下指定該層來實現。

為了使陰影具有特定的形狀,我們使用紋理影像的 alpha 通道來確定陰影的深淺。(因此,我們可以使用標準資源中燈光使用的餅乾紋理。)為了衰減幀緩衝器中的顏色,我們應該將其乘以 1 減去 alpha(即 alpha 等於 1 時為因子 0)。因此,合適的混合方程是

Blend Zero OneMinusSrcAlpha

Zero 表示我們不新增任何光線。即使陰影太暗,也不應新增光線;而是應該在片段著色器中減少 alpha 通道,例如透過將其乘以小於 1 的因子。為了獨立地調製幀緩衝器中的顏色元件,我們需要 Blend Zero SrcColorBlend Zero OneMinusSrcColor

實際上,不同的混合方程是著色器程式碼與新增光線版本相比唯一的變化。

Shader "GLSL projector shader for drop shadows" {
   Properties {
      _ShadowTex ("Shadow Shape", 2D) = "white" {}
   }
   SubShader {
      Pass {      
         Blend Zero OneMinusSrcAlpha // attenuate color in framebuffer 
            // by 1 minus alpha of _ShadowTex
 
         GLSLPROGRAM

         // User-specified properties
         uniform sampler2D _ShadowTex; 

         // Projector-specific uniforms
         uniform mat4 _Projector; // transformation matrix 
            // from object space to projector space 
                  
         varying vec4 positionInProjSpace; 
            // position of the vertex (and fragment) in projector space

         #ifdef VERTEX

         void main()
         {                                         
            positionInProjSpace = _Projector * gl_Vertex;            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

         #endif

         #ifdef FRAGMENT

         void main()
         {
            if (positionInProjSpace.w > 0.0) // in front of projector?
            {
               gl_FragColor = texture2D(_ShadowTex , 
                  vec2(positionInProjSpace) / positionInProjSpace.w); 
               // alternatively: gl_FragColor = texture2DProj(
               //    _ShadowTex, vec3(positionInProjSpace));
            }
            else // behind projector
            {
               gl_FragColor = vec4(0.0);
            }
         }

         #endif
         
         ENDGLSL
      }
   }  
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Projector/Light"
}

總結

[edit | edit source]

恭喜,本教程到此結束。我們已經瞭解了

  • Unity 的投影儀是如何工作的。
  • 如何實現一個投影儀著色器來為物體新增光線。
  • 如何實現一個投影儀著色器來衰減物體的顏色。

進一步閱讀

[edit | edit source]

如果您還想了解更多

  • 關於光空間(與投影空間非常相似),您可以閱讀 “餅乾”部分
  • 關於紋理對映,特別是 alpha 紋理對映,您可以閱讀 “透明紋理”部分
  • 關於固定功能 OpenGL 中的投影紋理對映,您可以閱讀 NVIDIA 的白皮書 “Projective Texture Mapping”,作者為 Cass Everitt(可在 線上 獲取)。
  • 關於 Unity 的投影儀,您可以閱讀 Unity 關於投影儀的文件


< GLSL Programming/Unity

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