判断像素的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
}
}
这个纹理上每个方格透明度都不一样,当透明度小于阈值时就会舍弃当前像素的输出颜色
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"
}
上面的立方体只能看到模型的外面,看不到里面,这是因为,默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。,可以使用Cull指令来控制需要剔除哪个面
Cull Back //剔除背面,这是默认设置
Cull Front //剔除正面
Cull Off //不剔除,两面都会渲染
在上面的shader中,增加一行代码就可以渲染背面,注意关闭剔除后需要渲染的图元数目会成倍增加,出于性能考虑,一般是不会关闭剔除的
Pass
{
Cull Off //增加这一行
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
使用clip函数后,在一些物体边缘处,也就是透明与不透明的边界位置有锯齿,如左图所示,右图是优化后的效果
Pass
{
AlphaToMask On //增加这一行
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
当使用多重采样抗锯齿(MultiSampling Anti-Aliasing,MSAA)的时候,可以通过在Pass中添加AlphaToMask On指令开启显卡的alpha-to-coverage功能。增加MSAA采样等级会相应提高多重采样的边界覆盖范围,从而消除透明测试着色器上的锯齿现象,用在植被上比较有用。
将当前像素的颜色和颜色缓冲区中的颜色进行混合,Alpha值为混合因子(公式中的系数),需要注意的是
渲染半透明物体要关闭ZWrite,这是因为开启深度写入后,当检测到一个更近的物体时,深度测试通过,会把它的深度写入深度缓冲,它的颜色写入颜色缓冲,替换掉原来位置的颜色,而对于半透明物体不能直接替换,应该是把它的颜色和颜色缓冲中已有的颜色进行混合
下面几个例子展示控制渲染顺序的重要性
B在A的前面,假设A的深度值是5,B的深度值是3,开启ZWrite,不管A,B谁先渲染,最后的深度缓冲和颜色缓冲的结果类似上图,因为深度写入确保只渲染最近物体的颜色,深度缓冲会为每个像素存储一个深度值,这里简化表示
先渲染A,开启ZWrite,写入深度值,颜色也写入颜色缓冲,渲染B时,关闭ZWrite,B在A的前面,通过深度测试,进行颜色混合,得到正确的半透明效果
这里修改A的Render Queue,让B先渲染,关闭ZWrite,只写入颜色值,再渲染A,开启ZWrite,由于深度缓冲中没有值,通过深度测试,写入深度值,并替换颜色缓冲中已有的颜色,这样看上去A就在B的前面,得到错误的结果
两个都关闭ZWrite,先渲染A,写入颜色缓冲,再渲染B,进行颜色混合,得到正确的半透明效果
修改A的Render Queue,让B先渲染,写入颜色值,再渲染A,A的颜色和B的颜色混合,混合效果反了,看上去A在B的前面,得到错误的结果
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
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,不能像上面的Alpha Test一样直接使用Cull Off命令,因为Alpha Test没有关闭ZWrite,可以保证先渲染背面,再渲染正面,而Alpha Blending关闭了ZWrite,无法保证先背面后正面的渲染顺序。为此,我们选择把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass只渲染正面,这样就可以保证渲染顺序
下面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"
}
当模型本身有复杂的遮挡关系或是包含了复杂的非凸网格的时候,关闭深度写入就会有各种各样因为排序错误而产生的错误的透明效果,因为无法对模型进行像素级别的深度排序
一种解决方法是使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。但这种方法的缺点在于,多使用一个Pass会对性能造成一定的影响
使用这种方法,可以实现模型与它后面的模型混合的效果,但模型内部之间不会有任何真正的半透明效果
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"
}
通过Frame Debug可以查看这两个pass相关的一些设置
参考 《Shader 入门精要》