Unity ShaderGraph 可视化构建行星大气层:从瑞利散射到菲涅尔效应 1. 项目概述从“一个球”到“一颗星球”在游戏开发、影视特效甚至一些数据可视化项目中创造一个逼真的行星从来不只是建模一个球体那么简单。决定一颗星球视觉上是否“活”起来、是否拥有“灵魂”的关键往往在于它周围那层若隐若现、包裹着整个星球的大气层。无论是地球的蓝色光晕还是火星的橙红色薄纱大气层赋予了星球深度、体积感和真实的环境互动。过去实现这种效果通常需要编写复杂的着色器代码对美术和TA技术美术的门槛都不低。但现在借助Unity的ShaderGraph我们可以用节点化的、可视化的方式像搭积木一样构建出令人惊叹的行星大气效果。这个项目就是带你一步步拆解这个“积木”的搭建过程。我们将完全在ShaderGraph中操作不写一行代码从零开始实现一个包含瑞利散射Rayleigh Scattering近似、基于视角的菲涅尔效应Fresnel Effect以及可自定义颜色和密度的动态行星大气着色器。最终效果是一个在太空背景中自转的星球其边缘会因为视角关系呈现出明亮的大气光晕而面向恒星太阳的一面则会因为光线穿透大气层而产生内发光效果。这不仅是一个Shader教学更是一套可以直接用到你的太空场景、科幻游戏或者概念艺术中的生产级解决方案。2. 核心原理拆解光线如何与大气“玩耍”在动手连接节点之前我们必须先搞明白想要模拟的物理现象是什么。行星大气看起来复杂但其核心视觉原理可以简化为三个关键点散射、深度和边缘光。2.1 瑞利散射天空为什么是蓝色的这是我们效果的核心。瑞利散射描述了光线穿过介质如大气时被比波长小得多的粒子如空气分子散射的现象。其特点是散射强度与波长的四次方成反比。这意味着短波长的蓝光比长波长的红光更容易被散射到各个方向。在Shader中我们无法完全实时模拟复杂的物理计算但可以采用一个经典的、视觉上可信的近似模型大气密度随高度指数衰减散射强度与视线穿过大气的距离相关。简单来说当我们的视线从摄像机到星球表面某点穿过大气层时穿过的路径越长遇到的大气粒子就越多散射现象就越明显。因此在星球边缘视线几乎与大气层相切我们看到的大气最厚颜色也最浓。这个“视线穿过大气的距离”就是我们后面要计算的关键数据。2.2 菲涅尔效应为什么边缘更亮菲涅尔效应描述了物体表面反射率随观察角度变化的现象。对于绝缘体如大气这种气体介质在视线与表面法线夹角越大即掠射角看边缘时反射或者说在这里是散射光的出射会越强。在大气着色中我们利用这一点来强化边缘光晕。即使不考虑复杂的散射计算仅仅在球体表面应用一个基于视角的菲涅尔也能立刻让球体产生“被一层发光外壳包裹”的初步感觉。我们将把菲涅尔效应作为控制大气透明度的一个主要乘数。2.3 深度与内发光大气是有厚度的一个真实的大气层不是贴在星球表面的贴纸它是一个有体积的球壳。我们需要在着色器中体现出这个“厚度”。这通常通过计算两个值来实现到星球表面的距离视线与星球球体的交点。到大气层外边界或内边界的距离视线与一个比星球半径稍大的球体的交点。这两者之间的差值就代表了视线在“大气体积”中穿行的距离。这个距离值将直接用于计算散射的强度。同时当光线从星球背后即背对光源的方向照射过来时光线会穿透相对较薄的大气层在星球边缘形成一个明亮的“逆光”光晕这就是内发光效果的来源。3. ShaderGraph 环境准备与核心节点解析接下来我们进入Unity的ShaderGraph。建议使用Unity 2019.4 LTS或更高版本并确保安装了Universal RPURP或High Definition RPHDRP因为ShaderGraph是内置在这些可编程渲染管线中的。3.1 创建与基础设置在Project窗口中右键 - Create - Shader - Universal Render Pipeline - Shader Graph。我们将创建一个Unlit Shader Graph因为大气是自发光效果不需要复杂的光照模型。创建好后打开Graph窗口。首先关注几个关键的主节点Position节点获取当前像素在物体空间Object Space或世界空间World Space的位置。计算距离时世界空间更直观。View Direction节点获取从当前像素指向摄像机的方向向量在URP中通常是世界空间下的。这是计算菲涅尔效应的关键。Normal节点获取当前像素的法线方向通常使用Object Space的即可因为我们是对一个球体操作。Sphere Mask节点这是我们的“秘密武器”。它可以根据一个中心点和半径计算输入位置相对于该球形区域的遮罩强度。我们将用它来模拟大气密度衰减。3.2 构建基础球体与菲涅尔光晕让我们先搭建最基础的视觉效果——一个带有边缘光晕的球。建立菲涅尔Fresnel添加一个Fresnel Effect节点。将Normal连接到Normal端口View Direction连接到View Dir端口。Power参数控制菲涅尔效应的衰减强度。值越大光晕越集中在边缘。可以从5.0开始尝试。此时将Fresnel节点的输出直接连接到主节点的Color你就能在预览球上看到一个从中心黑到边缘白的渐变效果。这就是大气光晕的雏形。赋予颜色添加一个Color节点选择一个你喜欢的天空蓝或行星大气色例如地球可用#87CEEB。用一个Multiply节点将颜色与菲涅尔强度相乘。这样边缘光晕就带上了颜色。注意此时的菲涅尔是基于模型表面法线的这对于一个完美的球体没问题。但如果你的星球表面有起伏如地形直接用表面法线计算菲涅尔会导致地形轮廓破坏大气光晕的平滑性。一个高级技巧是我们可以单独计算一个“球形法线”即从星球中心指向表面点的方向用它来代替实际模型法线进行菲涅尔计算这样无论地表如何起伏大气光晕始终保持完美的球形。这可以通过将Position世界空间减去星球中心坐标通常是模型原点然后Normalize来实现。4. 实现体积感从“壳”到“层”仅有边缘光晕看起来像个发光的壳没有体积感。接下来我们要模拟视线穿过大气体积的效果。4.1 计算视线大气穿透距离这是最关键的一步。我们需要计算从摄像机到星球表面点这段视线在大气层内穿行的长度。定义星球与大气层假设我们的星球模型半径是R_planet例如0.5。大气层外半径是R_atmosphere例如0.55。这个厚度可以暴露为一个Vector1属性方便调节。星球中心C在物体空间下通常是(0,0,0)。射线与球体相交算法在ShaderGraph中实现完整的射线-球体相交计算节点较多但我们可以利用一个巧妙的近似。我们的目标是得到一个值在星球边缘视线相切时最大在星球中心视线垂直穿透时较小在星球背面视线未击中星球时为0或另一个值。一个高效的近似方案使用Dot点积和Sphere Mask的组合。计算视线方向V与表面点指向球心的方向L的点积。Dot(V, L)。当视线看向星球边缘时V和L接近垂直点积接近0。当视线看向星球中心时V和L方向相反点积接近-1。我们可以用1 - saturate(-Dot(V, L))来得到一个从中心值小到边缘值大的因子。saturate将值限制在0-1。为了更精确地控制“大气厚度”的感觉我们将这个因子与一个基于距离的衰减结合。使用Sphere Mask节点。输入Position世界空间。中心Center设为星球中心的世界坐标如果星球移动可能需要通过脚本传递。半径Radius设为大气外半径R_atmosphere。Hardness参数非常关键它控制从半径边界向内衰减的硬度。设置一个较高的值如10使得在半径边界处衰减非常剧烈。这样Sphere Mask的输出在星球模型内部接近1在大气层外接近0。我们用1 - SphereMaskOutput来得到一个“大气密度”图在星球表面处密度最高随着向外远离星球而指数衰减。组合最终穿透因子将上面计算得到的“边缘因子”和“大气密度”相乘FresnelFactor * AtmosphereDensity。这个乘积结果我们称之为AtmosphereThickness。它综合反映了观察角度和空间位置对大气视觉效果的影响在星球边缘且位于大气层内时值最大。4.2 应用散射颜色与强度现在我们用这个AtmosphereThickness因子来控制大气的最终表现。颜色与强度控制添加两个Color属性比如_InnerColor内层颜色通常较深和_OuterColor外层/边缘颜色通常较亮。使用Lerp线性插值节点用AtmosphereThickness作为插值因子在_InnerColor和_OuterColor之间进行插值。这样大气厚的边缘就呈现外层亮色薄的中心区域呈现内层深色。再添加一个_Intensity强度属性与AtmosphereThickness相乘用于全局调节大气发光的亮度。与星球表面混合我们的星球本身应该有纹理如陆地、海洋。我们需要将大气效果与星球表面纹理混合。采样星球纹理得到表面颜色SurfaceColor。计算大气颜色AtmosphereColor LerpedColor * _Intensity * AtmosphereThickness。混合方式通常采用加法混合Additive或屏幕混合Screen。在ShaderGraph中你可以直接将SurfaceColor和AtmosphereColor用Add节点相加。加法混合能很好地模拟发光、半透明叠加的效果。将混合结果连接到主节点的Color。主节点的Alpha通道可以设置为一个由AtmosphereThickness控制的值用于半透明渲染或者如果不需要与背景混合可以设为1。5. 高级优化与动态效果基础效果完成后我们可以让它更生动、性能更好。5.1 模拟日夜交替与内发光一个静态的大气层是单调的。我们可以模拟星球自转带来的日夜变化以及背光面的内发光。光源方向添加一个Vector3属性_LightDirection来表示恒星主光源的方向。在Shader中计算表面点法线N与光源方向L的点积dot(N, L)。这个值在向阳面为正背阳面为负。背光面内发光当dot(N, L) 0时表示该点在背阳面。我们可以增强背阳面边缘的大气效果。思路是在背阳面使用一个更强的菲涅尔或厚度因子。计算backlitFactor saturate(-dot(N, L))在背阳面这个值大于0。用backlitFactor去增强我们之前计算的AtmosphereThickness例如FinalThickness AtmosphereThickness * (1 backlitFactor * _BacklitStrength)。_BacklitStrength是一个可调节的属性。这样在星球背对光源的边缘会出现一道明亮的“逆光”光晕视觉效果立刻提升一个档次。5.2 性能优化技巧着色器可能会用于整个星球优化很重要。简化计算尽可能在SubGraph中封装复杂的节点网络如“计算大气厚度”可以做成一个子图使主图更清晰且子图可复用。避免在片段着色器Fragment Shader中进行过于复杂的循环或分支。我们的方案基本是数学运算性能很好。LOD细节层次为星球材质创建两个Shader变体一个高精度的使用完整的散射和菲涅尔计算一个低精度的可能只使用简单的菲涅尔和颜色。在Unity的LOD Group组件中根据摄像机距离切换不同的材质或调节材质的参数如降低_Intensity或简化颜色过渡。针对移动平台减少纹理采样次数。如果星球纹理和大气颜色计算可以合并或简化就尽量去做。降低Sphere Mask节点的Hardness值过高的硬度可能需要更多计算。考虑将部分计算如基于光源方向的因子放到顶点着色器Vertex Shader中计算然后插值到片段着色器。在ShaderGraph中可以通过选择节点的Precision为Half来节省带宽和计算量。6. 常见问题与调试实录在实际操作中你肯定会遇到一些预期之外的效果。这里记录几个我踩过的坑和解决方法。6.1 问题大气光晕在场景中看起来不自然像贴图可能原因与排查缺乏深度交互检查你的AtmosphereThickness计算是否真的与视角相关。确保View Direction节点被正确使用并且计算中包含了与视线相关的因子如我们之前用的点积方法。颜色过纯直接使用一个纯色会让效果像卡通画。尝试用Lerp混合两个或三个颜色。添加一个非常微弱的、基于世界空间位置或UV的Noise节点强度设置到0.05左右与大气颜色相乘模拟大气中细微的不均匀性。菲涅尔过强降低Fresnel Effect节点的Power值或者将它的输出通过一个Power节点再处理一次让过渡更柔和。6.2 问题星球旋转时大气效果抖动或闪烁可能原因与排查精度问题尤其是在世界空间进行计算时当星球远离世界原点可能会产生浮点数精度误差。尝试将计算转换到模型局部空间Object Space进行。Position节点选择Object空间View Direction可能需要通过变换矩阵转换到物体空间。使用Precision为Float而不是Half但要注意性能开销。Z-Fighting如果大气效果使用半透明渲染且与星球表面网格非常接近可能会产生深度冲突。确保星球表面材质的渲染队列Render Queue在大气材质之前例如表面是Geometry大气是Transparent。6.3 问题在特定角度下大气完全消失或变得全黑可能原因与排查数学计算溢出检查所有Dot操作和Saturate节点的使用。确保在计算1 - saturate(x)这类操作时x的值域是合理的。有时候需要先Clamp再计算。法线方向错误确认用于计算菲涅尔的法线方向是否正确。对于球体物体空间法线是向外的。如果你使用了自定义的“球形法线”确保其计算正确从中心指向表面点并归一化。Sphere Mask 中心错误如果星球在移动Sphere Mask节点的Center必须使用星球中心的世界坐标而不能是固定的(0,0,0)。这通常需要通过脚本将Transform.position传递给Shader的Vector3属性。6.4 效果微调参数表把关键参数暴露为材质属性在Inspector窗口中实时调节是找到最佳视觉效果的最高效方式。下表是一个参考属性名 (示例)功能描述典型初始值调节效果_AtmosphereRadius大气层外半径相对于星球模型半径1.1值越大大气层视觉上越“厚”越膨胀。_AtmosphereFalloff大气密度衰减硬度对应Sphere Mask的Hardness10.0值越大大气层边界越锐利值越小边界越模糊、弥散。_FresnelPower菲涅尔边缘光衰减强度5.0值越大光晕越集中在极边缘值越小光晕向中心扩散。_InnerColor大气层内侧/薄处的颜色深蓝色影响星球中心区域大气色调。_OuterColor大气层外侧/厚处的颜色浅蓝/白色影响星球边缘光晕的颜色。_GlobalIntensity大气效果全局强度1.0整体调亮或调暗大气效果。_BacklitIntensity背光面逆光内发光强度2.0值越大星球背对光源的边缘光晕越亮。_NoiseStrength大气不均匀性噪声强度0.05为大气添加微弱的动态纹理避免颜色过于平滑。最后别忘了将完成的ShaderGraph保存创建一个新的材质球并使用它然后拖拽到你的星球模型上。在Scene窗口中旋转视角调整光源方向实时地微调上面表格中的参数直到获得最令你满意的、仿佛在纪录片中看到的那个包裹着脆弱蓝白色光晕的星球。这个过程本身就是技术美术工作中最具创造力和成就感的部分。