《Unity Shader入门精要》学习笔记第八章 透明效果

本文章用于帮助自己学习,因此只记录一些个人认为比较重要或者还不够熟悉的内容。
原作者:http://blog.csdn.net/candycat1992/article/

第八章 透明效果

透明是游戏中经常要使用的一种效果。在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,它还有另一个属性——透明度。当透明度为1时,表示该像素是完全不透明的,而当其为0时,则表示该像素完全不会显示。

在Unity中,我们通常使用两种方法来实现透明效果:
第一种是使用透明度测试(Alpha Test), 这种方法其实无法得到真正的半透明效果;另一种是透明度混合(Alpha Blending)。

在之前的学习中,我们从没有强调过渲染顺序的问题。事实上,对于不透明(opaque)物体,不考虑它们的渲染顺序也能得到正确的排序效果,这是由于强大的深度缓冲(depth buffer,也被称为z-buffer)的存在。在实时渲染中,它可以决定哪个物体的哪些部分会被渲染在前面,而哪些部分会被其他物体遮挡。
它的基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)。
使用深度缓冲,可以让我们不用关心不透明物体的渲染顺序。但如果想要实现透明效果,事情就不那么简单了,这是因为,当使用透明度混合时,我们关闭了深度写入(ZWrite)。
简单来说,透明度测试和透明度混合的基本原理如下:

  • 透明度测试:它釆用一种“霸道极端”的机制,只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理, 也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它, 即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其 他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样
  • 透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与巳经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入(我们下面会讲为什么需要关闭),这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。

8.1为什么渲染顺序很重要

对于透明度混合技术,需要关闭深度写入,因为如果不关闭深度写入,一个半透明表面背后的表面本来是可以透过它被我们看到的,但由于深度测试时判断结果是该半透明表面距离摄像机更近,导致后面的表面将会被剔除,我们也就无法透过半透明表面看到后面的物体了。但是,我们由此就破坏了深度缓冲的工作机制,,因此关闭深度写入导致渲染顺序将变得非常重要。

我们来考虑最简单的情况。假设场景里有两个物体A和B,如下图所示,其中A是半透明物体,而B是不透明物体。

  • 第一种情况,先渲染B,再渲染A。那么由于不透明物体开启了深度测试和深度检验, 而此时深度缓冲中没有任何有效数据,因此B首先会写入颜色缓冲和深度缓冲。随后渲染A,透明物体仍然会进行深度测试,因此我们发现和B相比A距离摄像机更近, 因此,我们会使用A的透明度来和颜色缓冲中的B的颜色进行混合,得到正确的半透明效果。
  • 第二种情况,先渲染A,再渲染B。渲染A时,深度缓冲区中没有任何有效数据, 因此A直接写入颜色缓冲,但由于对半透明物体关闭了深度写入,因此A不会修改深度缓冲。等到渲染B时,B会进行深度测试,它发现,“咦,深度缓存中还没有人来过,那我就放心地写入颜色缓冲了! ”,结果就是B会直接覆盖A的颜色。从视觉上来看,B就出现在了A的前面,而这是错误的。
    《Unity Shader入门精要》学习笔记第八章 透明效果

还是假设场景里有两个物体A和B,如下图所示,其中A和B都是半透明物体

  • 第一种情况,先渲染B,再渲染A。那么B会正常写入颜色缓冲,然后A会和颜色缓冲中的B颜色进行混合,得到正确的半透明效果。
  • 第二种情况,先渲染A,再渲染B。那么A会先写入颜色缓冲,随后B会和颜色缓冲中的A进行混合,这样混合结果会完全反过来,看起来就好像B在A的前面,得到的就是错误的半透明结构。
    《Unity Shader入门精要》学习笔记第八章 透明效果

基于这两点,渲染引擎一般都会先对物体进行排序,再渲染。常用的方法是:

  1. 先渲染所有不透明物体,并开启它们的深度测试和深度写入。
  2. 把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。

