Unity_Shader初级篇_8_Unity Shader入门精要

透明效果
透明是游戏中经常要使用的一种效果。在实时渲染中要实现透明效果,通常会子渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外。他还有另一个属性——透明度。当透明度为1时,表示该像素是完全不透明的,而当其为0时,则表示该像素完全不会显示。
在Unity中,我们通常使用两种方法来实现透明效果:第一种是使用透明度测试(Alpha Test),这种方法其实无法得到真正的半透明效果;另一种是透明度混合(Alpha Blending)
在之前的学习中,我们从没有强调过渲染顺序的问题。也就是说,当场景中包含很多模型时,我们并没有考虑是先渲染A,再渲染B,最后再渲染C,还是按照其他的顺序来渲染。事实上,对于不透明(opaque)物体,不考虑他们的渲染顺序也能得到正确的排列效果,这是由于强大的深度缓冲(depth buffer,也被称为z-buffer)的存在。在实时渲染中,深度缓冲是用于解决可见性(visibility)问题的,它可以决定那个物体的那些部分会被渲染在前面,而那些部分会被其他物体遮挡。它的基本思想是:根据深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)/
使用深度缓冲,可以让我们不用关心不透明物体的渲染顺序,例如A挡住了B,即便我们先渲染A再渲染B也不用担心B会遮盖掉A,因为在进行深度测试时会判断出B距离摄像机更远,也就不会写入到颜色缓冲中。但如果想要实现透明效果,事情就不那么简单了,这是因为,当使用透明度混合时,我们关闭了深度写入(ZWrite)。
简单来说,透明度测试和透明度混合的基本原理如下。
·透明度测试:它采用了一种“霸道极端”的机制,只要一个片元的透明度不满足条件(通常是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会在进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样。
·透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄影机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。

8.1 渲染顺序很重要

8.2 渲染顺序
Unity为了解决渲染顺序的问题提供了渲染队列(render queue)这一解决方案。我们可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部使用了一系列整数索引来表示每个渲染列队,且索引号越小表示越早被渲染。在Unity5中,Unity提前定义了5个渲染列队(与Unity5之前的版本相比多了一个AlphaTest渲染队列),当然在每个队列中间我们可以使用其他队列。
Unity_Shader初级篇_8_Unity Shader入门精要_第1张图片
因此,如果我们想要通过透明度测试实现透明效果,代码中应该包含类似下面的代码:

SubShader{
    Tags{ "Queue" = "AlphaTest"}
    Pass{
      ...
  }
}

如果我们想要通过透明度混合来实现透明效果,代码中应该包含类似下面的代码:

SubShader{
    Tags{ "Queue" = "Transparent"}
    Pass{
        ZWrite Off
        ...
  }
}

其中, ZWrite Off用于关闭深度写入,在这里我们选择把它写在Pass中。我们也可以写在SubShader中,这意味着该SubShader下的所有Pass都会关闭深度写入。

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

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

在本节中,我们使用下图中的半透明纹理来实现透明度测试。(transparent_texture.psd)
Unity_Shader初级篇_8_Unity Shader入门精要_第2张图片
Unity_Shader初级篇_8_Unity Shader入门精要_第3张图片
为此,我们需要进行如下准备工作。
(1)创建新场景(Scene_8_3)。
(2)创建材质(AlphaBlendMat)。
(3)创建Unity Shader(Chapter8-AlphaTest),并赋给第二步中的材质。
(4)创建立方体,并把第二步中的材质赋给该模型。

Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
    Properties {
        //为了在材质面板中控制透明度测试时使用的阀值,我们在Properties语义块中声明一个范围在[0,1]之间的属性_Cutoff
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    }
        //_Cutoff参数用于决定我们调用clip进行透明度测试时使用的判断条件。它的范围是[0,1],
        //这是因为纹理像素的透明度就是在此范围内。
    SubShader {
        Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
        //已知渲染顺序的重要性,并且知道在Unity中透明度测试使用的渲染队列是名为AlphaTest的队列,
        //因此我们需要把Queue标签设置为AlphaTest。而RenderType标签可以让Unity把这个Shader
        //归入到提前定义的组(这里就是TransparentCutout组)中,以指明该Shader是一个使用了
        //透明度测试的Shader。RenderType标签通常被用于着色器替换功能。我们还把IgnoreProjector
        //设置为True,这意味着这个Shader不会受到投影器(Projectors)的影响。通常,使用了透明度测试的
        //Shader都应该在SubShader中设置这三个标签。最后,LightMode标签是Pass标签中的一种,它用于定义
        //该Pass在Unity的光照流水线中的角色。只有定义了正确的LightMode,我们才能正确得到一些Unity的
        //内置光照变量,例如_LightColor0。
        Pass {
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            //由于_Cutoff的范围在[0,1],因此我们可以使用fixed精度来存储它。

            //然后,我们定义了顶点着色器的输入和输出结构体,接着定义顶点着色器。
            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 = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(_Object2World, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

                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);

                // Alpha testα测试
                clip (texColor.a - _Cutoff);
                // Equal to 如果
//              if ((texColor.a - _Cutoff) < 0.0) {
//                  discard;丢弃;
//              }

                fixed3 albedo = texColor.rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

                return fixed4(ambient + diffuse, 1.0);
            }
            //前面我们已经提到过clip函数的定义,它会判断它的参数,即texColor.a-_Cutoff是否为负数,
            //如果是就会舍弃该片元的输出。也就是说,当texColor.a小于材质参数_Cutoff时,该片元就
            //会产生完全透明的效果。使用clip函数等同于先判断参数是否小于零,如果是就使用discard指令
            //来显示剔除该片元。后面的代码和之前使用过的完全一样,我们计算得到环境光照和漫反射光照,
            //把他们相加后再进行输出。
            ENDCG           
        }
            //和之前使用的Diffuse和Specular不同,这词我们使用内置的Transparent/Cutout/VertexLit;来作为
            //回调Shader。这不仅能够保证在我们编写的SubShadr无法在当前显卡上工作时可以有合适的代替
            //Shader,还可以保证使用透明度测试的物体可以正确的向其他物体投射阴影,具体原理(9.4.5)
    } 
    FallBack "Transparent/Cutout/VertexLit"
}

