跳转到内容

Cg 编程/Unity/透明纹理

来自维基教科书,开放世界开放书籍
地球地图,水体透明,即水体的 alpha 分量为 0,陆地的 alpha 分量为 1。

本教程涵盖了alpha 纹理贴图的各种常见用途,即具有 A (alpha) 分量的 RGBA 纹理图像,该分量指定了纹素的不透明度。

它将“纹理球体”部分的着色器代码与在“切除”部分“透明度”部分中介绍的概念相结合。

如果您还没有阅读这些教程,现在是一个很好的机会。

丢弃透明片段

[编辑 | 编辑源代码]

让我们从“切除”部分中解释的丢弃片段开始。按照“纹理球体”部分中描述的步骤,并将左侧的图像分配给带有以下着色器的球体的材质

Shader "Cg 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

         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;    
         uniform float _Cutoff;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            float4 textureColor = tex2D(_MainTex, input.tex.xy);  
            if (textureColor.a < _Cutoff)
               // alpha value less than user-specified threshold?
            {
               discard; // yes: discard this fragment
            }
            return textureColor;
         }
 
         ENDCG
      }
   }
   Fallback "Unlit/Transparent Cutout"
}

片段着色器读取 RGBA 纹理并将 alpha 值与用户指定的阈值进行比较。如果 alpha 值小于阈值,则丢弃该片段,表面看起来透明。

请注意,discard 指令在某些平台上速度相当慢,尤其是在移动设备上。因此,混合通常是一个更有效的替代方案。

“透明度”部分描述了如何使用 alpha 混合渲染半透明物体。将此与 RGBA 纹理相结合,得到以下代码

Shader "Cg 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
         
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            return tex2D(_MainTex, input.tex.xy);  
         }
 
         ENDCG
      }

      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
         
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            return tex2D(_MainTex, input.tex.xy);  
         }
 
         ENDCG
      }
   }
   Fallback "Unlit/Transparent"
}

请注意,所有 alpha 值为 0 的纹素在此特定纹理图像中都是黑色的。实际上,此纹理图像中的颜色已“预乘”其 alpha 值。(此类颜色也称为“不透明度加权”。)因此,对于此特定图像,我们实际上应该在混合方程中指定预乘颜色的混合方程,以避免在混合方程中将颜色与其 alpha 值再次相乘。因此,对于此特定纹理图像,着色器的改进是采用以下混合规范在两个通道中

混合一 一减源alpha

半透明球体通常用于徽标和预告片。

使用自定义颜色混合

[编辑 | 编辑源代码]

我们在结束本教程之前,不应该遗漏对所介绍技术的更实用的应用。左侧是具有半透明蓝色海洋的地球图像,我在维基共享资源上找到的。有一些灯光(或轮廓增强)在进行,我没有尝试复制。相反,我只尝试用以下着色器复制半透明海洋的基本理念,该着色器忽略纹理贴图的 RGB 颜色,并根据 alpha 值用特定颜色替换它们

Shader "Cg 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
         
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            float4 color =  tex2D(_MainTex, input.tex.xy);  
            if (color.a > 0.5) // opaque back face?
            {
               color = float4(0.0, 0.0, 0.2, 1.0); 
                  // opaque dark blue
            }
            else // transparent back face?
            {
               color = float4(0.0, 0.0, 1.0, 0.3); 
                  // semitransparent green
            }
            return color;
         }
 
         ENDCG
      }

      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
         
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform sampler2D _MainTex;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            float4 color = tex2D(_MainTex, input.tex.xy);  
            if (color.a > 0.5) // opaque front face?
            {
               color = float4(0.0, 1.0, 0.0, 1.0); 
                  // opaque green
            }
            else // transparent front face
            {
               color = float4(0.0, 0.0, 1.0, 0.3); 
                  // semitransparent dark blue
            }
            return color;
        }
 
         ENDCG
      }
   }
   Fallback "Unlit/Transparent"
}

当然,在该着色器中添加灯光和轮廓增强会很有趣。也可以更改不透明的绿色以考虑纹理颜色,例如使用

color = float4(0.5 * color.r, 2.0 * color.g, 0.5 * color.b, 1.0);

这通过将绿色分量乘以 2 来强调绿色分量,并通过将红色和蓝色分量乘以 0.5 来使它们变暗。但是,这会导致绿色过饱和,并被钳制到最大强度。可以通过将绿色分量与最大强度 1 之间的差值减半来避免这种情况。这个差值是 1.0 - color.g;它的二分之一是 0.5 * (1.0 - color.g),与该减少的距离到最大强度相对应的值是:1.0 - 0.5 * (1.0 - color.g)。因此,为了避免绿色过饱和,我们可以使用(代替不透明的绿色)

color = float4(0.5 * color.r, 1.0 - 0.5 * (1.0 - color.g), 0.5 * color.b, 1.0);

实际上,人们必须尝试此类颜色转换的各种可能性。为此,使用数字着色器属性(例如上面行中的因子 0.5)对于以交互方式探索可能性特别有用。

恭喜!您已完成本篇相当长的教程。我们已经了解了

  • 如何将丢弃片段与 alpha 纹理贴图结合使用。
  • 如何将 alpha 纹理贴图用于混合。
  • 如何将 alpha 纹理贴图用于确定颜色。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多信息

< Cg 编程/Unity

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