01这里拿来比较的是哪几套东西
下面说的 UE 原生云,是 UE 5.8.1 的 VolumetricCloud 渲染器,不是某个示例关卡里那张云材质。UE 没有规定云只能用哪种噪声,三维贴图、SDF、解析形状都可以放进材质里。
游戏这边,我看的主要是参考截帧里实际跑起来的 Cloudly。它也在 UE 里,但云的追踪、阴影、AO 和重建,用了自己的实现。
我做的复现分了两步。第一步保留 UE 原生渲染,只把恢复出来的密度接进去,先看形状。第二步才把游戏里的追踪和重建接成 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 五组历史 |
| 场景集成 | 使用引擎组件和材质接口 | 还需要维护自定义资源、参数与生命周期 |
两边有不少相同的大方向,具体做法又不一样。下面挑对画面影响比较大的几个地方展开。
02都能用 SDF,为什么搬完密度还不够
UE 会在射线上的采样位置计算 Volume 材质,再读取消光、反照率等属性。所以把游戏的 SDF 解码和密度公式放进去,是能工作的。我第一版只改了“这个位置有没有云、云有多密”,追踪和光照都还用引擎的。
游戏这一帧的 Base、Alto 共用一张 512 × 512 × 64 的双通道布局纹理,上面再加两级位移、云底软相交和随高度变化的边缘。近处还有 Hotsure 细节,远一点就慢慢淡掉。这部分大多能在原生材质里写出来。
但原版拿 SDF 做的事更多。它会用距离场决定下一步走多远,配合粗追踪跳过没有云的区间,查询和阴影也会读这套密度。只把最后算出的密度交给 UE,原生追踪器不会连这些规则一起接过去。
最早的材质版:SDF → 密度 → 消光 → UE 原生积分
游戏里的做法:SDF → 密度、步长、粗区间和查询 → 专用积分
所以第一版的轮廓能很快接近参考,细节和开销却还没跟上。云长得像了,射线在云里怎么取样,仍然是两回事。

03云边上的几步,走法也不一样
我看的这版 UE 源码,会先按射线穿过云层的长度和采样预算求样本数,再算基础步长 StepT。遇到空位置时,Conservative Density 可以帮它少算昂贵的材质,另外还有 Empty Space Skipping 路径。
游戏这边先跑 CoarseTrace,圈出可能有云的区间。进到区间以后,再根据 GetSteppingSDF、视线方向、离表面有多远、已经连续走了多少空步等条件调整步长,最后加上蓝噪声。哪里可以大步走、哪里要放慢,和它自己的云形表示联系得更紧。
UE 也会跳空区,所以我不会把这个区别写成“UE 固定步长,游戏才会自适应”。要看的还是实际用了哪些信息来选采样位置。
连积分也有差别。UE 的一条主要路径把每一小段介质看成常量,做解析积分;恢复出来的游戏代码里,则有前后样本的梯形项和边界修正。薄云、云边本来就没几个有效样本,位置稍微变一点,透射率就可能不同。
// UE 每段常量介质积分的简化写法
deltaL = T_before * source * (1 - exp(-sigma * stepMeters)) / sigma;
加采样能缓解一部分误差,但不会把这两套走法变成同一套。做到这里,光在材质里继续加参数,已经不太够用了。
04颜色差一截,问题不全在曝光
早期最明显的问题就是颜色。云团看着已经挺像,暗部却总有点不对。我一开始也调了灯光和地面贡献,后面看相函数才发现,这里本来就有差别。
UE 的 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 值填进去,再调 Blend,也没法普遍得到 max 的结果。原版还有根据局部条件变化的 DarkEdge,云的暗边会跟着变。
多次散射也得一起看。参考帧用了 4 个贡献项,早期 UE 版本是 2 个额外 octave,加上第一项,一共 3 项。UE 编辑器公开的范围是 0 到 2 个额外 octave,不过 Shader 的 MSCOUNT 来自宏,这不是引擎永远只能算三项的硬限制。除了数量,每一项怎么衰减光学深度和散射贡献,也得对得上。
环境光又多了一层区别。游戏从 DistantSkyLightLut 取值,做去色、蓝偏移和云 AO 处理,等整条视线积分结束后,再对直射加环境光的总亮度做一次补偿。原生云的天空输入、云底和地面贡献走的是另外一套公式。
最后这一步很难只靠 Volume 材质补。材质正在算一个采样点,它拿不到这条射线最终累积的 direct、ambient 总和。往 Emissive 里加环境项可以试效果,但和原版最后那次补偿还不是一回事。继续调曝光,则会把亮部和暗部一起带着走。


