【UE】材质与半透明 - 01. 基于Masked遮罩的抖动半透明 DitherMask

在深入探讨真正的半透明渲染之前,我们有必要先了解一种在游戏引擎中极度高效的"伪半透明"方案:DitherMask 抖动半透明 (在 Unreal Engine 等引擎中也被称为 Dithered Opacity)。


一、 为什么 Masked 能实现半透明?

这种利用不透明材质制造"透明错觉"的魔术,在日常生活中随处可见。例如:地铁的孔洞玻璃广告、家用的纱窗、纱帘等。之所以在工业和建筑中广泛应用,是因为不透明材料的物理稳定性通常远高于半透明材料

在计算机图形学中,我们同样可以利用这个原理。通过控制像素级孔洞的密度,就能模拟出不同的光线透过率,从而在宏观上呈现出半透明的效果。

通过精确控制"网格孔洞"的大小与空间分布,即可完美模拟半透明的视觉变化:


二、 什么是"抖动(Dithering)"?

如果我们在微观(像素尺度)上观察这种渐变过渡,会发现它呈现出明显的颗粒感断层

为了消除这种规律性网格带来的死板感,在 Shader 的实际应用中,我们通常会引入 随机噪声Noise 来打乱孔洞的排列顺序:

核心思路 :通过抖动(Dithering)技术在屏幕空间创建时序随机孔洞 ,并配合 TAA(时序抗锯齿)TSR(超分辨率时序抗锯齿) 等时序复用,在时间上将这些孔洞累积融合,形成丝滑的透明视觉错觉。


三、 何时应该使用 Masked 抖动半透明?

在现代游戏开发中,任何需要使用半透明的场景,都应该首先 尝试 能否用 DitherMask 替代。

甚至可以在水上使用,用非透明材质实现水面透明:

尤其是当物体本身的物理结构就带有颗粒、微孔特征时,DitherMask 是不二之选。例如:水花、气泡、烟雾、纱网、植被叶片等。

下图一些效果都是用DitherMask制作。观察其优势:反射、深度、光照、阴影、排序的正确响应:

关闭抖动累积,可以看到噪点颗粒特征:

完整的光线和阴影支持:


标准的延迟渲染流程:


四、 优缺点全面剖析

由于 DitherMask 在底层管线中本质上仍然属于不透明(Opaque/Masked)材质,它完美避开了传统半透明材质的诸多问题。

优点 👍

  • 完美的延迟渲染支持(光照与阴影)
    传统的半透明材质无法直接写入深度,通常需要在前向渲染(Forward Pass)阶段单独处理光照,这会带来极大的性能开销。而 DitherMask 可以完美享受延迟管线的所有红利,并能正确投射和接收阴影。
  • 无渲染排序(Sorting)问题
    半透明最头疼的就是从后到前的排序问题(Alpha Sorting)。一旦对象互相交错,极易出现穿插错误。
  • 无过度绘制(Overdraw)压力
    半透明渲染会引发严重的 Overdraw,即同一个像素点因为多层透明物重叠而被反复绘制和混合。DitherMask 属于"非黑即白"的二值遮罩,像素要么通过要么裁剪,不会产生像素混合层叠的开销。
  • 原生支持 Nanite 虚拟几何体
    若想开启 Nanite,模型必须 不包含半透明 材质。使用 DitherMask 方案,你无需为了做一块玻璃而把整个建筑模型拆得七零八落,可以直接对整体模型启用 Nanite,同时还能保证内外玻璃的渲染顺序完全正确。

Nanite 车辆案例观察

可以看到即使在极其复杂的车辆内饰与双层玻璃结构下,使用 DitherMask 依然不会出现任何排序穿插错误。


缺点与不足 👎

DitherMask由于其"非黑即白"的底层逻辑,它也存在无法克服的物理局限:

  • 无法完全避免的噪点与颗粒感(依赖时序抗锯齿)
    受限于显示器分辨率,"孔洞"最小的单位就是像素。如果关闭时序抗锯齿(TAA/TSR),材质会暴露明显的"噪点"或"铁丝网感",不够细腻平滑。

为了让模型有完整的光照支持,以及避免半透明性能劣势,很多游戏根本不在乎颗粒感,直接无视。

甚至艺术设计其噪点样式,将其作为风格:

  • 无法实现真正的折射
    折射需要依赖物体表面的法线方向对背景屏幕纹理(SceneColor)进行偏移采样。DitherMask 只是在决定像素"显不显示",无法做到让像素背后的光线发生物理偏转。因此,高精度玻璃、光学透镜等必须依赖真正的半透明管线或复杂的屏幕空间折射算法。
  • 无法进行颜色叠加
    标准的半透明是通过 Alpha 值对源颜色和目标背景色进行加权混合(例如:白光穿过红玻璃再穿过绿玻璃,由于波长过滤会产生特定的色彩杂糅)。
    但 DitherMask 不走渲染管线的混合阶段(Blending Stage),它只有 0 和 1 的遮罩逻辑。它所谓的透明度只是空间分布上的"疏密程度",因此无法实现精确的逐像素多层色彩滤光叠加。

