Alpha Test 与 Alpha Blending

Alpha Test(透明度测试)

判断像素的Alpha是否满足条件,不满足则丢弃这个像素
渲染队列为AlphaTest

Unity定义的5个渲染队列,索引号小的先渲染

名称 队列索引号 描述
Background 1000 这个渲染队列会在任何其他队列之前被渲染,通常使用该队列来渲染那些需要绘制在背景上的物体
Geometry 2000 不透明物体使用这个队列
AlphaTest 2450 需要透明度测试的物体使用这个队列
Transparent 3000 半透明物体使用该队列
Overlay 4000 用于实现一些叠加效果。任何需要在最后渲染的物体都应该使用该队列

Alpha Test一般包含以下指令

    SubShader
    {
        Tags{ "Queue" = "AlphaTest" "RenderType" = "TrannsparentCutout" "IgnoreProjector" = "True" }

        Pass
        {
            CGPROGRAM
            fixed4 frag (v2f i) : SV_Target
            {
                // 开启Alpha测试
                clip(color.a - alphaCutoffValue);
            }
            ENDCG
        }
    }

Alpha Test 与 Alpha Blending_第1张图片

这个纹理上每个方格透明度都不一样,当透明度小于阈值时就会舍弃当前像素的输出颜色

Shader "MyCustom/AlphaTest" 
{
	Properties 
	{
		_MainTex ("Main Tex", 2D) = "white" {}
		_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader 
	{
		Tags {"Queue"="AlphaTest" "RenderType"="TransparentCutout" "IgnoreProjector"="True"}
		LOD 100
		
		Pass 
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.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;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target
			{
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				// Alpha test
				clip (texColor.a - _Cutoff);
				// Equal to 
				// if ((texColor.a - _Cutoff) < 0.0) {
				// 	discard;
				// }
				
				return texColor;
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

Alpha Test 双面渲染

上面的立方体只能看到模型的外面,看不到里面,这是因为,默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。,可以使用Cull指令来控制需要剔除哪个面

Cull Back    //剔除背面,这是默认设置
Cull Front   //剔除正面
Cull Off     //不剔除,两面都会渲染

Alpha Test 与 Alpha Blending_第2张图片

在上面的shader中,增加一行代码就可以渲染背面,注意关闭剔除后需要渲染的图元数目会成倍增加,出于性能考虑,一般是不会关闭剔除的

		Pass 
		{
			Cull Off //增加这一行
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

Alpha Test 抗锯齿

Alpha Test 与 Alpha Blending_第3张图片
使用clip函数后,在一些物体边缘处,也就是透明与不透明的边界位置有锯齿,如左图所示,右图是优化后的效果

		Pass 
		{
			AlphaToMask On //增加这一行
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

当使用多重采样抗锯齿(MultiSampling Anti-Aliasing,MSAA)的时候,可以通过在Pass中添加AlphaToMask On指令开启显卡的alpha-to-coverage功能。增加MSAA采样等级会相应提高多重采样的边界覆盖范围,从而消除透明测试着色器上的锯齿现象,用在植被上比较有用。

Alpha Blending(透明度混合)

将当前像素的颜色和颜色缓冲区中的颜色进行混合,Alpha值为混合因子(公式中的系数),需要注意的是

  • 控制渲染顺序,先远再近,先渲染不透明物体(开启ZWrite,即深度写入),再渲染半透明物体(关闭ZWrite)
  • 半透明物体的渲染类型和渲染队列为Transparent
  • 设置混合模式,如Blend SrcAlpha OneMinusSrcAlpha

渲染半透明物体要关闭ZWrite,这是因为开启深度写入后,当检测到一个更近的物体时,深度测试通过,会把它的深度写入深度缓冲,它的颜色写入颜色缓冲,替换掉原来位置的颜色,而对于半透明物体不能直接替换,应该是把它的颜色和颜色缓冲中已有的颜色进行混合

下面几个例子展示控制渲染顺序的重要性

A和B都是不透明物体

Alpha Test 与 Alpha Blending_第4张图片

B在A的前面,假设A的深度值是5,B的深度值是3,开启ZWrite,不管A,B谁先渲染,最后的深度缓冲和颜色缓冲的结果类似上图,因为深度写入确保只渲染最近物体的颜色,深度缓冲会为每个像素存储一个深度值,这里简化表示

A是不透明物体,B是半透明物体,先渲染A

Alpha Test 与 Alpha Blending_第5张图片

先渲染A,开启ZWrite,写入深度值,颜色也写入颜色缓冲,渲染B时,关闭ZWrite,B在A的前面,通过深度测试,进行颜色混合,得到正确的半透明效果

A是不透明物体,B是半透明物体,先渲染B

Alpha Test 与 Alpha Blending_第6张图片

这里修改A的Render Queue,让B先渲染,关闭ZWrite,只写入颜色值,再渲染A,开启ZWrite,由于深度缓冲中没有值,通过深度测试,写入深度值,并替换颜色缓冲中已有的颜色,这样看上去A就在B的前面,得到错误的结果

A和B都是半透明物体,先渲染A

Alpha Test 与 Alpha Blending_第7张图片

两个都关闭ZWrite,先渲染A,写入颜色缓冲,再渲染B,进行颜色混合,得到正确的半透明效果

A和B都是半透明物体,先渲染B

Alpha Test 与 Alpha Blending_第8张图片

修改A的Render Queue,让B先渲染,写入颜色值,再渲染A,A的颜色和B的颜色混合,混合效果反了,看上去A在B的前面,得到错误的结果

Shader实现,1个pass

Blend命令中用到的一些参数

参数 描述
One 因子为0
Zero 因子为1
SrcColor 片元着色器计算后的rgb值
SrcAlpha 片元着色器计算后的alpha值
DrcColor 颜色缓冲区中原有的rgb值
DrcAlpha 颜色缓冲区中原有的alpha值
OneMinusSrcColor 1 - SrcColor
OneMinusSrcAlpha 1 - SrcAlpha
OneMinusDstColor 1 - DstColor
OneMinusDstAlpha 1 - DstAlpha

Blend SrcAlpha OneMinusSrcAlpha 表示混合后新的颜色是

DstColorNew = SrcAlpha × SrcColor + (1 − SrcAlpha) × DstColorOld

Alpha Test 与 Alpha Blending_第9张图片

Shader "MyCustom/AlphaBlending"
{
    Properties
    {
        _Color      ("_Color", Color)            = (1, 1, 1, 1)
        _MainTex    ("Main Tex", 2D)             = "white" {}
        _AlphaScale ("_AlphaScale", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100
        
        ZWrite off
        //混合后新的颜色是:DstColorNew = SrcAlpha × SrcColor + (1 − SrcAlpha) × DstColorOld
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv     : TEXCOORD0;
            };

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

            float _AlphaScale;
            sampler2D _MainTex;
			float4 _MainTex_ST;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                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 float4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

Alpha Blending 双面渲染,2个pass

对于Alpha Blending,不能像上面的Alpha Test一样直接使用Cull Off命令,因为Alpha Test没有关闭ZWrite,可以保证先渲染背面,再渲染正面,而Alpha Blending关闭了ZWrite,无法保证先背面后正面的渲染顺序。为此,我们选择把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass只渲染正面,这样就可以保证渲染顺序

Alpha Test 与 Alpha Blending_第10张图片

下面Shader中,两个pass除了Cull这一行不一样,其他内容都是一样的,第一个pass,Cull Front 剔除正面,第二个pass,Cull Back剔除背面

Shader "MyCustom/AlphaBlending"
{
    Properties
    {
        _Color      ("_Color", Color)            = (1, 1, 1, 1)
        _MainTex    ("Main Tex", 2D)             = "white" {}
        _AlphaScale ("_AlphaScale", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            Cull Front
            ZWrite off
            //混合后新的颜色是:DstColorNew = SrcAlpha × SrcColor + (1 − SrcAlpha) × DstColorOld
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv     : TEXCOORD0;
            };

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

            float _AlphaScale;
            sampler2D _MainTex;
			float4 _MainTex_ST;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                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 float4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
        
        Pass
        {
            Cull Back
            ZWrite off
            //混合后新的颜色是:DstColorNew = SrcAlpha × SrcColor + (1 − SrcAlpha) × DstColorOld
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv     : TEXCOORD0;
            };

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

            float _AlphaScale;
            sampler2D _MainTex;
			float4 _MainTex_ST;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                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 float4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

复杂模型的透明度混合,2个pass

Alpha Test 与 Alpha Blending_第11张图片
当模型本身有复杂的遮挡关系或是包含了复杂的非凸网格的时候,关闭深度写入就会有各种各样因为排序错误而产生的错误的透明效果,因为无法对模型进行像素级别的深度排序
一种解决方法是使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。但这种方法的缺点在于,多使用一个Pass会对性能造成一定的影响
Alpha Test 与 Alpha Blending_第12张图片
使用这种方法,可以实现模型与它后面的模型混合的效果,但模型内部之间不会有任何真正的半透明效果

Shader "MyCustom/AlphaBlending"
{
    Properties
    {
        _Color      ("_Color", Color)            = (1, 1, 1, 1)
        _MainTex    ("Main Tex", 2D)             = "white" {}
        _AlphaScale ("_AlphaScale", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100
        
        // 写入深度缓冲的pass
        Pass
        {
            ZWrite On
            // 设置颜色通道的写掩码
            // ColorMask R 表示只有R通道会被写入
            // ColorMask RG 表示R,G通道会被写入
            ColorMask 0  // 表示不写入颜色
        }

        Pass
        {
            ZWrite off
            //混合后新的颜色是:DstColorNew = SrcAlpha × SrcColor + (1 − SrcAlpha) × DstColorOld
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv     : TEXCOORD0;
            };

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

            float _AlphaScale;
            sampler2D _MainTex;
			float4 _MainTex_ST;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                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 float4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

Alpha Test 与 Alpha Blending_第13张图片

Alpha Test 与 Alpha Blending_第14张图片
通过Frame Debug可以查看这两个pass相关的一些设置

参考 《Shader 入门精要》

你可能感兴趣的:(技术美术,unity)