跳转到内容

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

除非另有说明,否则本页上的所有示例源代码均授予公有领域。
华夏公益教科书