1. 透明度混合
透明度混合用来模拟真正的半透效果。
1.1 基本原理
为了方便后续描述,我们先提前对一些表示符号进行介绍
符号 | 称呼 | 意义 |
src | 源 | 用于表示当前正在处理的片元的相关信息 |
dst | 目标 | 用于表示颜色缓冲区中与当前正处理的片元对应位置上已经存储的相关信息 |
O | 输出 | 用于表示最终输出的颜色信息 |
透明度混合的基本原理是,在片元着色器输出时,将该片元的颜色信息(源)和目标缓冲区中已经存在的颜色信息(目标)按照某种方式进行计算,并用最终的计算结果更新目标颜色缓冲区。
O = f( src, dst )
1.2 混合因子
在实际混合时,源和目标都会分配一个系数,称为混合因子(Factor),因此,实际的混合公式更类似与如下表示:
O = f( srcFactor * srcColor, dstFactor * dstColor )
其中 srcColor 和 dstColor 都是包含了RGBA四个通道的颜色值,我们可以给四个通道分配同样的混合因子,也可以为RGB通道分配一个混合因子,而给A通道分配另一个混合因子。具体分配混合因子时是通过在 SubShader 中设置混合指令 Blend 来进行的,因此Blend指令包含如下两种格式:
格式 | 说明 | 对应公式 |
Blend SrcFactor DstFactor | 为源颜色的四个通道都分配混合因子SrcFactor,同时为目标颜色的四个通道都分配混合因子DstFactor | O = f( srcFactor * srcColor, dstFactor * dstColor ) |
Blend SrcFactor DstFactor, SrcFactorA DstFactorA | 为源颜色的RGB通道分配混合因子SrcFactor,A通道分配混合因子SrcFactorA,同时为目标颜色的RGB通道分配混合因子DstFactor,A通道分配混合因子DstFactorA | ORGB = f( srcFactor * srcRGB, dstFactor * dstRGB ),OA = f( srcFactorA * srcA, dstFactorA * dstA ) |
当通过以上两种格式设置了混合指令后,Unity会自动为我们开启透明度混合,除此以外,我们还可以通过 Blend Off 的方式指定关闭透明度混合。
注意:Blend指令是通知Unity开启透明度混合的开关,如果没有设置Blend指令,即使片元着色器中输出颜色的Alpha值小于1也不会有混合的效果。
SrcFactor和DstFactor并不是我们可以随意指定的,而是由Unity定义好的,Unity支持的混合因子如下:
参数 | 描述 |
One | 1 |
Zero | 0 |
SrcAlpha | 源颜色的 Alpha 值 |
SrcColor | 源颜色的颜色值。当混合RGB时,用源颜色的RGB分量,当混合A通道时,用源颜色的A分量 |
DstAlpha | 目标颜色的 Alpha 值 |
DstColor | 目标颜色的颜色值。当混合RGB时,用目标颜色的RGB分量,当混合A通道时,用目标颜色的A分量 |
OneMinusSrcAlpha | 1减源颜色的 Alpha 值 |
OneMinusSrcColor | 1减源颜色的颜色值 |
OneMinusDstAlpha | 1减目标颜色的 Alpha 值 |
OneMinusDstColor | 1减目标颜色的颜色值 |
1.3 混合操作
除了可以通过 Blend 指令分配混合因子外,还可以通过 BlendOp 指令指定混合的操作方法,即 f() 的部分。Unity支持的混合操作如下:
操作 | 描述 |
Add | 相加,O = SrcFactor * SrcColor + DstFactor * DstColor |
Sub | 相减,O = SrcFactor * SrcColor - DstFactor * DstColor |
RevSub | 反向相减,O = DstFactor * DstColor - SrcFactor * SrcColor |
Min | 各分量取最小值,O = (min(SR, DR), min(SG, DG), min(SB, DB), min(SA, DA)) |
Max | 各分量取最大值,O = (max(SR, DR), max(SG, DG), max(SB, DB), max(SA, DA)) |
当不指定BlendOp时,默认为 Add
1.4 常见的混合效果
通过对混合因子和混合操作进行组合,可以得到一些类似于PhotoShop混合模式中的混合效果:
效果 | 组合 |
正常 | 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 |
2. 示例
- 创建 Chapter_8_AlphaBlend_Mat 作为测试材质
- 创建 Chapter_8_AlphaBlend_Shader 作为测试shader,并赋给 Chapter_8_AlphaBlend_Mat 材质
- 场景中创建一个用于测试的立方体,将 Chapter_8_AlphaBlend_Mat 材质赋给立方体
- 场景中添加一盏平行光,并调整平行光角度
- 为了便于观察效果,在立方体下方创建一个Plane
在SubShader中设置标签
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True"}
在Pass中设置混合因子
Blend SrcAlpha OneMinusSrcAlpha
为了避免半透物体挡住其后物体的问题,需要在Pass中关闭深度写入
ZWrite Off
最后,在片元着色器的输出颜色中,Alpha值不能再像之前一样直接输出1,而是要根据实际情况输出(这里我没有设置Alpha值的缩放系数,直接用采样得到的Alpha输出)
最终Shader如下:
Shader "MyShader/Chapter_8/Chapter_8_AlphaBlend_Shader"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True"}
Pass
{
Tags {"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(a2v i)
{
v2f o;
o.vertex = UnityObjectToClipPos(i.vertex);
o.worldNormal = mul(i.normal, (float3x3)unity_WorldToObject);
o.uv = TRANSFORM_TEX(i.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
float3 _worldNormal = normalize(i.worldNormal);
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
float4 _mainColor = tex2D(_MainTex, i.uv);
fixed3 _diffuse = _LightColor0.rgb * _mainColor.xyz * (0.5 * dot(_worldLight, _worldNormal) + 0.5);
return fixed4(_ambient + _diffuse, _mainColor.w);
}
ENDCG
}
}
}
效果如下: