three.js shader 打造动态天空云层效果

张开发
2026/4/15 22:30:27 15 分钟阅读

分享文章

three.js shader 打造动态天空云层效果
1. 从零开始理解three.js shader云层效果第一次看到three.js实现的动态云层效果时那种逼真的天空流动感让我震撼。作为前端开发者我们通常处理的是平面UI而shader让我们能够直接操作GPU渲染管线创造出令人惊叹的3D视觉效果。云层效果特别适合作为3D场景的背景比静态天空盒更有生命力。理解shader云层需要掌握几个核心概念噪声算法生成云朵形状、光线步进(raymarching)计算光照、以及时间变量实现动态效果。这听起来可能很复杂但实际操作起来你会发现只要理解了基本原理代码实现并不困难。我刚开始学习shader时最大的误区是试图一次性理解所有数学公式。后来发现更好的方法是先关注整体效果再逐步深入细节。云层shader的核心代码通常不超过200行但产生的视觉效果却非常专业。2. 云层生成的核心算法解析2.1 噪声函数云朵形状的基础云层效果的核心是噪声函数。不同于完全随机的噪点我们需要的是具有连续性的噪声这样才能形成自然的云朵形状。在shader中常用的Perlin噪声和Simplex噪声都能满足这个需求。下面是一个简化的噪声函数实现它会产生类似云朵的基础图案float hash(float n) { return fract(sin(n)*43758.5453); } float noise(vec3 x) { vec3 p floor(x); vec3 f fract(x); f f*f*(3.0-2.0*f); float n p.x p.y*57.0 113.0*p.z; return mix(mix(mix(hash(n0.0), hash(n1.0),f.x), mix(hash(n57.0), hash(n58.0),f.x),f.y), mix(mix(hash(n113.0), hash(n114.0),f.x), mix(hash(n170.0), hash(n171.0),f.x),f.y),f.z); }这个函数的工作原理是通过三维空间中的点坐标生成伪随机值然后通过插值使数值变化平滑。多次叠加不同频率的噪声称为分形噪声可以产生更丰富的细节。2.2 光线步进体积渲染的关键技术传统3D渲染使用多边形网格但对于云这种无固定形状的体积效果光线步进(raymarching)更为适合。它的原理是从摄像机发出光线沿着光线方向逐步采样累积颜色和不透明度。vec4 raymarch(vec3 ro, vec3 rd) { vec4 sum vec4(0); float t 0.0; for(int i0; i64; i) { if(sum.a 0.99) continue; vec3 pos ro t*rd; vec4 col map(pos); // 光照计算 float dif clamp((col.w - map(pos0.3*sundir).w)/0.6, 0.0, 1.0); vec3 lin vec3(0.65,0.68,0.7)*1.35 0.45*vec3(0.7, 0.5, 0.3)*dif; col.xyz * lin; // 累积颜色 col.a * 0.35; col.rgb * col.a; sum sum col*(1.0 - sum.a); t max(0.1,0.025*t); } sum.xyz / (0.001sum.w); return clamp(sum, 0.0, 1.0); }这段代码中ro是光线起点(摄像机位置)rd是光线方向。循环中不断沿着光线前进采样当前点的云密度并考虑光照影响。步进距离t会根据距离动态调整远处采样点更稀疏以提高性能。3. 完整实现步骤详解3.1 基础场景搭建首先我们需要设置three.js的基础环境。创建一个球体作为天空穹顶并将shader材质应用在上面const geometry new THREE.SphereGeometry(5000, 50); const material new THREE.ShaderMaterial({ transparent: true, side: THREE.BackSide, uniforms: { iGlobalTime: { value: 0 }, iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, iMouse: { value: new THREE.Vector2(0, 0) } }, vertexShader: varying vec2 vUv; void main() { vUv uv; gl_Position projectionMatrix * modelViewMatrix * vec4(position,1.0); } , fragmentShader: fragmentShaderCode // 上面介绍的shader代码 }); const mesh new THREE.Mesh(geometry, material); scene.add(mesh);这里有几个关键点球体半径要足够大(5000单位)确保它包裹整个场景材质设置为双面渲染且透明这样才能看到内部的云使用BackSide只渲染球体内侧提高性能通过uniforms传递时间、分辨率和鼠标位置等参数到shader3.2 动态效果实现为了让云层动起来我们需要在动画循环中更新时间uniformfunction animate() { requestAnimationFrame(animate); controls.update(); material.uniforms.iGlobalTime.value clock.getElapsedTime(); renderer.render(scene, camera); }在shader中我们使用这个时间变量来移动噪声采样位置vec3 q p - vec3(1.0,0.1,0.0)*iGlobalTime;这行代码让云层沿着x轴方向缓慢移动产生飘动效果。你可以调整系数来改变移动速度和方向。4. 高级技巧与性能优化4.1 光照模拟增强真实感真实的云层光照需要考虑散射和透射。我们可以简化这个物理过程用以下方式模拟vec3 sundir vec3(-1.0,0.0,0.0); // 太阳方向 // 在raymarch循环内 float dif clamp((col.w - map(pos0.3*sundir).w)/0.6, 0.0, 1.0); vec3 lin vec3(0.65,0.68,0.7)*1.35 0.45*vec3(0.7, 0.5, 0.3)*dif; col.xyz * lin;这段代码计算了当前采样点与太阳方向偏移点的密度差密度差越大表示光照越强。然后混合基础天空色和阳光色来模拟光照效果。4.2 性能优化技巧云层shader是性能敏感型效果特别是光线步进部分。以下是几个实测有效的优化方法动态步长距离越远步长越大t max(0.1, 0.025*t);提前终止当不透明度接近1时停止计算if(sum.a 0.99) continue;降低迭代次数在移动设备上可以减少raymarch的迭代次数for(int i0; i32; i) // 桌面用64移动端用32分辨率适配根据设备性能调整渲染分辨率renderer.setPixelRatio(window.devicePixelRatio 1 ? 1 : 0.75);5. 常见问题与调试技巧实现云层效果时经常会遇到各种问题这里分享几个我踩过的坑和解决方法。问题1云层出现带状条纹这通常是因为噪声函数的参数设置不当。尝试调整噪声函数的频率增加噪声叠加的层数检查插值函数是否平滑问题2性能低下除了前面提到的优化方法外还可以使用更简单的噪声函数减少raymarch迭代次数降低渲染分辨率问题3边缘出现接缝当使用球体作为天空穹顶时UV接缝处可能出现问题。解决方法确保球体细分足够在shader中检查uv坐标是否连续考虑使用立方体天空盒替代球体调试shader时我习惯使用调试颜色技术 - 将中间变量可视化为颜色输出。例如要检查噪声函数的结果可以临时修改返回语句return vec4(vec3(noiseValue), 1.0); // 将噪声值直接显示为灰度6. 创意扩展与个性化定制掌握了基础云层效果后你可以尝试各种创意扩展天气变化通过uniform参数控制云量密度float cloudCover 0.5; // 0到1之间 d clamp(d * (1.0 cloudCover), 0.0, 1.0);日夜循环随时间改变太阳位置和颜色vec3 sundir vec3(cos(iGlobalTime*0.1), 0.0, sin(iGlobalTime*0.1));特殊效果添加闪电、彩虹等天气现象// 简单闪电效果 if(sin(iGlobalTime*10.0) 0.99) { col vec3(1.0)*pow(sun, 16.0)*2.0; }我在一个项目中曾经实现过暴风雨效果通过组合多层不同速度的云层加上闪电和雨滴粒子效果非常震撼。关键是要理解基础原理然后大胆尝试各种参数组合。

更多文章