Three.js实战:用粒子系统与着色器打造沉浸式3D飘雪效果
在3D网页开发中,模拟自然现象是提升场景沉浸感的关键。本文将深入探讨如何利用Three.js的粒子系统与自定义着色器,实现一个视觉效果细腻、性能高效的3D雪花飘落动画。这不仅是一个炫酷的视觉效果,更是理解WebGL着色器编程、粒子系统优化以及Three.js核心渲染流程的绝佳实践。无论你是使用JavaScript、TypeScript,还是希望将类似效果集成到Python的Web应用、Java的后台服务或C++的图形程序中,其背后的图形学原理都是相通的。
一、效果预览与核心技术栈
我们的目标是构建一个无限循环的3D飘雪场景。雪花将呈现中心明亮、边缘柔和的圆形光晕,并以自然随机的轨迹飘落。用户可以通过鼠标拖拽和滚轮进行360度观察。实现这一效果,我们主要依赖以下技术:
- Three.js:作为WebGL的高级封装库,负责3D场景、相机、渲染器的管理。
- 粒子系统 (Points):用于高效渲染大量雪花粒子。
- 自定义着色器 (Shader):在GPU上运行,是塑造雪花视觉形态(圆形、光晕、抗锯齿)的核心。
- BufferGeometry:高性能几何体,用于高效存储和更新粒子位置数据。

