Bootstrap

透明效果 -- Shader入门精要学习(7)

透明效果

对于不透明的物体,不考虑其渲染顺序也能得到正确的结果,这是由于强大的深度缓冲的存在

几个概念:

  • 深度缓冲(z-buffer):用于解决可见性问题,它可以决定哪个物体的哪些部分会被渲染在前面,而哪些部分会被其他物体遮挡,存储片元的深度值

  • 深度测试:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓存中的值作比较,如果它的值距离摄像机更远,那么说明这个值不应该渲染到屏幕上,否则,这个片元应该覆盖掉此时颜色缓存中的像素值

  • 深度写入:将深度值写入到深度缓冲中,用于后续比较

在Unity中,通常有两种方法实现透明效果

  • 透明度测试(Alpha Test):无法得到半透明的效果,不需要关闭深度写入。采用一种霸道极端的机制,只要一个片元的透明度不满足某个条件(一般是小于某个阈值),那么它对应的片元就会被舍弃,被舍弃的片元不会进行任何处理;否则,就会按照普通的不透明物体的方式来处理它,产生的效果就是要么完全透明,要么完全不透明
  • 透明度混合(Alpha Blending):可以得到真正的半透明效果,需要关闭深度写入。使用当前片元的透明度作为混合因子,和已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。只关闭了深度写入,但需要保留深度测试。也就是说,当使用透明度混合一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值。对于透明度混合来说,深度缓冲是只读不可写的

1 为什么渲染顺序很重要

对于透明度混合技术,关闭深度写入的原因:如果不关闭深度写入,透明度物体后面的物体就将看不见

对于以下物体A和B,考虑不同的渲染顺序会有什么结果:

在这里插入图片描述

  • 先渲染B再渲染A:
    • B:开启深度测设和深度检验,此时深度缓冲区中没有数据,B首先写入颜色缓冲和深度缓冲
    • A:透明物体仍然进行深度测试(不进行深度写入),法线A比B距离摄像机更近,使用A的透明度来和颜色缓冲中的B进行混合
  • 先渲染A再渲染B:
    • A:未写入深度缓冲区任何数据,A直接写入颜色缓冲
    • B:进行深度测试,B发现深度缓冲区中未写入任何数据,直接写入颜色缓冲,此时B会覆盖A的颜色,视觉上看B就出现在A的前面,出现错误

对于以下物体A和B,考虑不同渲染顺序:

在这里插入图片描述

  • 先渲染B再渲染A:
    • B:不进行深度写入,正常写入颜色缓冲
    • A:和颜色缓冲中的B进行混合,得到正确的半透明效果
  • 先渲染A再渲染B:
    • A:A先写入颜色缓冲
    • B:B回合颜色缓冲中的A进行混合,导致混合结果反过来,B好像在A前面,得到错误的半透明结构

渲染引擎一般都会先对物体进行排序,在渲染,常用的方法是:

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

由于某些情况下摄像机对物体的排序存在循环重叠的情况,会导致渲染无法得到正确的结果

2 Unity Shader的渲染顺序

Unity为了解决渲染顺序的问题提供了**渲染队列(render queue)**这一解决方案,使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部使用了一系列整数索引来表示每个渲染队列,且索引号越小表示越早被渲染。Unity提供了以下5个渲染队列:

名称队列索引号描述
Background1000会在任何其他队列之前渲染,通常使用该队列来渲染那些需要绘制在背景上的物体
Geometry2000默认的渲染队列,大多数物体使用这个队列,不透明物体使用这个队列
AlphaTest2450需要透明度测试的物体使用这个队列。Unity 5中将它从Geometry中分离出来。在所有不透明物体渲染之后再渲染需要透明度测试的物体更加有效
Transparent3000这个队列的物体会在所有的Geometry和AlphaTest物体渲染后,再按从后往前的顺序进行渲染。任何使用了透明底混合(例如关闭了深度写入的Shader)的物体都应该使用该队列
Overlay4000用于实现一些叠加效果,最后渲染

