Unity星球渲染实战:从PBR材质到多层Shader实现月球与地球

📅 2026/7/28 7:11:59 👁️ 阅读次数 📝 编程学习
Unity星球渲染实战:从PBR材质到多层Shader实现月球与地球

1. 项目概述:从“贴图”到“世界”的材质探索

在Unity里做一个球,然后贴上月球或者地球的图片,这大概是每个3D开发新手都尝试过的操作。但当你真的把那个粗糙的、毫无生气的球体摆在面前时,你会发现,这离你想象中的那颗星球,差了十万八千里。它没有晨昏线的柔和过渡,没有云层的立体流动,没有从太空俯瞰时大陆板块的质感,更没有在自转中光影变幻的生命力。这个名为“探索宇宙:Unity中的月球与地球材质样本项目”的初衷,就是解决这个“看起来不像”的核心问题。它不是一个简单的贴图展示,而是一套完整的、可复现的材质解决方案,旨在教会开发者如何用Unity的Shader和渲染管线,将一张张平面的图片,转化为一个有深度、有故事、有科学依据的虚拟天体。

这个项目适合所有对Unity图形渲染感兴趣的人,无论你是刚入门想做出更酷炫效果的新手,还是已经有一定基础、希望深入理解PBR(基于物理的渲染)流程和Shader编程的中级开发者。通过拆解月球和地球这两个极具代表性的案例,你将掌握一套从纹理处理、Shader编写到后期调优的完整方法论。月球代表了无大气、高反照率、表面布满撞击坑的岩石星球;地球则涵盖了海洋、大气、云层、城市灯光等复杂系统的融合渲染。搞定这两个“样板工程”,你就能举一反三,去创造火星的锈红色荒漠、木星的气态条纹,乃至任何你想象中的外星世界。

2. 核心设计思路:基于物理的差异化渲染策略

这个项目的核心思路,是摒弃“一刀切”的材质方法,为月球和地球设计两套独立的、基于其真实物理特性的渲染方案。这不仅仅是换张贴图那么简单,而是从Shader结构、纹理输入到渲染状态的全方位定制。

2.1 月球材质的设计哲学:极致还原表面微观细节

月球没有大气层,这意味着它的渲染完全依赖于其表面物质(月壤)与太阳光的直接相互作用。核心挑战在于如何表现其高光反射的粗糙感宏观与微观的明暗对比(高反差),以及缺乏大气散射导致的“死黑”阴影

我的设计策略是采用增强型标准PBR工作流。基础色贴图(Albedo)必须是一张经过严格去色的灰度图,因为月球的颜色信息非常单一。法线贴图(Normal Map)至关重要,它负责提供陨石坑和山脉的立体感。但仅此还不够,我引入了高度贴图(Height Map)与视差遮蔽映射(Parallax Occlusion Mapping)技术。这不是为了真正的几何位移,而是为了在视角变化时,让陨石坑的边缘产生正确的视觉遮挡和光线变化,这是实现“沉浸感”的关键一步。

在Shader中,我会着重调整粗糙度贴图(Roughness Map)。月表的粗糙度并非均匀的,向阳面由于太阳风轰击和微陨石撞击,会比背阳面更“磨砂”一些。通过一张精心绘制的粗糙度图,可以让阳光照射下的月面呈现出一种细腻的、非金属的漫反射光泽,而阴影区域则几乎完全吸光。环境光遮蔽(AO)贴图在这里强度可以适当加大,以强化撞击坑内部的深度感。

2.2 地球材质的设计哲学:多层动态系统的融合

地球是一个动态的、多层的复杂系统。其材质渲染需要模拟至少四个层面的视觉效果:静态地表(陆地与海洋底色)动态云层大气散射光环,以及夜间城市灯光。这要求一个完全不同于月球的、多层叠加的Shader架构

我的方案是构建一个多通道混合Shader。基础层是标准的陆地海洋PBR材质,使用彩色Albedo贴图、法线贴图表现地形起伏。关键在于第二层:云层。云层不能直接用一张贴图贴在球面上,那样会显得很假。我采用了两张无缝的、流动速度不同的云图进行混合,并利用顶点动画或UV偏移在Shader中驱动其缓慢旋转,模拟全球尺度的大气环流。云层需要有半透明效果,并且其透明度应随昼夜变化——夜晚背阳面云层可视度降低。

