在Unity中,我们通常使用两种方式来实现透明效果:1.使用透明度测试 2.透明度混合.
透明度测试:只要一个片元的透明度不满足条件,它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何英雄,不需要关闭深度写入.
透明度混合: 使用当前片元的透明度作为混合因子,与以及存储在颜色缓冲中的颜色值进行混合,得到新的颜色。需要关闭深度写入,要非常小心物体的渲染顺序.
问题1:在透明度混合中为什么需要关闭深度写入呢?
如果不关闭深度写入,一个透明物体背后的物体本来是可以被我们看到的,但由于深度测试时判断结果是该半透明物体表面距离摄像机更近,导致后面的物体被剔除,也就无法透过透明物体看到后面的物体了。
问题2:在透明度混合中为什么渲染顺序很重要呢?
假设场景里面有物体A和B,A是半透明物体,B是不透明物体,A在B的前面
考虑两种情况:
1.先渲染B,在渲染A:
由于不透明物体开启了深度测试和深度写入,一开始深度缓冲中没有任何数据,B首先会写入颜色缓冲和深度缓冲,然后渲染A,透明物体仍然会进行深度测试,但是A比B距离摄像机更近,因此我们会使用A的透明度和颜色缓冲中的B的颜色进行混合,得到正确的半透明效果.
2.先渲染A,在渲染B:
首先先渲染A,此时深度缓冲中没有任何有效数据,但是由于对半透明物体关闭了深度写入,A不会修改深度缓冲,然后渲染B,B进行深度测试,由于深度缓冲没有发生变化,B就直接写入颜色缓冲和深度缓冲,造成的结果就是B出现在了A的前面.
渲染顺序总结如下:
1.先绘制所有不透明的物体,并开启它们的深度测试和深度写入.
2.对所有透明的物体排序。
3.按从后往前的顺序绘制所有透明的物体。
透明度测试
通常,我们会在片元着色器中使用clip函数来进行透明度测试,如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色.它等同于下面的代码:
void clip(float4 x)
{
if(any(x < 0))
discard;
}
第一步创建一个Shader,在属性里面添加一个变量_Cutoff用来控制透明度测试时使用的阈值:
_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5//用于决定调用clip进行透明度测试时使用的判断条件,0-1像素透明度的范围
第二步在SubShader语义块中定义一个Pass语义块:
Tags{"Queue " = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
//使用渲染队列名为AlphaTest的队列,RenderType 让Unity把这个shader归入到提前定义的组,以指明该shader是一个使用了
//透明度测试的shader. ignoreProjector 意味着shader不会受到投影器的影响
pass
{
Tags{"LightMode" = "ForwardBase"}
第三步在CG代码块中声明对应属性的变量:
fixed _Cutoff;
然后定义顶点着色器的输入和输出结构体:
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 wordlNormal : TEXCOORD;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
接着定义顶点着色器:
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wordlNormal = 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.wordlNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
clip (texColor.a - _Cutoff);
//if((texColor.a - _Cutoff) < 0.0)//检测透明度,如果是负数就舍弃输出
//{
// discard;
//}
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, 1.0);
}
效果如下:
完整代码如下:
Shader "AlphaTest"
{
Properties
{
_Color("Color Tint",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "white"{}
_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5//用于决定调用clip进行透明度测试时使用的判断条件,0-1像素透明度的范围
}
SubShader
{
Tags{"Queue " = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
//使用渲染队列名为AlphaTest的队列,RenderType 让Unity把这个shader归入到提前定义的组,以指明该shader是一个使用了透明度测试的Shader
//透明度测试的shader. ignoreProjector 意味着shader不会受到投影器的影响
pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //使用Unity内置变量 如_LightColor0
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 : TEXCOORD;
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);
//if((texColor.a - _Cutoff) < 0.0)//检测透明度,如果是负数就舍弃输出
//{
// discard;
//}
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, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
透明度混合
为了进行混合,需要使用Unity提供的混合命令-Blend
第一步声明属性:
_AlphaScale("Alpha Scale", Range(0, 1)) = 1
用来控制整体的透明度.
第二步修改标签:
Tags{"Queue " = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
第三步,关闭深度写入,设置混合模式:
Write Off
Blend SrcAlpha OneMinusSrcAlpha
第四步,修改片元着色器:
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.wordlNormal);
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);
//设置片元着色器返回值中的透明通道
}
效果如下:
完整代码如下:
Shader "Unlit/AlphaTest"
{
Properties
{
_Color("Color Tint",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "white"{}
_AlphaScale("Alpha Scale", Range(0, 1)) = 1
}
SubShader
{
Tags{"Queue " = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//使用透明度混合的物体使用Transparent队列
pass
{
Tags{"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //使用Unity内置变量 如_LightColor0
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 wordlNormal : TEXCOORD;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wordlNormal = 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.wordlNormal);
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开启深度写入,但不输出颜色,它的目的仅仅是为了把模型的深度值写入深度缓冲中。第二个Pass进行正常的透明度混合.该Pass可以按照像素级别的深度排序结果进行透明渲染
完整代码如下
Shader "Unlit/AlphaTest"
{
Properties
{
_Color("Color Tint",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "white"{}
_AlphaScale("Alpha Scale", Range(0, 1)) = 1
}
SubShader
{
Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//使用透明度混合的物体使用Transparent队列
pass//目的是把模型的深度信息写入深度缓冲中
{
ZWrite On//开启深度写入
ColorMask 0 //ColorMask用于设置颜色通道的写掩码 语义 ColorMask RGB|A|0
//当ColorMask设为0时,意味着Pass不写入任何颜色通道,不会输出任何颜色。
}
pass
{
Tags{"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //使用Unity内置变量 如_LightColor0
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 wordlNormal : TEXCOORD;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wordlNormal = 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.wordlNormal);
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通道里面添加下面的代码就行了:
Cull Off

透明度混合的双面渲染
相比于透明度测试,透明度混合会更复杂,因为透明度混合需要关闭深度写入。想要得到正确的透明效果,渲染顺序是非常重要的,要保证物体是从后往前渲染的.
为此,我们把双面渲染分成两个Pass,第一个Pass渲染背面,第二个Pass渲染正面,这样可以保证背面总是在正面被渲染之前渲染,从而可以保证正确的深度渲染关系.
完整代码如下:
Shader "Unlit/AlphaTest"
{
Properties
{
_Color("Color Tint",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "white"{}
_AlphaScale("Alpha Scale", Range(0, 1)) = 1
}
SubShader
{
Tags{"Queue " = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//使用透明度混合的物体使用Transparent队列
pass
{
Tags{"LightMode" = "ForwardBase"}
Cull Front
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //使用Unity内置变量 如_LightColor0
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 wordlNormal : TEXCOORD;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wordlNormal = 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.wordlNormal);
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" //使用Unity内置变量 如_LightColor0
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 wordlNormal : TEXCOORD;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wordlNormal = 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.wordlNormal);
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分别使用Cull指令剔除不同朝向的渲染图元
效果如下: