片元输出与渲染状态:Blend/ZWrite/ZTest/Cull 速查

Unity 知识 · Shader 区域 · 语言与结构 · 第 3 篇 更新于 2026-10-15

片元程序算出颜色只是故事的一半,颜色怎么写进帧缓冲由 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 通道还有意义吗?
  • 两个互相穿插的粒子系统透明排序错乱,根因是什么?(提示:排序依据)