Z赵崇逍Technical Artist / Shader Developer

UE Volumetric Cloud / Custom Cloud Renderer

游戏云与 UE 原生云实现究竟差在哪里

同样的云形,为什么仍会画出不同的光影?

从本机 UE 5.8.1 源码和游戏实际截帧出发,对照密度、步长、光照、缓存与跨帧重建,说明材质能做到哪一步,以及何时需要进入渲染器。

新 UE 工程近景实际单帧当前专用路径 · 近景单帧
原生侧UE 5.8.1 源码与材质接口
游戏侧RDC 实际执行与原生写入
比较重点实现差异 · 视觉影响 · 工程取舍

Technical Comparison

密度交给材质以后 还有哪些环节不同

这次分析体积云,我最想弄清楚的是:同样在 UE 里运行,为什么把云形状还原出来以后,颜色、暗边、穿云细节和动态稳定性仍然不一样?

沿着截帧和代码查下来,区别落在整条渲染路径上。UE 原生云把介质属性交给 Volume 材质,把追踪、光照和重建交给引擎;游戏里的 Cloudly 则让布局距离场同时服务于造型、步进和查询,并配上自己的光照、三维阴影缓存与历史重建。下面以本机 UE 5.8.1 的源码和实际捕获到的游戏配置,逐项说明这些差异。

Shared Background / Implementation Stages

原生积分与专用路径分别对照游戏参考

UE 侧是使用恢复密度并经过灯光校准的高质量静态原型,不是默认云资产;两边统一背景和显示变换。这组图不是等成本性能测试。

UE 原生积分 · 定制密度游戏参考 · 捕获输出

城市来自原帧底图,尚未在 UE 重建。各阶段内部网格、光照与历史处理不完全相同,不能把图像变化归因于某一个开关;动态单帧见正文。

Captured Frame / Resolution Chain

参考帧有两层重建

  1. 01 · 专用云主追踪598 × 336 网格
  2. 02 · 专用云时域升采样1196 × 672
  3. 03 · 场景合成云重建与合成有效 1707 × 960
  4. 04 · UE 后处理TSR 最终输出2560 × 1440

尺寸来自所分析的参考帧,不是所有质量档位的固定规格。低分辨率网格比例不等于 GPU 耗时比例。

01先分清原生云 游戏定制云和当前复现

这里的“UE 原生云”指引擎的 VolumetricCloud 渲染器,不特指某一张默认云材质。UE 没有规定所有云都必须长成同一种噪声。材质可以读取三维纹理、距离场和解析形状,再提供消光、反照率、自发光等介质属性。

“游戏定制云”指 RDC 中实际执行的 Cloudly 追踪、阴影、AO 和重建路径。它仍然运行在 UE 中,但核心云计算没有直接沿用原生 VolumetricCloud 的整套算法。

“当前复现”也经历了两个阶段:最早把恢复的密度放进原生 Volume 材质,后面再把专用追踪和重建接成 RDG 插件。前者适合检验云的形状,后者才开始覆盖原生材质接口之外的行为。

环节 UE 5.8.1 原生云 本次捕获的游戏定制云
云形与介质 Volume 材质定义,可用 SDF 或噪声 Base、Alto、Cirrus 分层,布局 SDF 加多级位移
主视线步进 按交段与预算求基础步长,结合空区跳过 粗追踪区间加 SDF 驱动的自适应步长
相函数 两个 HG 项按 Blend 插值 两个 HG 项取 max,并有动态暗边分支
多次散射近似 材质 octave 控制,公开编辑范围最多 2 个额外项 参考帧使用 4 个贡献项及自己的衰减参数
太阳遮蔽 二次体积 raymarch 或 Beer 阴影贴图 近场有限步追踪加远场级联三维光学深度缓存
环境光 引擎天空输入、材质 AO 和地面贡献等 专用天空 LUT、云 AO 与积分后的亮度补偿
云历史 原生 VolumetricRenderTarget 的重投影与重建 颜色、距离、可见性、Aux 和 Info 五组历史
场景集成 使用引擎组件和材质接口 还需要维护自定义资源、参数与生命周期