抗锯齿优化对比

在没有特写微观观察的情况下,开启 TAA 或 TSR 可以非常完美地模糊并平滑这些噪点,达到以假乱真的效果。

无论是《黑客帝国:觉醒》Demo 中的高精度车窗玻璃,还是复杂的环境渲染,DitherMask 配合当时的最强时序抗锯齿,都交出了兼顾画面与性能的完美答卷。

五、 几种实现方式

在了解了原理后,我们来看看在引擎中具体如何实现。

首先,我们需要构建一个基础的透明度逻辑(这与传统半透明材质的 Alpha 制作完全一致,将你原本用于透明的输出用于它)。

这里我们使用一个简单的菲涅尔节点作为透明度(Opacity)的输入源进行演示:

在 Masked 模式下,我们将该不透明度信号输入到材质的 Opacity Mask 端口。


1. 启用 颤动不透明蒙版

这是引擎原生的扩展。一般情况下,勾选这个选项,并将原本连入不透明度的输出,连入不透明模板即可:

如果可以,首先使用它来实现。

虽然这个原生的复选框用起来非常方便。但某些情况下不允许勾选这个选项、或者这个选项不适用或者失效。

尤其是遇到 下面这个原因 ,更需要我们手动实现它。

我们首先看看当前版本勾选后发生了什么呢:

HLSL 复制代码
##if MATERIALBLENDING_MASKED
float GetDitheredMaterialMask(FPixelMaterialInputs PixelMaterialInputs, uint2 SvPosition, uint FrameIndex)
{
	#if MATERIAL_DITHER_OPACITY_MASK
	    // New dithering based on Blue noise
		// 注:使用屏幕空间坐标和帧索引,执行 **ViewScalarBlueNoise** 函数获取蓝噪声
		float Noise2  = ViewScalarBlueNoise(SvPosition.xy, FrameIndex);
		
		// This is required to get the same look and empty areas as the legacy one.
		//  注:乘以一个 0.83 的系数,可以和旧版UE的抖动效果保持视觉一致,也就是 **DitherTemporalAA**
		const float LegacyAdjustement = 0.83f; 
		float Dither = LegacyAdjustement * Noise2;

		// 将 **不透明度** 输入加上噪声,再减去0.5
		return GetMaterialMask(PixelMaterialInputs) + Dither - 0.5f;
	#else
		return GetMaterialMask(PixelMaterialInputs);
	#endif
}
#endif

根据代码,我们知道,目前使用的是ViewScalarBlueNoise,并且旧版是DitherTemporalAA

  • 新旧UE,会触发不同的处理方式。

那么接下来,分别介绍这两种实现方式。供读者自由选择。


2. DitherTemporalAA(时序抗锯齿抖动)

按照时间顺序,首先来看 UE4 早期、也是官方文档中演示的实现方式:

2.1 算法:5级静态网格 + 64x64随机噪点

查看 DitherTemporalAA 的算法,你会发现它为了追求极致的性能,并没有在全屏范围内去计算高精度的连续随机噪点,而是通过极低的算力开销,在屏幕空间创造出 5 个固定挡位(阶梯) 的静态棋盘格交错网格:

如果仅靠这 5 个阶梯网格,画面的透明度过渡就会像低色深的漫画网点一样,出现极为严重的色彩断层

因此,官方在算法底层引入了一张内置的 64x64 随机噪点图,利用它去抖动这 5 个阶梯的边界,尝试将断层模糊化。

+
2.2 实际表现与局限性

由于这种快速算法本身的数学特性,在实际渲染时,宏观上会暴露非常明显的特征斜纹

如上图所示,当应用于材质上时,过渡阶梯依然明显。

关闭时序抗锯齿,这种低频的网格感和粗糙的斜纹会非常明显。


3. Blue Noise(蓝噪声抖动)

现代 Shader 越来越倾向于使用**蓝噪声(Blue Noise)**作为抖动源。蓝噪声在频域上的特性是"抑制低频、保留高频",在视觉上表现为颗粒分布极度均匀,几乎没有任何规律性的条纹或聚集色块:

3.1 为什么是蓝噪声

蓝噪声能够有效消除 DitherTemporalAA 方案中的阶梯断层与斜纹,换取更加平滑、连续的渐变过渡