Unity_Shader初级篇_8_Unity Shader入门精要_第4张图片
从图8.7可以看出,透明度测试得到的透明效果和“极端”——要么完全透明,要么完全不透明,它的效果往往像在一个不透明物体上挖了一个空洞。而且,得到的透明效果在边缘处往往参差不齐,有锯齿,这是因为在边界处纹理的透明度的变化精度问题。为了得到更加柔滑的透明效果,就可以使用透明度混合。

8.4 透明度混合
透明度混合的实现要比透明度测试复杂一些,这是因为我们在处理透明度测试时,实际上跟对待普通的不透明物体几乎是一样的,只是在片元着色器中增加了对透明度判断并裁剪片元的代码。而想要实现透明度混合就没有这么简单了。我们回顾之前提到的透明度混合的原理:
透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,以已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
为了进行混合,我们需要使用Unity提供的混合命令——Blend。Blend是Unity提供的设置混合模式的命令。想要实现半透明的效果就需要把当前自身的颜色和已经存在于颜色缓冲中的颜色值进行混合,混合时使用的函数就是由该指令决定的。
Unity_Shader初级篇_8_Unity Shader入门精要_第5张图片
在本节中,我们使用第二种语义,即Blend SrcFactor DstFactor来进行混合。需要注意的是,这个命令在设置混合因子的同时也开启了混合模式。这是因为,只有开启了混合之后,设置片元的透明通道才有意义,而Unity在我们使用Blend命令的时候就自动帮我们打开了。
为了在Unity中实现透明度混合,我们先进行如下准备工作。
(1)创建场景(Scene_8_4)。
(2)创建材质(AlphaBlendMat)。
(3)创建Unity Shader (Chapter8-AlphaBlend),并赋给第二步中的材质。
(4)创建立方体,并把第二步中的材质赋给模型。