这张表描述实现选择。它不代表 UE 缺少对应的大类功能,也不能仅凭这些差异判断哪套一定更快。

02密度都能用 SDF 区别在它参与了哪些计算

原生 UE 云在射线采样位置求值 Volume 材质,再从材质输出中读取介质属性。因此完全可以把游戏的 SDF 解码和密度公式放进去。我最早就是这样做的:先保留引擎积分,只替换“这个位置有没有云、云有多密”。

游戏这一帧的 Base、Alto 使用 512 × 512 × 64 的双通道布局纹理,再叠加两级位移、云底软相交和随高度变化的密度边缘。近处的 Hotsure 细节还会按距离淡出。这些首先属于密度模型,原生材质可以表达其中很大一部分。

真正的区别在消费者。专用路径会继续用距离场决定下一步走多远,用粗追踪得到的区间跳过无云区域,并让查询和阴影使用相应的密度逻辑。若只把最终密度送进材质,原生追踪器并不会自动知道原版距离场的步进规则。

原生材质原型:布局 SDF → 密度 → 消光 → UE 原生积分
游戏专用路径:布局 SDF → 密度、步长、粗区间和查询 → 专用积分

这也解释了为什么原生材质原型能很快接近参考轮廓,却仍然有不同的细节和采样成本。云形接近,不能证明沿射线的采样位置也相同。

把恢复密度放入 UE 原生云后的早期原型
早期原生 UE 云原型。这里已经替换了密度模型,主追踪、光照和重建仍由引擎承担。它是高质量静态预览,不是原生方案的实时性能基准。

03步长和积分不同 薄云与边缘就会不同

本机 UE 5.8.1 的原生主循环,先根据云壳交段长度与采样预算计算样本数,再得到基础步长 StepT。它也能跳过空区域:Conservative Density 可以减少空位置的昂贵材质求值,源码还提供独立的 Empty Space Skipping 路径。

游戏专用路径则先做 CoarseTrace,给出可能含云的区间。主追踪在区间里根据 GetSteppingSDF、视线方向、表面距离、连续空步和最小步长等条件调整前进距离,再叠加蓝噪声。它对“哪里值得多算”有一套与自身云形表示配合的规则。

这里不能简单写成 UE 等步长、游戏变步长,更不能写 UE 没有空跳步。准确的差别是:两者用于决定采样位置的输入与策略不同;游戏直接使用专用 SDF 和粗区间,而原生云通过自己的循环和材质接口工作。

积分方法也不完全相同。原生云的一条主要路径把每段介质视为常量,使用解析积分;游戏恢复的代码包含前后样本的梯形项和边界修正。即便连续密度相同,离散采样位置与积分方法也会改变薄云厚度和边缘透射率。

// UE 原生每段常量介质积分的简化形式
deltaL = T_before * source * (1 - exp(-sigma * stepMeters)) / sigma;

提高采样数能减小一部分离散误差,却不会把一种步进策略变成另一种。这是后面需要进入渲染插件,而不只继续调材质参数的原因之一。

04相函数 暗边和环境光决定了颜色差异

原生云的 SamplePhaseFunction 计算两个 HG 相函数,再按 PhaseBlend 插值。游戏这张参考帧的基础组合则是:

UE:      phase = lerp(HG(mu, g1), HG(mu, g2), blend)
参考帧:  phase = max(HG(mu, 0.2), 0.5 * HG(mu, 0.69))

两条曲线对观察方向与太阳夹角的响应不同。把同样的 g 值填进 UE,再调整 Blend,也不能一般性地得到 max 的结果。原版还启用了根据局部条件变化的 DarkEdge 分支,所以暗边也不是一条固定调色曲线。

多次散射方面,两套都是实时近似。参考帧使用 4 个贡献项;早期 UE 原型用了 2 个额外 octave,共 3 个项。本机原生材质公开的编辑范围是 0 到 2 个额外 octave,但 Shader 的 MSCOUNT 来自宏,不能把这个编辑范围说成 UE 永远不能算更多项。更关键的是,每项如何缩放光学深度、贡献和相函数,仍要逐项对应。