05阴影里的六步,省下的工作去了哪里
UE 原生云有二次体积 raymarch 和 Beer cloud shadow map 两条路。一个继续沿光线采样,一个查阴影贴图,距离、细节和成本各有取舍。Epic 的说明里也有这两种效果的对比。
游戏参考帧把远近分开了:近太阳方向最多做 6 步局部追踪,后面的采样距离按自己的规则展开;更远的光学深度直接从级联三维纹理里查,再混合前后缓存。它存的是空间中不同位置和高度的遮蔽信息,和直接切到 UE 的 Beer 阴影模式并不一样。
刚看到“6 步”时,很容易觉得阴影已经很便宜了。但三维缓存也要生成、更新和读取,这些工作只是放到了别处。要比性能,不能只数主追踪里那几步。
Cloud AO 也是类似的思路:游戏有独立三维数据,主追踪按位置查询。UE 也有天空光云 AO,不过同样叫 AO,生成和取样未必相同。云内遮蔽、屏幕空间 AO、Lumen GI 和太阳自阴影,排查时还是要拆开。
这次关了 Lumen,噪点还在,所以还得往下找。光凭这个现象,没法直接认定是动态 AO。现在复现里的云内阴影仍有一部分读的是捕获输入,生成后的数据怎么更新、什么时候切换,还没有全部接回原版的做法。
06云算完以后,还没到最终画面
UE 原生云已经有低分辨率追踪、历史重投影和按深度重建、上采样的流程。这些大方向两边都有,差别藏在每一步用的数据里。
游戏参考帧先在 598 × 336 的网格上主追踪,云时域输出是 1196 × 672,再重建到有效 1707 × 960 的场景。到这里还没结束,后面还有 UE TSR,最终输出 2560 × 1440。云自己的重建和整帧 TSR,不能混在一起看。
恢复的云时域阶段一共留了五组历史:颜色、距离、可见性、Aux 和 Info。Aux 存的是首次命中云之后几段距离里的透射率,后面会拿它调整重建。历史被拒绝时,颜色和这些辅助数据还要选到同一个深度合适的当前样本。
这不是多存几张图就完了。假设颜色已经靠历史稳定下来,决定滤波范围的数据却还跟着当前抖动采样跳,后级一样可能出颗粒。最后显示用的平滑结果,如果又塞回下一帧历史,几帧以后也可能越抹越糊。
原生云也在处理这些取舍。它的源码注释里就提到,近处关掉一部分重投影可以隐藏某些问题,但会让云更吵。换成专用重建以后,噪点和拖影也不会自动消失,还是得看具体镜头。

07为什么最后还是写了渲染插件
只想先把云做出来,我觉得原生材质很好用。布局、SDF 解码、位移、密度边缘,都可以先在这里验证,再用引擎自己的质量模式看效果。我最早就是靠这一版把形状理顺的。
但要继续贴近原版,就得改采样位置、相函数组合、整条射线的环境补偿,还有阴影缓存和那五组历史。这些已经超出了改一张云材质能做的事。后来的插件保留 UE 场景和 RDG 调度,把云的几个核心阶段换成恢复的实现,最后再接回场景合成。
麻烦也跟着来了。前后帧相机、曝光、尺寸、场景深度、历史什么时候能用、缓存什么时候换新,都要自己接好。用原生云时引擎已经做掉的一些事,现在得重新检查。
比如 CameraSpeedMeter,看名字很像速度,追到游戏 CPU 的实际写入才发现,它传的是米制帧位移,没有除时间。再比如捕获的 70% 重建变体,不能只把尺寸改成 100% 就当作同一套算法。公式抄对了,传进去的东西不对,画面还是会出问题。
接进新工程后,我关掉原生云绘制,免得两套云叠着画,Lumen 和 Nanite 都保留。云组件现在能控制这套云显不显示,不过风速、天气、太阳和动态曝光还没全部接上。能在关卡里看到云,离完整替掉原生组件还有一段。
08那到底哪套更快
从代码能看出来,游戏把不少重复计算换成了可以反复查的数据:先圈出粗区间,按 SDF 走步,远处阴影读缓存,再靠历史补回低分辨率采样。省下了一部分当帧计算,也多了纹理、生成流程和跨帧更新。
UE 原生也能省这些方向的开销。真要比较,原生云的 Mode、阴影方式、密度复杂度和最后想要的画质都得说清楚。一边开全分辨率高采样做静态截图,另一边用低分辨率加缓存,得出的帧率差说明不了多少。
目前还没有同一 GPU、相近目标画质,并且算上完整缓存更新周期的双方耗时,我先不给“快几倍”的数字。后面要测,会把生成、主追踪、重建和合成分开,再看静止、转镜头和穿云几种情况。
少采样能省计算,剩下的画面得靠过滤和重建补回来。这个配合做不好,先露出来的就是云边上的颗粒。
09现在做到哪了
现在这套云已经能在新 UE 工程里自由看了。网页上的同背景图主要看云形和颜色,实时单帧和连续序列拿来看转镜头。图里的城市是原帧背景,我没有在 UE 里把它重建出来。

在新工程 1280 × 720 的近景测试里,静止和只转 yaw 的连续画面都没有再出现之前那么强的闪烁,实际看也好了不少。但平移、穿云,以及刚从遮挡后露出来的地方还得继续测。这次补的最终视口采集工具,只是帮我看清输出,没有顺手改去噪算法。


如果是做自己项目里的云,我还是会先用原生材质,把造型和光照需求试清楚。这次想尽量贴近游戏里的效果,才继续往它的追踪、缓存和重建里走。接下来要补的还有天气动画、动态阴影和宿主场景适配,尤其是转镜头和穿云时那些容易露馅的地方。
10参考资料
原生实现对照为本机 UE 5.8.1 的 VolumetricCloud.usf、VolumetricRenderTarget.cpp 和材质接口源码;游戏侧以 RDC 实际绑定、预处理 HLSL、资源配置和原生写入证据为准。各阶段画面条件见图注,原游戏图像与资源属于其权利方。