下图清晰地展示了构建此效果所需的核心技术组件及其关系:
| 技术点 | 作用 |
|---|---|
| + | 高效存储1000级雪花粒子坐标数据,减少CPU与GPU数据传输开销,支撑流畅渲染 |
| 自定义粒子视觉算法(着色器实现) | 1. 实现圆形抗锯齿雪花粒子,替代默认方形粒子;2. 打造“中心亮、边缘暗”的柔和光晕,还原雪花反光特性;3. 传递全局统一颜色/尺寸,实现雪花视觉一致性 |
| (顶点/片元着色器) | 运行在GPU上,具备并行处理能力,高效实现雪花的像素级视觉效果,兼顾性能与质感 |
| (加法混合) | 雪花重叠处亮度叠加,营造朦胧柔和的光晕感,模拟雪花群的视觉层次感,避免粒子重叠生硬遮挡 |
| 雪花双动态逻辑(垂直下落+水平偏移) | 微观雪花垂直下落+水平随时间偏移,宏观实现循环重置,营造自然且持久的雪花飘落氛围 |
| (轨道控制器) | 支持拖拽旋转/滚轮缩放,全方位查看3D雪花群的飘落效果,便捷观察雪花的光晕与运动轨迹 |
二、项目初始化:搭建3D舞台
一切始于一个基础的Three.js环境。我们需要创建场景、相机、渲染器,并添加轨道控制器以实现交互。这里的配置细节直接影响最终体验。
关键配置解析:
- 相机视角:将相机设置在
的位置,既能纵览全局飘雪,又能看清单朵雪花的细节。(0, 0, 200) - 渲染优化:启用
抗锯齿,与后续着色器中的抗锯齿逻辑配合,让雪花边缘无比平滑。antialias: true - 交互体验:为轨道控制器启用阻尼,使鼠标拖拽旋转带有惯性,观察体验更自然流畅。
基础环境搭建的核心代码如下:
// 1. 导入核心库与轨道控制器
import * as THREE from 'https://esm.sh/three@0.174.0';
import { OrbitControls } from 'https://esm.sh/three@0.174.0/examples/jsm/controls/OrbitControls.js';
// 2. 基础环境初始化(场景/相机/渲染器)
const scene = new THREE.Scene();
// 透视相机:配置合理参数,确保能完整观察粒子群
const camera = new THREE.PerspectiveCamera(
40, // 视角(FOV):40°视野适中,无雪花粒子变形
window.innerWidth / window.innerHeight, // 宽高比:适配浏览器窗口
1, // 近裁切面:过滤过近无效对象
10000 // 远裁切面:保证雪花群完整处于可见范围
);
// 相机初始位置:(0, 0, 200) 正面平视,清晰观察雪花飘落的纵深与水平偏移效果
camera.position.set(0, 0, 200);
// 渲染器:开启抗锯齿,提升雪花粒子边缘细腻度,避免光晕锯齿感
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio); // 高清适配Retina屏幕,雪花光晕无模糊
document.body.appendChild(renderer.domElement);
// 轨道控制器:支持拖拽旋转/滚轮缩放,便捷观察3D雪花群
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼:拖拽旋转有惯性,交互更顺滑自然
三、塑造雪花灵魂:自定义着色器详解
这是本项目的精髓所在。Three.js的默认点精灵是方形,我们要通过编写运行在GPU上的着色器程序,将其“改造”成带光晕的圆形雪花。这体现了与在CPU端用Python、Go或Java进行像素操作截然不同的高性能图形编程思想。
顶点着色器 主要负责将3D坐标转换到2D屏幕空间,并传递尺寸等属性。其核心转换链为:。projectionMatrix * modelViewMatrix * vec4(position, 1.0)
片元着色器 则决定了每个像素的颜色,是视觉效果的直接创造者。其实现圆形光晕雪花的逻辑分为四步:
- 获取UV坐标:使用
获取点在0到1范围内的坐标。gl_PointCoord - 计算距离:通过
计算当前像素到点中心的距离。distance() - 边缘抗锯齿:利用
函数实现从中心到边缘的平滑透明度过渡,消除锯齿。smoothstep() - 增强光晕:使用
函数放大亮度差异,使中心更亮,模拟雪花反光。pow()
完整的自定义着色器代码如下:
// 4. 自定义着色器(顶点着色器 + 片元着色器)
// 顶点着色器:处理雪花位置、尺寸,完成3D→2D透视变换
const vertexShader = `
uniform float pointSize; // 全局统一雪花尺寸(uniform:所有雪花共享)
void main() {
// 核心:透视变换链,将雪花局部坐标转换为屏幕裁剪坐标,3D渲染必备
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_PointSize = pointSize; // 为每个雪花设置屏幕尺寸,保证视觉一致性
}
`;
// 片元着色器:处理雪花像素级视觉效果(形状、颜色、透明度、亮度)
const fragmentShader = `
uniform vec3 uColor; // 全局统一雪花颜色(uniform:所有雪花共享)
void main() {
// 步骤1:计算当前像素到雪花中心的距离(gl_PointCoord:粒子内UV坐标,范围(0,0)~(1,1))
float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
// 步骤2:边缘渐隐抗锯齿,避免雪花边缘生硬锯齿,提升光晕柔和度
// smoothstep(0.4, 0.5, d):d<0.4返回0,d>0.5返回1,中间平滑插值
float alpha = 1.0 - smoothstep(0.4, 0.5, distanceToCenter);
// 步骤3:增强中心亮度,实现“中心亮、边缘暗”的光晕效果,还原雪花反光特性
// pow(指数5.0):放大亮度差异,让中心更亮,边缘更暗,光晕更有层次感
float brightness = pow(1.0 - distanceToCenter, 5.0);
// 步骤4:设置最终像素颜色(基础色*亮度 + 渐隐透明度)
gl_FragColor = vec4(uColor * brightness, alpha);
}
`;
四、组装与优化:材质、几何体与粒子系统
有了着色器代码,我们需要将其封装成Three.js材质,并创建几何体来存储粒子数据。
1. 创建着色器材质:使用 绑定我们的着色器。关键配置包括:ShaderMaterial
- 定义
全局变量(如uniform和pointSize),便于统一控制所有雪花。uColor - 启用
和transparent: true(加法混合),让雪花重叠时亮度叠加,形成朦胧光晕。AdditiveBlending - 关闭
,避免透明物体间不正确的深度遮挡。depthWrite: false
// 5. 构建着色器材质(连接两个着色器,配置参数与渲染模式)
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader, // 绑定顶点着色器
fragmentShader: fragmentShader, // 绑定片元着色器
uniforms: { // 向着色器传递的全局统一参数
pointSize: { value: 10.0 }, // 雪花屏幕尺寸:10.0兼顾细腻度与可见度
uColor: { value: new THREE.Color(0xffffff) } // 雪花基础颜色:纯白色,还原真实雪花
},
transparent: true, // 启用透明:支持alpha通道,实现边缘渐隐光晕
depthTest: true, // 启用深度测试:避免远雪花遮挡近雪花,保证正常渲染层级
depthWrite: false, // 优化:关闭深度写入,避免透明雪花互相遮挡,光晕更连贯
blending: THREE.AdditiveBlending // 优化:启用加法混合,雪花重叠处亮度叠加,提升光晕质感
});
2. 构建高效几何体:使用 和 BufferGeometry(类型化数组)来存储1000个雪花的初始位置。这种组合能极大减少CPU到GPU的数据传输开销,是高性能粒子系统的基石,其思想同样适用于C++或Java中的高效缓冲区管理。Float32Array
// 6. 构建粒子几何体(BufferGeometry:高效存储大量顶点数据)
// 创建浮点型数组存储雪花顶点坐标(每个雪花3个值:x/y/z,共count*3个元素)
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i += 3) {
// 雪花坐标范围:(-100, -100, -100) ~ (100, 100, 100),均匀分布在3D空间中
positions[i] = Math.random() * 200 - 100; // x轴坐标:水平左右分布
positions[i + 1] = Math.random() * 200 - 100; // y轴坐标:垂直上下分布(用于下落动画)
positions[i + 2] = Math.random() * 200 - 100; // z轴坐标:纵深前后分布,提升3D感
}
// 初始化BufferGeometry,绑定顶点位置数据
const geometry = new THREE.BufferGeometry();
// Float32BufferAttribute(数据数组, 每个顶点的分量数):此处3对应x/y/z
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
// 7. 构建粒子对象(THREE.Points:专门用于渲染点粒子系统)
const mesh = new THREE.Points(geometry, material);
scene.add(mesh); // 将雪花群添加到场景中
3. 创建粒子系统:最后,将几何体和材质结合,创建 对象并加入场景。至此,静态的雪花群已就位。[AFFILIATE_SLOT_1]THREE.Points
五、注入生命:实现自然飘落动画
静态的雪花毫无生气。我们需要在动画循环中不断更新每个粒子的位置,模拟下落和随机飘动,并实现循环再生。
动画更新逻辑:
- 垂直下落:
,模拟重力。positions[i + 1] -= 0.2 - 水平随机偏移:利用
Math.sin和时间为每个粒子生成独特的摆动轨迹,避免运动过于整齐。time - 循环重置:当雪花落出视野底部(
),将其重置到顶部并给予新的随机水平位置,形成无限循环。positions[i + 1] < -100
⚠️ 关键步骤:更新 数组后,必须设置 positions,否则GPU不会接收新数据。needsUpdate = true
// 8. 粒子动态更新函数(实现下落+偏移动画,重置超出边界的雪花)
function update(time) {
if (!material || !mesh) return; // 安全判断:避免对象未初始化报错
// 获取雪花顶点位置数组(geometry.getAttribute返回的是属性对象,.array获取原始数据)
const positions = mesh.geometry.getAttribute("position").array;
// 遍历所有雪花,更新坐标(每3个元素对应一个雪花的x/y/z)
for (let i = 0; i < positions.length; i += 3) {
// 步骤1:垂直下落(y轴递减,速度0.2):模拟雪花的重力下落
positions[i + 1] -= 0.2;
// 步骤2:水平轻微偏移(结合时间参数,让偏移随时间变化,运动更自然)
// 原始代码Math.sin(i)是固定值,优化为Math.sin(i + time),实现动态左右/前后偏移
positions[i] -= Math.sin(i + time) * 0.1; // x轴偏移:左右晃动
positions[i + 2] -= Math.sin(i + time * 0.8) * 0.1; // z轴偏移:前后晃动,避免偏移同步
// 步骤3:重置超出下边界的雪花(y < -100时,重置到顶部100,实现循环下落)
if (positions[i + 1] < -100) {
positions[i + 1] = 100;
// 重置时同步更新x/z坐标,避免雪花重置后位置单一,提升自然感
positions[i] = Math.random() * 200 - 100;
positions[i + 2] = Math.random() * 200 - 100;
}
}
// 关键:标记位置属性需要更新,告诉Three.js重新上传数据到GPU
mesh.geometry.getAttribute("position").needsUpdate = true;
}
下表清晰地展示了雪花飘落动画的完整循环逻辑:
| 步骤 | 逻辑 | 关键代码 | 作用 |
|---|---|---|---|
| 1 | 垂直下落 | 模拟重力下落,实现雪花的基础运动轨迹 | |
| 2 | 动态偏移 | 实现无规则水平/纵深晃动,提升自然感 | |
| 3 | 边界判断 | 检测雪花是否超出下边界,触发重置逻辑 | |
| 4 | 顶部重置 | 将雪花重置到顶部,实现无限循环 | |
| 5 | 数据更新 | 通知Three.js上传新数据到GPU,动画生效 |
六、驱动与适配:动画循环与响应式
最后,我们需要一个引擎来驱动一切。使用 请求动画帧,它比 requestAnimationFrame 更高效、更平滑。在每一帧中,我们执行以下操作:setInterval
- 计算经过的时间(使用缩放系数
控制速度)。0.0005 - 调用雪花更新函数。
- 更新轨道控制器。
- 渲染场景。
同时,别忘了监听窗口大小变化,并调用 来更新相机和渲染器,确保雪花在不同屏幕上都正确显示。camera.updateProjectionMatrix()
// 9. 动画循环(驱动粒子动画,实现流畅渲染)
function animate() {
requestAnimationFrame(animate); // 绑定浏览器刷新率(60帧/秒),避免卡顿
const time = Date.now() * 0.0005; // 获取当前时间(缩放系数0.0005,让动画速度适中)
update(time); // 调用雪花更新函数,传递时间参数
controls.update(); // 更新轨道控制器阻尼(必须在动画循环中调用)
renderer.render(scene, camera); // 渲染场景(将3D雪花群转换为2D画布显示)
}
// 启动动画循环
animate();
// 10. 窗口响应式适配(适配不同屏幕尺寸,避免雪花拉伸变形)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比
camera.updateProjectionMatrix(); // 关键:更新相机投影矩阵,让宽高比修改生效
renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器尺寸
});
七、性能核心、参数调整与扩展思路
性能优势:本项目能流畅渲染千级粒子,得益于 + BufferGeometry + GPU着色器 的组合。数据存储高效,计算在GPU上并行完成,远超CPU端(如用纯Python模拟)的性能。ShaderMaterial
快速调整:如果你想微调效果,下表提供了关键参数速查:
| 参数分类 | 参数名 | 当前取值 | 核心作用 | 修改建议 |
|---|---|---|---|---|
| 雪花基础配置 | 1000 | 雪花总数,决定飘落效果的浓密感 | 改为500:雪花更稀疏,低配设备更流畅;改为2000:雪花更浓密,氛围感更强(需注意性能) | |
| 雪花视觉配置 | 10.0 | 雪花屏幕尺寸,决定单朵雪花的大小 | 改为5.0:雪花更小更细腻,模拟细雪;改为20.0:雪花更大更醒目,模拟鹅毛大雪 | |
| 雪花视觉配置 | 雪花基础颜色,决定雪花的整体色调 | 改为(淡蓝色):模拟寒冬冷雪;改为(米白色):模拟暖光下的雪花 | ||
| 雪花动画配置 | 下落速度 | 0.2 | 雪花垂直下落的速度,决定动画节奏 | 改为0.1:下落更慢,节奏更舒缓;改为0.5:下落更快,动态感更强 |
| 雪花动画配置 | 偏移幅度 | 0.1 | 雪花水平/纵深偏移的幅度,决定晃动程度 | 改为0.05:偏移更小,飘落更平稳;改为0.2:偏移更大,飘落更杂乱,模拟大风天气 |
| 动画配置 | 时间缩放系数 | 0.0005 | 动画的整体速度系数,决定偏移的快慢 | 改为0.0003:偏移更慢,更自然;改为0.001:偏移更快,更动感 |
| 相机配置 | 200(z轴距离) | 相机与雪花群的距离,决定观察视角 | 改为150:相机更近,雪花更大;改为300:相机更远,可观察雪花群的整体飘落态势 |
扩展建议:
- 形态升级:修改片元着色器,将圆形变为六角形,更接近真实雪花。
- 动态变化:通过Uniform传递时间,让雪花在下落过程中尺寸变小、颜色变淡。
- 场景丰富:添加背景图(使用
)或地面积雪平面。THREE.TextureLoader - 性能飞跃:对于更大量级(如百万)的雪花,可研究
(实例化渲染)进行优化。InstancedBufferGeometry
完整的项目代码如下,你可以直接复制到HTML文件中运行:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>雪花 - Three.js</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
</style>
</head>
<body>
<script type="module">
// 1. 导入核心库与轨道控制器
import * as THREE from 'https://esm.sh/three@0.174.0';
import { OrbitControls } from 'https://esm.sh/three@0.174.0/examples/jsm/controls/OrbitControls.js';
// 2. 基础环境初始化(场景/相机/渲染器)
const scene = new THREE.Scene();
// 透视相机:配置合理参数,确保能完整观察粒子群
const camera = new THREE.PerspectiveCamera(
40, // 视角(FOV):40°视野适中,无粒子变形
window.innerWidth / window.innerHeight, // 宽高比:适配浏览器窗口
1, // 近裁切面:过滤过近无效对象
10000 // 远裁切面:保证粒子群完整处于可见范围
);
// 修复:设置相机初始位置(原始代码缺失,导致无法看到粒子)
camera.position.set(0, 0, 200);
// 渲染器:开启抗锯齿,提升粒子边缘细腻度
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio); // 高清适配Retina屏幕
document.body.appendChild(renderer.domElement);
// 3. 粒子系统核心配置
const count = 1000; // 粒子总数
// 4. 自定义着色器(顶点着色器 + 片元着色器)
// 顶点着色器:处理粒子位置、尺寸,完成3D→2D透视变换
const vertexShader = `
uniform float pointSize; // 全局统一粒子尺寸(uniform:所有粒子共享)
void main() {
// 核心:透视变换链,将粒子局部坐标转换为屏幕裁剪坐标
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_PointSize = pointSize; // 为每个粒子设置屏幕尺寸
}
`;
// 片元着色器:处理粒子像素级视觉效果(形状、颜色、透明度、亮度)
const fragmentShader = `
uniform vec3 uColor; // 全局统一粒子颜色(uniform:所有粒子共享)
void main() {
// 步骤1:计算当前像素到粒子中心的距离(gl_PointCoord:粒子内UV坐标,范围(0,0)~(1,1))
float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
// 步骤2:边缘渐隐抗锯齿,避免粒子边缘生硬锯齿
// smoothstep(0.4, 0.5, d):d<0.4返回0,d>0.5返回1,中间平滑插值
float alpha = 1.0 - smoothstep(0.4, 0.5, distanceToCenter);
// 步骤3:增强中心亮度,实现“中心亮、边缘暗”的光晕效果
// pow(指数5.0):放大亮度差异,让中心更亮,光晕更有层次感
float brightness = pow(1.0 - distanceToCenter, 5.0);
// 步骤4:设置最终像素颜色(基础色*亮度 + 渐隐透明度)
gl_FragColor = vec4(uColor * brightness, alpha);
}
`;
// 5. 构建着色器材质(连接两个着色器,配置参数与渲染模式)
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader, // 绑定顶点着色器
fragmentShader: fragmentShader, // 绑定片元着色器
uniforms: { // 向着色器传递的全局统一参数
pointSize: { value: 10.0 }, // 粒子屏幕尺寸
uColor: { value: new THREE.Color(0xffffff) } // 粒子基础颜色(白色)
},
transparent: true, // 启用透明:支持alpha通道,实现边缘渐隐
depthTest: true, // 启用深度测试:避免远粒子遮挡近粒子(正常渲染层级)
depthWrite: false, // 优化:关闭深度写入,避免透明粒子互相遮挡,光晕更连贯
blending: THREE.AdditiveBlending // 优化:启用加法混合,粒子重叠处亮度叠加,提升光晕质感
});
// 6. 构建粒子几何体(BufferGeometry:高效存储大量顶点数据)
// 创建浮点型数组存储粒子顶点坐标(每个粒子3个值:x/y/z,共count*3个元素)
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i += 3) {
// 粒子坐标范围:(-100, -100, -100) ~ (100, 100, 100),均匀分布在空间中
positions[i] = Math.random() * 200 - 100; // x轴坐标
positions[i + 1] = Math.random() * 200 - 100; // y轴坐标(垂直方向,用于下落动画)
positions[i + 2] = Math.random() * 200 - 100; // z轴坐标
}
// 初始化BufferGeometry,绑定顶点位置数据
const geometry = new THREE.BufferGeometry();
// Float32BufferAttribute(数据数组, 每个顶点的分量数):此处3对应x/y/z
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
// 7. 构建粒子对象(THREE.Points:专门用于渲染点粒子系统)
const mesh = new THREE.Points(geometry, material);
scene.add(mesh); // 将粒子对象添加到场景中
// 8. 轨道控制器:支持拖拽旋转/滚轮缩放,便捷观察3D粒子群
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼:拖拽旋转有惯性,交互更顺滑
// 9. 粒子动态更新函数(实现下落+偏移动画,重置超出边界的粒子)
function update(time) {
if (!material || !mesh) return; // 安全判断:避免对象未初始化报错
// 获取粒子顶点位置数组(geometry.getAttribute返回的是属性对象,.array获取原始数据)
const positions = mesh.geometry.getAttribute("position").array;
// 遍历所有粒子,更新坐标(每3个元素对应一个粒子的x/y/z)
for (let i = 0; i < positions.length; i += 3) {
// 步骤1:垂直下落(y轴递减,速度0.2)
positions[i + 1] -= 0.2;
// 步骤2:水平轻微偏移(结合时间参数,让偏移随时间变化,运动更自然)
// 原始代码Math.sin(i)是固定值,优化为Math.sin(i + time),实现动态偏移
positions[i] -= Math.sin(i + time) * 0.1; // x轴偏移
positions[i + 2] -= Math.sin(i + time * 0.8) * 0.1; // z轴偏移(不同时间系数,避免偏移同步)
// 步骤3:重置超出下边界的粒子(y < -100时,重置到顶部100,实现循环下落)
if (positions[i + 1] < -100) {
positions[i + 1] = 100;
// 重置时同步更新x/z坐标,避免粒子重置后位置单一
positions[i] = Math.random() * 200 - 100;
positions[i + 2] = Math.random() * 200 - 100;
}
}
// 关键:标记位置属性需要更新,告诉Three.js重新上传数据到GPU
mesh.geometry.getAttribute("position").needsUpdate = true;
}
// 10. 动画循环(驱动粒子动画,实现流畅渲染)
function animate() {
requestAnimationFrame(animate); // 绑定浏览器刷新率(60帧/秒),避免卡顿
const time = Date.now() * 0.0005; // 获取当前时间(缩放系数0.0005,让动画速度适中)
update(time); // 调用粒子更新函数,传递时间参数
controls.update(); // 更新轨道控制器阻尼(必须在动画循环中调用)
renderer.render(scene, camera); // 渲染场景(将3D粒子群转换为2D画布显示)
}
// 启动动画循环
animate();
// 11. 窗口响应式适配(适配不同屏幕尺寸,避免粒子拉伸变形)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比
camera.updateProjectionMatrix(); // 关键:更新相机投影矩阵,让宽高比修改生效
renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器尺寸
});
</script>
</body>
</html>
总结
通过这个3D飘雪项目,我们实践了Three.js粒子系统的完整流程,并深入到了WebGL着色器编程的层面。核心在于:利用片元着色器(、distance()、smoothstep())实现视觉细节,通过CPU端的循环逻辑(pow())驱动动画,并借助 needsUpdate = true/BufferGeometry 保障性能。掌握这些,你不仅能创造美丽的雪景,更能将这套方法论应用于其他粒子效果,如火焰、星空、烟雾等,大大提升你在Web 3D图形开发中的能力。ShaderMaterial
THREE.BufferGeometryFloat32ArrayTHREE.ShaderMaterialTHREE.AdditiveBlendingTHREE.OrbitControlspositions[i + 1] -= 0.2positions[i] -= Math.sin(i + time) * 0.1if (positions[i + 1] < -100)positions[i + 1] = 100needsUpdate = truecountpointSizeuColornew THREE.Color(0xffffff)0xf0f8ff0xfffff0positions[i + 1] -= 0.2* 0.1* 0.0005camera.position.set(0, 0, 200)
浙公网安备 33010602011771号