环境光带来的差异更明显。游戏从 DistantSkyLightLut 读取环境输入,经过自己的去色、蓝偏移、强度和云 AO 处理;在整条视线积分完成之后,还会对直射与环境项的合计亮度做补偿。原生云使用引擎天空输入及对应的 AO、云底和地面贡献,并不是同一组公式。

其中“积分完成之后”很关键。Volume 材质是在单个采样点求值,不能直接取得这条射线最后累计的 direct 与 ambient 总和。把环境光塞进 Emissive 可以做近似实验,但不等于恢复了原版的整条射线补偿;再调整曝光,也只会同时影响亮部和暗部。

早期 UE 原生积分在共同背景下的云画面
早期 UE 原生积分结果,使用恢复的定制密度。统一背景以后,云本身的偏色与阴影差异更容易辨认。
UE 原生积分经过灯光校准后的结果
调整实际 UE 灯光与地面贡献后的结果。调参能收敛部分颜色差异,但没有改变相函数、积分与时域算法。

05阴影和 AO 的差别是表示方式与更新方式

原生 UE 云可选二次体积 raymarch,或 Beer cloud shadow map。前者沿光线继续求介质,后者复用阴影贴图;适用距离、颜色细节和成本各有取舍。Epic 的体积云说明也把它们作为两条不同路径介绍。

参考帧的专用方案采用近远分工:近太阳方向最多做 6 步局部追踪,后续距离按自己的规则展开;更远的光学深度来自级联三维纹理,并在前后缓存之间混合。它缓存的是空间中不同高度和位置的遮蔽信息,不能简单等同于切换成 UE 的 Beer 阴影模式。

“6 步”也不是全部阴影成本。三维缓存需要生成、更新和读取;只拿局部步数和 UE 的阴影采样上限比,漏掉的恰好是这套方案最重要的资源更新部分。

游戏的 Cloud AO 也有独立三维数据,主追踪再按位置查询。原生 UE 同样有天空光云 AO 相关路径,但具体表示、生成与采样方式不能仅凭名字视为相同。这里的云体积遮蔽、屏幕空间 AO、Lumen GI,以及太阳自阴影,需要分别核对。

这次噪点在关闭 Lumen 后仍然出现,只能说明 Lumen 不是问题出现的必要条件,不能反过来直接证明动态 AO 就是根因。当前复现默认仍使用部分捕获的云内阴影输入,实验性生成桥接也尚未完整对应原生的更新策略;这一点和原游戏完整动态阴影系统还有距离。

06两套方案都有重建 但保留的历史语义不同

UE 原生 VolumetricRenderTarget 已经有低分辨率追踪、历史重投影和深度相关的重建、上采样。不能把“云先少算一些像素,再重建”写成游戏专有技术。

游戏这张参考帧的网格是主追踪 598 × 336、云时域 1196 × 672,再重建到有效 1707 × 960 场景,最后由 UE TSR 输出 2560 × 1440。因此有两层需要分开:专用云自己的历史重建,以及后面的整帧抗锯齿与升采样。

更具体的差异在中间数据。当前恢复的专用时域阶段有颜色、距离、可见性、Aux 和 Info 五组历史;Aux 记录首次命中云后若干距离段的透射率,后级会用它调整重建。历史拒绝时,还需要让颜色与这些元数据选择同一份深度相容的当前样本。

它们并不是简单多存几张纹理就会更好,而是整套滤波判断依赖这些数据的语义。颜色已经做了历史稳定,控制核半径的元数据如果还来自当前抖动采样,前后两级就可能产生不一致。最后用于显示的空间平滑,也不能随意反馈成下一帧时域历史。

原生云同样面临穿云时的深度近似、遮挡失效与稳定性取舍。本机源码甚至明确说明,近距离关闭部分重投影可以隐藏某些错误,但也会让云更吵。专用云有自己的处理方式,并不表示它不需要在噪点和拖影之间取舍。

复现过程中云暗部和边缘的噪点反馈
复现过程中的颗粒反馈。它用于说明需要检查的动态问题,不作为 UE 原生云的普遍画质结论。

07哪些能靠材质完成 哪些需要改渲染器

