Unity中使用两种方法实现透明效果:
- 透明度测试(Alpha Test)
- 透明度混合(Alpha Blending)
开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色和深度值之外,还具有透明度的属性。透明度为1时,完全不透明,透明度为0时,完全透明。
关于渲染顺序
对于不透明物体的渲染由于有深度缓冲(depth buffer,z-buffer),不考虑渲染顺序也能得到正确的渲染结果。使用深度缓冲在渲染不透明物体时,可以不用考虑其渲染顺序,在进行深度测试时会判断物体距离摄像机的远近,远的物体是不会写入到颜色缓冲中的。
而在处理透明物体的渲染时,需要注意渲染顺序,因为在进行透明渲染时关闭了深度写入
透明度测试(Alpha Test)
透明度测试会根据片元的透明度进行对应操作。
如果透明度小于某一个值,对应的片元就被舍弃,被舍弃的片元不会再进行任何处理,不会对颜色缓冲产生影响,即完全透明,没有颜色一样。
否则,按照不透明物体进行处理,进行深度测试和深度写入等。
透明度测试不需要关闭深度写入,得到的效果要么完全透明, 要么完全不透明。
透明度混合(Alpha Blending)
透明度混合使用片元的透明度作为混合因子,与在颜色缓冲区内的颜色进行混合,得到新的颜色。透明度混合在渲染具有透明度的物体时需要关闭深度写入,但没有关闭深度测试。当使用透明度混合渲染片元时,会比较深度值与当前深度缓冲中的深度值,如果距离摄像机更远,就不会进行混合操作。因此,先渲染不透明物体,渲染时会写入深度缓冲,再进行透明物体的渲染,进行深度测试。 透明度混合是可以得到真正的半透明效果的。
为何关闭深度写入?
在对透明物体进行渲染需要关闭深度写入,这是因为假如现在有一个半透明物体离摄像机更近,后面有一个不透明物体,正常的渲染结果应该是透过半透明物体能够看到后面的不透明物体,而开启深度写入后,在对后面物体进行深度测试时,半透明物体的深度值已经写入深度缓冲中,通过比较会将后面不透明的物体剔除掉,因为 深度值比深度缓冲中的要大,因此不会被渲染出来。所以在对透明物体进行渲染需要关闭深度写入,而深度写入的关闭需要对渲染顺序做出调整,即先进行不透明物体渲染,在进行透明物体渲染,以得到正确的渲染结果。
渲染引擎中的渲染排序:
- 先渲染所有的不透明物体,并开启深度写入和深度测试
- 再把半透明物体按照距离摄像机的远近进行排序,按照从后往前的顺序进行渲染半透明物体,关闭深度写入,开启深度测试
UnityShader的渲染顺序
Unity使用渲染队列(render queue)解决渲染顺序问题。通过使用SubShader的Queue标签决定渲染的模型属于哪一个渲染队列。Unity内部使用一系列索引号表示渲染顺序,索引号越小渲染越早。
若是使用透明度测试实现透明效果:
SubShader{
Tags{"Queue"="AlphaTest"}
Pass{
}
}
若是使用透明度混合实现透明效果:
SubShader{
Tags{"Queue"="Transparent"}
Pass{
Zwrite Off
}
}
使用透明度混合是需要关闭深度写入的。
透明度测试(Alpha Test)实例
使用透明度测试的方式实现透明是对一个片元的透明度进行判断,如果小于某一个阈值,则舍弃该片元,作为完全透明处理。通常在片元着色器中使用clip函数进行透明度测试。函数定义:
函数:void clip(float4 x) ; void clip(float3 x) ; void clip(float2 x) ; void clip(float x)
参数 :裁剪时使用的标量或矢量条件
描述:如果给定参数的任何一个分量是负数,就舍弃当前像素的输出颜色,即
void clip(float x){
if(any(x<0))
discard;
}
实例代码:
Shader "Custom/Chapter8_AlphaTest" {
Properties{
_Color("Color",Color)=(1,1,1,1)
_MainTex("MainTex",2D)="white"{}
_Cutoff("Alpha Cutoff",Range(0,1))=0.5 //在材质面板显示和调节透明度测试的控制阈值
}
SubShader{
Tags{"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
//通常,使用透明度测试的Shader都应该在SubShader中设置这三个标签
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
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.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
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);
clip(texColor.a-_Cutoff);
//clip函数做透明度的比较后进行裁剪操作
fixed3 albedo=texColor.rgb*_Color;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient+diffuse,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
实例效果:
从左往右阈值依次是0.55,0.65,0.75,漫反射贴图自带透明通道a,使用clip函数根据传入的透明度与阈值的插值判断片元舍弃还是按照不透明处理,可以看出透明度测试效果要么为全透明,要么为完全不透明。
透明度混合
透明度混合可以得到真正的半透明效果,使用指定的混合因子对当前片元颜色和颜色缓冲区中的颜色进形混合,得到新的颜色。透明度混合需要关闭深度写入,因此需要注意渲染的顺序。
混合需要使用混合命令Blend。Blend是Unity提供的设置混合模式的命令。Blend的语义:
实例代码:
Shader "Custom/Chapter8_AlphaBlend" {
Properties{
_Color("Color",Color)=(1,1,1,1)
_MainTex("MainTex",2D)="white"{}
_AlphaScale("AlphaScale",Range(0,1))=1
}
SubShader{
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
//"RenderType"="Transparent"指明该shader为使用了透明度混合的shader
Pass{
Tags{"LightMode"="ForwardBase"}
Zwrite Off //关闭深度写入
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
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.xyz*albedo;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
实例效果:
从左至右AlphaScale值依次是 1 0.5 0.2
开启深度写入的半透明效果
当模型本身具有复杂的遮挡关系或者包含复杂非凸网格时,未开启深度写入,会有因为排序错误产生的错误透明效果。一种解决办法是使用两个Pass来对模型进行渲染,第一个Pass开启深度写入,不输出颜色,第二个进行正常透明度混合。
实例代码:
Shader "Custom/Chapter8_AlphaBlendZwrite" {
Properties{
_Color("Color",Color)=(1,1,1,1)
_MainTex("MainTex",2D)="white"{}
_AlphaScale("AlphaScale",Range(0,1))=1
}
SubShader{
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
//"RenderType"="Transparent"指明该shader为使用了透明度混合的shader
//开启深度写入的Pass是为了将模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元
Pass{
ZWrite On
ColorMask 0
}
Pass{
Tags{"LightMode"="ForwardBase"}
Zwrite Off //关闭深度写入
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
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.xyz*albedo;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
在第一个Pass中
Pass{
ZWrite On
ColorMask 0
}
ColorMask用于设置颜色通道的写掩码,语义:
ColorMask RGB | A | 0
设置为0时,表示该Pass不写入任何颜色通道,不会输出任何颜色。
实例效果:
左侧为一个Pass的透明度混合效果,可以看到模型内部的透明效果显示关系出现混乱,右侧为添加深度写入的Pass后,模型内部的遮挡效果正常。
双面渲染的透明效果
前面实现的透明效果中,透明度测试和透明度混合均无法看到物体内部结构。这是由于默认情况下渲染引擎剔除了物体背面的(相对于摄像机的方向)渲染图元,通过使用Cull命令控制需要剔除的面或得到双面渲染效果。 Cull语义:
Cull | Back | Front | Off
设置为Back 背对摄像机的图元不会被渲染,设置为Front朝向摄像机的图元不会被渲染,设置为Off,双面渲染。
- 透明度测试双面渲染
实例代码:
Pass{
Tags{"LightMode"="ForwardBase"}
Cull Off //开启双面渲染效果
}
透明度测试的双面渲染只需在原来基础上添加Cull Off的命令
实例效果:
左侧为双面渲染效果,可以看到内部结构,右边为默认剔除背面渲染图元效果。
- 透明度混合双面渲染
由于透明度混合是关闭了深度写入的,因此如果直接关闭剔除,并不能保证一个物体的正面和背面按照正确定的渲染顺序进行渲染,可能得到错误效果。因此实现双面透明度混合可以通过两个Pass,先剔除正面,渲染背面,再剔除背面渲染正面。
实例代码:
Shader "Custom/Chapter8_AlphaBlendBothSide" {
Properties{
_Color("Color",Color)=(1,1,1,1)
_MainTex("MainTex",2D)="white"{}
_AlphaScale("AlphaScale",Range(0,1))=1
}
SubShader{
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
//"RenderType"="Transparent"指明该shader为使用了透明度混合的shader
Pass{
Tags{"LightMode"="ForwardBase"}
Cull Front //先渲染背面
Zwrite Off //关闭深度写入
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
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.xyz*albedo;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
Pass{
Tags{"LightMode"="ForwardBase"}
Cull Back //再渲染正面
Zwrite Off //关闭深度写入
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
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.xyz*albedo;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}