最出彩也最复杂的是第三层:大气散射。这是让地球“活”过来的灵魂。我并没有使用昂贵的体积渲染,而是采用了一种屏幕后处理与顶点着色器结合的简化方案。在球体的外围,渲染一个略大的、半透明的“大气层”球壳。在其Shader中,根据顶点位置与相机和光源(太阳)的方向,计算并模拟瑞利散射(Rayleigh Scattering,产生蓝色)和米氏散射(Mie Scattering,产生晨昏线的红色辉光)。这样,在太空视角,你会看到地球边缘有一圈漂亮的蓝色光晕,而在昼夜交界线处,则有温暖的红色过渡。

夜间灯光层则相对简单,使用一张标记了城市亮区的黑白贴图,在球体背向太阳的一面(通过Shader中的点积计算判断)叠加自发光(Emission)即可。

2.3 工具链选型与资源准备

工欲善其事,必先利其器。一套高效的工具链能事半功倍。

  1. 纹理来源与处理

    • NASA/ESA等机构:这是最权威、最科学的纹理数据来源。例如,NASA的Blue Marble项目提供了极其精美地球纹理。对于月球,可以寻找LRO(月球勘测轨道飞行器)拍摄的高清图像。
    • 处理软件PhotoshopGIMP是必备的,用于调整色阶、去色、拼接。对于法线贴图生成,CrazyBumpMaterializeSubstance Designer是专业选择。这里我强烈推荐开源免费的Materialize,它可以从一张高度图或普通图片一键生成法线、粗糙度、AO等全套PBR贴图,对月球表面这种高度依赖法线细节的材质非常友好。
  2. Unity版本与渲染管线选择

    • Unity版本:建议使用2021 LTS或更新版本,以获得稳定的Shader Graph和渲染管线支持。
    • 渲染管线:对于这个学习型项目,内置渲染管线(Built-in RP)依然是很好的起点,资料最多,Shader编写直观。如果你想追求更现代化的效果和更好的性能,通用渲染管线(URP)是最佳选择。URP的Shader Graph可视化编程,能让你更直观地搭建地球的多层材质混合逻辑。本项目后续的示例将主要以URP为基础,因为它是未来趋势。
  3. 辅助资产

    • 天空盒:一个深邃的、带星点的HDR天空盒(如“AllSky”免费资源包)是营造宇宙氛围的基石。
    • 光源:一个方向光(Directional Light)模拟太阳。务必使用平行光,并开启阴影。通过旋转这个光源,就能直观地看到月球和地球上光影的移动,模拟昼夜交替。

注意:在下载和使用NASA等机构的科学数据时,要注意其分辨率可能极高(如8192x4096)。在Unity项目中,需要根据性能预算将其压缩为合适的尺寸(如2048x1024)。同时,确保纹理导入设置正确,特别是法线贴图类型要设置为“Normal map”,sRGB(颜色纹理)选项要根据贴图类型正确开关。

3. 月球材质样本的深度实现

让我们先从相对简单的月球开始,将设计思路转化为具体的Unity工程。

3.1 纹理集的制作与导入