Shader "Unity Shaders Book/Chapter 8/Alpha Blend" {
    Properties {
        //修改Properties语义块;
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
        //我们使用了一个新的属性_AlphaScale来替代原先的_Cutoff属性。_AlphaScale用于在透明纹理
        //的基础上控制整体的透明度。
    }
    SubShader {
        //修改SubShader使用的标签
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        //我们已经知道在Unity中透明度混合使用的渲染队列是名为Transparent的队列,因此我们需要把
        //Queue标签设置为Transparent。RenderType标签可以让Unity把这个Shader归入到提前定义的组(这里就是Transparent租)
        //中,用来指明该Shader是一个使用了透明度混合的Shader。RenderType标签通常被用于着色器替换功能。
        //我们还把IgnoreProjector设置为True,这意味着这个Shader不会受到投影器(Projectors)的影响。。
        //通常,使用了透明度混合的Shader都应该在SubShader中设置这三个标签。
        Pass {
            //与透明度测试不同的是,我们还需要在Pass中为透明度混合进行合适的混合状态设置
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            //Pass的标签仍和之前一样,即把LightMode设为ForwardBase,这是为了让Unity能够安前向
            //渲染路径的方式为我们正确提供各个光照变量。除此之外,我们还把该pass的深度写入(ZWrite)
            //设置为关闭状态(Off),这是非常重要的。然后,我们开启并设置了该Pass的混合模式。
            //我们将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha,把目标颜色(已经存在于颜色
            //缓冲中的颜色)的混合因子设为OneMinusSrcAlpha,以得到合适的半透明效果。
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"
            //相应的,我们也需要在Pass中修改和属性对应的变量;
            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 = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(_Object2World, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

                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.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            //上述代码和8.3中的几乎完全一样,只是移除了透明度测试的代码,并设置了该片元着色器
            //返回值中的透明通道,他是纹理像素的透明通道和材质参数_AlphaScale的乘积。只有Blend
            //命令打开混合后,我们在这里设置透明通道才有意义,否则,这些透明度并不会对片元的透明度效果有任何影响
            ENDCG
        }
    } 
    FallBack "Transparent/VertexLit"
}

Unity_Shader初级篇_8_Unity Shader入门精要_第6张图片

8.5 开启深度写入的半透明效果
使用这种方法,我们仍然可以实现模型与它后面的背景混合的效果,但模型内部之间不会有任何真正的半透明效果。
(1)创建场景(Scene_8_5)。
(2)创建材质(AlphaBlendZWriteMat)。
(3)创建Unity Shader (Chapter8-AlphaBlendZWrite),并赋给第二步中的材质。
(4)创建立方体,并把第二步中的材质赋给模型。
这次使用的代码和8.4(上面)使用的Chapter8-AlphaBlend几乎完全一样。我们只需要在原来使用的Pass前面再增加一个新的Pass即可:

Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}        
        // Extra pass that renders to depth buffer only
        Pass {
            ZWrite On
            ColorMask 0
        }       
        Pass {
            //和8.4节同样的代码
            }           
            ENDCG
        }
    } 
    FallBack "Transparent/VertexLit"
}

这个新添加的Pass的目的仅仅是为了把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元。因此,Pass的第一行开启了深度写入。在第二行,我们使用了一个新的渲染命令——ColorMask。在ShaderLab中,ColorMask用于设置颜色通道的写掩码(write mask)。它的语义如下:

ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合

当ColorMask设为0时,意味着该Pass不写入任何颜色通道,既不会输出任何颜色。这正是我们需要的——该Pass只需写入深度缓存即可。
Unity_Shader初级篇_8_Unity Shader入门精要_第7张图片