第一阶段适合留在材质里做的,是云布局、SDF 解码、位移、密度边缘,以及公开接口能表达的介质与光照参数。我能很快验证轮廓,也能用 UE 自己的质量模式观察云形。这一阶段不需要先接完整专用渲染器。

当目标转向原版的采样位置、相函数组合、整条射线的环境补偿、三维阴影缓存和五组历史时,就超出了仅修改云材质的范围。后来的插件保留 UE 的场景和 RDG 调度,在云的核心阶段使用恢复的专用实现,再接回场景合成。

这会增加工程责任:当前和上一帧相机、曝光、尺寸、场景深度、历史有效条件,以及资源代际都要自己维护。原生方案已经和引擎组件协同;自定义方案需要重新接好这些关系。多了一条能定制的路径,也多了一批需要验证的接口。

例如原生 CPU 写给 CameraSpeedMeter 的实际值是米制帧位移,不是米每秒;捕获的 70% 重建变体也不一定能直接改尺寸当成 100% 使用。Shader 数学公式相同,桥接语义错了,画面仍然会有问题。

接进新工程时,我关闭的是原生云绘制,避免与专用云重复合成。Lumen 和 Nanite 保持开启。当前云组件负责场景中的可见开关,但宿主风速、天气、太阳和动态曝光还没有全部映射到专用参数,不能把“能显示”视作已完成原生组件的全部替代。

08性能优势要用相同目标画质来比较

从算法可以看出,游戏把一部分成本转成可复用数据:粗区间减少空位置工作,SDF 调整步长,远场阴影从缓存查,稀疏屏幕采样交给历史重建。代价则是额外资源、生成流程和跨帧维护。

原生 UE 也有自己的省算路径。比较时,不能一边用全分辨率、高采样的原生诊断模式,另一边用低分辨率和历史缓存,再把帧率差全归到算法。原生的 Mode、阴影路径、密度复杂度、目标误差和镜头运动都要对齐。

目前我没有同一 GPU、相同目标画质和完整缓存更新周期下的双方稳态 GPU 耗时,因此不写“快几倍”。合理的测法是把密度或缓存生成、主追踪、时域重建和合成分开统计,再同时检查静止、转向与穿云时的画面。

低采样不会自然带来稳定画面。原生云或专用云都需要让采样、过滤和重建配合;缓存也必须把新增的生成成本计入平均帧预算。

09当前画面能说明什么 还不能说明什么

目前专用路径已经能在新 UE 工程自由观察。共同背景对照用于看云形与颜色,新工程的实际单帧和连续序列用于看动态稳定性。网页对照中的城市来自同一张原帧底图,并不是已经在 UE 重建的场景。

专用云路径的共同背景诊断结果
专用路径静态诊断图,来自采集中选取的 16 个相位样本线性平均。与原版内部网格并不相同,不能用它替代逐帧的噪点评估。

新工程 1280 × 720 的固定近景与纯 yaw 连续测试中,最终视口异步读回没有复现早期那么强的闪烁;实际查看的观感也改善了。不过纯旋转测试覆盖不了平移视差和穿云,新露出区域的历史失效还需要继续检查。这次增加的最终视口采集工具本身没有修改云去噪算法。

新工程近景静止序列中的实际单帧
新工程近景实际单帧,没有做离线多帧平均。这里使用预览场景自己的曝光,不与上面的诊断图直接比较颜色。
新工程纯 yaw 连续序列中的实际单帧
纯 yaw 序列中的实际单帧。它与上一张分别来自静止和运动序列,不是修复前后的两张图。

如果目标是做一套自己的云,我会先用原生材质验证造型和光照需求;如果目标是贴近这个游戏已经形成的视觉结果,就需要继续对应它的追踪、缓存和重建。当前复现已经走到后一条路径,但天气动画、动态阴影与宿主场景适配还没有闭合。这个工程上的差距,也需要和两种渲染方案本身的差异分开看。

10参考资料

原生实现对照为本机 UE 5.8.1 的 VolumetricCloud.usf、VolumetricRenderTarget.cpp 和材质接口源码;游戏侧以 RDC 实际绑定、预处理 HLSL、资源配置和原生写入证据为准。各阶段画面条件见图注,原游戏图像与资源属于其权利方。