跳轉到內容

GLSL 程式設計/Unity/透明紋理

來自 Wikibooks,開放世界的開放書籍
帶有透明水體的地球地圖,即水的 alpha 分量為 0,陸地的 alpha 分量為 1。

本教程涵蓋了 **alpha 紋理貼圖** 的各種常見用途,即具有指定紋素不透明度的 A(alpha)分量的 RGBA 紋理影像。

它將 “紋理球體”部分 的著色器程式碼與在 “截面”部分“透明度”部分 中介紹的概念結合起來。

如果您還沒有閱讀這些教程,現在是一個很好的機會去閱讀它們。

丟棄透明片段

[編輯 | 編輯原始碼]

讓我們從 “截面”部分 中解釋的丟棄片段開始。按照 “紋理球體”部分 中描述的步驟,並將左側的影像分配給帶有以下著色器的球體材質

Shader "GLSL texturing with alpha discard" {
   Properties {
      _MainTex ("RGBA Texture Image", 2D) = "white" {} 
      _Cutoff ("Alpha Cutoff", Float) = 0.5
   }
   SubShader {
      Pass {	
         Cull Off // since the front is partially transparent, 
            // we shouldn't cull the back

         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	
         uniform float _Cutoff;

         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, vec2(textureCoordinates));
            if (gl_FragColor.a < _Cutoff)
               // alpha value less than user-specified threshold?
            {
               discard; // yes: discard this fragment
            }
         }
         
         #endif

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

片段著色器讀取 RGBA 紋理,並將 alpha 值與使用者指定的閾值進行比較。如果 alpha 值小於閾值,則丟棄該片段,表面看起來是透明的。

Alpha 測試

[編輯 | 編輯原始碼]

與上面描述的效果相同,可以透過 alpha 測試實現。alpha 測試的優勢在於它在不支援 GLSL 的舊硬體上也能執行。以下程式碼的結果與上面的著色器大體一致

Shader "GLSL texturing with alpha test" {
   Properties {
      _MainTex ("RGBA Texture Image", 2D) = "white" {} 
      _Cutoff ("Alpha Cutoff", Float) = 0.5
   }
   SubShader {
      Pass {	
         Cull Off // since the front is partially transparent, 
            // we shouldn't cull the back
         AlphaTest Greater [_Cutoff] // specify alpha test: 
            // fragment passes if alpha is greater than _Cutoff 

         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	
         uniform float _Cutoff;

         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, vec2(textureCoordinates));
         }
         
         #endif

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

在這裡,不需要顯式的 discard 指令,但必須將 alpha 測試配置為僅透過 alpha 值大於 _Cutoff 屬性的片段;否則它們將被丟棄。有關 alpha 測試的更多詳細資訊,請參閱 Unity 的 ShaderLab 文件

請注意,alpha 測試和 discard 指令在某些平臺上速度很慢,尤其是在移動裝置上。因此,混合通常是更有效率的替代方法。

“透明度”部分 描述瞭如何使用 alpha 混合渲染半透明物件。將此與 RGBA 紋理相結合,得出以下程式碼

Shader "GLSL texturing with alpha blending" {
   Properties {
      _MainTex ("RGBA Texture Image", 2D) = "white" {} 
   }
   SubShader {
      Tags {"Queue" = "Transparent"} 

      Pass {	
         Cull Front // first render the back faces
         ZWrite Off // don't write to depth buffer 
            // in order not to occlude other objects
         Blend SrcAlpha OneMinusSrcAlpha 
            // blend based on the fragment's alpha value
         
         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	

         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, vec2(textureCoordinates));
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Cull Back // now render the front faces
         ZWrite Off // don't write to depth buffer 
            // in order not to occlude other objects
         Blend SrcAlpha OneMinusSrcAlpha 
            // blend based on the fragment's alpha value
         
         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	

         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, vec2(textureCoordinates));
         }
         
         #endif

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

請注意,在這個特定的紋理影像中,所有 alpha 值為 0 的紋素都是黑色的。實際上,此紋理影像中的顏色是“預乘”了它們的 alpha 值。(這種顏色也稱為“不透明度加權”。)因此,對於此特定影像,我們實際上應該在混合方程中指定預乘顏色的混合方程,以避免在混合方程中將顏色與它們的 alpha 值再次相乘。因此,對著色器(對於此特定紋理影像)的改進是採用以下混合規範,用於兩個通道

Blend One OneMinusSrcAlpha

半透明球體通常用於標識和預告片。

使用自定義顏色混合

[編輯 | 編輯原始碼]

我們不應該在沒有對所呈現技術的更實用應用的情況下結束本教程。左側是帶有半透明藍色海洋的地球影像,我在 Wikimedia Commons 上找到的。有一些照明(或輪廓增強)正在進行,我沒有嘗試重現。相反,我只嘗試重現帶有以下著色器的半透明海洋的基本想法,它忽略了紋理貼圖的 RGB 顏色,並根據 alpha 值將其替換為特定顏色

Shader "GLSL semitransparent colors based on alpha" {
   Properties {
      _MainTex ("RGBA Texture Image", 2D) = "white" {} 
   }
   SubShader {
      Tags {"Queue" = "Transparent"} 

      Pass {	
         Cull Front // first render the back faces
         ZWrite Off // don't write to depth buffer 
            // in order not to occlude other objects
         Blend SrcAlpha OneMinusSrcAlpha 
            // blend based on the fragment's alpha value
         
         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	

         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, vec2(textureCoordinates));
            if (gl_FragColor.a > 0.5) // opaque back face?
            {
               gl_FragColor = vec4(0.0, 0.0, 0.2, 1.0); 
                  // opaque dark blue
            }
            else // transparent back face?
            {
               gl_FragColor = vec4(0.0, 0.0, 1.0, 0.3); 
                  // semitransparent dark blue
            }
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Cull Back // now render the front faces
         ZWrite Off // don't write to depth buffer 
            // in order not to occlude other objects
         Blend SrcAlpha OneMinusSrcAlpha 
            // blend based on the fragment's alpha value
         
         GLSLPROGRAM
                  
         uniform sampler2D _MainTex;	

         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, vec2(textureCoordinates));
            if (gl_FragColor.a > 0.5) // opaque front face?
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // opaque green
            }
            else // transparent front face
            {
               gl_FragColor = vec4(0.0, 0.0, 1.0, 0.3); 
                  // semitransparent dark blue
            }
         }
         
         #endif

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

當然,為這個著色器新增照明和輪廓增強會很有趣。還可以更改不透明的綠色顏色以考慮紋理顏色,例如,使用

gl_FragColor = vec4(0.5 * gl_FragColor.r, 2.0 * gl_FragColor.g, 0.5 * gl_FragColor.b, 1.0);

這會透過乘以 2 來突出綠色分量,並透過乘以 0.5 來使紅色和藍色分量變暗。但是,這會導致過飽和的綠色,它會被鉗制到最大強度。可以透過將綠色分量與最大強度 1 之間的差值減半來避免這種情況。這個差值是 1.0 - gl_FragColor.g;它的半值是 0.5 * (1.0 - gl_FragColor.g),與這個減小到最大強度距離相對應的值是:1.0 - 0.5 * (1.0 - gl_FragColor.g)。因此,為了避免綠色過飽和,我們可以使用(代替不透明的綠色顏色)

gl_FragColor = vec4(0.5 * gl_FragColor.r, 1.0 - 0.5 * (1.0 - gl_FragColor.g), 0.5 * gl_FragColor.b, 1.0);

在實踐中,人們必須嘗試這種顏色轉換的各種可能性。為此,使用數字著色器屬性(例如,對於上面的行中的因子 0.5)對於以互動方式探索可能性特別有用。

恭喜您!您已經完成了這個相當長的教程。我們已經瞭解了

  • 如何將丟棄片段與 alpha 紋理貼圖結合起來。
  • 如何使用 alpha 測試來實現相同的效果。
  • 如何使用 alpha 紋理貼圖進行混合。
  • 如何使用 alpha 紋理貼圖來確定顏色。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多


< GLSL 程式設計/Unity

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