一、透明度测试(Alpha Test)
透明度测试的目的很直接,如果像素的 Alpha 值小于一个定值,那么这个像素就直接丢弃:
Shader "Jaihk662/AlphaTest1"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
_SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
_MainTex ("MainTex", 2D) = "white" {}
_Gloss ("Gloss", Range(8.0, 256)) = 20
_CutOff ("AlphCutOff", Range(0, 1)) = 0.5
}
SubShader
{
LOD 200
Tags { "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
PASS
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert //声明顶点着色器的函数
#pragma fragment frag //声明片段着色器的函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Gloss;
fixed _CutOff;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float3 wPos: TEXCOORD0;
float3 wNormal: TEXCOORD1;
float2 uv: TEXCOORD2;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));
fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
clip(texColor.a - _CutOff);
//if ((texColor.a - _CutOff) < 0)
// discard;
fixed3 albedo = texColor.rgb * _DiffuseColor.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));
fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
代码中的标签:
- "Queue" = "AlphaTest":使用 AlphaTest 渲染队列
- "IgnoreProjector" = "True":这个 shader 不会受到投影器(Projectors)的影响
- "RenderType" = "TransparentCutout":将 Shader 归入到 TransparentCutout 组中,以指明该 Shader 是一个使用了透明度测试的 Shader
关于渲染队列(RenderQueue):
为了解决渲染顺序问题,Unity 内部使用一系列整数索引来表示每个渲染队列,数字越小越早被渲染
有5个索引已经被 Unity 提前定义了:
- Background:索引值1000,一般用于绘制背景
- Geometry:索引值2000,默认渲染队列,一般大部分不透明物体都使用这个队列
- AlphaTest:索引值2450,一般需要透明度测试的物体使用这个队列
- Transparent:索引值3000,这个队列中的所有物体按照从后往前的顺序进行渲染,一般需要透明度混合的物体使用这个队列
- Overlay:索引值4000,最后渲染的物体
二、深度测试与渲染次序
想要正确渲染透明物体,其实要考虑的东西并不少,特别是对于复杂的物体
参考于前置章节的混合公式:
- :源颜色向量,来自当前纹理的本来的颜色向量
- :目标颜色向量。这是储存在颜色缓冲中当前位置的颜色向量
- :源因子。设置了对源颜色的alpha值影响
- :目标因子。设置了对目标颜色的alpha影响
可以看出,对于多个简单透明物体排成一排依次遮盖的情况,想要得到正确的渲染效果,必须要按照从后往前的顺序依次渲染物体(混合函数不满足交换律),并且不可进行深度测试(哪怕是被遮盖的物体,也会对最终显示在屏幕上的的颜色有贡献)。基于此透明物体的渲染方法步骤如下:
- 开启深度测试和深度写入,渲染所有不透明物体
- 关闭深度写入,所有半透明物体按照其距离摄像机的远近进行排序,然后按照从后往前的顺序依次渲染这些半透明物体
但是,有破绽!
要知道,实际的深度测试是像素级别的,即每个像素都有一个深度值,可是我们却是对物体远近进行的排序的,若遇到下面两个比较出名的反例就不对了:
对于第一张图,三张透明纸条相互覆盖,对于第二张图,按照距离A物体排在B物体的前面,但是事实上反而是B物体遮盖住了A物体
一个解决方案是:分割网格,可以理解为是将这个物体给“拆掉”,然后逐块去排序。很好理解,但是这样做的成本可不低,因此大部分情况下只能退而求其次保证透明物体不要过于复杂,并且尽量是凸物体
三、透明度混合(Alpha Blending)
深度测试明显不靠谱,它过于“暴力”了,直接将小于某个 alpha 值的所有物体全部丢弃。想要得到真正的半透明效果,还是需要进行透明度混合
Unity 内部混合命令 → Blend:
- Blend Off:关闭混合
- Blend SrcFactor DstFactor:开启混合,设置混合因子并计算最终颜色存入颜色缓冲,遵循公式 ,其中 SrcFactor 对应 ,DstFactor 对应
- Blend SrcFactor DstFactor SrcFactorA DstFactorA:同上,不过 alpha 通道的计算使用因子 SrcFactorA DstFactorA
- BlendOp BlendOperation:使用其它公式进行颜色混合操作
- BlendOp OpColor, OpAlpha:使用其它公式进行颜色混合操作,其中 RGB 通道计算方法为 OpColor,Alpha 通道计算方法为 OpAlpha
一般来讲, ,在 Unity中, 对应 OneMinusSrcAlpha,也就是下面这些数据
- One:float4(1.0, 1.0, 1.0, 1.0)
- Zero:float4(0.0, 0.0, 0.0, 0.0)
- SrcColor:fragment_output(片元颜色,对应 )
- SrcAlpha:fragment_output.aaaa
- DstColor:pixel_color(当前颜色缓冲区中的颜色,对应 )
- DstAlpha:pixel_color.aaaa
- OneMinusSrcColor:One - SrcColor
- OneMinusSrcAlpha:One - SrcAlpha
- OneMinusDstColor:One - DstColor
- OneMinusDstAlpha:One - DstAlpha
而对于其它的混合公式 BlendOp:
- Add:,默认计算公式
- Sub:
- RevSub:
- Min:逐个通道比较取较小值
- Max:逐个通道比较取较大值
有了这些之后,代码就很好写了:
Shader "Jaihk662/AlphaTest1"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
_SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
_MainTex ("MainTex", 2D) = "white" {}
_Gloss ("Gloss", Range(8.0, 256)) = 20
_AlphaScale ("AlphCutOff", Range(0, 1)) = 1
}
SubShader
{
LOD 200
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
PASS
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Gloss;
fixed _AlphaScale;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float3 wPos: TEXCOORD0;
float3 wNormal: TEXCOORD1;
float2 uv: TEXCOORD2;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));
fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));
fixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseColor.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));
fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, tex2D(_MainTex, i.uv).a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
这个代码相对于之前反而更加简单