一套标准的月球PBR纹理集应包含:Albedo(反照率)、Normal(法线)、Height(高度)、Roughness(粗糙度)、Ambient Occlusion(环境光遮蔽)。你可以从NASA的网站下载到月球的灰度地形图(可作为高度图基础)。

  1. 生成Albedo贴图:将下载的灰度图在Photoshop中打开,使用“色相/饱和度”工具将饱和度降至-100,得到纯粹的黑白图。然后轻微调整曲线,增加对比度,让环形山和月海的明暗区别更明显。最后保存为JPG或PNG格式。
  2. 生成法线贴图:在Materialize中,将处理好的高度图(或直接用Albedo图)拖入“Height”槽。软件会自动计算出法线贴图。你可以调整“Strength”参数来控制凹凸的强度。对于月球,一个中等偏高的强度(如0.7)比较合适,能突出陨石坑的陡峭边缘。
  3. 生成粗糙度贴图:在Materialize的“Roughness”标签页下,通常可以直接从高度图生成。月球的粗糙度整体很高,但可以手动用画笔工具在向阳面(根据你的光源方向假设)稍微画得亮一点(表示更光滑),背阳面保持深色(更粗糙),以增加视觉变化。
  4. 导入Unity:将制作好的纹理导入Unity项目。关键步骤:
    • 选中Albedo贴图,在Inspector中,确保“Texture Type”为“Default”,勾选“sRGB (Color Texture)”。
    • 选中法线贴图,“Texture Type”必须选择“Normal map”,Unity会自动为其进行正确的压缩和解码。
    • 高度图和粗糙度图通常设置为“Single Channel”的R通道,并关闭“sRGB (Color Texture)”,因为它们存储的是数值数据而非颜色信息。

3.2 URP Shader Graph的搭建

在URP中,我们使用Shader Graph来可视化地创建月球材质。

  1. 创建Shader Graph:右键点击Project窗口 -> Create -> Shader -> Universal Render Pipeline -> Shader Graph。
  2. 构建主节点网络
    • 纹理采样:创建四个Sample Texture 2D节点,分别连接到你的Albedo、Normal、Roughness和Height贴图。将它们的UV端口连接到一个统一的UV节点。
    • 视差效果:为了实现简单的视差,将Height贴图的采样输出(通常取R通道)连接到一个Parallax Mapping节点。这个节点需要View Dir (Tangent Space)Height作为输入。将View Dir连接到摄像机方向向量(需转换到切线空间),Height输入一个缩放系数(如0.02)。然后将Parallax节点输出的UV偏移量,叠加到基础的UV上,再输入给其他纹理采样节点。这一步是让表面细节“动起来”的灵魂。
    • PBR主节点:找到PBR Master节点(或URP版本中的Lit Master)。将Albedo贴图连接到Albedo,法线贴图连接到Normal,粗糙度贴图连接到RoughnessMetallic保持为0(月球是非金属)。Occlusion可以连接AO贴图。
    • 高光调整:月球几乎没有镜面反射,但有一种特殊的“逆反射”特性,即光线会部分沿原路返回。为了模拟这一点,可以在Smoothness(光滑度,与粗糙度相反)输入上,用一个很小的值(如0.1)和一个Multiply节点与粗糙度图进行混合,确保即使最“光滑”的地方也只是略带光泽。

3.3 材质球配置与场景调试

  1. 创建材质:将制作好的Shader Graph保存,然后基于它创建一个新的材质球,命名为“Mat_Moon”。
  2. 赋予模型:在场景中创建一个Sphere(球体),将“Mat_Moon”材质拖给它。将球体缩放调整到合适大小。
  3. 光源与场景设置
    • 创建一个Directional Light,旋转其角度,模拟太阳光照射。观察月球表面的明暗变化。
    • 调整主光源的强度、颜色(偏冷白色)。
    • 为了突出月球的立体感,可以适当添加一个微弱的、蓝色的环境光(在Window -> Rendering -> Lighting -> Environment中设置),模拟地球反射的“地照”,但这部分光强度必须非常低,以免冲淡主体阴影。
  4. 调试技巧
    • 在Scene视图中,使用Shader的Debug模式,可以单独查看法线、粗糙度等通道的效果,确保纹理正确加载。
    • 拖动光源,观察陨石坑内部的阴影是否随着光线角度变化而自然移动。如果阴影“粘”在模型上不动,说明法线贴图或视差效果可能没起作用。
    • 拉近和拉远摄像机,检查纹理在不同距离下的表现。你可能需要为材质球配置多级渐远纹理(Mipmaps),并在远处使用更低的纹理采样精度以避免闪烁。

实操心得:月球的真实感,很大程度上取决于粗糙度贴图的细节。不要只用纯色或简单的噪声。建议基于高度图,用Photoshop的“高反差保留”滤镜处理一下,再叠加一些噪波,这样生成的粗糙度图会有丰富的微观细节变化,在特定角度下能捕捉到非常微妙的漫反射高光,质感立刻提升一个档次。

