Unity风格化云朵Shader实现:从噪声叠加到动态天气系统

📅 2026/8/5 16:11:02
Unity风格化云朵Shader实现:从噪声叠加到动态天气系统
1. 项目概述为什么我们需要一个风格化的云朵Shader在Unity项目里尤其是那些追求独特视觉风格的游戏或交互应用中天空盒往往是奠定整体氛围基调的关键。一个写实的天空盒固然不错但对于卡通渲染、低多边形风格、或是带有强烈艺术感的独立游戏来说写实的云朵反而会显得格格不入。这就是“风格化渲染”的价值所在——它不追求物理上的绝对真实而是追求艺术表达上的和谐与美感。“UnlitClouds”这个项目从名字就能看出它的核心一个不依赖复杂光照计算的、专注于表现风格化云朵的Shader。对于很多开发者特别是独立开发者或技术美术TA新手来说这是一个极具吸引力的起点。市面上的云渲染方案无论是基于体积雾Volumetric Fog的实时体积云还是使用3D噪声函数模拟的复杂Shader其性能和实现门槛都相当高。而UnlitClouds的思路则非常直接它通常基于片元着色器Fragment Shader利用多层2D噪声纹理如Perlin Noise或Worley Noise进行叠加、扭曲和混合通过简单的颜色和透明度控制在屏幕空间或模型表面比如一个面片或球体渲染出具有动态效果的风格化云层。它的核心价值在于轻量、可控、易定制。你不需要理解复杂的体积光照散射Rayleigh/Mie Scattering模型也不用担心性能被拖垮。通过调整几个直观的参数——比如噪声的缩放Scale、滚动速度Speed、颜色渐变Gradient和边缘柔和度——你就能快速创造出从《塞尔达传说旷野之息》那种清新的卡通云朵到带有蒸汽波Vaporwave美学色彩的梦幻云霞。这对于快速原型开发、风格化项目甚至是作为更复杂体积云效果的性能后备方案如移动端都极具实用意义。2. 核心思路与方案设计拆解一个风格化云Shader的骨架要理解UnlitClouds我们可以把它想象成在画布上“画”云。这个画布可以是一个覆盖整个天空的球体Skybox也可以是场景中漂浮的多个面片Billboard。Shader的工作就是告诉GPU如何在这个画布上通过计算生成云朵的图案。2.1 核心算法多层噪声叠加这是所有程序化云朵包括风格化的基石。单一层的噪声纹理看起来过于规整和“假”。真实的云即使是风格化的其边缘应该是破碎、不规则且有层次感的。基础噪声层使用一张或多次采样Perlin噪声纹理定义云朵大致的形状和分布。通过调整UV的缩放_CloudScale可以控制云朵的尺寸值越小云朵显得越庞大、稀疏值越大云朵越细碎、密集。细节噪声层使用另一张缩放比例更大即更“高频”的噪声纹理叠加在基础层之上。这一层通常会和基础层进行某种形式的混合如相乘multiply或屏幕混合screen用于添加云朵内部的细节和纹理让云看起来不那么“平”。扭曲与动态为了让云“动”起来我们需要让噪声“流动”。最常见的方法是让噪声纹理的UV坐标随时间_Time.y偏移。更高级的做法是使用一张流动图Flow Map或另一层低频噪声来扭曲高频噪声的UV坐标模拟云层被风吹拂时那种缓慢变形、流动的感觉这比简单的平移要自然得多。注意噪声纹理的选择至关重要。Unity自带的Noise/Perlin 2D节点在Shader Graph中很方便但有时自定义的Worley噪声细胞噪声能产生更符合某些艺术风格的、块状或絮状的云朵形态。在实际项目中我通常会准备几套不同的噪声纹理组合根据项目风格进行A/B测试。2.2 着色与透明度塑造云的“体感”云是半透明的而且不同部位的厚度不同透光性也不同。在Unlit无光照模型中我们通过颜色和透明度Alpha来模拟这种体感。基于噪声值的遮罩我们将多层混合后的最终噪声值视为一个“密度”图。值越高的地方代表云越“厚”越“实”值越低的地方代表云越“薄”越“透”甚至完全消失。重映射与平滑原始的噪声值范围通常在[0,1]。我们可以通过一个Remap节点将其映射到新的范围比如[0.2, 0.8]。然后通过一个Smoothstep函数来处理这个值。Smoothstep是风格化渲染的“魔法函数”之一它能将输入值进行平滑的阶梯化处理。例如Smoothstep(_EdgeSoftness, 1.0, noiseValue)。这里_EdgeSoftness控制着从“无云”到“有云”过渡区域的柔和程度。值越小云朵边缘越锐利值越大云朵边缘越模糊、越蓬松。这个操作直接决定了云朵的视觉风格是偏向硬朗的卡通剪影还是柔软的棉花糖。颜色与透明度输出颜色Color通常不是简单的纯白色。我们可以根据噪声值或一个独立的时间变量来采样一个颜色渐变纹理Gradient。例如让云朵底部颜色偏灰蓝模拟阴影或环境光遮蔽顶部偏亮白。对于夕阳场景可以采样从橙红到紫红的渐变。透明度Alpha经过Smoothstep处理后的噪声值直接作为透明度输出。值高的地方不透明值低的地方透明。为了实现更逼真的半透明边缘有时会将这个值进行幂运算Power让透明度变化更非线性。2.3 方案选型Shader Graph vs 手写HLSL这是实现UnlitClouds时面临的首要选择。Shader Graph可视化着色器编辑器优势直观、快速迭代、无需深厚Shader编程基础。节点式的操作让参数调整和逻辑可视化非常适合技术美术和初学者快速搭建和调试效果。Unity官方支持良好社区资源丰富。劣势生成的代码可能不够优化对于极其复杂的逻辑如循环、自定义函数支持有限调试深层问题不如代码直接。适用场景快速原型、风格相对固定、团队中TA主导视觉效果的项目。对于UnlitClouds这种逻辑相对线性的ShaderShader Graph完全能够胜任且效率很高。手写HLSL/CG代码优势性能控制精准灵活性极高可以实现任何复杂算法。便于代码复用、模块化管理适合构建自己的Shader库。劣势学习曲线陡峭调试困难迭代速度慢。适用场景对性能有极致要求如移动端、需要高度定制化算法、或作为团队核心渲染管线的一部分。我的建议对于大多数寻求风格化云朵解决方案的开发者从Shader Graph开始是绝佳选择。你可以先通过节点连接快速实现核心效果理解数据流动。如果后期发现性能瓶颈或需要更复杂功能再将其中关键部分用Custom Function节点替换为手写HLSL代码。UnlitClouds项目本身很可能就是一个用Shader Graph构建的、参数暴露完善的.unitypackage或示例场景开箱即用。3. 关键参数深度解析与实操调优拿到一个UnlitClouds Shader后面对一堆参数可能会不知所措。下面我以常见的参数为例拆解它们的具体影响和联动调节技巧。3.1 控制云朵形态的核心参数参数名示例功能描述调节技巧与视觉影响_BaseScale/_DetailScale控制基础噪声层和细节噪声层的UV缩放。_BaseScale这是云朵的“大局观”。调小如0.1云朵巨大、绵延适合远景天空盒调大如1.0云朵细碎、密集适合近景云团。_DetailScale通常设为_BaseScale的2-4倍用于添加小尺度细节。两者差异越大云朵的层次感越强。_CloudSpeed控制噪声UV偏移的速度制造云层飘动动画。通常是一个二维向量Vector2对应UV的X和Y方向。值很小如(0.01, 0.005)。技巧让X和Y速度略有不同避免过于机械的直线运动。可以连接一个基于时间的正弦波到速度上制造风速变化的感觉。_DensityThreshold密度阈值。低于此值的噪声区域将被视为“无云”完全透明。这是控制云量多少的最直接参数。提高阈值云朵变少、变稀疏降低阈值云朵变多、连成一片。通常配合_EdgeSoftness一起调节。_EdgeSoftness控制云朵边缘的柔和度/羽化程度。这是风格化的关键设为0附近云朵边缘锐利像剪纸或卡通画设为0.3以上边缘非常柔和像水彩或雾气。实操心得在Shader Graph中这个值通常作为Smoothstep函数的第一个输入参数。你可以将其暴露为一个Slider0, 0.5。3.2 控制颜色与外观的参数参数名示例功能描述调节技巧与视觉影响_ColorGradient一个渐变纹理Texture2D或Gradient资产用于根据云密度映射颜色。这是实现艺术风格化的利器。例如1.卡通风格使用2-3个色阶的硬过渡渐变。2.梦幻风格使用从淡紫到粉红再到亮黄的柔和渐变。3.阴沉风格使用从深灰到中灰的渐变。注意在Shader Graph中采样Gradient Texture时需要将噪声值或重映射后的值从[0,1]映射到UV的U方向通常水平方向。_Brightness/_Contrast整体亮度和对比度调节。在最终颜色输出前进行调节。提高对比度可以让云朵的亮部和暗部区分更明显体感更强。这比单纯调节颜色倍增更有效。_Opacity全局透明度乘数。用于快速调整整个云层的淡入淡出效果常用于场景切换或天气变化。它应乘在由噪声计算出的局部透明度之上。3.3 高级效果深度交融与天空盒适配一个常见的需求是让云朵和场景中的物体如山、建筑产生正确的遮挡关系即远处的云应该在山后面。深度测试Depth Test在Shader的渲染设置中确保深度测试ZTest设置为LEqual默认。这样云朵面片只有在比场景物体更近时才会被绘制。深度交融Depth Fade这是实现云朵边缘与场景物体平滑融合的关键技巧。Shader Graph中有一个Depth Fade节点。它的原理是计算当前片元与深度缓冲区中对应位置场景物体的距离差。当云朵非常靠近一个物体表面时让它的透明度逐渐降低为0从而产生“融入”背景的效果避免生硬的穿插。参数_FadeDistance控制交融的范围。值越大云朵在物体周围“消散”的范围越广。实操将Depth Fade节点的输出值一个0到1的系数1代表完全可见0代表完全消融与之前计算出的云朵透明度相乘作为最终的Alpha输出。天空盒集成如果你把UnlitClouds Shader用在天空盒材质上需要注意天空盒默认是渲染在无限远处的深度测试可能不按预期工作。一种更灵活的做法是不直接修改天空盒材质而是创建一个独立的、巨大的半球体或球体模型贴上UnlitClouds材质并放置在摄像机远裁剪面之内。这样可以更方便地控制云层的高度、旋转和动态效果也更容易与场景中的其他雾效、后期处理如调色配合。4. 在Unity中实现UnlitClouds的完整流程假设我们使用Shader Graph来创建一个最基础的风格化云朵Shader并应用到天空球体上。4.1 资源准备与场景搭建创建Shader Graph在Project窗口中右键 - Create - Shader - Universal Render Pipeline - Unlit Shader Graph如果你的项目是URP。命名为SG_UnlitClouds。准备噪声纹理你可以使用Shader Graph自带的Procedural Noise节点但为了更好的控制和性能我建议使用预烘焙的2D噪声纹理。可以去一些免费资源网站如OpenGameArt搜索“Perlin Noise Texture”或“Cloud Noise”下载几张灰度图。导入Unity后确保它们的Wrap Mode设置为RepeatFilter Mode设置为Bilinear或Trilinear以便无缝平铺和平滑采样。创建材质右键刚才创建的Shader Graph - Create - Material。命名为Mat_StylizedClouds。创建天空球体在Hierarchy中创建一个Sphere球体Scale放大到(500,500,500)左右使其远大于你的场景。将其Mesh Renderer的材质指定为Mat_StylizedClouds。关键步骤由于球体内外都能看到我们需要只渲染背面否则会看到云在球体内侧。创建一个新的Shader Graph或修改现有Shader在Graph Inspector中找到Surface Options-Render Face设置为Back只渲染背面。或者更简单的方法在材质球上勾选Double Sided双面渲染并确保球体法线朝外默认就是。4.2 Shader Graph节点搭建详解打开SG_UnlitClouds。我们将构建一个基础的双层噪声云。创建属性Properties_BaseNoiseTex(Texture2D): 基础噪声纹理。_DetailNoiseTex(Texture2D): 细节噪声纹理。_BaseScale(Vector1): 默认值0.05。_DetailScale(Vector1): 默认值0.2。_CloudSpeed(Vector2): 默认值(0.01, 0.005)。_DensityThreshold(Vector1): Slider(0, 1)默认值0.2。_EdgeSoftness(Vector1): Slider(0, 0.5)默认值0.1。_ColorGradient(Texture2D): 一张1x256的渐变纹理。_Opacity(Vector1): Slider(0, 1)默认值1。构建主图Master GraphUV与滚动使用Time节点获取_Time.y与_CloudSpeed相乘得到一个二维的偏移向量。将其与UV节点的输出相加得到滚动的UV。分别乘以_BaseScale和_DetailScale得到两套缩放后的UV。采样噪声用两套UV分别去采样_BaseNoiseTex和_DetailNoiseTex得到两个灰度值R通道即可。噪声混合将_DetailNoise乘以一个较小的系数如0.3然后与_BaseNoise相加Add节点。这会让细节噪声作为扰动叠加到基础噪声上。也可以使用Multiply效果更强烈。密度与平滑将混合后的噪声值输入到一个Remap节点从(0,1)重映射到(-0.5, 1.5)。这个范围可以手动调整目的是为了后续的Smoothstep有更大的操作空间。然后将重映射后的值输入Smoothstep节点第一个输入端口Edge1连接_DensityThreshold第二个输入端口Edge2连接_DensityThreshold加上_EdgeSoftness。输出结果我们称为cloudMask这是一个在[0,1]范围内的、边缘平滑的云遮罩。颜色映射将cloudMask作为U坐标通常需要乘以一个小的系数如0.99避免采样到纹理边缘V坐标设为0.5去采样_ColorGradient纹理得到颜色。透明度计算将cloudMask与_Opacity相乘得到最终的Alpha值。连接主节点将计算出的颜色连接到Master Stack的Color端口Alpha值连接到Alpha端口。在Master Stack的Surface配置中将Blend Mode设置为AlphaSrcAlpha,OneMinusSrcAlpha这样云朵才能正确半透明。4.3 材质调试与场景适配实时调试将材质赋给天空球体后在Scene视图或Game视图就能看到效果。选中材质球在Inspector中实时拖动各个参数滑块观察变化。这是Shader Graph最大的优势。解决常见视觉问题云朵有接缝检查噪声纹理的导入设置Wrap Mode必须是Repeat。检查UV计算中是否有不连续的操作。云朵边缘闪烁Z-fighting这是因为云朵面片和远处背景或天空盒深度值过于接近。可以轻微地将球体向摄像机方向移动一点或者为Shader添加一个很小的Depth Offset在Master Stack的Advanced设置中。云朵太“平”尝试添加“视差Parallax”效果。简单做法是根据视角向量和噪声值轻微地偏移UV采样位置模拟云层的体积感。但这会增加一些复杂度。性能考虑如果云层覆盖全屏且持续滚动对移动端可能构成压力。可以考虑1) 降低噪声纹理分辨率2) 减少噪声层数从两层减为一层3) 在远处使用更低精度的LOD材质。5. 进阶技巧与性能优化实战当基础效果满足后我们可以通过一些技巧让云朵更生动并确保它在不同平台上都能流畅运行。5.1 实现动态天气与昼夜变化一个静态的云层是乏味的。我们可以通过脚本动态控制Shader参数来模拟天气变化。// CloudController.cs using UnityEngine; public class CloudController : MonoBehaviour { public Material cloudMaterial; // 指向云朵材质 public float weatherChangeDuration 10.0f; // 天气变化时长 private float targetDensity; private float currentDensity; private float timer; void Start() { if (cloudMaterial null) cloudMaterial GetComponentMeshRenderer().material; currentDensity cloudMaterial.GetFloat(_DensityThreshold); targetDensity Random.Range(0.1f, 0.6f); // 随机一个目标云量 } void Update() { timer Time.deltaTime; float lerpFactor timer / weatherChangeDuration; // 平滑过渡云量 float newDensity Mathf.Lerp(currentDensity, targetDensity, lerpFactor); cloudMaterial.SetFloat(_DensityThreshold, newDensity); // 也可以同时改变颜色渐变、速度等 // cloudMaterial.SetColor(_TintColor, Color.Lerp(dayColor, nightColor, lerpFactor)); if (lerpFactor 1.0f) { // 一次变化结束设定新的目标 currentDensity targetDensity; targetDensity Random.Range(0.1f, 0.6f); timer 0f; } } }将这个脚本挂载到天空球体上并拖入材质引用。它会让云量在设定的范围内周期性随机变化模拟云层聚散。5.2 移动端与性能敏感平台优化风格化云朵本身已经很轻量但在低端设备上仍需精打细算。纹理压缩与尺寸将噪声纹理和渐变纹理的Max Size设置为512甚至256压缩格式使用ASTC或ETC2移动端。对于渐变纹理1x128或1x64的分辨率通常就足够了。简化Shader减少纹理采样尝试只用一层高质量的噪声纹理通过不同的UV缩放和数学运算如Fract,Sin衍生出细节。降低计算精度在Shader Graph的节点属性中可以将某些计算节点的Precision从Float改为Half这在移动GPU上可能更快。移除不必要的效果如果深度交融Depth Fade不是必须的可以移除Scene Depth节点的采样这是一个相对耗时的操作。使用LOD细节层次为云朵材质创建两个变体一个高质量High Quality用于PC/主机一个低质量Low Quality用于移动端。可以通过脚本来根据平台或质量设置动态切换材质。void AdjustCloudQuality() { Material targetMat; #if UNITY_IOS || UNITY_ANDROID targetMat mobileCloudMaterial; // 预分配好的低配材质 #else targetMat standardCloudMaterial; #endif GetComponentMeshRenderer().material targetMat; }批处理与合批如果你的场景中有多个独立的云朵面片Billboard确保它们使用相同的材质并且材质上没有开启GPU Instancing禁止的属性如不同的纹理。这样可以促进动态批处理或GPU Instancing大幅降低Draw Call。5.3 与Unity渲染管线URP/HDRP的集成URP通用渲染管线上文创建的Unlit Shader Graph就是为URP设计的兼容性最好。确保你的项目使用的是URP并且在Graphics Settings中指定了正确的URP Asset。URP的后期处理如Bloom, Color Grading可以很好地增强云朵的视觉效果。HDRP高清渲染管线HDRP对Shader有更高要求。你需要创建HDRP Unlit Shader Graph。HDRP的优势在于可以更轻松地与体积雾、物理天空系统结合实现更统一的视觉体验。但HDRP本身的性能开销更大通常不用于移动端或风格化项目。内置渲染管线Built-in虽然也可以用但Shader Graph的支持不如URP/HDRP原生。你可能需要手动编写Surface Shader或Unlit Shader。对于风格化云朵内置管线的简单Unlit Shader加上纹理动画也是一个稳定高效的选择。6. 常见问题排查与调试心得在实际操作中你肯定会遇到各种“坑”。这里记录了一些典型问题和我自己的解决思路。6.1 云朵完全不显示或显示为纯色检查渲染队列Render Queue和混合模式这是最常见的原因。确保材质的渲染队列是Transparent3000并且混合模式Blend Mode是Alpha BlendSrcAlpha, OneMinusSrcAlpha。如果设置为Opaque透明度通道会被忽略。检查Alpha输出在Shader Graph中确保最终连接到Master Stack的Alpha端口的值不是常数0或1。用一个Split节点检查你的cloudMask值是否在合理范围0到1之间。可以临时将Color端口也连接到cloudMask灰度显示看看云朵形状是否正常先排除颜色映射的问题。检查模型和法线确认你使用的模型如球体足够大并且摄像机在模型内部如果只渲染背面。检查模型的Mesh Renderer组件是否被禁用或材质球是否被错误覆盖。6.2 云朵边缘闪烁或锯齿严重深度冲突Z-fighting如前所述调整物体的位置或使用Depth Offset。在Shader Graph的Master Stack-Depth-Offset中尝试将Factor和Unit设置为很小的正值如0.01和0。纹理过滤与Mipmap确保噪声纹理的导入设置中Generate Mip Maps是勾选的并且Filter Mode不是Point无过滤。Bilinear或Trilinear可以提供更平滑的采样结果减少移动时的锯齿。抗锯齿Anti-aliasing在项目质量设置或URP Asset中开启MSAA或FXAA等后处理抗锯齿可以平滑云朵边缘。6.3 性能开销过大使用Frame Debugger或Profiler定位打开Window - Analysis - Profiler查看GPU耗时。找到渲染你云朵的Pass看其耗时是否异常。Frame Debugger可以查看具体的Draw Call和渲染状态。检查纹理采样次数在Shader Graph中每一个Sample Texture 2D节点都是一个纹理采样指令。尽量减少采样次数考虑复用采样结果。检查复杂的数学运算Pow、Sin、Noise节点相对昂贵。避免在片元着色器中无节制地使用。如果某些计算在顶点间变化不大可以尝试移到顶点着色器阶段在Shader Graph中使用Custom Function节点并指定Stage为Vertex。6.4 动态效果不自然UV动画生硬简单的UV偏移UV Time * Speed是线性的看起来机械。可以尝试使用两个不同速度的层叠加在一起。使用Time的正弦或余弦值来调制速度制造“阵风”效果Speed * (1.0 0.3 * sin(Time * 0.5))。使用一张流动纹理Flow Texture来扭曲主噪声的UV模拟更复杂的风场。颜色变化突兀直接切换ColorGradient纹理会显得很跳。可以通过脚本使用两个材质属性如_ColorGradientA和_ColorGradientB以及一个混合系数_LerpFactor在Shader中进行插值实现平滑的昼夜或天气色调过渡。最后风格化渲染没有绝对的对错只有是否适合你的项目。UnlitClouds项目提供了一个强大而灵活的起点但最美的云朵永远来自于你对参数的反复调试和艺术感觉的注入。多参考优秀的游戏和艺术作品思考它们的云朵在形状、颜色、动态上有什么特点然后大胆地去尝试用Shader Graph复现。这个过程本身就是技术美术成长中最有乐趣的部分。