学习Three.js--雪花
前置核心说明
开发目标
基于Three.js的粒子系统+自定义着色器实现真实感3D雪花飘落效果,还原雪花的自然视觉与动态特征,核心能力包括:
- 模拟雪花的视觉形态:通过圆形抗锯齿粒子+中心亮边缘暗的光晕,还原雪花在暗光环境下的柔和反光效果;
- 实现自然飘落动画:垂直下落+水平随机偏移,避免雪花运动轨迹过于规整,还原真实雪花的飘落姿态;
- 构建循环动画逻辑:雪花超出下边界后自动重置到顶部,实现无限循环的雪花飘落效果,无粒子缺失;
- 借助
ShaderMaterial提升视觉质感:启用加法混合让雪花重叠处光晕更柔和,兼顾性能与视觉细腻度; - 支持轨道交互查看:通过
OrbitControls实现360°拖拽旋转、滚轮缩放,全方位观察3D雪花群的飘落效果。
核心技术栈(关键知识点)
| 技术点 | 作用 |
|---|---|
THREE.BufferGeometry + Float32Array | 高效存储1000级雪花粒子坐标数据,减少CPU与GPU数据传输开销,支撑流畅渲染 |
| 自定义粒子视觉算法(着色器实现) | 1. 实现圆形抗锯齿雪花粒子,替代默认方形粒子;2. 打造“中心亮、边缘暗”的柔和光晕,还原雪花反光特性;3. 传递全局统一颜色/尺寸,实现雪花视觉一致性 |
THREE.ShaderMaterial(顶点/片元着色器) | 运行在GPU上,具备并行处理能力,高效实现雪花的像素级视觉效果,兼顾性能与质感 |
THREE.AdditiveBlending(加法混合) | 雪花重叠处亮度叠加,营造朦胧柔和的光晕感,模拟雪花群的视觉层次感,避免粒子重叠生硬遮挡 |
| 雪花双动态逻辑(垂直下落+水平偏移) | 微观雪花垂直下落+水平随时间偏移,宏观实现循环重置,营造自然且持久的雪花飘落氛围 |
THREE.OrbitControls(轨道控制器) | 支持拖拽旋转/滚轮缩放,全方位查看3D雪花群的飘落效果,便捷观察雪花的光晕与运动轨迹 |
分步开发详解
步骤1:基础环境搭建(场景/相机/渲染器/控制器)
搭建Three.js 3D场景的基础框架,为雪花飘落效果提供展示载体,保证场景的流畅交互与高清渲染,贴合雪花的暗光视觉氛围。
1.1 核心代码
// 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; // 启用阻尼:拖拽旋转有惯性,交互更顺滑自然
1.2 关键说明
- 相机位置:
(0, 0, 200)采用「正面平视+稍远」视角,既可以完整捕捉雪花群的飘落纵深效果,又能清晰观察单朵雪花的光晕细节,避免视角过近导致雪花光晕过曝、无法观察整体飘落态势。 - 渲染器
antialias: true:开启抗锯齿,配合后续片元着色器的smoothstep抗锯齿逻辑,让雪花粒子的边缘和光晕更细腻——这对白色雪花尤为重要,可避免雪花出现“锯齿边”,提升暗光环境下的视觉柔和度。 - 控制器阻尼:启用阻尼后,拖拽旋转查看雪花群时,场景会有自然的惯性减速,更贴合3D场景的交互体验,便于长时间观察雪花的飘落轨迹与光晕叠加效果。
步骤2:雪花粒子核心配置(数量/视觉参数)
定义雪花粒子的基础配置参数,为后续着色器与几何体构建提供数据支撑,保证雪花群的密集度与视觉一致性。
2.1 核心代码
// 3. 粒子系统核心配置
const count = 1000; // 雪花总数:保证飘落效果的浓密感,同时兼顾低配设备流畅性
2.2 关键说明
count参数的平衡:1000是“视觉效果”与“性能”的最优平衡值——少于500会导致雪花过于稀疏,缺乏漫天飘落的氛围感;多于2000会增加GPU渲染开销,低配设备可能出现卡顿,尤其在开启加法混合后,粒子重叠计算量会增加。- 雪花的视觉预设:本步骤仅定义粒子数量,后续通过着色器统一配置雪花的尺寸(
pointSize)与颜色(uColor),保证所有雪花视觉风格一致,还原真实雪花的统一性。
步骤3:自定义着色器(雪花视觉效果的核心)
通过顶点着色器与片元着色器,实现雪花的圆形形态、柔和光晕、抗锯齿边缘,这是雪花视觉质感的核心,所有逻辑运行在GPU上,高效且细腻。
3.1 核心代码
// 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);
}
`;
3.2 关键技术点解析
-
顶点着色器的核心职责:
- 完成3D坐标到2D屏幕坐标的透视变换链:
projectionMatrix * modelViewMatrix * vec4(position, 1.0),这是Three.js 3D渲染的通用逻辑,保证雪花在屏幕上的正确显示与“近大远小”的透视效果。 - 统一设置雪花尺寸:通过
uniform变量pointSize为所有雪花设置相同的屏幕尺寸,保证视觉一致性,避免单朵雪花过大或过小破坏整体效果。
- 完成3D坐标到2D屏幕坐标的透视变换链:
-
片元着色器的雪花视觉实现:
gl_PointCoord:雪花粒子内的UV坐标,范围从(0,0)(左下角)到(1,1)(右上角),vec2(0.5)对应雪花中心,通过计算像素到中心的距离,实现圆形雪花形态。distance():计算欧几里得距离,获取当前像素与雪花中心的间距,为后续圆形裁剪与光晕实现提供数据支撑。smoothstep():实现边缘渐隐,避免圆形雪花出现生硬锯齿——这是雪花视觉柔和度的关键,让雪花边缘从亮到暗平滑过渡,形成自然光晕。pow():幂指数5.0放大亮度差异,让雪花中心更明亮(模拟雪花的反光核心),边缘更暗淡(模拟光晕的衰减),还原真实雪花在暗光环境下的视觉特征。
步骤4:构建着色器材质(连接着色器,优化视觉效果)
将自定义着色器与Three.js的ShaderMaterial绑定,配置全局uniform参数与渲染模式,实现雪花的透明光晕与叠加效果,提升视觉层次感。
4.1 核心代码
// 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 // 优化:启用加法混合,雪花重叠处亮度叠加,提升光晕质感
});
4.2 关键说明
- 全局
uniform参数:pointSize与uColor是所有雪花共享的全局参数,后续可通过修改material.uniforms.xxx.value实现雪花尺寸与颜色的动态调整(如随时间变浅/变大)。 transparent: true:启用透明通道,支持片元着色器中的alpha值,实现雪花边缘的渐隐效果——如果关闭该参数,雪花会变成不透明的白色方块,丢失光晕质感。depthWrite: false:关闭深度写入,允许透明雪花互相叠加,避免“后渲染的雪花被先渲染的雪花遮挡”的问题,让雪花群的光晕更连贯,尤其在雪花密集区域,视觉效果更柔和。AdditiveBlending(加法混合):雪花重叠处的亮度会叠加,形成更明亮的白色光晕,模拟漫天雪花飘落的朦胧氛围——如果关闭该参数,雪花重叠区域会显得灰暗,缺乏氛围感。
步骤5:构建BufferGeometry(高效存储雪花坐标数据)
通过Float32Array与BufferGeometry高效存储雪花的3D坐标数据,减少CPU与GPU之间的数据传输开销,支撑1000级粒子的流畅渲染。
5.1 核心代码
// 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); // 将雪花群添加到场景中
5.2 关键技术点解析
Float32Array的高效性:这是一种二进制浮点数组,相比普通JS数组,占用内存更少(每个元素仅4字节)、传输到GPU的速度更快,专门用于存储大量粒子的坐标数据,是高性能粒子系统的必备选择。BufferGeometry的优势:Three.js推荐的高性能几何体,相比已废弃的Geometry,它直接将数据存储在GPU显存中,减少CPU与GPU之间的频繁数据传输,能够轻松支撑1000级甚至万级粒子的流畅渲染。- 雪花的3D空间分布:x/y/z三轴均在
(-100, 100)范围内随机分布,形成立体的雪花群——z轴的纵深分布让雪花飘落时呈现“近快远慢”的透视效果,提升3D场景的真实感,避免平面化的飘落效果。 THREE.Points:专门用于渲染点粒子系统的对象,将BufferGeometry(坐标数据)与ShaderMaterial(视觉效果)结合,生成最终的雪花群,相比Mesh对象,它的渲染开销更低,更适合大量粒子场景。
步骤6:雪花动态更新函数(实现自然飘落与循环动画)
编写粒子更新逻辑,实现雪花的垂直下落、水平随机偏移,以及超出边界后的重置,打造无限循环的自然飘落效果,避免雪花消失后场景空洞。
6.1 核心代码
// 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;
}
6.2 关键技术点解析
-
自然飘落的双动效逻辑:
- 垂直下落:
positions[i + 1] -= 0.2(y轴递减),模拟雪花受重力下落的效果,0.2的速度兼顾流畅度与视觉舒适度——速度过快会显得杂乱,过慢会缺乏动感。 - 水平/纵深偏移:通过
Math.sin(i + time)实现动态偏移,i保证每个雪花的偏移相位不同,time保证偏移随时间变化,0.8的时间系数让z轴偏移与x轴不同步,避免雪花“整齐划一”的晃动,还原真实雪花的无规则飘落轨迹。
- 垂直下落:
-
循环动画的核心逻辑:
- 边界判断:
positions[i + 1] < -100,当雪花下落至下边界以下时,触发重置逻辑。 - 顶部重置:将雪花的y轴坐标重置为100(顶部),同时重新随机生成x/z轴坐标,避免雪花在同一位置重复出现,提升飘落效果的自然感。
needsUpdate = true:这是动态更新粒子坐标的关键——修改positions数组后,必须标记位置属性需要更新,否则Three.js不会将新数据上传到GPU,雪花将无法实现下落与重置动画。
- 边界判断:
步骤7:动画循环与窗口适配(驱动动画,适配不同屏幕)
通过requestAnimationFrame驱动动画循环,更新雪花位置与控制器阻尼,同时实现窗口响应式适配,保证雪花效果在不同屏幕尺寸下正常显示。
7.1 核心代码
// 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); // 更新渲染器尺寸
});
7.2 关键说明
requestAnimationFrame:绑定浏览器的刷新率(通常为60帧/秒),相比setInterval,它能避免动画卡顿与掉帧,保证雪花飘落效果的流畅性,同时节省CPU与GPU资源。- 时间缩放系数
0.0005:Date.now()返回的是毫秒级时间戳,直接使用会导致动画速度过快,通过* 0.0005将时间缩放为秒级且放缓速度,让雪花的偏移与下落更自然。 camera.updateProjectionMatrix():窗口尺寸变化时,相机的宽高比会修改,必须调用该方法更新投影矩阵,否则雪花群会出现拉伸变形,破坏3D视觉效果。
核心技术深度解析
1. 雪花视觉效果的核心实现(着色器逻辑)
雪花的柔和光晕与圆形形态是通过片元着色器的四层逻辑实现的,每层逻辑环环相扣,最终还原真实雪花的视觉特征,具体流程如下:
graph LR
A[获取粒子内UV坐标 gl_PointCoord] --> B[计算到中心的距离 distanceToCenter]
B --> C[smoothstep实现边缘渐隐 alpha]
C --> D[pow放大亮度差异 brightness]
D --> E[组合颜色与透明度 gl_FragColor]
- 核心亮点:通过像素级计算实现视觉效果,运行在GPU上,具备并行处理能力,即使1000级雪花,也能保持60帧流畅渲染,这是普通JS逻辑无法实现的。
- 关键优化:
AdditiveBlending加法混合让雪花重叠处亮度叠加,形成朦胧的光晕氛围,模拟漫天飞雪的视觉效果,同时depthWrite: false避免透明粒子互相遮挡,保证光晕的连贯性。
2. 雪花飘落动画的循环逻辑(CPU端动态更新)
雪花的无限循环飘落是通过“下落-判断-重置”的闭环逻辑实现的,核心要点如下:
| 步骤 | 逻辑 | 关键代码 | 作用 |
|---|---|---|---|
| 1 | 垂直下落 | positions[i + 1] -= 0.2 | 模拟重力下落,实现雪花的基础运动轨迹 |
| 2 | 动态偏移 | positions[i] -= Math.sin(i + time) * 0.1 | 实现无规则水平/纵深晃动,提升自然感 |
| 3 | 边界判断 | if (positions[i + 1] < -100) | 检测雪花是否超出下边界,触发重置逻辑 |
| 4 | 顶部重置 | positions[i + 1] = 100 | 将雪花重置到顶部,实现无限循环 |
| 5 | 数据更新 | needsUpdate = true | 通知Three.js上传新数据到GPU,动画生效 |
3. BufferGeometry与ShaderMaterial的性能优势
本代码能够高效支撑1000级粒子渲染,核心在于BufferGeometry与ShaderMaterial的组合,其性能优势主要体现在:
- 数据高效存储:
BufferGeometry使用二进制数组存储粒子数据,一次性上传到GPU显存,后续仅需更新少量数据(如位置属性),减少CPU与GPU之间的频繁数据传输。 - GPU并行处理:着色器逻辑运行在GPU上,具备强大的并行处理能力,可同时处理上千个雪花的视觉渲染,性能远超普通JS驱动的粒子系统。
- 低渲染开销:
THREE.Points对象的渲染开销远低于Mesh对象,配合ShaderMaterial的自定义视觉逻辑,无需额外的几何体与纹理资源,进一步提升渲染效率。
核心参数速查表(快速调整雪花效果)
| 参数分类 | 参数名 | 当前取值 | 核心作用 | 修改建议 |
|---|---|---|---|---|
| 雪花基础配置 | count | 1000 | 雪花总数,决定飘落效果的浓密感 | 改为500:雪花更稀疏,低配设备更流畅;改为2000:雪花更浓密,氛围感更强(需注意性能) |
| 雪花视觉配置 | pointSize | 10.0 | 雪花屏幕尺寸,决定单朵雪花的大小 | 改为5.0:雪花更小更细腻,模拟细雪;改为20.0:雪花更大更醒目,模拟鹅毛大雪 |
| 雪花视觉配置 | uColor | new THREE.Color(0xffffff) | 雪花基础颜色,决定雪花的整体色调 | 改为0xf0f8ff(淡蓝色):模拟寒冬冷雪;改为0xfffff0(米白色):模拟暖光下的雪花 |
| 雪花动画配置 | 下落速度 positions[i + 1] -= 0.2 | 0.2 | 雪花垂直下落的速度,决定动画节奏 | 改为0.1:下落更慢,节奏更舒缓;改为0.5:下落更快,动态感更强 |
| 雪花动画配置 | 偏移幅度 * 0.1 | 0.1 | 雪花水平/纵深偏移的幅度,决定晃动程度 | 改为0.05:偏移更小,飘落更平稳;改为0.2:偏移更大,飘落更杂乱,模拟大风天气 |
| 动画配置 | 时间缩放系数 * 0.0005 | 0.0005 | 动画的整体速度系数,决定偏移的快慢 | 改为0.0003:偏移更慢,更自然;改为0.001:偏移更快,更动感 |
| 相机配置 | camera.position.set(0, 0, 200) | 200(z轴距离) | 相机与雪花群的距离,决定观察视角 | 改为150:相机更近,雪花更大;改为300:相机更远,可观察雪花群的整体飘落态势 |
完整代码
<!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>
总结与扩展建议
核心总结
- 视觉核心:通过片元着色器的
distance()+smoothstep()+pow()逻辑,实现雪花的圆形抗锯齿与柔和光晕,配合AdditiveBlending加法混合,还原漫天飞雪的朦胧氛围感,这是雪花效果的核心亮点。 - 动态核心:“垂直下落+动态偏移+循环重置”的闭环逻辑,实现雪花的自然飘落与无限循环,
needsUpdate = true是动态更新的关键,保证动画流畅生效。 - 性能核心:
BufferGeometry+ShaderMaterial的组合,高效存储与渲染1000级粒子,借助GPU并行处理能力,在保证视觉质感的同时,兼顾低配设备的流畅性。 - 交互核心:
OrbitControls轨道控制器提供360°交互查看能力,让用户能够全方位观察3D雪花群的飘落效果,提升场景的沉浸感。
扩展建议
- 雪花效果增强:
- 自定义雪花形状:修改片元着色器,将圆形雪花改为六角形(通过UV坐标计算与裁剪实现),还原真实雪花的形态;
- 动态尺寸/颜色:通过
material.uniforms.uTime传递时间参数,让雪花的尺寸与颜色随时间轻微变化(如下落过程中逐渐变小、变浅),提升动态感; - 添加雪花反光:在片元着色器中添加光源计算,模拟雪花对环境光的反光,让雪花效果更真实。
- 场景氛围增强:
- 添加背景图:通过
THREE.TextureLoader加载夜空/森林背景图,作为场景背景,提升漫天飞雪的氛围感; - 添加地面积雪:在场景中添加平面几何体,模拟地面,实现雪花落地堆积的效果;
- 添加风力控制:通过鼠标位置或键盘事件,控制雪花的偏移方向与幅度,模拟不同风力下的雪花飘落效果。
- 添加背景图:通过
- 功能扩展:
- 参数控制面板:提供可视化面板,允许用户调整雪花数量、尺寸、下落速度等参数,实时预览效果;
- 相机自动漫游:实现相机的自动路径漫游,让用户无需手动操作,即可全方位观察雪花飘落效果;
- 移动端适配:优化触摸交互,支持移动端的捏合缩放与拖拽旋转,提升移动端用户体验。
- 性能优化:
- 视锥体裁剪:剔除屏幕外的雪花粒子,减少GPU渲染开销,支持更多粒子数量;
- 实例化几何体:使用
InstancedBufferGeometry替代BufferGeometry,进一步减少DrawCall,支持百万级雪花粒子渲染; - 渲染器优化:开启
renderer.powerPreference = "high-performance",优先使用高性能GPU,提升渲染效率。