在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