8.6 ShaderLab的混合命令
混合是如何实现的:当片元着色器产生一个颜色的时候,可以选择与颜色缓存中的颜色进行混合。这样一来,混合就和两个操作数有关:源颜色(source color)目标颜色(destination color)。源颜色,我们用S表示,指的是由片元着色器产生的颜色值;目标颜色,我们用D表示,指的是从颜色缓冲中读取到的颜色值。对他们进行混合后得到的输出颜色,我们用O表示,它会重生新写入到颜色缓冲中。需要注意的是,当我们谈及混合中的源颜色、目标颜色和输出颜色时,他们都包含了RGBA四个通道的值,而非仅仅是RGB通道。
想要使用混合,我们必须首先开启它。在Unity中,当我们使用Blend(Blend Off命令除外)命令时,除了设置混合状态外也开启了混合。但是,在其他图形API中我们需要手动开启的。例如在OpenGL中,我们需要使用glEnable(GL_BLEND)来开启混合。但在unity中,它已经在背后为我们做了这些工作。
混合等式和参数
混合是一个逐片元的操作,而且它不是可编程的,但其实高度可配置的。也就是说,我们可以设置混合时使用的运算操作、混合因子等来影响混合。
现在,我们已知两个操作数:源颜色S和目标颜色D,想要得到输出颜色O就必须使用一个等式来计算。我们把这个等式称为混合等式(blend equation)。当进行混合时,我们需要使用两个混合等式:一个用于混合RGB通道,一个用于混合A通道。当设置混合状态时,我们实际上设置的就是混合等式中的操作和因子。在默认情况下,混合等式使用的操作都是加操作(我们也可以使用其他操作),我们只需要再设置一下混合因子即可。由于需要两个等式(分别用于混合RGB通道和A通道),每个等式有两个因子(一个用于和源颜色相乘,一个用于和目标颜色相乘),因此一共需要四个因子。
Unity_Shader初级篇_8_Unity Shader入门精要_第8张图片
可以发现,第一个命令只提供了两个因子,这意味着将使用同样的混合因子来混合RGB通道和A通道,即此时SrcFactorA将等于SrcFactor,DstFactorA将等于DstFactor。下面就是使用这些因子进行加法混合时使用的混合公式:

O(rgb) = SrcFactor × S(rgb) + DstFactor × D(rgb)
O(a) = SrcFactorA × S(a) + DstFactorA × D(a)

Unity_Shader初级篇_8_Unity Shader入门精要_第9张图片
使用上面的指令进行设置时,RGB通道的混合因子和A通道的混合因子都是一样的,有时我们希望可以使用不同的参数混合A通道,这时就可以利用 Blend SrcFactor DstFactor,SrcFactorA DstFactorA指令。例如,如果我们想要在混合后,输出颜色的透明度值就是源颜色的透明度,可以使用下面的命令:

Blend SrcAlpha OneMinusSrcAlpha , One Zero

混合操作
我们使用ShaderLab的BlendOp BlendOperation 命令,即混合操作命令,可以实现不同的算法来修改最后的输出颜色。
Unity_Shader初级篇_8_Unity Shader入门精要_第10张图片
这里写图片描述
混合操作命令通常是与混合因子命令一起工作的。但需要注意的是,当使用Min或Max混合操作时,混合因子实际上是不起任何作用的,它们仅会判断原始的源颜色和颜色之间的比较结果。

常见的混合类型
通过混合操作和混合因子命令的组合,我们可以得到一些类似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   // When using Min operation, these factors are ignored 当使用最小操作时,这些因素将被忽略
//          
//          //  Lighten 变亮
//          BlendOp Max
//          Blend One One // When using Max operation, these factors are ignored 当使用最大操作时,这些因素会被忽略
//          
//          // Screen 滤色
//          Blend OneMinusDstColor One
            // Or等同于
//          Blend One OneMinusSrcColor
//          
//          // Linear Dodge 线性减淡
            Blend One One