例:想要通过透明度测试实现透明效果:

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

例:想要通过透明度混合实现透明效果

SubShader {
	Tags {"Queue" = "Transparent"}
	Pass { 
		ZWrite Off   // 用于关闭深度写入,也可以写在SubShader中,意味着SubShder下的所有Pass都会关闭深度写入
		...
	}
}

3 透明度测试

原理:无法得到半透明的效果,不需要关闭深度写入。采用一种霸道极端的机制,只要一个片元的透明度不满足某个条件(一般是小于某个阈值),那么它对应的片元就会被舍弃,被舍弃的片元不会进行任何处理;否则,就会按照普通的不透明物体的方式来处理它,产生的效果就是要么完全透明,要么完全不透明

在片元着色器中使用clip函数进行透明度测试:如果给定的参数的任何一个分量是负数,就会舍弃当前像素的输出颜色

Shader "MyShader/Tranparent/AlphaTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { 
            "Queue"="AlphaTest"
            "IgnoreProjector" = "True"
            "RenderType" = "TransparentCutout"
        }

        Pass
        {
            Tags { "LightMode" = "UniversalForward" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.cginc"

            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 f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.worldNormal = UnityObjectToWorldNormal(v.normal);
                f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return f;
            }

            fixed4 frag (v2f f) : SV_Target {
                fixed3 worldNormal = normalize(f.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
                fixed4 albedo = tex2D(_MainTex, f.uv);
                clip(albedo.a - _Cutoff);   // 如果 texColor.a - _Cutoff < 0 则直接舍弃当前像素的输出颜色
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir));
                return fixed4(ambient + diffuse, 1);
;            }
            ENDCG
        }
    }
    FallBack "Transparent/Cutout/VertexLit"
}

4 透明度混合

原理:可以得到真正的半透明效果,需要关闭深度写入。使用当前片元的透明度作为混合因子,和已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。只关闭了深度写入,但需要保留深度测试。也就是说,当使用透明度混合一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值。对于透明度混合来说,深度缓冲是只读不可写的

为了进行混合,我们需要使用Unity提供的混合命令 – Blend。Blend是Unity提供的设置混合模式的命令,想要实现半透明的效果就需要把当前自身的颜色和已经存在于颜色缓冲中的颜色值进行混合,混合时使用的函数就是由该指令决定的

语义描述
Blend Off关闭混合
Blend SrcFactor DstFactor开启混合,并设置混合因子,源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后两者相加后再存入颜色缓冲中
Blend SrcFactor DstFactor, SrcFactorA DstFactorA和上面几乎一样,只是使用不同的混合因子来混合透明通道
BlendOp BlendOperation并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其他操作

本节中会使用第二种语义Blend SrcFactor DstFactor来进行混合,这个命令在设置混合因子的同时也开启了混合模式。只有开启了混合,设置片元的透明通道才有意义,而Unity在我们使用Blend命令的时候就自动帮我们打开了。

我们会把源颜色的混合因子SrcFactor设为SrcAlpha,而目标颜色的混合因子DstFactor设为OneMinusSrcAlpha,这意味着经过混合后的新颜色是:
D s t C o l o r n e w = S r c A l p h a × S r c C o l o r + ( 1 − S r c A l p h a ) × D s t C o l o r o l d DstColor_{new} = SrcAlpha \times SrcColor + (1-SrcAlpha)\times DstColor_{old} DstColornew=SrcAlpha×SrcColor+(1SrcAlpha)×DstColorold

