「UnityShader笔记」11.透明度混合

Part1.原理简介

什么是透明度混合?

透明度混合是与透明度测试截然不同的一种实现半透明效果的方式,相比于透明度测试只有完全不透明和完全透明(被剔除)两种情况,透明度混合可以实现真正的半透明效果,其基本思想是半透明物体的颜色会和其背后被遮挡物体的颜色按一定比例混色,从而模拟半透明效果

透明度混合必须关闭深度写入

在进行透明度混合时,深度写入必须被关闭,否则将可能得到错误的结果,考虑如下情况:若先渲染半透明物体A,再渲染其背后不透明物体B,正常效果应当是可以透过A看到B,但如果渲染A后进行了深度写入,则渲染物体B时,物体B将无法通过深度测试,即被剔除,更不谈和A的颜色进行混合了

透明度混合时,渲染顺序是重要的

透明度混合时,由于关闭了深度写入,渲染顺序变得十分重要,不论是半透明物体和不透明物体之间,还是半透明物体互相之间,如果没有按照从远到近的顺序渲染,得到效果都将是错误的,我们分别考虑这两种情况

半透明物体和不透明物体之间

假设半透明物体A离摄像机更近,但比不透明物体B更先被渲染。由于关闭了深度写入,渲染不透明物体B时,由于B通过了深度测试,将直接覆盖写入颜色缓存覆盖了A的颜色信息,从而产生B在A前面的视觉效果,这是错误的

半透明物体互相之间

半透明物体互相之间的渲染顺序之所以重要,是由颜色混合公式决定的,颜色混合公式描述如下:

DstColorNew( 新颜色 ) = SrcAlpha × SrcColor(源颜色) + (1 - SrcAlpha) × DstColorOld( 存储在颜色缓存中的旧颜色 )

可以看到,谁是后来的源颜色,谁是先被渲染的旧颜色,它们的顺序是有影响的,因为它们在混色公式中所乘的比例系数不同,分别为SrcAlpha和1-SrcAlph,最终混出的颜色肯定也是不同的

Part2.代码逐段解析

Properties {
    _Color ("Color Tint", Color) = (1, 1, 1, 1)
    _MainTex ("Main Tex", 2D) = "white" {}
    _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}

在属性块中,我们需要额外定义透明度尺度,初始化为1,代表完全不透明

//渲染顺序队列Queue设置为Transparent
//渲染类型设置为Transparent;注意:透明度测试时设置的是TransparentCutout
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

我们随后需要设定Shader的一些tag,包括指定渲染队列为"Transparent",忽略投影器设置为True,渲染模式设定为Transparent(注意:在透明度测试时,这一项设置的是TransparentCutout)

Tags { "LightMode"="ForwardBase" }

//关闭深度写入
ZWrite Off

//设置混合因子,混合颜色 = SrcAlpha * SrcColor(源颜色) + (1-SrcAlpha) * DstColor(目标颜色)
Blend SrcAlpha OneMinusSrcAlpha

随后进入Pass块,我们需要手动关闭深度写入,然后设置混合因子,这里我们设置源颜色的混合系数为SrcAlpha,目标颜色的混合系数为1 - SrcAlpha

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

在片元着色器的输入结构体中,我们需要获取像素点在裁剪空间、世界空间的位置,以及世界空间下的法向量,还有纹理UV

v2f vert(a2v v){
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    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 * albedo;

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

    return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}

在片元着色器中,只需要在标准光照计算后,对颜色输出的α通道(透明度通道)进行处理即可

Part3.效果图

Part4.完整代码

Shader "Chapter8/alphaBlendZ-Write"
{
    Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
    SubShader
    {
        //渲染顺序队列Queue设置为Transparent
        //渲染类型设置为Transparent;注意:透明度测试时设置的是TransparentCutout
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

        Pass{
            ZWrite On
            ColorMask 0
        }

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

            //关闭深度写入
			ZWrite Off

            //设置混合因子,混合颜色 = SrcAlpha * SrcColor(源颜色) + (1-SrcAlpha) * DstColor(目标颜色)
			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.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                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 * albedo;

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

                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

 

posted @ 2022-06-15 00:37  睦月兔  阅读(141)  评论(0编辑  收藏  举报