上面不同设置下得到的结果。(Scene_8_6_3)。
Unity_Shader初级篇_8_Unity Shader入门精要_第11张图片
需要注意的是,虽然上面使用Min和Max混合操作时仍然设置了混合因子,但实际上它们并不会对结果有任何影响,因为Min和Max混合操作会忽略混合因子。另一点是,虽然上面有些混合模式并没有设置混合操作的种类,但是它们默认就是使用加法操作,相当于设置了BlendOp Add。

8.7 双面渲染的透明效果
在现实生活中,如果一个物体是透明的,意味着我们不仅可以透过它看到其他物体的样子,也可以看到它内部的结构。但是前面实现的透明效果中,无论是透明度测试还是透明度混合,我们都无法观察到正方体内部以及背面的形状,导致物体看起来就好像只有半个一样。这是因为,默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。如果我们想要得到双面渲染的效果,可以使用Cull指令来控制需要剔除那个面的渲染图元。在Unity中,Cull指令的语法如下:

Cull Back | Front | Off

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

透明度测试的双面渲染
我们需要在Pass的渲染设置中使用Cull指令来关闭剔除即可。为此,我们新建了一个场景(Scene_8_7_1),场景中包含一个正方体,它使用的材质和Unity Shader分别名为AlphaTestBothSidedMat和Chapter8-AlphaTestBothSided。Chapter8-AlphaTestBothSided的代码和8.3节中的Chapter8-AlphaTest几乎完全一样,只添加一行代码:

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

    // Turn off culling 关掉剔除功能
       Cull Off

Unity_Shader初级篇_8_Unity Shader入门精要_第12张图片
如上所示:这行代码的作用是关闭剔除功能,使得该物体的所有的渲染图元都会被渲染。因此,我们可以得到上图的效果。此时,我们可以透过正方体的镂空区域看到内部的渲染结果。

透明度混合的双面渲染
相对于透明度测试而言,会复杂一点,这是因为透明度混合需要关闭深度写入,而这是“一切混乱的开端”。我们知道,想要得到正确的透明效果,渲染顺序是非常重要的——我们想要保证图元是从后面往前渲染的。对于透明度测试而说,由于我们没有关闭深度写入,因此可以利用深度缓冲按逐像素的粒度进行深度排序,从而保证渲染的正确性。然而一旦关闭了深度写入,我们就需要小心地控制渲染顺序来得到正确的深度关系。如果我们仍然采用8.7.1节中的方法,直接关闭剔除功能,那么我们就无法保证同一个物体的正面和背面图元的渲染顺序,就有可能得到错误的半透明效果。
为此,我们选择把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass只渲染正面,由于Unity会顺序执行SubShader中的各个Pass,因此我们可以保证背面总是在正面被渲染之前渲染,从而可以保证正确的深度渲染关系。
我们新建了一个场景,在本章资源中,场景(Scene_8_7_2)中的正方体,它使用的材质和Unity Shader分别名为AlphaBlendBothSidedMat和Chapter8-AlphaBlendBothSided。相较于8.4节的Chapter8-AlphaBlend,我们对Chapter8-AlphaBlendBothSided的代码做了两个改动。
(1)复制原Pass的代码,得到另一个Pass。
(2)在两个Pass中分别使用Cull指令剔除不同朝向的渲染图元:

Shader "Unity Shaders Book/Chapter 8/Alpha Blend With Both Side" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

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

            // First pass renders only back faces 第一次传递只渲染回(反)面
            Cull Front
            //和之前一样的代码
        }

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

            // Second pass renders only front faces  第二遍只渲染正面
            Cull Back

            //和之前一样的代码
        }
    } 
    FallBack "Transparent/VertexLit"
}

通过上面的代码,得到如下的效果:
Unity_Shader初级篇_8_Unity Shader入门精要_第13张图片

你可能感兴趣的:(Shader,观后感)