然而,在一些情况下,半透明物体还是会出现“穿帮镜头”。因为,深度缓冲中的值其实是像素级别的,即每个像素有一 个深度值,但是现在我们对单个物体级别进行排序,这意味着排序结果是,要么物体A全部在B前面渲染,要么A全部在B后面渲染。但如果存在循环重叠的情况,那么使用这种方法就永远无法得到正确的结果。下图给出了 3个物体循环重叠的情况。
《Unity Shader入门精要》学习笔记第八章 透明效果
在上图中,由于3个物体互相重叠,我们不可能得到一个正确的排序顺序。这种时候,我们可以选择把物体拆分成两个部分,然后再进行正确的排序。但即便我们通过分割的方法解决了 循环覆盖的问题,还是会有其他的情况来”捣乱”。考虑下图给出的情况。
《Unity Shader入门精要》学习笔记第八章 透明效果
这里的问题是:如何排序?物体的网格结构上每一个点的深度值可能都是不一样的,我们选择哪个深度值来作为整个物体的深度值和其他物体进行排序呢?是网格中点吗?还是最远的点?还是最近的点?不幸的是, 对于上图中的情况,选择哪个深度值都会得到错误的结果,我们的排序结果总是A在B的前面, 但实际上A有一部分被B遮挡了。这也意味着,一旦选定了一种判断方式后,在某些情况下半透明物体之间一定会岀现错误的遮挡问题。这种问题的解决方法通常也是分割网格
尽管结论是,总是会有一些情况打乱我们的阵脚,但由于上述方法足够有效并且容易实现,因此大多数游戏引擎都使用了这样的方法。为了减少错误排序的情况,我们可以尽可能让模型是凸面体, 并且考虑将复杂的模型拆分成可以独立排序的多个子模型等

8.2Unity Shader的渲染顺序

Unity为了解决渲染顺序的问题提供了渲染队列(render queue)这一解决方案。我们可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部使用一系列整数索引来表示每个渲染队列,且索引号越小表示越早被渲染
《Unity Shader入门精要》学习笔记第八章 透明效果

8.3透明度测试

透明度测试:只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它。
通常,我们会在片元着色器中使用clip函数来进行透明度测试。clip是CG中的一个函数, 它的定义如下。
函数:void clip(float4 x); void clip(float3 x); void clip(float2 x); void clip(floatl x); void clip(float x);
描述:如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。它等同于下面的代码:

void clip(float4 x)
(
if (any(x < 0))
discard;
}

在本节中,我们使用下图中的半透明纹理来实现透明度测试。
《Unity Shader入门精要》学习笔记第八章 透明效果

代码:

Shader "Unity Shaders Book/Chapter 8/Alphatest"
{
    Properties
    {
        _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        //为了在材质面板中控制透明度测试时使用的阈值,声明一个属性_Cutoff
        _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
    }
    SubShader
    {
        //通常,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。
        Tags { 
        //在Unity中透明度测试使用的渲染队列是名为AlphaTest的队列
        "Queue"="AlphaTest" 
        //把IgnoreProjector设置为True,意味着这个Shader不会受到投影器(Projectors)的影响。
        "IgnoreProjector"="Ture"
        //RenderType标签可以让Unity把这个Shader归入到提前定义的组(TransparentCutout组)中,
        //以指明该Shader是一个使用了透明度测试的Shader。
        "RenderType"="TransparentCutout"
        }


        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };



            v2f vert (a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                //第一个参数是纹理坐标,第二个参数是纹理名
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                //等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                fixed4 texColor = tex2D(_MainTex, i.uv);

                //透明度测试
                clip(texColor.a - _Cutoff);
                // 等于
            // if ((texColor.a - _Cutoff) < 0.0) {
            // discard;
            // }
                //如果texColor的a通道数值小于阈值参数_Cutoff,
                //该片元就产生完全透明的效果。

                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rbg * albedo * max(0, dot(worldNormal, worldLightDir));

                return fixed4(ambient + diffuse, 1.0);
            }
            ENDCG
        }
    }
            //之前使用的Diffuse和Specular不同,
            //这次我们使用内置的Transparent/Cutout/VertexLit来作为回调Shader。
            //这不仅能够保证在我们编写的SubShader无法在当前显卡上工作时可以有合适的代替Shader,
            //还可以保证使用透明度测试的物体可以正确地向其他物体投射阴影。
            Fallback "Transparent/Cutout/VertexLit"
}

效果:
《Unity Shader入门精要》学习笔记第八章 透明效果
《Unity Shader入门精要》学习笔记第八章 透明效果

可以通过调整材质面板中的Alpha cutoff参数调整透明度测试时使用的阈值,随着参数Alpha cutoff的增大,立方体上的网格会逐渐消失(变透明)。

8.4透明度混合

