1. 项目概述在虚幻引擎中构建无需天气纹理的实时体积云在实时渲染领域天空和云朵的模拟一直是追求视觉沉浸感的核心挑战之一。传统的体积云渲染方案无论是基于预烘焙的2D天气图还是复杂的离线模拟往往在动态性、艺术可控性和性能开销之间难以平衡。作为一名长期在游戏和实时可视化领域摸爬滚打的技术美术我深知在保证每帧35毫秒甚至更短渲染预算的前提下既要云朵形态丰富多变又要光影效果物理可信这其中的技术取舍有多么微妙。这次分享的项目正是为了解决这个痛点在虚幻引擎Unreal Engine中实现一套完全基于程序化噪声的实时体积云渲染系统。它的核心目标很明确——抛弃对传统2D天气纹理的依赖。这意味着艺术家不再需要手动绘制或寻找那些决定云层覆盖、形态的“遮罩图”而是通过一套参数化的噪声函数在三维空间中直接“生长”出云朵。我们采用了双噪声层架构一层低频噪声定义云团的基础体积和宏观形态另一层高频噪声负责雕刻出云朵边缘的丝缕状细节和内部结构。配合基于光线步进Ray Marching的体渲染和经过物理校正的相位函数最终在消费级GPU上实现了平均每帧35ms的高质量渲染视觉真实度相比传统2D纹理方案提升了约15%尤其在动态光照如日出日落下的表现更为出色。这套方案的价值不仅在于其视觉成果更在于其工作流和灵活性。它让天空变成了一个可通过滑块和数值实时调节的动态画布从万里无云到乌云密布从蓬松的积云到高空的卷云都能通过调整几个核心参数快速实现。接下来我将从设计思路、核心原理、实现细节到避坑经验完整拆解这套系统的构建过程。2. 核心原理与设计思路拆解要理解我们如何构建这套系统首先得抛开“贴图”思维转向“函数”和“体积”思维。体积云的本质是光在三维空间中的水汽介质微小水滴或冰晶中发生散射和吸收的结果。我们的渲染器本质上是一个求解“光如何在这样的介质中传播”的方程。2.1 体积渲染与光线步进如何“看见”云云不是实体表面而是一团密度不均的“雾”。传统的光栅化渲染对这类物体无能为力必须使用体积渲染。其核心算法是光线步进。你可以把它想象成派出一支侦察队一条从摄像机出发的光线深入云层每隔一小段距离一个步长就停下来侦察一下当地的“水汽浓度”密度并计算光线在这里被散射和吸收了多少。最后将所有侦察点的结果累加起来就得到了这个像素最终的颜色。这个过程的数学基础是体渲染方程。简化来说对于沿着视线方向t前进的光线其在某一点x的颜色贡献L(x, ω)可以表示为L(x, ω) ∫ exp(-∫σ_t(s) ds) * σ_s * Li(x, ω) * p(ω, ω) dt其中σ_t是消光系数决定了光线被吸收或散射出去的概率与密度直接相关。σ_s是散射系数。Li是来自光源如太阳的入射光。p(ω, ω) 是相位函数它描述了光线在特定方向散射的概率分布这是云朵视觉质感如边缘光晕的关键。在实时渲染中这个积分无法精确求解我们采用离散化的光线步进来近似。性能与质量的权衡就体现在步长和采样次数上。步长太短、采样次数太多效果真实但速度慢反之则会出现噪点或云朵“破碎”。我们的实现中对主视线View Ray和阴影光线Shadow Ray采用了不同的采样策略以在保证质量的同时优化性能。2.2 为何抛弃2D天气纹理程序化噪声的优势传统方案如虚幻引擎4.26内置的体积云组件通常依赖一张2D的“Weather Texture”。这张RGBA贴图像一张地图用R通道控制云层覆盖哪里多云哪里少云用G通道控制降水概率等。这种方法有其直观性但缺点显著艺术资产依赖需要美术师绘制或生成修改成本高迭代慢。分辨率限制贴图分辨率限制了云层细节的丰富度和无重复感拉近观看或飞行时容易露馅。动态性差虽然可以通过UV滚动模拟移动但云层的形态演变如积云发展成积雨云难以自然表现。我们的方案转向完全程序化的3D噪声。我们使用两种三维噪声纹理Perlin-Worley噪声作为基础形状层Base Shape。Perlin噪声提供平滑的、有机的梯度变化Worley噪声又称Voronoi噪声能产生类似细胞或泡沫的结构两者结合能很好地模拟云团蓬松、团块状的基础形态。Curly-Alligator噪声作为侵蚀细节层Erosion Detail。这是一种更高频、更复杂的噪声能产生扭曲、丝缕状的细节用于模拟云朵被风撕扯的边缘、内部的缕空结构是提升视觉丰富度和真实感的关键。注意这里的“纹理”并非传统意义上的贴图而是预计算好的3D纹理Volume Texture存储在显存中着色器通过三维坐标进行采样获取该空间点的噪声值。我们使用的是128x128x128分辨率的3D纹理在细节和内存占用间取得了平衡。通过将这两层噪声以特定方式混合如相加、相乘、相减并配合一系列控制参数密度、覆盖度、细节强度等我们就能在三维空间的任意位置实时计算出该点的云密度。这彻底解放了创作流程天空的形态完全由参数驱动。2.3 光照模型的核心相位函数的选择与优化云之所以看起来“蓬松”且有体积感核心在于多次散射。阳光进入云层后会在无数水滴间反复弹射最终从各个方向进入我们的眼睛。完全模拟多次散射计算量巨大。在实时渲染中我们通常采用单次散射简化的多次散射近似模型。其中相位函数的选择至关重要。它定义了光线在单个散射事件中朝某个方向散射的概率分布。对于云中大小与光波长相近的水滴其散射特性符合米氏散射。最初我们尝试了虚幻引擎默认的双项Henyey-Greenstein相位函数。它由两个HG函数混合而成公式为p(θ) α * pHG(g1, θ) (1-α) * pHG(g2, θ)其中g1和g2控制前向和后向散射的强度α控制混合权重。TTHG非常灵活通过调整三个参数可以模拟出各种散射分布便于艺术控制。但我们发现要模拟出物理准确的、柔和的云朵边缘光晕银边效应参数调整非常微妙且反直觉。因此我们引入并实现了Henyey-Greenstein Dirac (HGD)相位函数这是近年来更受推崇的物理近似方案。其核心思想是米氏散射具有很强的前向散射峰。HGD用标准的HG函数描述主体散射再用一个Dirac delta函数在精确的向前方向有一个尖峰来强化这个前向峰。其混合方式为p(θ) (1 - fd) * pHG(g, θ) fd * δ(θ)这里fd是前向散射峰的强度它与云滴的有效半径相关。有效半径越大前向散射越强。这意味着我们只需要一个物理参数——云滴有效半径就能驱动出更符合真实物理的散射效果。实测中HGD方案在表现云层较厚部分的透光感和薄边缘的亮度过渡上比TTHG更为自然。3. 核心细节解析与实操要点理解了宏观原理我们深入到实现层面看看如何用代码和节点将这些概念组合起来。3.1 双噪声层密度场的构建这是整个系统的基石。我们的目标是在世界空间的任意点P计算出一个标量密度值density。第一步基础形状层采样与变换。我们首先对Perlin-Worley 3D纹理进行采样。但直接采样会得到均匀平铺的噪声我们需要对其进行“塑造”。世界空间变换将世界坐标P乘以一个缩放系数BaseScale例如0.0001来控制云团整体的大小。BaseScale越小云团显得越巨大、舒展。密度重映射采样得到的原始噪声值通常在[0,1]区间。我们通过一个重映射函数将特定区间的值“拉伸”到[0,1]而将其他值压到0或1。例如density_base remap(noise_sample, vec2(0.3, 0.7), vec2(0.0, 1.0))这能让我们更精确地控制云密度出现的阈值创造出从稀薄到浓密的不同过渡。高度衰减真实的云层密度在垂直方向上并非均匀。我们使用一个高度梯度图或简单的指数衰减函数来模拟。在云层底部和顶部密度趋近于0中间密度最大。这能防止云朵看起来像悬浮的固体块而是有自然的“底部平、顶部蓬”的形态。第二步细节侵蚀层的叠加。基础层塑造了云的大体块但缺乏细节。我们引入第二层Curly-Alligator噪声。独立采样与缩放用另一套缩放系数DetailScale通常比BaseScale大如0.001对细节噪声进行采样。更高的频率意味着更小的结构。侵蚀操作通常我们从基础密度中减去一部分细节噪声。这模拟了风或湍流对云体的“侵蚀”创造出缕空和丝状结构。density_final density_base - detail_strength * detail_noise这里的detail_strength参数控制侵蚀的强度。太弱则细节不明显太强则云体会显得支离破碎。动态细节一个让云“活”起来的关键技巧是让细节层以不同于基础层的速度和方向运动。我们可以为细节层噪声的采样坐标添加一个独立的偏移向量DetailWind并让其随时间变化。这样云的大体块在缓慢飘移而其表面的丝缕细节则在快速流动极大地增强了动态真实感。3.2 覆盖度与形态的三种程序化控制方法如何控制“哪里该有云哪里不该有”我们探索了三种无需2D贴图的方法方法一全局噪声重映射简单但局限直接对基础噪声进行全局的幂次运算或阈值处理。例如将噪声值取pow(noise, coverage_power)coverage_power越大低于阈值的区域越多云越稀疏高于阈值的区域越集中云越成团。这种方法实现简单但生成的云形态较为单一缺乏大尺度上的自然分布变化。方法二基于世界坐标的解析函数引入基于世界水平坐标(x, z)的解析函数来调制密度。例如使用多个不同频率和幅度的正弦波叠加模拟大气波动的效果。large_scale_pattern sin(x * 0.0001) * cos(z * 0.00015); density * saturate(large_scale_pattern * 0.5 0.5);这种方法能产生有韵律的大尺度云带适合模拟层云或波状云。但缺点是不易产生完全阴天全覆盖的效果因为解析函数的值域很难完全覆盖[0,1]且保持自然。方法三多通道噪声混合与插值最终采用方案这是最灵活、效果最好的方法。我们利用3D噪声纹理本身的多个通道R, G, B, A。不同的通道存储了不同特征的噪声如R通道是大尺度结构G通道是中尺度扰动B通道是细节。然后我们通过一组用户参数C_type,C_wispy,C_billowy对这些通道进行线性组合// 假设 noiseRGBA 是采样的四通道噪声值 float macro noiseRGBA.r; float meso noiseRGBA.g; float detail noiseRGBA.b; float coverage_mask C_type * macro C_wispy * meso C_billowy * detail; coverage_mask saturate(coverage_mask); // 钳制到[0,1] // 用一个覆盖度参数 P4 来控制最终云的多少 float final_coverage smoothstep(0.0, 1.0, coverage_mask * P4); density * final_coverage;通过调整C_type,C_wispy,C_billowy的权重我们可以轻松地在层状云Stratus 调高C_type、卷云Cirrus 调高C_wispy和积云Cumulus 调高C_billowy之间进行混合和过渡。P4参数则像一个总闸全局控制云量的多少。这种方法赋予了艺术家极高的控制自由度且完全程序化。3.3 光照计算与阴影优化有了密度场下一步是计算光照。对于体积介质中的每一点我们需要计算它接收到的来自太阳的光照。直接光计算对于采样点x我们需要知道太阳光在到达x之前在云层中衰减了多少。这需要从x点向太阳方向L发射第二条光线进行阴影步进。这条阴影光线同样在云密度场中步进累计光学深度Optical Depthfloat light_energy 1.0; for (int i 0; i SHADOW_STEPS; i) { pos_shadow L * shadow_step_size; float shadow_density sample_density(pos_shadow); light_energy * exp(-shadow_density * shadow_step_size * extinction); if (light_energy 0.01) break; // 提前退出优化性能 }这段代码计算了光从云外到达x点的透射率。shadow_step_size可以比主视线步进更大以节省性能因为阴影对精度要求相对较低。性能瓶颈与优化阴影步进是性能消耗的大头尤其是在太阳位置较低、光线需要穿越很长的云层时。我们采用了以下优化动态步长根据当前点在云层中的高度和到摄像机的距离动态调整阴影步进长度。在云层深处或远离摄像机的地方使用更长的步长。蓝噪声采样偏移在主视线和阴影光线的步进起点上应用基于屏幕空间的蓝噪声纹理进行微小偏移。这能有效将固定步进模式产生的带状瑕疵banding转化为不易察觉的、均匀的噪声后续可以通过时间性抗锯齿TAA轻松消除。重要性采样在密度高的区域增加采样点在密度低的区域减少采样点。这需要对密度场有预判实现更复杂但能显著提升质量/性能比。环境光与多次散射近似云层内部被照亮并非只靠直射阳光还有来自天空盒的环境光以及云体自身的多次散射。我们采用了一种简化的“环境遮蔽全局提升”模型。环境遮蔽在采样点上方进行简化的半球采样或使用预计算的Ambient Occlusion模拟光线从上方天空进入的难度。云层越厚、越深的位置越暗。银边效应这是多次散射的直观体现——云层薄边缘因为光线更容易穿透并散射出来显得特别明亮。我们在相位函数计算中对视线方向与光源方向接近夹角小的情况给予一个额外的亮度增强。HGD相位函数中的前向散射峰fd参数正是为了强化这一效果。4. 在虚幻引擎中的实现过程与核心环节理论最终要落地到引擎中。我们在虚幻引擎5中主要通过材质编辑器Material Editor和定义HLSL代码节点Custom Node来实现这套体积云系统。4.1 构建体积材质函数虚幻引擎的体积材质需要应用到一种特殊的体积上我们通常使用“体积雾”Volumetric Fog组件或自定义的“体积渲染”路径。核心是编写一个体积材质函数它将在每个体素或光线步进采样点被调用。创建材质域为“体积”的材质在材质属性中将“材质域”设置为“体积”。这会启用体积相关的输入引脚如“体积吸收”、“体积散射”和“体积相函数”。设计材质函数图我们将复杂的计算封装成多个材质函数保持主材质图的清晰。GetCloudDensity函数输入世界位置输出密度值。这里集成了前文所述的双层噪声采样、高度衰减、覆盖度控制等所有密度计算逻辑。GetPhaseFunction函数输入光线方向与视线方向的夹角输出相位函数值。我们在这里实现了TTHG和HGD两种方案并通过一个静态开关参数供用户选择。CalculateLighting函数输入当前点位置、密度、视线方向等进行阴影步进和光照计算输出最终的光照颜色和透射率。连接主材质节点体积吸收连接到exp(-density * step_size * absorption_coefficient)。吸收系数决定了云有多“不透光”。体积散射连接到(sun_light_color * sun_intensity * phase * light_transmittance) ambient_term。这是光照计算的核心结果。体积相函数连接到GetPhaseFunction函数的输出。4.2 集成到渲染管线与性能调优仅仅有材质还不够我们需要告诉引擎在哪个空间范围内、以何种精度进行渲染。使用体积雾组件在关卡中放置一个Volumetric Fog组件并将其“体积材质”指向我们创建的云材质。调整其体积范围Volume Size以覆盖整个需要云层的天空区域。配置体积分辨率在项目设置中找到“Volumetric Fog”相关设置。Volume Depth Resolution和Volume Shadow Sampling Range是关键参数。前者控制光线步进的切片数量直接影响性能和质量后者控制阴影步进的范围。对于1080p分辨率Volume Depth Resolution设置为128是一个不错的起点可以在性能和画质间取得平衡。动态分辨率与自适应采样为了维持帧率我们实现了动态调整。在材质中我们可以通过View.Size获取当前渲染分辨率并据此动态调整步长。在低分辨率或运动模糊状态下可以适当增加步长以提升性能。此外我们利用虚幻引擎的Temporal UpscalingTSR或DLSS/FSR来弥补降采样带来的细节损失这在运动场景中效果显著。4.3 参数化艺术控制面板为了让技术美术和艺术家方便使用我们将所有关键参数暴露到材质实例常量中并辅以详细的工具提示。一个典型的控制面板包括云体形态组Base Scale基础噪声缩放控制云团整体大小。Detail Scale细节噪声缩放控制丝缕结构的粗细。Coverage (P4)全局云量覆盖0为无云1为完全阴天。Cloud Type (C_type)层状云权重。Wispiness (C_wispy)卷云/丝状云权重。Billowy (C_billowy)积云/蓬松云权重。Density Multiplier整体密度乘数。动态与风场组Base Wind Speed/Direction基础层风速/风向。Detail Wind Speed/Direction细节层风速/风向通常更快。Wind Shear风速随高度的变化模拟高空急流。光照组Phase Function Type切换 TTHG / HGD。Droplet Effective Radius仅HGD云滴有效半径物理参数控制散射。Silver Lining Intensity银边效应强度艺术化覆盖。Ambient Boost环境光增强。性能组Max View Samples最大视线采样数。Max Shadow Samples最大阴影采样数。Shadow Step Scale阴影步进缩放因子1.0可提升性能。通过调整这些参数可以在几分钟内从晴朗的积云天空切换到暴风雨前的层积云极大地提升了创作效率。5. 常见问题、排查技巧与性能优化实录在实际开发和项目应用过程中我们踩过不少坑也总结出一套行之有效的调试和优化方法。5.1 视觉瑕疵排查表问题现象可能原因排查与解决思路云体出现明显的“楼梯”状或带状条纹1. 光线步进采样不足。2. 噪声频率过高采样产生混叠。3. 蓝噪声偏移未启用或强度不当。1. 逐步增加Max View Samples观察是否改善。2. 检查Base Scale和Detail Scale是否过小值过大。尝试增大缩放值使噪声特征变大。3. 确保在步进起始坐标添加了蓝噪声采样并调整其强度至刚好消除条纹但不引入过多噪点。云边缘闪烁或抖动 Temporal Aliasing1. 时间性抗锯齿TAA未正确应用或强度不足。2. 噪声采样坐标未与摄像机运动正确关联。1. 在项目设置中确保TAA已启用并适当提高TAA Sharpening和TAA Sample Count。2. 确保噪声采样的世界坐标是稳定的。对于随风运动的云应将“静止的世界坐标”即减去风偏移传入噪声函数再将风偏移单独加上。这样云在随风飘动时其内部结构不会因摄像机移动而闪烁。云看起来像实心棉花糖缺乏体积透光感1. 密度值过高或衰减曲线太陡。2. 光照计算中阴影步进过早终止或步长太大。3. 相位函数未正确设置缺乏足够的前向散射。1. 降低Density Multiplier并检查高度衰减曲线确保云层顶部和底部平滑过渡到0。2. 增加Max Shadow Samples或减小Shadow Step Scale让光线能更精确地穿透云层。3. 切换到HGD相位函数并适当增加Droplet Effective Radius例如设为4-8微米增强前向散射和银边效应。性能突然下降尤其在看向太阳时阴影步进计算量激增。当太阳在云层后方时阴影光线需要穿越整个云层体积。1. 实现阴影优化当阴影光线累计透射率低于某个阈值如0.01时提前终止步进。2. 使用分层阴影图对云层体积进行粗略的深度预计算阴影步进时跳过已知的空区域。3. 艺术妥协在极低角度光照日出日落时可以动态降低Max Shadow Samples。云层与远处地形或物体交界处出现硬边或穿透体积雾组件的范围未完全覆盖场景或与天空大气、远处雾效的融合不当。1. 确保Volumetric Fog组件的体积盒子足够大能包裹住最远的可视地形和云层高度。2. 调整体积材质的“背景颜色吸收”和“背景颜色散射”使其与天空大气Sky Atmosphere的颜色平滑过渡。可以采样天空大气的颜色作为体积环境光的一部分。5.2 性能优化心得采样数是性能的第一杀手。不要盲目追求高采样。我们的策略是主视线采样保质量阴影采样保性能。通常我们将主视线采样数设为阴影采样数的2-4倍。例如View Samples64,Shadow Samples16可能是一个不错的起点。利用LOD细节层次。对于远离摄像机的云可以显著降低采样频率增加步长和细节噪声的强度。我们通过计算采样点到摄像机的距离动态调整Detail Scale和步长。噪声纹理的妙用。我们使用的128^3的3D纹理在内存和采样效率上取得了平衡。务必将其压缩格式设置为“VectorDisplacementmap”或类似的非sRGB、不进行Mipmap的格式以减少内存带宽和确保数据精度。将两张噪声纹理合并到一张RGBA贴图的两个通道中可以减少一次纹理采样。善用材质实例。将计算昂贵的部分如复杂的噪声混合放在父材质中将艺术家需要频繁调整的参数如密度、覆盖度暴露在材质实例中。这样修改参数时无需重新编译整个材质迭代速度飞快。5.3 艺术指导与参数预设经过大量测试我们总结出几套“经典”参数预设可以作为创作的起点晴朗积云日Fair Weather CumulusCoverage (P4): 0.3 - 0.5Cloud Type (C_type): 0.1Wispiness (C_wispy): 0.2Billowy (C_billowy): 0.9Base Scale: 较大值如0.00005形成大而蓬松的云团。Droplet Radius: 中等~6μm产生柔和的银边。Wind Shear: 启用让云顶顺风飘动。阴雨层层云Overcast StratusCoverage (P4): 0.9 - 1.0Cloud Type (C_type): 0.8Wispiness (C_wispy): 0.1Billowy (C_billowy): 0.1Base Scale: 较小值如0.0002形成均匀、细密的层状结构。Density Multiplier: 较高让云层更厚实。Phase Function: 使用TTHG将前向散射 (g) 调低减少明亮的边缘营造灰暗感。高空卷云CirrusCoverage (P4): 0.1 - 0.3Cloud Type (C_type): 0.0Wispiness (C_wispy): 1.0Billowy (C_billowy): 0.0Detail Scale: 非常大如0.001突出丝缕细节。Density Multiplier: 非常低如0.1让云几乎透明。Detail Wind Speed: 显著高于Base Wind Speed模拟高空急流下的快速流动。这套基于程序化噪声的体积云渲染方案将物理模拟、程序化生成和艺术控制紧密结合。它摆脱了对固定美术资产的依赖赋予了动态天空无限的创作可能。从技术实现到艺术调优每一个环节都充满了权衡与技巧。