4. 地球材质样本的进阶实现

地球材质是本次项目的重头戏,其多层结构需要更精巧的Shader设计。

4.1 多层纹理的准备与组织

地球需要多套纹理:

  • 地表层:彩色Albedo(含海洋和陆地)、法线贴图(地形起伏)。
  • 云层:至少两张无缝的、不同尺度的云图(例如一张大尺度条纹云,一张小尺度絮状云)。
  • 大气层:一张用于控制大气厚度的渐变贴图(中心透明,边缘渐白)。
  • 灯光层:一张黑白掩膜图,白色区域代表城市群。

这些纹理的UV可以共用一套(基于球体展开),但云层需要独立的UV动画。

4.2 在Shader Graph中构建多层混合逻辑

在URP中新建一个Shader Graph,这次的结构更复杂。

  1. 地表层(Base Layer)

    • 采样地表Albedo和法线贴图,连接至PBR主节点的AlbedoNormal。将Metallic设为0(陆地),但可以通过一张贴图单独为海洋部分赋予轻微的金属度(如0.3),以模拟海面的菲涅尔反射。
    • Smoothness同样可以为海洋设置一个较高的值(如0.8)。
  2. 云层(Cloud Layer)

    • 创建两个Sample Texture 2D节点用于云图。为它们的UV输入创建一个Time节点,乘以一个很小的速度系数(如0.1和0.05),然后通过Tiling And Offset节点对基础UV进行偏移,实现两种速度的云层流动。
    • 将两张云图采样结果用AddScreen模式混合,得到动态云层。
    • 关键步骤:云层透明度混合。需要根据顶点是否处于背光面来调整云层透明度。计算顶点法线与光源方向的点积(Dot Product)。如果点积小于0(背光面),则通过一个Smoothstep函数将云层Alpha值降低。然后将混合后的云层颜色,通过Lerp(线性插值)节点与地表颜色进行Alpha混合。这里需要将混合模式设置为Transparent,并处理好渲染顺序。
  3. 大气散射层(Atmosphere Scattering)

    • 这是最复杂的部分。一种简化实现是:创建一个新的Subgraph(子图),专门计算大气颜色。
    • 输入:表面法线(Normal Vector)、视线方向(View Direction)、光源方向(Light Direction)。
    • 核心计算:
      • Dot(N, V):视角与法线点积,用于模拟边缘光(rim light)。值越小(看向边缘),大气越厚。
      • Dot(V, L):视角与光源方向点积。这个值在晨昏线附近(视角与光源方向垂直)时最小,用于加强散射强度。
      • 将这两个因子结合,并用一个Pow节点控制衰减曲线,最后输出一个颜色。通常,Dot(N, V)因子产生从中心(蓝色)到边缘(白色)的渐变,Dot(V, L)因子在边缘处添加红/橙色色调。
    • 将这个子图的输出颜色,以Additive(相加)混合模式叠加到最终颜色上。
  4. 城市灯光层(Night Lights)

    • 采样城市灯光掩膜图。
    • 同样计算顶点法线与光源方向的点积。当点积小于某个阈值(如-0.1,表示完全进入黑夜)时,将灯光掩膜图的强度作为自发光(Emission)输出。
    • 可以乘以一个随时间缓慢变化的亮度系数,模拟一些城市灯光的闪烁感。

4.3 性能优化与效果平衡

这样一个多层Shader对性能是有消耗的,尤其是片元着色器中的计算。

  1. 纹理压缩与合并:尽可能将粗糙度、金属度、AO等非彩色贴图合并到一张纹理的不同通道(如RGB分别存储Roughness, Metallic, AO),减少纹理采样次数。
  2. LOD(细节层次):为地球模型设置LOD Group。在远距离时,使用一个简化版本的Shader,甚至只渲染带云层动画的简单球体,关闭大气散射和法线贴图等昂贵效果。
  3. Shader变体管理:URP Shader Graph会自动生成很多变体。在Project Settings -> Graphics -> Shader Stripping中,可以设置剥离未使用的变体,减少构建体积和运行时内存。
  4. 效果开关:在材质球上暴露一些参数(如_AtmosphereStrength,_CloudSpeed),方便在编辑器内实时调整,找到画质与性能的最佳平衡点。对于移动平台,可以考虑完全关闭视差效果和动态的大气散射,使用静态的渐变贴图来模拟大气边缘光。