更为重要的是,这些优势使他成为目前主流的噪声选择,这与目前 渲染管线有一致性,这种一致性会带来优势:

  • 与Lumen和光线追踪契合
    Lumen和实时光线追踪在进行蒙特卡洛采样时,底层大量依赖蓝噪声 来进行半随机采样。
    通过抖动将噪点转化为高频的颗粒,从而极其便于接下来的时序 降噪器 进行收敛和滤波。
  • 与 TSR 时序抗锯齿契合
    UE5 的超分辨率时序抗锯齿技术(TSR ),其底层的时序历史复用与采样权重算法,同样是围绕高频蓝噪声的数学分布进行深度优化的。

这样 噪点 在频率上就能与引擎的降噪器、TSR 保持 原生同步

借由抖动和时序降噪在多帧之间的复用,可以弥补蓝噪声产生的颗粒感

3.2 ScalarBlueNoise (TSR更新)

ScalarBlueNoise函数是随 TSR 一起更新出现的,直接和TSR时序绑定抖动,噪点同步变化。

3.2.1 算法

从之前展示的代码可以看到,它的计算是:
Opacity Mask=Opacity+ScalarBlueNoise−0.5\text{Opacity Mask} = \text{Opacity} + \text{ScalarBlueNoise} - 0.5Opacity Mask=Opacity+ScalarBlueNoise−0.5

3.2.2 屎山系数

但只是这样还不够,代码注释提到了0.83 作为对齐旧版视觉的校正系数存在。

那是什么?

依据引擎底层 HLSL 源码的映射算法:

Opacity Mask=Opacity+(0.83×ScalarBlueNoise)−0.5\text{Opacity Mask} = \text{Opacity} + (0.83 \times \text{ScalarBlueNoise}) - 0.5Opacity Mask=Opacity+(0.83×ScalarBlueNoise)−0.5

回头看看DitherTemporalAA的这里:

Custom 节点里写的是Mod(..., 5)。取模 5 的整数输出结果只能是 0 - 4,在加上噪点的0 - 1,最大值应为5

为了绝对安全和视觉上的修正,代码作者没有选择除以5,而是选择除以魔法 6( = 1/6 ≈ × 0.16665),这回输出的最大范围压到:

5×0.16665=0.833255 \times 0.16665 = 0.833255×0.16665=0.83325

我想他一定有他的道理,这就是0.833修正系数的由来。

不过既然我们根本不是DitherTemporalAA,那么我们也完全不需要考虑兼容旧版,使用这个修正。

或者根据我们需要 另行修正 也是可以的。

3.2.3 不透明蒙版剪切值

这里存在一个不透明蒙版剪切值

它的作用是决定像素是"完全显示"还是"完全隐藏",例如默认状态下当Mask输入小于 0.3333 则直接透明。

魔法值 0.16665 肯定也是考虑到它的存在

既然手工实现,我们需要一个干净的基底。

我们现在有两种选择:

  1. 保持默认设置,将透明度最低值映射到0.3333,我也不知道这样做谁会幸福:

  2. 将剪裁值修改为0,这样就可以了:

3.3 没有 ScalarBlueNoise

忘记了 TSR 是 UE5 哪个版本更新的,在此之前是没有ScalarBlueNoise 的,因此接下来就需要手搓一个 抖动的 BlueNoise

3.3.1 算法
  • 搜索ViewProperty节点,选择临时采样索引

  • 在编辑器中搜索FastBlueNoise,得到BlueNoise128x128x64 贴图,实际上分辨率为128x8192,旁边还有一个更小的可以用。

这部分就是Noise了

参考

老样子,最后附上一些案例参考



动漫量







相关推荐
草木深雨纷纷2 小时前
我的世界基岩版手机版(光影材质包大全)下载国际服集合下载分享
游戏·智能手机·游戏程序·材质
晴夏。5 小时前
UE5 motion warping 运动扭曲的用途
运维·ue5
蓝图大法5 小时前
ue5 血条 渲染方形的分辨率 血条缩放的问题
ue5
邪修king10 小时前
UE5 进阶篇第一弹:中期架构升级 —— 组件化开发与 Gameplay 框架实战
c++·游戏·架构·ue5
Heaphaestus,RC1 天前
Slate到UMG的封装原理揭秘
开发语言·ue5
XX風1 天前
OpenGL 着色器语言特性
着色器
归真仙人2 天前
【UE】VR一体机转场
ue5·ue4·vr·虚幻引擎·unreal engine
洋洋06172 天前
UE4/UE5 引擎常见面试题总结(1)
ue5·ue4
weixin_409383122 天前
cocos 3d粒子 让粒子能换成黑色不透明 复制默认材质后改blend state deepseek告诉我的
3d·材质·cocos