资讯动态

Babylon.js天空盒实战:从CubeTexture到PBR环境光照

发布时间:2026/10/15 9:17:22 来源:尧图企业网站定制
先把一句话放在前面天空盒做不好场景里那些花了大力气调整的 PBR 材质出来效果至少打七折。到第八篇这个 Babylon.js 入门系列已经从场景创建、相机控制、灯光系统一路过到了基础材质你能在页面上搭出像模像样的场景了。但八成也发现了问题——模型是有了整个画面却“飘”或者“闷”背景不是纯色就是死黑。这篇教材我们就专攻这个让场景真正落地的东西天空盒。这篇不打算只丢一段代码给你抄而是把天空盒拆成四个层面讲透它在渲染管线里到底做了什么怎么用 CubeTexture 做一张写实天空盒怎么用 SkyMaterial 程序化生成一套可动态切换的天空又怎么把天空盒变成 PBR 材质的环境光源。最后把我实操中踩过的一堆坑整理成速查清单。适合已经能跑通 Babylon 基础示例、但想让画面有质感的开发者也适合准备转 3D 场景方向的前端同学。1. 先理解天空盒在渲染管线里的真实角色1.1 一个巨大的立方体用六张贴图把相机围在中间天空盒这个概念本身不复杂。你可以把它想象成一间贴满风景壁纸的房间你站在房间正中央往哪个方向看都只能看到壁纸看不到房间的边界。在 Babylon.js 里这个“房间”通常就是一个巨大的立方体网格相机放在立方体的几何中心附近立方体的六个内表面各贴一张图覆盖前后左右上下六个方向就形成了无限远处背景的错觉。命名约定是三维图形里的老规矩px 代表正 X 方向nx 代表负 X 方向py 是正 Y朝上ny 是负 Y朝下pz 是正 Znz 是负 Z。Babylon.js 的 CubeTexture 在加载时会自动按这个约定去找文件比如 skybox_px.jpg、skybox_nx.jpg 这样一套六张图。我见过很多新手在这里卡住不是命名不对就是少了某一张导致整个天空盒纹理加载失败背景依然是 clearColor。为什么一定要用“盒子”因为立方体纹理 Sample 的坐标计算太简单了。相机往某个方向发射一条视线GPU 只需要根据视线方向的最大分量推算该采样哪个面再做个 2D 纹理采样就行性能开销极低。用球体当然也能做全景背景但纹理采样会出现极点附近的畸变处理起来并不划算。所以在绝大多数 WebGL 场景里天空盒 立方体网格 立方体纹理这是一个固定搭配。1.2 天空盒不只是背景它还是场景的环境光源很多教程止步于“把背景换成一张天空图”这个理解远远不够。天空盒的本质是一份“以场景为圆心、朝任意方向看去入射光是多亮”的数据。PBR 材质在处理间接光的时候需要知道环境里各个方向的光线分布从而计算出金属表面的反射、粗糙表面的环境漫反射、以及物体轮廓周围的菲涅尔效果。换句话说你的金属球为什么能反射出周围环境的颜色你的陶瓷材质为什么在暗部不是死黑、而是微微映出天空的蓝灰色这些信息有很大一部分来自天空盒。Babylon.js 里这份数据要单独设置给场景也就是scene.environmentTexture。如果你只是手动建了一个贴了 CubeTexture 的天空盒网格它只负责“显示背景”场景里的模型并不会自动知道“天空在哪儿”材质也不会自动反射天空的颜色。这是新手最常踩的认知坑。所以在工程实践中我一般把两件事分开一是背景显示二是环境光照。背景可以来自天空盒网格环境光照可以来自一张 HDR 环境贴图。两者可以取自同一套资源也可以完全独立。比如做一个产品展示页面背景可能是干净的纯色但环境光照依然来自一张摄影棚 HDR 贴图让金属产品表面有漂亮的反光带。理解了这一点后面的代码才有意义。1.3 CubeTexture 和 SkyMaterial两条实现路线怎么选Babylon.js 给天空盒提供了两种主流实现路线不同适用场景也完全不同。CubeTexture 路线是贴图驱动。你需要准备一组真实的天空照片或者从 HDR 环境图转换出六面图然后把它作为反射贴图贴到天空盒网格上。优点是画面写实包含丰富的光照信息对 PBR 材质非常友好缺点是素材成本高一张优秀的 HDR 天空图需要专门采集或购买而且如果想要动态切换天气就得准备多组贴图。SkyMaterial 路线是程序化生成。它不需要任何贴图而是用一套简化的大气散射数学模型在着色器里实时计算天空颜色。太阳在哪、雾霾有多重、天空有多蓝全部是参数控制的。优点是零素材成本、完全动态、文件体积小缺点是只能做自然天空做不了城市楼顶、森林山峦这类写实背景而且它生成的“光照”信息不能直接作为 PBR 环境光使用。我的建议是做产品展示、建筑可视化优先用 CubeTexture做游戏世界、Web 场景的原型探索、或者需要早晚昼夜切换的优先用 SkyMaterial。这不是二选一的问题而是两个工具你得知道这次要用哪个。2. 用 CubeTexture 做贴图驱动天空盒经典代码逐行拆解2.1 最经典的立方体贴图法每个参数都别改错先把完整代码放出来这段代码我用了很多项目是 Babylon.js 做天空盒的标准写法const createScene function () { const scene new BABYLON.Scene(engine); const camera new BABYLON.ArcRotateCamera( camera, -Math.PI / 2, Math.PI / 2, 10, new BABYLON.Vector3(0, 0, 0), scene ); camera.farPlane 10000; // 天空盒网格 const skybox BABYLON.MeshBuilder.CreateBox(skybox, { size: 5000 }, scene); // 天空盒专用材质 const skyboxMaterial new BABYLON.StandardMaterial(skyboxMat, scene); skyboxMaterial.backFaceCulling false; skyboxMaterial.disableLighting true; skyboxMaterial.diffuseColor new BABYLON.Color3(0, 0, 0); skyboxMaterial.specularColor new BABYLON.Color3(0, 0, 0); skyboxMaterial.reflectionTexture new BABYLON.CubeTexture(assets/skybox/, scene); skyboxMaterial.reflectionTexture.coordinatesMode BABYLON.Texture.SKYBOX_MODE; skyboxMaterial.reflectionTexture.level 1.0; skybox.material skyboxMaterial; skybox.infiniteDistance true; return scene; };这段代码里每一行都有自己的使命。size: 5000决定天空盒有多大这个数字必须明显大于场景中所有物体和相机的活动范围。你可以设成 10000 甚至更大但前提是相机的远裁剪面farPlane也要跟着调大否则天空盒会被裁剪掉你看到的依然是背景色。backFaceCulling false是最重要的一行。默认情况下引擎只渲染朝向相机的那一面天空盒是“从内部看”的如果保持默认你会看到一个空壳子。关闭背面剔除等于告诉引擎把六个面的里面也画出来。忘了这一行天空盒就是透明的。disableLighting true表示这个材质不参与场景的光照计算。天空盒不应该被平行光打亮也不应该有阴影它自己就是光源。diffuseColor和specularColor全部置黑是为了把其它颜色通道全部关掉只留下反射贴图作为最终显示内容。这个技巧很绕天空盒的颜色不是通过diffuseTexture显示的而是通过reflectionTexture显示的。把反射贴图坐标模式设为SKYBOX_MODE引擎就知道这张反射贴图在这里是当背景用的。level 1.0控制反射贴图的可见强度。如果调成 0.5天空盒会变暗出现类似半透明效果调成 1.0 就是完全不透明。最后infiniteDistance true让天空盒不随相机平移始终保持在“无限远”的感觉上。2.2 贴图从哪来六面图命名、HDR 转换与资源准备实现天空盒最大的实际障碍不是代码而是素材。常见的做法有三种第一种是直接找官方或者免费素材站下载六面图。下载时注意看命名规则通常是一张十字展开图把六个面排成十字形或者已经拆好的六个单独文件。如果拿到的是十字展开图你需要自己把它切成六份或者用支持这种格式的转换工具处理。第二种是用 3D 软件生成。比如在 Blender 里把世界环境的纹理输出为六面图。输出时注意统一图像格式和大小建议全部 1024 或 2048 分辨率后缀保持一致。第三种是从 HDR 环境图转换。很多免费 HDR 站提供的是等距柱状投影图一张横向全景图这种图可以直接作为scene.environmentTexture用于光照但如果想用它做天空盒网格的显示最好还是通过转换工具拆成六面图或者直接用预过滤后的 DDS 文件。素材放进项目后路径就成了最容易翻车的地方。new BABYLON.CubeTexture(assets/skybox/, scene)这里传入的是文件夹路径引擎会自动寻找skybox_px.jpg、skybox_nx.jpg、skybox_py.jpg、skybox_ny.jpg、skybox_pz.jpg、skybox_nz.jpg。如果文件不在根目录或者命名带了个前缀sky_就得把文件夹路径改成对应命名规则否则加载必定失败。调试时可以打开浏览器控制台看网络请求哪个文件 404一目了然。2.3 让天空盒跟着相机“走”infiniteDistance 与旋转的那些事infiniteDistance这个属性第一次见到容易误解。它不是“网格不参与渲染”而是“渲染时忽略相机的平移距离”。普通网格离相机越远在屏幕上越小但天空盒永远不能被缩小于是引擎让它始终处在相机坐标系中的“远方”相机动了它也相对不动看起来就像贴在天边。对于旋转情况不太一样。infiniteDistance只会忽略位移不会忽略旋转。当你旋转相机时天空盒会跟着转这是正确的行为因为现实里你转头天空当然是相对转动的。如果你希望某个固定方向保持不动比如太阳永远在正东方那就不要给天空盒网格加额外的旋转逻辑它默认相对世界坐标固定即可。还有一个容易忽略的地方天空盒网格放在场景根节点下不要试图把它作为相机的子节点挂载。如果你把天空盒塞进相机的子节点里它会跟着相机一起移动、一起旋转看起来像是整个世界被拖着跑很容易引发晕眩感。保持它在根节点加infiniteDistance就够了。3. 用 SkyMaterial 做程序化天空参数化的大气散射模型3.1 SkyMaterial 不是“加个天空颜色”而是一个简化大气模型SkyMaterial 是 Babylon.js 材质扩展库babylonjs-materials里的一个材质类型。它并不是把天空画成一张纹理贴图而是在片元着色器里用一套简化的物理光学模型计算每个方向上的天空颜色。这套模型同时考虑了大气分子对阳光的瑞利散射、气溶胶粒子的米氏散射、太阳周围的衍射光晕以及阳光穿过大气时的衰减。因此它呈现出来的色彩过渡非常自然不是简单的渐变填充能比的。使用之前需要先引入材质扩展库具体方式取决于你用的脚本加载方式。如果用的是打包工具直接 importbabylonjs/materials里的SkyMaterial即可。如果用的是全局脚本记得在 core 库之后把babylonjs.materials.js也引进来否则BABYLON.SkyMaterial会是 undefined控制台直接报错。代码同样很简单const skyMaterial new BABYLON.SkyMaterial(skyMat, scene); const skybox BABYLON.MeshBuilder.CreateBox(skyBox, { size: 1000 }, scene); skybox.material skyMaterial; skybox.infiniteDistance true;注意一个问题SkyMaterial 不是任何现有材质的“天空色版本”它是一个完全独立的材质。你不需要设置backFaceCulling false因为 SkyMaterial 材质内部已经处理好了从内部可见的问题。这个细节经常让人困惑很多照着 StandardMaterial 经验写 SkyMaterial 的人会画蛇添足地多写一行skyMaterial.backFaceCulling false其实完全没必要。3.2 参数实测从正午到黄昏的调参记录SkyMaterial 的可调参数不少我先把最常用的几个整理成表格后面再讲真实调参的经验值。参数作用经验说明turbidity空气浊度越大越灰暗正午晴天我常用 6 左右沙尘天气可以拉到 20luminance整体亮度白天 1.0黄昏 0.3夜晚直接压到 0.05 以下rayleigh瑞利散射强度影响蓝色浓度2.5 附近比较自然数值提高后天空会更蓝更深mieCoefficient米氏散射系数影响太阳光晕0.005 左右是晴天0.01 以上会出现明显雾蒙感mieDirectionalG太阳方向光晕集中度越高太阳周围光晕越窄越亮0.8 左右比较自然sunPosition太阳在世界空间的位置只取方向建议用 (100, 20, 100) 这类远点表示实际调起来我一般先把sunPosition定住再调其它参数。正午场景我常用的组合是turbidity 6、luminance 1.0、rayleigh 2.5、mieCoefficient 0.005、mieDirectionalG 0.8太阳放在new BABYLON.Vector3(100, 80, 0)这样能得到一个非常干净的蓝天。黄昏场景就要把太阳压低到new BABYLON.Vector3(100, 5, 50)同时把rayleigh调到 8 左右让地平线附近出现橙红色渐变再把mieCoefficient提到 0.01 增加一层薄雾感。夜晚更简单luminance压到 0.05turbidity拉到 18天空就会变成深蓝色而不是死黑适合做星空场景的底色。这里有个非常重要的点sunPosition的向量长度不重要引擎本质上只用它的方向。所以写new BABYLON.Vector3(1, 0.5, 0)和new BABYLON.Vector3(100, 50, 0)效果完全一样。我习惯写一个大数值的向量纯粹是为了让代码阅读者能一眼看出太阳的大致方位。3.3 程序化天空的扩展玩法昼夜切换与太阳方向绑定SkyMaterial 最大的优势是动态化。你可以在运行时直接修改参数天空会平滑地过渡到新状态。配合一个方向光就能做出一套简单的昼夜循环系统。具体做法是这样维护一个从 0 到 24 的时间变量每个小时计算太阳高度角把太阳位置传给 SkyMaterial同时把方向光的方向调整到太阳方向。关键点在于方向光的direction是光线的传播方向而从场景看向太阳的方向是sunPosition的归一化方向两者正好相反。也就是说如果太阳在(100, 20, 0)你的平行光方向要设置成const sunDir skyMaterial.sunPosition.scale(-1).normalize(); light.direction sunDir;这一步不做阴影就会和天空中的太阳位置对不上画面非常诡异。我之前在这个问题上花了整整一个晚上最后把太阳和阴影分别截了图叠加对比才明白过来。如果要进一步加云SkyMaterial 本身不带云层功能。轻量做法是在远处放几个半透明的平面网格贴上带 alpha 的云纹理复杂一点可以用粒子系统或者自定义着色器。我的经验是如果不是做飞行模拟类项目先用半透明平面加云纹理就够了性能开销小效果也能接受。4. 把天空盒变成光环境与 PBR 材质联动4.1 环境贴图为什么必须是预过滤的 DDS现在回到核心问题如何让天空盒不仅“看起来好看”还能真正照亮场景里的模型。Babylon.js 给出的标准答案是scene.environmentTexture。PBR 材质计算间接光时需要根据材质的粗糙度对环境贴图做不同程度的模糊采样。粗糙度高的表面反射模糊的倒影粗糙度低的表面反射清晰的倒影。如果引擎在运行时对 HDR 环境图做实时模糊和卷积性能扛不住所以行业通用做法是“预处理”把不同粗糙度对应的环境贴图模糊结果提前算好存进一张带有 MipMap 链的 DDS 文件里。运行时材质只需根据粗糙度选择合适层级的 Mip 采样性能开销极低。这就是为什么官方环境贴图通常以.dds格式分发而不是直接扔给你一张 HDR 图。.dds文件内部除了预过滤的环境反射信息往往还带一个 BRDF 积分查找表这个表负责处理菲涅尔和微表面阴影遮蔽的积分。没有它金属材质的边缘高光就会显得扁平。在 Babylon.js 里加载预过滤环境贴图的方法很简单const envTexture BABYLON.CubeTexture.CreateFromPrefilteredData( assets/environment.dds, scene ); scene.environmentTexture envTexture;这句代码执行后场景里所有带 PBR 材质的网格都能从环境贴图里获取间接光。你会发现普通模型瞬间有“入戏”的感觉金属表面有了反射带粗糙材质的环境阴影也柔和了。4.2 从 HDR 到场景光一套完整的环境贴图接入流程工程上一个完整的流程通常是这样的第一步准备一张 HDR 等距柱状投影图网上有很多免费 HDR 资源站可以下载。选图的时候注意看动态范围曝光度高的 HDR 图转出来的环境光亮度信息更丰富。第二步把 HDR 图转换成 Babylon.js 的预过滤 DDS。官方提供的环境纹理工具可以完成这件事操作上基本是“选择 HDR 图 → 选择输出格式 → 导出 DDS”。输出时通常会有体积选项我建议场景原型选 256 或 512 就够正式项目再上 1024体积和显存开销差距很大。第三步在代码里加载这个 DDS并赋给scene.environmentTexture。如果只是想要快速看效果Babylon.js 还提供了一个偷懒接口scene.createDefaultEnvironment()。它会基于你指定的环境贴图自动帮你把天空盒网格和环境光都建好非常适合做 Prototype 阶段验证。但正式项目中我还是建议手写天空盒和environmentTexture因为自动生成的天空盒往往缺乏对材质细节的控制后期调整参数反而费事。4.3 参数微调环境强度、曝光与色调映射接入环境贴图之后画面的明暗并不会天然完美。Babylon.js 有几个和环境光强相关的参数需要认识。scene.environmentIntensity控制环境光整体的强度默认值是 1。如果你发现场景中所有 PBR 材质都像镀了一层膜板儿亮试试把它降到 0.3 到 0.5。反过来如果模型暗得像在矿井里先检查不是灯光的问题而是环境贴图本身偏暗此时可以上调这个值。material.metallic和material.roughness是 PBR 材质自身的两个核心参数。环境反射的明显程度和 roughness 强相关roughness 接近 0 的表面是镜面会清晰反射环境roughness 接近 1 的表面非常粗糙环境反射几乎退化成均匀的漫反射。调试的时候我习惯在场景里放一个Sphere粗糙度设为 0再放一个粗糙度 0.8 的球两个球放在一起对照观察环境贴图的效果很快就能定位问题出在材质还是环境。最后是渲染管线的色调映射。Babylon.js 默认的imageProcessingConfiguration里可以选择开启 ACES 色调映射这会让高光更收敛、暗部更有层次尤其对 HDR 环境贴图来说效果明显scene.imageProcessingConfiguration.toneMappingEnabled true; scene.imageProcessingConfiguration.toneMappingType BABYLON.ImageProcessingConfiguration.TONEMAPPING_ACES;开了 ACES 之后原来过曝的天空反射区会显得更“电影感”。不过注意这个设置影响全场景开了之后可能需要回头重新调环境强度和灯光强度不然整体会变暗一点点。5. 避坑宝典我实测中踩过的天空盒陷阱5.1 黑屏、透明与贴图丢失先查这四个位置遇到天空盒不显示不用慌按顺序排查。第一步看相机裁剪。天空盒size 5000相机farPlane 2000那一定是黑的。把farPlane调成大于天空盒尺寸即可或者把天空盒尺寸缩小到远裁剪面之内。第二步看背面剔除。手写天空盒时backFaceCulling没关就是从外面看得到、从里面看不到等于没有天空盒。这行代码请放在材质创建后立即设置。第三步看纹理加载。CtrlShiftI 打开网络面板看_px、_nx这些文件是否都成功加载。404 基本是命名或路径问题。特别提醒CubeTexture 传入文件夹路径时它的查找后缀取决于文件实际后缀如果贴图是 png 而代码期望 jpg依然会失败。第四步看场景背景色。天空盒没加载成功时你会看到scene.clearColor的颜色。很多人没有显式设置 clearColor默认是黑色于是误以为是天空盒把背景刷黑了。搞清楚背景色不是天空盒显示出来的而是 clearColor 透过没渲染出来的位置被看到的。5.2 反射发黑或者过曝LDR 与 HDR 的差距比想象中大用一组 JPG 六面图做天空盒背景看着还行但金属球反射的环境却黑成一团。这不是材质的问题是信息量的问题。JPG 是 8 位 LDR 图像亮度范围只有 0 到 1拍下来的天空亮度在金属表面反射时会被压得特别平缺乏高光层次。HDR 图记录了超过 1 的辐射亮度值反射出来的金属光泽才有“亮到刺眼”的感觉。所以做产品展示时我的建议是背景天空盒可以用 JPG 图但scene.environmentTexture必须用 HDR 转换出来的 DDS。两者资源分开物尽其用。如果反射亮得刺眼先把scene.environmentIntensity降下来再检查环境贴图的曝光设置。如果反射整体偏灰可能是色调映射没开ACES 会让暗部更沉、高光更透质感提升明显。5.3 雾效、阴影和天空盒的色调统一问题场景开了雾效之后最常出现的诡异现象是远处模型融进一片灰黑色而背景天空却依然湛蓝。这是因为雾效默认颜色是灰色或黑色和天空盒地平线颜色完全脱节。解决办法是让雾色尽量贴合天空盒靠近地平线的颜色。用 SkyMaterial 时可以取地平线附近一个中间色比如#dfe9f0scene.fogColor new BABYLON.Color3.FromHexString(#dfe9f0); scene.fogMode BABYLON.Scene.FOGMODE_EXP2; scene.fogDensity 0.002;阴影和太阳位置不匹配的问题前面已经讲过平行光方向必须和sunPosition相反。另外如果阴影贴图的 bias 设置太大靠近地平线的物体会出现阴影“漂浮”感这时要微调shadowGenerator.bias。5.4 移动端性能天空盒也要有预算天空盒是渲染中覆盖面最大的对象但它不渲染阴影、不受光照本身并不消耗太多 GPU 片段着色器资源。真正的性能压力来自纹理显存和采样带宽。同一时间不要加载多个大尺寸 CubeTexture。六张 2048 的纹理显存占用轻松超过几十 MB移动端很容易吃不消。经验上移动端天空盒每面 1024 就够用环境贴图 DDS 用 256 到 512 的预过滤版本即可。此外启用纹理压缩格式例如 KTX、DDS 内置压缩能显著降低显存占用但需要在资源导出阶段就处理好运行时转换是来不及的。如果说还有什么心得值得单独拎出来那就是天空盒不是锦上添花的装饰它是整个场景统一光照、统一氛围的基石。先在场景里放一颗金属球和一颗粗糙球然后换不同的环境贴图对比你会立刻明白材质参数还没怎么动模型的高级感已经出来了。调试天空盒的时候把所有灯光先关掉只看环境贴图带来的反射和明暗这样才能真正看清天空盒在为场景做多少事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