5. 场景整合、光照与后期处理

单独的星球很美,但放入一个协调的宇宙场景中才能体现其价值。

5.1 构建宇宙场景环境

  1. 天空盒:导入一个高质量的星空天空盒。确保其HDR属性打开,这样它才能为场景提供真实的环境反射光。在Lighting面板中将其指定为天空盒材质。
  2. 光源系统:我们的主光源是模拟太阳的Directional Light。为了增加戏剧性,可以:
    • 启用光影(Shadows),设置为Soft Shadows,调整Shadow Distance和Resolution以适应场景尺度。
    • 添加一个微弱的、蓝色的第二个Directional Light作为“填充光”,模拟遥远的星光或其他恒星的微弱照明,避免背光面完全死黑。
  3. 相机与构图:使用一个空物体作为相机和星球的枢轴点,将相机作为其子物体。这样可以通过旋转父物体来实现平滑的环绕动画。设置相机的焦距(FOV)小一些(如30-45度),更能体现太空的广阔感。启用HDR和抗锯齿(MSAA或FXAA)。

5.2 后期处理栈(Post-Processing)调校

URP的后处理体积(Volume)是提升画面电影感的关键。

  1. 泛光(Bloom):为地球的云层、大气辉光以及城市灯光添加泛光效果。阈值(Threshold)设低一点,让微亮区域也能产生光晕,强度(Intensity)和散射(Scatter)根据预览适度调整。这是让星球“发光”的秘诀。
  2. 色调映射(Tonemapping):使用ACESNeutral模式,可以更好地处理高对比度场景,让星球的亮部和星空暗部都有丰富的细节。
  3. 颜色分级(Color Grading):轻微调整对比度、饱和度,并为阴影和高光加入一点点冷色调或暖色调,统一整个场景的色彩情绪。
  4. 镜头效果:可以轻微添加一点Vignette(暗角)和Grain(胶片颗粒),增强镜头的真实感。但切记要非常克制,过犹不及。

5.3 实现简单的天体运动动画

