Three.js Shader实战:5步打造动态天空云层效果(附完整代码)
Three.js Shader实战:5步打造动态天空云层效果(附完整代码)
在三维场景开发中,天空效果往往是营造沉浸感的关键元素。传统的静态天空盒已经无法满足现代交互式应用的需求,而通过Shader实现的动态云层不仅能提升视觉真实度,还能根据场景需求实时变化。本文将带你从零开始,用Three.js的ShaderMaterial构建一个会"呼吸"的云层系统。
1. 环境准备与基础搭建
首先确保你的开发环境已经配置好Three.js基础库。我们推荐使用Vite作为构建工具,它能完美支持GLSL语法的代码高亮和模块化导入。
npm create vite@latest threejs-cloud --template vanilla cd threejs-cloud npm install three创建一个基础的Three.js场景结构:
import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加基础光源 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); // 添加控制器 const controls = new OrbitControls(camera, renderer.domElement); camera.position.z = 5;提示:在Shader开发阶段,OrbitControls可以帮助我们多角度观察效果,建议始终保留。
2. 理解云层生成的噪声原理
云层的自然感来源于噪声算法。我们将使用经典的Perlin噪声变体来生成有机的云朵形态。噪声函数的核心思想是通过多层(octaves)不同频率的简单噪声叠加,形成复杂的自然图案。
关键参数说明:
| 参数 | 作用 | 典型值 |
|---|---|---|
| octaves | 噪声层数 | 4-8 |
| lacunarity | 频率倍增系数 | 2.0 |
| gain | 振幅衰减系数 | 0.5 |
| cloudScale | 整体云朵大小 | 0.2-1.0 |
在GLSL中实现基础噪声函数:
float hash(float n) { return fract(sin(n)*43758.5453); } float noise(in 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(n+0.0), hash(n+1.0),f.x), mix(hash(n+57.0), hash(n+58.0),f.x),f.y), mix(mix(hash(n+113.0), hash(n+114.0),f.x), mix(hash(n+170.0), hash(n+171.0),f.x),f.y), f.z); }3. 构建ShaderMaterial核心结构
创建一个包裹整个场景的球体作为天空穹顶,并为其赋予ShaderMaterial:
const skyGeometry = new THREE.SphereGeometry(500, 60, 60); const skyMaterial = new THREE.ShaderMaterial({ vertexShader: ` varying vec3 vWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 skyColor; uniform vec3 cloudColor; uniform float time; varying vec3 vWorldPosition; // 这里插入之前定义的噪声函数 void main() { vec3 direction = normalize(vWorldPosition); // 云密度计算将在这里实现 gl_FragColor = vec4(mix(skyColor, cloudColor, cloudDensity), 1.0); } `, uniforms: { skyColor: { value: new THREE.Color(0x87CEEB) }, cloudColor: { value: new THREE.Color(0xFFFFFF) }, time: { value: 0 } }, side: THREE.BackSide }); const sky = new THREE.Mesh(skyGeometry, skyMaterial); scene.add(sky);4. 实现动态云层效果
现在我们来完善片段着色器中的云密度计算。核心思路是将三维噪声应用于球面坐标,并添加时间变量实现动态效果。
float cloudDensity(in vec3 pos) { // 基础云形状 vec3 samplePos = pos * 0.2; samplePos.y += time * 0.05; // 云层缓慢飘动 float f = 0.0; f += 0.5000 * noise(samplePos); samplePos *= 2.02; f += 0.2500 * noise(samplePos); samplePos *= 2.03; f += 0.1250 * noise(samplePos); samplePos *= 2.01; f += 0.0625 * noise(samplePos); // 调整密度曲线 f = smoothstep(0.3, 0.8, f); // 添加高度衰减(地平线附近云更厚) float horizon = 1.0 - abs(pos.y); return f * horizon * 2.0; }在动画循环中更新time uniform:
function animate() { requestAnimationFrame(animate); skyMaterial.uniforms.time.value += 0.01; renderer.render(scene, camera); } animate();5. 高级效果优化
基础云层完成后,我们可以通过以下技巧提升真实感:
光照计算- 添加简单的光照模型让云有体积感:
vec3 sunDir = normalize(vec3(-1.0, 0.5, -1.0)); float sun = max(0.0, dot(sunDir, direction)); // 云边缘光效 vec3 finalColor = mix(skyColor, cloudColor, density); finalColor += 0.3 * vec3(1.0, 0.9, 0.7) * pow(sun, 8.0) * density;多重云层- 叠加不同高度的云层增加层次感:
float highClouds = cloudDensity(pos * 1.5 + vec3(0.0, time*0.03, 0.0)) * 0.5; float lowClouds = cloudDensity(pos * 0.8 + vec3(0.0, time*0.02, 0.0)); float totalDensity = max(highClouds, lowClouds * 0.7);性能优化技巧:
- 降低球体分段数(32x32通常足够)
- 减少噪声octaves数量(移动端建议3-4层)
- 使用低精度浮点(mediump)
完整代码示例已上传至GitHub仓库,包含所有进阶效果实现。在实际项目中,你可以根据需要调整云层移动速度、密度阈值和颜色渐变等参数,打造独特的天空氛围。