透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
为了进行混合,我们需要使用Unity提供的混合命令 ——Blend。Blend是Unity提供的设置混合模式的命令。想要实现半透明的效果就需要把当前自身的颜色和巳经存在于颜色缓冲中的颜色值进行混合,混合时使用的函数就是由该指令决定的。下表给出Blend的定义:
《Unity Shader入门精要》学习笔记第八章 透明效果
在本节里,我们会使用第二种语义,即Blend SrcFactorDstFactor来进行混合。需要注意的是, 这个命令在设置混合因子的同时也开启了混合模式。这是因为,只有开启了混合之后,设置片元的透明通道才有意义,而Unity在我们使用Blend命令的时候就自动帮我们打开了。很多初学者总是抱怨为什么自己的模型没有任何透明效果,这往往是因为他们没有在Pass中使用Blend命令, 一方面是没有设置混合因子,但更重要的是,根本没有打开混合模式。我们会把源颜色的混合因子SrcFactor设为SrcAlpha,而目标颜色的混合因子DstFactor设为OneMinusSrcAlpha。这意味着,经过混合后新的颜色是:
《Unity Shader入门精要》学习笔记第八章 透明效果
代码:

Shader "Unity Shaders Book/Chapter 8/AlphaBlend"
{
    Properties
    {
        _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex("Texture", 2D) = "white" {}
        //_AlphaScale用于在透明纹理的基础上控制整体的透明度
        _AlphaScale("Alpha Scale",Range(0,1)) = 1
    }
    SubShader
    {
        //通常,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。
        Tags {
        //Unity中透明度混合使用的渲染队列是名为Transparent的队列
        "Queue" = "Transparent"
        //把IgnoreProjector设置为True,意味着这个Shader不会受到投影器(Projectors)的影响。
        "IgnoreProjector" = "Ture"
        //RenderType标签可以让Unity把这个Shader归入到提前定义的组(Transparent组)中,
        //以指明该Shader是一个使用了透明度混合的Shader。
        "RenderType" = "Transparent"
        }


        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            //关闭Pass的深度写入
            Zwrite Off
            //将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha,
            //把目标颜色(巳经存在于颜色缓冲中的颜色)的混合因子设为OneMinusSrcAlpha,
            //以得到合适的半透明效果。
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _AlphaScale;


            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };



            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                //第一个参数是纹理坐标,第二个参数是纹理名
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                //等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);



                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rbg * albedo * max(0, dot(worldNormal, worldLightDir));

                //与透明度测试相比只设置该片元着色器返回值中的透明通道,
                //它是纹理像素的透明通道和材质参数_AlphaScale的乘积。
                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }

                Fallback "Transparent/VertexLit"
}

效果如下:
《Unity Shader入门精要》学习笔记第八章 透明效果
《Unity Shader入门精要》学习笔记第八章 透明效果
随着Alpha Scale参数的减小,模型变得越来越透明。

8.5开启深度写入的半透明效果

在8.4节最后,我们给出了一种由于关闭深度写入而造成的错误排序的情况。
一种解决方法是使用两个Pass来渲染模型:
第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;
第二个Pass进行正常的透明度混合,由于上一个Pass己经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。
但这种方法的缺点在于,多使用一个Pass会对性能造成一定的影响。虽然可以实现模型与它后面的背景混合的效果,但模型内部之间不会有任何真正的半透明效果。
代码:

Shader "Unity Shaders Book/Chapter 8/AlphaBlendZWrite"
{
    Properties
    {
        _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex("Texture", 2D) = "white" {}
        //_AlphaScale用于在透明纹理的基础上控制整体的透明度
        _AlphaScale("Alpha Scale",Range(0,1)) = 1
    }
        SubShader
    {
        //通常,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。
        Tags 
        {
	        //Unity中透明度混合使用的渲染队列是名为Transparent的队列
	        "Queue" = "Transparent"
	        //把IgnoreProjector设置为True,意味着这个Shader不会受到投影器(Projectors)的影响。
	        "IgnoreProjector" = "Ture"
	        //RenderType标签可以让Unity把这个Shader归入到提前定义的组(Transparent组)中,
	        //以指明该Shader是一个使用了透明度混合的Shader。
	        "RenderType" = "Transparent"
    	}

        //新增一个Pass,把模型的深度信息写入深度缓冲中,
        //从而剔除模型中被自身遮挡的片元
        Pass
        {
            //开启深度写入
            ZWrite On
            //在ShaderLab中,ColorMask用于设置颜色通道的写掩码
            //ColorMask为0时,意味着该Pass不写入任何颜色通道,不会输出任何颜色。
            ColorMask 0
        }

        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            //关闭Pass的深度写入
            Zwrite Off
            //将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha,
            //把目标颜色(巳经存在于颜色缓冲中的颜色)的混合因子设为OneMinusSrcAlpha,
            //以得到合适的半透明效果。
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _AlphaScale;


            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };



            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                //第一个参数是纹理坐标,第二个参数是纹理名
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                //等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);



                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rbg * albedo * max(0, dot(worldNormal, worldLightDir));

                //与透明度测试相比只设置该片元着色器返回值中的透明通道,
                //它是纹理像素的透明通道和材质参数_AlphaScale的乘积。
                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
    }
    }

        Fallback "Transparent/VertexLit"
}