为了让场景生动起来,需要添加动画。

  1. 自转动画:为月球和地球球体分别编写简单的C#脚本,在Update函数中,绕其自身的Y轴旋转。
    public class Rotator : MonoBehaviour { public float rotationSpeed = 10.0f; // 度/秒 void Update() { transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime); } }
    • 地球的自转速度可以设为360f / (24f * 60f * 60f)度/秒来模拟真实24小时一周。
    • 月球的自转速度要慢得多,可以设为地球的1/27左右。
  2. 公转动画(可选):创建一个空物体作为“太阳”(或轨道中心),将地球作为其子物体。然后让地球脚本同时绕父物体的Y轴旋转,实现公转。月球的公转则可以以地球为中心。
  3. 相机动画:通过Animation窗口或脚本,为相机创建一段缓慢的推拉、环绕或变焦的路径动画,用于展示。

6. 常见问题、性能瓶颈与调试实录

在实际操作中,你一定会遇到各种问题。以下是我踩过的一些坑和解决方案。

6.1 材质显示问题排查表

问题现象可能原因排查步骤与解决方案
球体显示为洋红色(Missing)Shader编译错误或资源丢失。1. 检查Console窗口是否有Shader编译错误。2. 在Project中重新找到Shader文件,确保其未损坏。3. 检查材质球引用的Shader是否正确。
纹理看起来模糊或拉伸纹理导入设置不当,或UV映射错误。1. 选中纹理,在Inspector中检查Max Size和Filter Mode。对于星球纹理,建议设为2048或4096,Filter Mode用Trilinear。2. 检查球体模型的UV是否正常(默认Sphere的UV是展开的)。
法线贴图无效,模型看起来平坦法线贴图未正确设置为Normal Map类型。1. 选中法线贴图,Texture Type必须为Normal map。2. 在Shader Graph中,确保Sample Texture 2D节点的Type设置为Normal
云层或大气层不透明,遮挡地表混合模式或渲染顺序错误。1. 在Shader Graph的Master节点,将Surface Type改为Transparent,Blend Mode设为Alpha Blend。2. 确保云层颜色的Alpha通道小于1。3. 检查材质球的渲染队列(Render Queue)是否在透明物体队列(>2500)。
背光面的城市灯光不亮判断背光面的Shader逻辑有误。1. 检查计算点积(Dot Product)时向量的空间是否正确(通常应为世界空间或物体空间)。2. 检查阈值是否合理,点积结果范围是[-1,1],背光面应为负值。3. 使用Shader Graph的Debug模式,将点积结果可视化输出到颜色,查看其分布。
在Game视图正常,但Build后效果丢失Shader变体被错误剥离,或纹理未包含在构建中。1. 检查Edit -> Project Settings -> Graphics中的Shader Stripping设置,确保未过度剥离。2. 确保所有用到的纹理在Resources文件夹或被场景直接引用,或使用了Addressables/AssetBundle正确管理。

6.2 性能优化深度指南

  1. Draw Call与合批:月球和地球通常是场景中最大的物体,且材质复杂,无法动态合批。确保它们静态标记(Static)正确,以便Unity进行静态合批(如果顶点数允许)。对于URP,可以利用SRP Batcher,确保Shader符合其规范(如使用相同的CBUFFER)。
  2. 纹理流式加载(Texture Streaming):对于4K甚至8K的星球纹理,内存占用巨大。在Player Settings中启用Texture Streaming,并设置合理的Mipmap偏置和最大内存预算。这能确保在远处使用低分辨率纹理,拉近时再加载高清部分。
  3. Shader复杂度分析:在Frame Debugger或URP的Render Graph Viewer中,查看绘制地球和月球所需的GPU指令数。如果过高,考虑:
    • 将大气散射计算从片元着色器移到顶点着色器,虽然精度下降,但远距离观看完全可以接受。
    • 简化云层动画,使用一张纹理和更简单的UV偏移。
    • 为低端设备制作一个“低配”Shader变体,关闭视差、动态大气等效果。
  4. LOD的实战技巧:不要只依赖模型的LOD。可以创建多个不同复杂度的材质球(如:高配版含所有效果,中配版无大气散射,低配版仅Albedo+云层),在LOD Group中为不同层级的模型指定不同的材质。这样切换时性能提升更显著。

6.3 进阶效果与扩展思路

当你完成了基础版本后,可以尝试以下扩展,让项目更具深度:

  1. 基于真实数据的替换:将美术纹理替换为NASA提供的真实科学数据图像,体验从“像”到“是”的飞跃。注意处理不同数据源之间的坐标对齐和色彩平衡。
  2. 日食与月食效果:通过动态控制光源(太阳)与月球、地球三者的位置关系,在Shader中计算本影和半影区域,可以实现逼真的日食(月球遮挡太阳光在地球上的投影)和月食(地球阴影落在月球上)效果。这涉及到球体之间的遮挡判断和阴影颜色的特殊处理。
  3. 交互与叙事:添加简单的UI,允许用户点击切换显示不同的图层(如关闭云层、只显示地形高程)、调整时间流速(自转/公转速度)、或者跳转到预设的观测点(如从北极俯瞰)。这能将技术演示转化为一个交互式的科普小应用。
  4. 集成到更大的项目中:这个星球渲染系统可以作为太空模拟游戏、数字孪生天文教学软件或科幻电影预演的核心视觉模块。思考如何将其与星图数据、飞船轨道模拟等其他系统进行数据对接和视觉整合。

完成这个项目的过程,远不止是学会了几个Shader节点或纹理技巧。它是一次系统的训练,训练你如何将抽象的物理现象(光照、散射、运动)分解为具体的图形学算法,再用引擎的工具将其实现。每一次调试参数、观察效果变化,都是对光与物质相互作用的一次深入理解。当你最终看到自己创造的星球在虚拟的宇宙中静静旋转,晨昏线缓缓划过大陆,城市灯火在夜幕中依次点亮时,那种成就感,正是图形编程最迷人的地方。