片元输出与渲染状态:Blend/ZWrite/ZTest/Cull 速查
片元程序算出颜色只是故事的一半,颜色怎么写进帧缓冲由 Pass 里的渲染状态决定。Blend、ZWrite、ZTest、Cull 四个状态的正确组合,就是透明、玻璃、描边、全息这些效果能不能成立的分界线。这篇给完整速查表和一个可跑的透明 shader。
状态在哪写
写在 Pass 内、CGPROGRAM 外(URP 里写在 Pass 的 RenderState {} 块里,语义相同):
Pass
{
Tags { "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest LEqual
Cull Back
// ... CGPROGRAM
}
四个状态速查
Cull(背面剔除,原理见 /notes/unity/principles-3/)
| 值 | 含义 | 典型用途 |
|---|---|---|
Back(默认) | 剔背面 | 绝大多数 |
Front | 只画背面 | 倒模描边(见 /notes/unity/effects-3/) |
Off | 双面都画 | 单面片(草地、粒子)、双面布料 |
ZTest:LEqual(默认,近者胜)、Always(无视深度,UI/描边覆盖用,会破坏 early-Z)、Greater(反深度,做「被遮挡部分显示」)。
ZWrite:On(默认,写入深度)/ Off(不写深度)。透明必须 Off,否则透明像素会挡住后面的透明。
Blend(源因子 目标因子):
| 写法 | 效果 | 用途 |
|---|---|---|
不写(默认 One Zero) | 直接覆盖 | 不透明 |
SrcAlpha OneMinusSrcAlpha | 标准 alpha 混合 | 玻璃、溶解、粒子 |
One OneMinusSrcAlpha | 预乘 alpha 混合 | 特效常用(导出纹理已预乘) |
One One | 相加 | 火焰、光晕、激光 |
DstColor Zero | 相乘 | 阴影、暗化 |
SrcAlpha One | 只加不扣底 | 亮部叠加 |
一个完整的透明 shader
Shader "MyGame/TransparentBasic"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Alpha ("Alpha", Range(0,1)) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct Attributes { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct V2F { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; };
sampler2D _MainTex; float4 _MainTex_ST; float _Alpha;
V2F vert(Attributes v) {
V2F o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(V2F i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
c.a *= _Alpha;
return c;
}
ENDCG
}
}
}
三个要点缺一不可:Queue = Transparent(进透明队列,从远到近画)、Blend(叠加方式)、ZWrite Off(不污染深度)。
为什么绘制顺序影响结果
不透明靠深度决胜,画序无关。透明靠混合,混合是「与已画好的颜色叠加」——所以 Unity 按 Queue 值分队列(Geometry 2000 → AlphaTest 2450 → Transparent 3000 → Overlay 4000,可写 Geometry+100 微调),透明队列内再按包围盒中心到摄像机的距离从远到近排。两个透明物体互相穿插时,无论怎么排都必有一帧错——这是排序毛刺的根源,缓解手段见 /notes/unity/performance-3/。
深度测试对透明仍然生效(读深度):玻璃会被先画的不透明墙挡住;但玻璃之间不互相遮挡(都不写深度),靠混合叠加——这就是「透明物体互相穿帮」的准确解释。
状态组合的常见配方
- 标准透明:
Queue Transparent+Blend SrcAlpha OneMinusSrcAlpha+ZWrite Off。 - 自遮挡透明(玻璃想有正确内外层次):两个 Pass——先
Cull Front只画内壁写颜色不写深度,再Cull Back画外壁;或用预排序。 - 全息/扫描线:
One One加法 +ZWrite Off,边缘菲涅尔见 /notes/unity/effects-2/。 - 描边覆盖一切:
ZTest Always+ 靠后 Queue——代价是破坏 early-Z,移动端慎用,见 /notes/unity/performance-1/。
自检三问
- 透明物体为什么既不能
ZWrite On,又不能ZTest Always? Blend One One的火焰材质,alpha 通道还有意义吗?- 两个互相穿插的粒子系统透明排序错乱,根因是什么?(提示:排序依据)