Shader "MyShader/Transparent/AlphaBlend"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader
    {
        Tags { 
            "Queue"="Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }

        Pass
        {
            Tags { "LightMode" = "UniversalForward" }
            ZWrite Off   // 关闭深度写入
            Blend SrcAlpha OneMinusSrcAlpha   // 开始并设置Pass的混合模式
            // 把源颜色的混合因子设为SrcAlpha,把目标颜色的混合因子设为OneMinusSrcAlpha,以得到合适的半透明效果

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.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 f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.worldNormal = UnityObjectToWorldNormal(v.normal);
                f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return f;
            }

            fixed4 frag (v2f f) : SV_Target {
                fixed3 worldNormal = normalize(f.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
                fixed4 albedo = tex2D(_MainTex, f.uv);
                // 移除了透明度测试的代码
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(worldNormal, worldLightDir));
                return fixed4(ambient + diffuse, albedo.a * _AlphaScale);   // 设置了返回值中的透明通道
;            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

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

对于关闭深度写入造成错误排序的情况,可以使用两个Pass来渲染模型:

  • 第一个Pass:开启深度写入,但不输出颜色,仅将该模型的深度值写入深度缓冲中
  • 第二个Pass:进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染,但是多使用一个Pass会对性能造成影响

修改后的代码如下:

Shader "MyShader/Transparent/AlphaBlend"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader
    {
        Tags { 
            "Queue"="Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }
        Pass {
            ZWrite On      // 把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元
            ColorMask 0    // ColorMask用于设置颜色通道的写掩码,当ColorMask设置为0时,意味着该Pass不写人任何颜色
        }
        Pass
        {
            // 由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染
            Tags { "LightMode" = "UniversalForward" }
            ZWrite Off   // 关闭深度写入
            Blend SrcAlpha OneMinusSrcAlpha   // 开始并设置Pass的混合模式
            // 把源颜色的混合因子设为SrcAlpha,把目标颜色的混合因子设为OneMinusSrcAlpha,以得到合适的半透明效果

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.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 f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.worldNormal = UnityObjectToWorldNormal(v.normal);
                f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return f;
            }

            fixed4 frag (v2f f) : SV_Target {
                fixed3 worldNormal = normalize(f.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
                fixed4 albedo = tex2D(_MainTex, f.uv);
                // 移除了透明度测试的代码
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(worldNormal, worldLightDir));
                return fixed4(ambient + diffuse, albedo.a * _AlphaScale);   // 设置了返回值中的透明通道
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

7.6 ShaderLab的混合命令

混合的实现:当片元着色器产生一个颜色时,可以选择与颜色缓存中的颜色进行混合,这样一来,混合就和两个操作数有关:源颜色(souce color)目标颜色(destination color)

  • 源颜色(souce color):用S表示,指的是由片元着色器产生的颜色值
  • 目标颜色(destination color):用D表示,指的是从颜色缓冲中读取到的颜色值

对他们进行混合后的输出颜色用O表示,它会重新写入颜色缓冲中 – 以上谈及的颜色都包含RGBA四个通道

想要使用混合,我们必须首先开启它。在Unity中,当我们使用Blend命令时,除了设置混合状态外也开启了混合

6.1 混合等式参数

混合是一个逐片元的操作,且是不可编程的,却是高度可配置的。我们可以设置混合时使用的运算操作、混合因子等来影响混合

已知源颜色S和目标颜色D,想要得到输出颜色O就必须使用一个等式来计算,这个等式称为混合等式(blend equation)。当进行混合时,我们需要使用两个混合等式:一个用于混合RGB通道,一个用于混合A通道

当设置混合状态时,实际上设置的就是混合等式中的操作因子,默认情况下使用的都是加操作,我们需要再设置一下混合因子即可。由于需要两个等式,每个等式有两个因子,因此一共需要四个因子

命令描述
Blend SrcFactor DstFactor开启混合,并设置混合因子,源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后两者相加后再存入颜色缓冲中
Blend SrcFactor DstFactor, SrcFactorA DstFactorA和上面几乎一样,只是使用不同的混合因子来混合透明通道

Blend SrcFactor DstFactor只提供了两个因子,这意味着将使用同样的混合因子来混合RGB和A通道,此时 S r c F a c t o r = S r c F a c t o r A SrcFactor = SrcFactor A SrcFactor=SrcFactorA D s t F a c t o r = D s t F a c t o r A DstFactor = DstFactorA DstFactor=DstFactorA
O r g b = S r c F a c t o r × S r g b + D s t F a c t o r × D r g b O a = S r c F a c t o r A × S a + D s t F a c t o r A × D a O_{rgb} = SrcFactor\times S_{rgb} + DstFactor\times D_{rgb} \\ O_{a} = SrcFactorA\times S_{a} + DstFactorA\times D_{a} \\ Orgb=SrcFactor×Srgb+DstFactor×DrgbOa=SrcFactorA×Sa+DstFactorA×Da
以下表中提供了ShaderLab中的混合因子

参数描述
One因子为1
Zero因子为0
SrcColor因子为源颜色值。用于混合RGB时,使用SrcColor的RGB分量作为混合因子。用于混合A时,使用SrcColor的A分量作为混合因子
SrcAlpha因子为源颜色的透明度值(A通道)
DstColor因子为目标颜色值。用于混合RGB时,使用DstColor的RGB分量作为混合因子。用于混合A时,使用DstColor的A分量作为混合因子
DstAlpha因子为目标颜色的透明度值(A通道)
OneMinusSrcColor因子为**(1-源颜色)**值。用于混合RGB时,使用SrcColor的RGB分量作为混合因子。用于混合A时,使用SrcColor的A分量作为混合因子
OneMinusSrcAlpha因子为**(1-源颜色的透明度)**值(A通道)
OneMinusDstColor因子为**(1-目标颜色)**值。用于混合RGB时,使用DstColor的RGB分量作为混合因子。用于混合A时,使用DstColor的A分量作为混合因子
OneMinusDstAlpha因子为**(1-目标颜色的透明度)**值(A通道)

例如,想要在混合后输出颜色的透明度值就是源颜色的透明度

Blend SrcAlpha OneMinusSrcAlpha,One Zero

6.2 混合操作

使用Blend BlendOpereation混合操作,达到不同的混合效果

操作描述
Add将混合后的颜色相加,默认的混合操作
O r g b = S r c F a c t o r × S r g b + D s t F a c t o r × D r g b O a = S r c F a c t o r A × S a + D s t F a c t o r A × D a O_{rgb} = SrcFactor\times S_{rgb} + DstFactor\times D_{rgb} \\ O_{a} = SrcFactorA\times S_{a} + DstFactorA\times D_{a} Orgb=SrcFactor×Srgb+DstFactor×DrgbOa=SrcFactorA×Sa+DstFactorA×Da
Sub用混合后的源颜色减去混合后的目的颜色
O r g b = S r c F a c t o r × S r g b − D s t F a c t o r × D r g b O a = S r c F a c t o r A × S a − D s t F a c t o r A × D a O_{rgb} = SrcFactor\times S_{rgb} - DstFactor\times D_{rgb} \\ O_{a} = SrcFactorA\times S_{a} - DstFactorA\times D_{a} Orgb=SrcFactor×SrgbDstFactor×DrgbOa=SrcFactorA×SaDstFactorA×Da
RevSub用混合后的目的颜色减去混合后的源颜色
O r g b = D s t F a c t o r × D r g b − S r c F a c t o r × S r g b O a = D s t F a c t o r A × D a − S r c F a c t o r A × S a O_{rgb} = DstFactor \times D_{rgb} - SrcFactor\times S_{rgb}\\ O_{a} = DstFactorA\times D_{a} - SrcFactorA \times S_{a} Orgb=DstFactor×DrgbSrcFactor×SrgbOa=DstFactorA×DaSrcFactorA×Sa
Min使用源颜色和目的颜色中较小的值,是逐分量比较的
O r g b a = ( m i n ( S r , D r ) , m i n ( S g , D g ) , m i n ( S b , D b ) , m i n ( S a , D a ) ) O_{rgba} = (min(S_r, D_r), min(S_g, D_g), min(S_b, D_b), min(S_a, D_a)) Orgba=(min(Sr,Dr),min(Sg,Dg),min(Sb,Db),min(Sa,Da))
Max使用源颜色和目的颜色中较大的值,是逐分量比较的
O r g b a = ( m a x ( S r , D r ) , m a x ( S g , D g ) , m a x ( S b , D b ) , m a x ( S a , D a ) ) O_{rgba} = (max(S_r, D_r), max(S_g, D_g), max(S_b, D_b), max(S_a, D_a)) Orgba=(max(Sr,Dr),max(Sg,Dg),max(Sb,Db),max(Sa,Da))
其他逻辑操作仅在DirectX11.1中支持

6.3 常见的混合类型

// 正常,即透明度混合
Blend SrcAlpha OneMinusSrcAlpha

// 柔和相加 
Blend OneMinusDstColor One

// 正片叠底
Blend DstColor Zero

// 两倍相乘
Blend DstColor SrcColor

// 变暗
BlendOp Min
Blend One One

// 变亮
BlendOp Max
Blend One One

// 滤色
Blend OneMinusDstColor One
// 等同于
Blend One OneMinusSrcColor

// 线性减淡
Blend One One

在这里插入图片描述

需要注意的是,虽然上面的Min和Max混合操作时仍然设置了混合因子,但实际上它们并不会对结果有任何影响,因为Min和Max混合操作会忽略混合因子

7 双面渲染的透明效果

由于默认情况下渲染引擎剔除了物体背面的渲染图元,而只渲染物体的正面,如果想要使用双面渲染的效果,可以使用Cull指令来控制需要剔除哪个面的渲染图元,Cull指令语法如下:

Cull Back | Front | Off

  • Back:背对摄像机的图元将不会被渲染、
  • Front:朝向摄像机的渲染图元将不会被渲染
  • Off:关闭剔除功能,所有背对和朝向摄像机的图元都会被渲染

7.1 透明度测试的双面渲染

Pass
	{
        Tags { "LightMode" = "UniversalForward" }
        Cull Off
        // 只需在Pass后加一句

7.2 透明度混合的双面渲染

Shader "MyShader/Transparent/AlphaBlendBothSide"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader
    {
        Tags { 
            "Queue"="Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }

        Pass
        {
            // 由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染
            Tags { "LightMode" = "UniversalForward" }
            Cull Front
            ZWrite Off   // 关闭深度写入
            Blend SrcAlpha OneMinusSrcAlpha   // 开始并设置Pass的混合模式
            // 把源颜色的混合因子设为SrcAlpha,把目标颜色的混合因子设为OneMinusSrcAlpha,以得到合适的半透明效果

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.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 f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.worldNormal = UnityObjectToWorldNormal(v.normal);
                f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return f;
            }

            fixed4 frag (v2f f) : SV_Target {
                fixed3 worldNormal = normalize(f.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
                fixed4 albedo = tex2D(_MainTex, f.uv);
                // 移除了透明度测试的代码
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(worldNormal, worldLightDir));
                return fixed4(ambient + diffuse, albedo.a * _AlphaScale);   // 设置了返回值中的透明通道
            }
            ENDCG
        }
        Pass
        {
            // 由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染
            Tags { "LightMode" = "UniversalForward" }
            Cull Back
            ZWrite Off   // 关闭深度写入
            Blend SrcAlpha OneMinusSrcAlpha   // 开始并设置Pass的混合模式
            // 把源颜色的混合因子设为SrcAlpha,把目标颜色的混合因子设为OneMinusSrcAlpha,以得到合适的半透明效果

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.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 f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.worldNormal = UnityObjectToWorldNormal(v.normal);
                f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return f;
            }

            fixed4 frag (v2f f) : SV_Target {
                fixed3 worldNormal = normalize(f.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
                fixed4 albedo = tex2D(_MainTex, f.uv);
                // 移除了透明度测试的代码
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(worldNormal, worldLightDir));
                return fixed4(ambient + diffuse, albedo.a * _AlphaScale);   // 设置了返回值中的透明通道
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}
;