效果如下:
《Unity Shader入门精要》学习笔记第八章 透明效果
如图,使用这种方法,虽然可以实现模型与它后面的背景混合的效果,但模型内部之间不会有任何真正的半透明效果。
至于为什么会这样,通过问了TA群内的大佬(发现很多大佬也不清楚原理,都是直接拿来用,已经不是大佬们第一次被我问住了),以及查阅资料,我的理解如下:
模型内部之间为什么不会有任何真正的半透明效果?这是因为在渲染同一物体时,物体后方的部位深度测试不通过,片元被舍弃,也自然就走不到着色混合阶段。
既然这样,为什么后方的其他物体就可以着色混合呢?这是因为Transparent是从后向前渲染的,而depth的写入是要看渲染顺序的,先绘制的物体虽然深度小,但是因为先绘制,在此时的屏幕depth下,不存在depth比它大的对象,所以Ztest通过,仍然写入片元做混合。
总结一下,核心就是:渲染队列决定的是物体的渲染顺序,而深度写入则是片元的深度。

8.6ShaderLab的混合命令

我们首先来看一下混合是如何实现的。当片元着色器产生一个颜色的时候,可以选择与颜色缓存中的颜色进行混合。这样一来,混合就和两个操作数有关:源颜色(source color)和目标颜色(destination color)。
源颜色用S表示,指的是由片元着色器产生的颜色值
目标颜色用D表示,指的是从颜色缓冲中读取到的颜色值
对它们进行混合后得到的输出颜色用O表示,它会重新写入到颜色缓冲中
需要注意的是,当我们谈及混合中的源颜色、目标颜色和输出颜色时,它们都包含了 RGBA四个通道的值,而并非仅仅是RGB通道

8.6.1混合等式和参数

混合是一个逐片元的操作,而且它不是可编程的,但却是高度可配置的。也就是说,我们可以设置混合时使用的运算操作、混合因子等来影响混合。那么,这些配置又是如何实现的呢?
现在,我们已知两个操作数:源颜色S和目标颜色D,想要得到输出颜色O就必须使用一个等式来计算。我们把这个等式称为混合等式(blend equation)。
当进行混合时,我们需要使用两个混合等式:一个用于混合RGB通道,一个用于混合A通道。当设置混合状态时,我们实际上设置的就是混合等式中的操作和因子。在默认情况下,混合等式使用的操作都是加操作,我们只需要再设置一下混合因子即可。由于需要两个等式(分别用于混合 RGB通道和A通道),每个等式有两个因子(一个用于和源颜色相乘,一个用于和目标颜色相乘), 因此一共需要4个因子。下表给出了ShaderLab中设置混合因子的命令。
《Unity Shader入门精要》学习笔记第八章 透明效果
可以发现,第一个命令只提供了两个因子,这意味着将使用同样的混合因子来混合RGB通 道和A通道,即此时SrcFactorA将等于SrcFactor, DstFactorA将等于DstFactor。下面就是使用这些因子进行加法混合时使用的混合公式:
《Unity Shader入门精要》学习笔记第八章 透明效果
那么,这些混合因子可以有哪些值呢?下表给出了ShaderLab支持的几种混合因子。
《Unity Shader入门精要》学习笔记第八章 透明效果
例如,如果我们想要在混合后,输出颜色的透明度值就是源颜色的 透明度,可以使用下面的命令:

Blend SrcAlpha OneMinusSrcAlpha, One Zero

8.6.2混合操作

