跳到主要内容

unity shader - 透明效果(ZWrite / ZTest / Blend / Cull)

在 Unity 里实现透明效果时,除了最常见的 颜色 + Alpha 通道 以外, 还有几个经常一起出现、但容易混淆的开关:ZWrite、ZTest、Blend、Cull

这篇笔记主要是把这些概念捋一遍,并给出一个「既有正确遮挡、又有半透明」的两 Pass 写法。


一、透明相关的三个概念

1. Alpha Channel:透明通道

  • 纹理的 A 通道通常用来存放「不透明度」。
  • 在 Fragment Shader 中可以直接读取 color.a 当作透明度因子。
  • 之后可以选择:要么做 透明度测试(Alpha Test),要么做 透明度混合(Alpha Blending)

2. Alpha Test:透明度测试(极端:要么画、要么不画)

透明度测试是一个 二选一 的逻辑:

  • 当透明度小于某个阈值时,直接丢弃当前片元;
  • 否则按普通不透明物体来处理(包括深度测试、深度写入)。

典型写法:

clip(alpha - _Cutoff);

等价于:

if (alpha - _Cutoff < 0)
    discard;

特点:

  • 不需要关闭 ZWriteZWrite On)——因为留下来的片元当作完全不透明渲染;
  • 边缘会比较硬,适合做树叶、栅栏这类「掏空」几何。

3. Alpha Blending:透明度混合(真正的半透明)

透明度混合是我们印象中那种「能看到后面、颜色叠加」的透明:

  • 当前片元的颜色作为「源颜色(SrcColor)」;
  • 帧缓冲里已经存在的颜色是「目标颜色(DstColor)」;
  • 当前片元的 Alpha 作为混合因子。

数学形式一般是:

DstColor_new = SrcAlpha * SrcColor + (1 - SrcAlpha) * DstColor_old

在 Shader Lab 中写成:

Blend SrcAlpha OneMinusSrcAlpha

要点:

  • 做半透明时,通常需要关闭深度写入ZWrite Off
  • 否则先画的透明层会把深度写死,后画的物体就再也画不上来了。

二、ZWrite:深度写入

ZWrite 控制的是:这一片元的深度值是否写入深度缓冲。

  • ZWrite On:渲染时会把当前片元的深度写入深度缓冲。
    之后的片元会跟深度缓冲比较,深度更远的会被挡掉。
  • ZWrite Off:不往深度缓冲写值,只读。
    绘制顺序就变得很重要——通常要依赖渲染队列从远到近排好。

常见搭配:

  • 不透明物体ZWrite On + ZTest LEqual(默认设置);
  • 普通半透明物体ZWrite Off + ZTest LEqual + Blend SrcAlpha OneMinusSrcAlpha

三、ZTest:深度测试

ZTest 控制的是:当前片元的深度值与深度缓冲对比时,用什么规则决定「通过 / 丢弃」。

常见模式:

  • ZTest Less:当前片元深度 小于 缓冲中的值才通过;
  • ZTest Greater:当前片元深度 大于 才通过;
  • ZTest LEqual:小于或等于通过(默认值);
  • ZTest Always:永远通过,不参考深度缓冲。

一般情况下:

  • 默认的 LEqual 就够用了;
  • 特殊效果(例如总是盖在最上层的 UI、描边、遮罩)会用到 Always 或配合自定义深度。

四、问题:半透明 + 正确遮挡怎么一起做?

如果你直接用「半透明写法」:

ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

会遇到一个典型问题:

  • 虽然看起来是透明的,但由于不写入深度,后画的对象可能穿插在前面的透明物体中
  • 如果场景里透明物体很多,仅靠渲染排序很难完全正确。

一个常用的折中方案是:两 Pass 处理

两 Pass 半透明思路

  1. 第一个 Pass

    • 只写入深度,不写颜色:ColorMask 0
    • 开启深度写入:ZWrite On
    • 这样可以先得到一份「逐像素正确的深度信息」。
  2. 第二个 Pass

    • 关闭深度写入:ZWrite Off
    • 开启透明混合:Blend SrcAlpha OneMinusSrcAlpha
    • 开启正常的深度测试:ZTest LEqual

伪代码示例:

// Pass 1:只写深度,不写颜色
Pass
{
    ZWrite On
    ColorMask 0      // 屏蔽 RGBA 输出,只更新深度缓冲
}

// Pass 2:正常半透明
Pass
{
    ZWrite Off
    ZTest LEqual
    Blend SrcAlpha OneMinusSrcAlpha

    // 这里写你的常规顶点 / 片元程序
    // ...
}

这样做的效果:

  • 第一个 Pass 先把透明物体当成「不透明壳」写入深度;
  • 第二个 Pass 在这份深度信息基础上做半透明渲染,其他物体仍然能被正确遮挡。

代价是:

  • 渲染开销翻倍(同一个物体绘制两次);
  • 对复杂场景要评估是否值得。

五、小结

整理一下几个关键点:

  • Alpha Test:二选一(画 or 不画),适合硬边镂空;
  • Alpha Blending:真正的半透明,需要 Blend,通常配合 ZWrite Off
  • ZWrite:决定是否写入深度缓冲,半透明一般关闭,特殊情况用两 Pass 技巧;
  • ZTest:控制深度测试规则,大部分情况下用默认 LEqual 即可。

理解这些基础开关的组合方式, 在写透明 Shader、做遮挡、轮廓、溶解等效果时,会更有底气。


版权声明:本文最初发布于 CSDN「uniGame」,遵循 CC 4.0 BY-SA 协议,转载请附上原文链接及本声明。
原文链接:https://blog.csdn.net/alla_Candy/article/details/121419479