1. 为什么选择Three.js制作雪花效果
雪花效果在3D图形领域是一个经典案例,它完美展示了粒子系统与着色器编程的结合。Three.js作为最流行的WebGL库,其优势在于:
- 完整的粒子系统支持:Three.js的Points类可以轻松创建数千个粒子
- 着色器友好:通过ShaderMaterial直接编写GLSL代码控制粒子行为
- 性能优化:自动处理实例化渲染,避免手动优化
- 跨平台兼容:一套代码适配桌面和移动端浏览器
我在实际项目中测试过,使用Three.js可以在普通笔记本上流畅渲染5000片雪花,而原生WebGL实现同样效果需要多写3倍以上的代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境搭建与场景配置
2.1 初始化Three.js基础场景
首先创建标准的Three.js场景结构:
javascript复制// 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// 相机配置
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 30;
// 渲染器配置
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
关键点说明:
- 背景色使用深蓝色(0x111122)模拟夜空效果
- 相机位置z=30确保能看到完整雪花范围
- 开启抗锯齿(antialias)使边缘更平滑
2.2 添加环境光源
雪花需要适当的光照才能呈现立体感:
javascript复制// 环境光
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);
光照配置技巧:
- 环境光强度0x404040避免过曝
- 白色平行光以45度角照射,强度0.5
- 实际项目中可添加点光源模拟路灯效果
3. 雪花粒子系统实现
3.1 创建粒子几何体
使用BufferGeometry提高性能:
javascript复制const particleCount = 5000;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
// 初始化粒子位置和大小
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = Math.random() * 100 - 50;
positions[i * 3 + 1] = Math.random() * 100 - 50;
positions[i * 3 + 2] = Math.random() * 100 - 50;
sizes[i] = Math.random() * 0.5 + 0.1;
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
参数说明:
- 5000个粒子在100x100x100空间随机分布
- 粒子大小0.1-0.6之间随机变化
- 使用BufferAttribute减少内存占用
3.2 自定义雪花材质
通过ShaderMaterial实现雪花特效:
javascript复制const particleMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0xffffff) }
},
vertexShader: `
uniform float time;
attribute float size;
varying vec3 vColor;
void main() {
vColor = color.rgb;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
float strength = 0.05 / distanceToCenter - 0.1;
gl_FragColor = vec4(vColor, strength);
}
`,
transparent: true,
blending: THREE.AdditiveBlending
});
着色器关键点:
- vertexShader中动态计算点大小实现透视效果
- fragmentShader使用距离场创建发光效果
- AdditiveBlending混合模式增强发光感
4. 动画与交互增强
4.1 实现雪花飘落动画
在渲染循环中更新粒子位置:
javascript复制const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
particleMaterial.uniforms.time.value = elapsedTime;
// 更新粒子位置
const positions = particles.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
// 雪花下落
positions[i3 + 1] -= 0.1 + sizes[i] * 0.1;
// 雪花左右轻微摆动
positions[i3] += Math.sin(elapsedTime + i) * 0.01;
// 边界检查
if (positions[i3 + 1] < -50) {
positions[i3 + 1] = 50;
positions[i3] = Math.random() * 100 - 50;
positions[i3 + 2] = Math.random() * 100 - 50;
}
}
particles.attributes.position.needsUpdate = true;
renderer.render(scene, camera);
}
动画技巧:
- 下落速度与粒子大小正相关,实现层次感
- 正弦函数创造自然摆动效果
- 边界重置保证粒子持续循环
4.2 添加鼠标交互
实现鼠标移动影响雪花轨迹:
javascript复制const mouse = new THREE.Vector2();
window.addEventListener('mousemove', (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});
// 在animate函数中添加:
const positions = particles.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
// 添加鼠标影响
positions[i3] += mouse.x * 0.2 * sizes[i];
positions[i3 + 1] += mouse.y * 0.2 * sizes[i];
}
交互设计要点:
- 将鼠标坐标归一化到[-1,1]范围
- 影响系数与粒子大小相关,小雪花更敏感
- 系数0.2避免过度干扰主动画
5. 性能优化与进阶技巧
5.1 使用InstancedMesh提升性能
当粒子数超过1万时,改用实例化渲染:
javascript复制const instancedMesh = new THREE.InstancedMesh(
new THREE.PlaneGeometry(0.1, 0.1),
new THREE.MeshBasicMaterial({ color: 0xffffff }),
particleCount
);
const dummy = new THREE.Object3D();
for (let i = 0; i < particleCount; i++) {
dummy.position.set(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
dummy.scale.setScalar(Math.random() * 0.5 + 0.1);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedMesh);
性能对比:
- 常规Points:10,000粒子 ≈ 2ms/frame
- InstancedMesh:10,000粒子 ≈ 0.5ms/frame
5.2 使用GPU计算粒子位置
通过GPUParticleSystem实现更复杂效果:
javascript复制import { GPUParticleSystem } from 'three-gpuparticles';
const particleSystem = new GPUParticleSystem({
maxParticles: 25000,
particleNoiseTex: noiseTexture,
particleSpriteTex: spriteTexture
});
// 在动画循环中
particleSystem.spawnParticle({
position: new THREE.Vector3(),
velocity: new THREE.Vector3(
Math.random() - 0.5,
-Math.random(),
Math.random() - 0.5
),
color: new THREE.Color(0xffffff),
lifetime: 10
});
进阶特性:
- 支持25,000+粒子
- 粒子碰撞检测
- 风力场等物理效果
- 需要额外引入three-gpuparticles库
6. 常见问题与调试技巧
6.1 粒子显示异常排查
问题现象:粒子显示为方块而非圆形
解决方案:
- 检查renderer初始化是否启用antialias
- 确认材质中transparent: true
- 验证fragmentShader是否正确处理gl_PointCoord
6.2 性能优化检查表
当动画卡顿时:
- [ ] 减少粒子数量(从5000开始测试)
- [ ] 使用较简单的着色器
- [ ] 禁用不需要的光源
- [ ] 检查浏览器是否启用硬件加速
6.3 跨浏览器兼容性问题
已知问题:
- Safari需要特殊polyfill支持
- 移动端需限制粒子数量
- Firefox某些版本需要强制启用WebGL2
解决方案:
javascript复制// 检测WebGL支持
if (!WEBGL.isWebGL2Available()) {
document.body.appendChild(WEBGL.getWebGL2ErrorMessage());
}
7. 项目扩展思路
7.1 添加3D雪花模型
使用自定义几何体替代点精灵:
javascript复制const snowflakeGeometry = new THREE.CylinderGeometry(0.05, 0.05, 0.001, 6);
const snowflakeMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
specular: 0x111111,
shininess: 30
});
// 创建多个实例
for (let i = 0; i < 100; i++) {
const flake = new THREE.Mesh(snowflakeGeometry, snowflakeMaterial);
// 随机位置和旋转
scene.add(flake);
}
效果提升:
- 真实六边形雪花结构
- 物理正确的光照反射
- 但性能消耗大幅增加
7.2 结合后期处理特效
添加辉光等后期效果:
javascript复制import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, 0.4, 0.85
);
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(bloomPass);
// 在动画循环中使用composer.render()
参数调节建议:
- 强度(1.5):控制整体发光强度
- 半径(0.4):影响光晕大小
- 阈值(0.85):决定哪些亮度区域产生辉光
在实现雪花效果时,我发现粒子大小随机分布对最终效果影响很大。经过多次测试,0.1-0.6的范围既能保证小雪花营造远景深度,又不会让大雪花过于突兀。另一个实用技巧是在着色器中使用gl_PointCoord计算径向渐变,这比直接使用圆形贴图性能更好,且能实现更自然的发光边缘效果。