在上面涉及的混合等式中,当把源颜色和目标颜色与它们对应的混合因子相乘后,我们都是把它们的结果加起来作为输出颜色的。那么可不可以选择不使用加法,而使用减法呢?答案是肯定的,我们可以使用ShaderLab的BlendOp BlendOperation命令,即混合操作命令。下表给出了 ShaderLab中支持的混合操作。
《Unity Shader入门精要》学习笔记第八章 透明效果

8.6.3常见的混合类型

通过混合操作和混合因子命令的组合,我们可以得到一些类似Photoshop混合模式中的混合效果:

//正常(Normal ),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加(Soft Additive )
Blend OneMinusDstColor One
//正片叠底(Multiply ),即相乘
Blend DstColor Zero
// 两倍相乘(2x Multiply )
Blend DstColor SrcColor
// 变暗(Darken )
BlendOp Min
Blend One One
// 变亮(Lighten )
BlendOp Max
Blend One One
// 滤色(Screen )
Blend OneMinusDstColor One
//等同于
Blend One OneMinusSrcColor
// 线性减淡(Linear Dodge )
Blend One One

下图给出了上面不同设置下得到的结果。
《Unity Shader入门精要》学习笔记第八章 透明效果
需要注意的是,虽然上面使用Min 和 Max 混合操作时仍然设置了混合因子,但实际上它们并不会对结果有任何影响,因为Min和Max混合操作会忽略混合因子。另一点是,虽然上面有些混合模式并没有设置混合操作的种类,但是它们默认就是使用加法操作,相当于设置了BlendOp Add

8.7双面渲染的透明效果

如果我们想要得到双面渲染的效果,可以使用Cull指令来控制需要剔除哪个面的渲染图元。在Unity中,Cull指令的语法如下:

Cull Back | Front | Off

如果设置为Back,那么那些背对着摄像机的渲染图元就不会被渲染,这也是默认情况下的剔除状态;如果设置为Front,那么那些朝向摄像机的渲染图元就不会被渲染;如果设置为Off,就会关闭剔除功能,那么所有的渲染图元都会被渲染,但由于这时需要渲染的图元数目会成倍增加, 因此除非是用于特殊效果,例如这里的双面渲染的透明效果,通常情况是不会关闭剔除功能的。

8.7.1透明度测试的双面渲染

让使用了透明度测试的物体实现双面渲染的效果,只需要在Pass的渲染设置中使用Cull指令来关闭剔除即可。只要在之前的透明度测试代码Chapter8-AlphaTest中添加一行代码:

//双面材质透明度测试
Pass
{  
    Tags {"LightMode"="ForwardBase"}  
    //关闭剔除功能
    Cull Off
}  

效果如下:
《Unity Shader入门精要》学习笔记第八章 透明效果

8.7.2透明度混合的双面渲染

对于透明度测试来说,由于我们没有关闭深度写入,因此可以利用深度缓冲按逐像素的粒度进行深度排序,从而保证渲染的正确性。然而 一旦关闭了深度写入,我们就需要小心地控制渲染顺序来得到正确的深度关系。如果我们仍然釆 用8.7.1节中的方法,直接关闭剔除功能,那么我们就无法保证同一个物体的正面和背面图元的渲染顺序,就有可能得到错误的半透明效果。
为此,我们选择把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass 只渲染正面。
由于Unity会顺序执行SubShader中的各个Pass,因此我们可以保证背面总是在正面被渲染之前渲染,从而可以保证正确的深度渲染关系。代码修改如下:

SubShader
    {
        //通常,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。
        Tags {
        //Unity中透明度混合使用的渲染队列是名为Transparent的队列
        "Queue" = "Transparent"
        //把IgnoreProjector设置为True,意味着这个Shader不会受到投影器(Projectors)的影响。
        "IgnoreProjector" = "Ture"
        //RenderType标签可以让Unity把这个Shader归入到提前定义的组(Transparent组)中,
        //以指明该Shader是一个使用了透明度混合的Shader。
        "RenderType" = "Transparent"
        }


        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            //第一个Pass只渲染背对摄像机的图元
            Cull Front
 			//和之前一样的代码

            ENDCG
        }

        Pass
        {
            	Tags{"LightMode" = "ForwardBase"}

                //第二个Pass只渲染面向摄像机的图元
                Cull Back
                //和之前一样的代码

                ENDCG
        }

效果如下:
《Unity Shader入门精要》学习笔记第八章 透明效果

上一篇:pixijs shader 传入多张图片到片段着色器的方法


下一篇:Unity Shader总结(一)——渲染流水线