在实时渲染中要实现透明效果, 通常会在渲染模型时控制它的透明通道( Alpha Channel)。 当开启透明混合后, 当一个物体被渲染到屏幕上时, 每个片元除了颜色值和深度值之外, 它还有另一个属性—透明度。 当透明度为 1 时, 表示该像素是完全不透明的, 而当其为 0 时, 则表示该像素完全不会显示。
在 Unity 中, 我们通常使用两种方法来实现透明效果:
第一种是使用透明度测试( Alpha Test), 这种方法其实无法得到真正的半透明效果;
另一种是透明度混合(Alpha Blendhig)。
对于不透明物体,不考虑它们的渲染顺序也能得到正确的排序效果,这是由于强大的深度缓冲的存在。深度缓冲的基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的值进行比较(如果开启了深度测试,默认判断是小于等于),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)。
透明度测试和透明度混合的基本原理如下:
透明度测试:只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。
透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么久不会再进行混合操作了。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的(不会进行写入操作,只会进行读取操作)。
渲染引擎一般都会先对物体进行排序,再渲染。常用的方法是:
1)先渲染所有不透明的物体,并开启它们的深度测试和深度写入。
2)把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。
索引号越小表示越早被渲染
透明度测试shader:
Shader "MyShader/AlphaTest"
{
Properties
{
_Color("Main Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
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);
or
//if ((texColor.a - _Cutoff) < 0.0)
//{
// discard;
//}
up
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldNormal));
return fixed4(ambient + diffuse, 1.0);
}
ENDCG
}
}
Fallback "Transparent/Cutout/VertexLit"
}
为了在材质面板中控制透明度测试时使用的阈值,我们在 Properties 语义块中声明一个范围在 [0,1] 之间的属性 _Cutoff,
_Cutoff 参数用于决定我们调用 clip 进行透明度测试时使用的判断条件。
范围在 [0,1] 之间的属性 _Cutoff,可以用 fixed 精度来存储它。
discard 指令来显式剔除该片元。
效果,_Cutoff = 0.49
效果,_Cutoff = 0.59
效果,_Cutoff = 0.69
效果,_Cutoff = 0.79
效果,_Cutoff = 0.81
效果很极端,要么完全透明,要么完全不透明,它的效果往往像在一个不透明物体上挖了一个空洞。而且。得到的透明效果在边缘处往往参差不齐,有锯齿,这是因为在边界处纹理的透明度的变化精度问题。
透明度测试的双面渲染
Shader "MyShader/AlphaTest"
{
Properties
{
_Color("Main Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
Cull Off
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);
or
//if ((texColor.a - _Cutoff) < 0.0)
//{
// discard;
//}
up
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldNormal));
return fixed4(ambient + diffuse, 1.0);
}
ENDCG
}
}
Fallback "Transparent/Cutout/VertexLit"
}
效果
参考 我买的 unity shader 入门精要