来源:《UNITY SHADER入门精要》
1、透明度测试
透明度测试(Alpha Test),这出来的效果不是真的透明,而更像是会使得物体的一部分消失。通过设置一个 Alpha 通道的阈值,不满足条件的片元就直接舍弃,这样,一个物体可能就不会完全的显示。
2、透明度混合
透明度混合(Alpha Blending),这种方法可以得到真正的半透明效果,它会使用当前已有的片元颜色 和 透明度值 进行混合,得到新的颜色。
开启了透明度混合就一定要关闭深度写入,所以必须注意渲染顺序,否则会让透明的物体不会因为在前面而遮挡致使不透明的物体不画。同时,对于透明混合来说,深度缓冲只是只读的。
为了使得半透明的效果正常,我们会对物体的渲染顺序进行排序,然后再渲染:
(1)先渲染所有不透明的物体,此时开启他们的深度测试和深度写入。
(2)对于半透明的物体,按照离摄像机的远近进行排序,然后从后往前渲染这些半透明的物体,此时开启它们的深度测试,但是关闭深度写入。
3、UnityShader的渲染队列
为了解决渲染顺序问题,Unity 提供了**渲染队列(Render Queue)**这一解决方案。我们可以使用 SubShader Tags 设置为 AlphaTest
或者 Transparent
来决定我们的模型处于哪个渲染队列。序号越小,越早被渲染。例子:
SubShader{
Tags { "Queue" = "Transparent" }
Pass {
ZWrite Off
//..
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOqe8yzR-1663118270470)(assets/image-20220624172932425.png)]
4、半透明测试的代码解读
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
为了减少负担,我们这里不再进行光照的计算,仅仅是采样贴图纹理,然后设置 Alpha Test 的阈值。
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
这里的 Tags 设置的比较多,第一个,Queue 设置为 AlphaTest,指明了这个 SubShader 使用了透明度测试。
第二个,我们设置 IgnoreProjector 为 True。意味着这个 Shader 不会受到 投影器(Projectors)的影响,也就是说,不会被其他物体打上阴影。
第三个,RenderType可以把这个 SubShader 设置提前定义的组中,这里是 TransparentCutout 组中。
最后,我们在 Pass 中定义了 LightMode 的方式为 ForwardBase。这样,我们定义了这个 Pass 在光照流水管线中的角色。只有定义了正确的 LightMode,我们才能正确的到一些 Unity 的内置光照变量,例如 _LightColor0
。
通常我们进行 透明度测试 的 Shader 都应该在 SubShader 中设置这三个标签。
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);
// Alpha test
clip (texColor.a - _Cutoff);
// Equal to
// 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);
}
与之前不一样的地方是:这里没有计算高光,这里用 clip()
裁剪之后的 texColor
来计算反射率。
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}