本文详解使用three.js r98版本实现3D粒子过渡效果的全过程。涵盖场景、相机、灯光配置,模型加载与Points系统转换,结合TWEEN库实现坐标平滑过渡及鼠标交互控制。针对大规模粒子导致的卡顿问题,提供减少粒子数量与采用GPU片元着色器渲染的优化方案,助力提升Web端3D动效性能。
背景与概述
一、背景
粒子特效旨在模拟现实中的水、火、雾、气等视觉现象,通常由三维软件提供的制作模块实现。其核心原理是将大量单个粒子组合成固定形态,并通过控制器或脚本控制整体或单个粒子的运动,从而呈现逼真的视觉效果。three.js是一个基于JavaScript编写的WebGL第三方库,提供了丰富的API以辅助开发者实现3D动效。本文主要介绍如何利用three.js实现粒子过渡效果,并包含基本的鼠标交互操作。
(注:本文使用的关于three.js的API都是基于版本r98的。)

实现步骤详解
二、实现步骤
1. 创建渲染场景scene
Scene对象相当于一个三维空间,用于承载和显示所有定义的元素,包括相机、物体、灯光等。在实际开发中,为了方便观察场景结构,可以添加一些辅助工具,例如网格(GridHelper)或坐标轴。
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x05050c, 10, 60);
scene.add( new THREE.GridHelper( 2000, 1 ) ); // 添加网格
2. 添加照相机camera
THREE库中实现了多种相机类型:PerspectiveCamera(透视相机)、OrthographicCamera(正交投影相机)、CubeCamera(立方体或全景相机)以及StereoCamera(3D相机)。本文主要介绍最常用的PerspectiveCamera(透视相机):
视觉效果表现为近大远小。
配置参数PerspectiveCamera(fov, aspect, near, far)。
fov:相机的可视角度。
aspect:相机可视范围的长宽比。
near:相对于深度剪切面的近的距离。
far:相对于深度剪切面的远的距离。
camera = new THREE.PerspectiveCamera(45, window.innerWidth /window.innerHeight, 5, 100);
camera.position.set(10, -10, -40);
scene.add(camera);
3. 添加场景渲染需要的灯光
three.js支持多种光源:AmbientLight(环境光)、DirectionalLight(平行光)、HemisphereLight(半球光)、PointLight(点光源)、RectAreaLight(平面光源)和SpotLight(聚光灯)等。配置光源时需注意颜色的叠加效果,例如环境光的颜色会直接作用于物体的当前颜色。不同光源的配置参数有所差异,以下是本文案例中使用的两种光源。
let ambientLight = new THREE.AmbientLight(0x000000, 0.4);
scene.add(ambientLight);
let pointLight = new THREE.PointLight(0xe42107);
pointLight.castShadow = true;
pointLight.position.set(-10, -5, -10);
pointLight.distance = 20;
scene.add(pointLight);
4. 创建、导出并加载模型文件loader
创建模型时,可以使用three.js editor进行创建,或利用three.js的基础模型生成类进行生成。对于相对复杂或特殊的模型,需要使用专业的建模工具(如c4d、3dmax等)进行创建。
使用three.js editor进行创建,可添加基本几何体,并调整几何体的各种参数(位置、颜色、材质等)。

使用模型类生成。
let geometryCube = new THREE.BoxBufferGeometry( 1, 1, 1 );
let materialCube = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
let cubeMesh = new THREE.Mesh( geometryCube, materialCube );
scene.add( cubeMesh );
导出需要的模型文件(此处使用的是obj格式的模型文件)。
加载并解析模型文件数据。
let onProgress = function (xhr) {
if (xhr.lengthComputable) {
// 可进行计算得知模型加载进度
}
};
let onError = function () {};
particleSystem = new THREE.Group();
var texture = new THREE.TextureLoader().load('./point.png');
new THREE.OBJLoader().load('./model.obj', function (object) {
// object 模型文件数据
}, onProgress, onError);
5. 将导入到模型文件转换成粒子系统Points
获取模型的坐标值。
拷贝粒子坐标值到新建属性position1上,这个作为粒子过渡效果的最终坐标位置。
给粒子系统添加随机三维坐标值position,目的是把每个粒子位置打乱,设定起始位置。
let color = new THREE.Color('#ffffff');
let material = new THREE.PointsMaterial({
size: 0.2,
map: texture,
depthTest: false,
transparent: true
});
particleSystem= new THREE.Group();
let allCount = 0
for (let i = 0; i < object.children.length; i++) {
let name = object.children[i].name
let _attributes = object.children[i].geometry.attributes
let count = _attributes.position.count
_attributes.positionEnd = _attributes.position.clone()
_attributes.position1 = _attributes.position.clone()
for (let i = 0; i < count * 3; i++) {
_attributes.position1.array[i]= Math.random() * 100 - 50
}
let particles = new THREE.Points(object.children[i].geometry, material)
particleSystem.add(particles)
allCount += count
}
particleSystem.applyMatrix(new THREE.Matrix4().makeTranslation(-5, -5,-10));
6. 通过tween动画库实现粒子坐标从position到position1点转换
利用TWEEN的缓动算法计算出各个粒子每一次变化的坐标位置,从初始位置到结束位置时间设置为2s(可自定义)。每次执行计算之后都需要将attributes的position属性设置为true,用来提醒场景需要更新,在下次渲染时,render会使用最新计算的值进行渲染。
let pos = {
val: 1
};
tween = new TWEEN.Tween(pos).to({
val: 0
}, 2500).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(callback);
tween.onComplete(function () {
console.log('过渡完成complete')
})
tween.start();
function callback() {
let val = this.val;
let particles = particleSystem.children;
for (let i = 0; i < particles.length; i++) {
let _attributes = particles[i].geometry.attributes
let name = particles[i].name
if (name.indexOf('_') === -1) {
let positionEnd =_attributes.positionEnd.array
let position1 =_attributes.position1.array
let count =_attributes.position.count
for (let j = 0; j < count *3; j++) {
_attributes.position.array[j] = position1[j] *val + positionEnd[j] * (1 - val)
}
}
_attributes.position.needsUpdate = true // 设置更新
}
}
7. 添加渲染场景render
创建容器。
定义render渲染器,设置各个参数。
将渲染器添加到容器里。
自定义的渲染函数render,在渲染函数里面我们利用TWEEN.update去更新模型的状态。
调用自定义的循环动画执行函数animate,利用requestAnimationFrame方法进行逐帧渲染。
let container = document.createElement('div');
document.body.appendChild(container);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(scene.fog.color);
renderer.setClearAlpha(0.8);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement); // 添加webgl渲染器
function render() {
particleSystem.rotation.y += 0.0001;
TWEEN.update();
particleSystem.rotation.y += (mouseX + camera.rotation.x) * .00001;
camera.lookAt(new THREE.Vector3(-10, -5, -10))
controls.update();
renderer.render(scene, camera);
}
function animate() { // 开始循环执行渲染动画
requestAnimationFrame(animate);
render();
}
8. 添加鼠标操作事件实现角度控制
我们还可以添加鼠标操作事件实现角度控制,其中winX、winY分别为window的宽高的一半,当然具体的坐标位置可以根据自己的需求进行计算,具体的效果如下图所示。
document.addEventListener('mousemove', onDocumentMouseMove, false);
function onDocumentMouseMove(event) {
mouseX = (event.clientX - winX) / 2;
mouseY = (event.clientY - winY) / 2;
}

性能优化方案
三、优化方案
1. 减少粒子数量
随着粒子数量的增加,需要的计算每个粒子的位置和大小将会非常耗时,可能会造成动画卡顿或出现页面假死的情况,所以我们在建立模型时可尽量减少粒子的数量,能够有效提升性能。
在以上示例中,我们改变导出模型的精细程度,可以得到不同数量的粒子系统,当粒子数量达到几十万甚至几百万的时候,在动画加载时可以感受到明显的卡顿现象,这主要是由于fps比较低,具体的对比效果如下图所示,左边粒子数量为30万,右边粒子数量为6万,可以明显看出左边跳帧明显,右边基本保持比较流畅的状态。

2. 采用GPU渲染方式
编写片元着色器代码,利用webgl可以为canvas提供硬件3D加速,浏览器可以更流畅地渲染页面。目前大多数设备都已经支持该方式,需要注意的是在低端的设备上由于硬件设备原因,渲染的速度可能不及基于cpu计算的方式渲染。
总结
四、总结
综上所述,实现粒子动效的关键在于计算、维护每个粒子的位置状态,而three.js提供了较为便利的方法,可以用于渲染整个粒子场景。当粒子数量极为庞大时,想要实现较为流畅的动画效果需要注意优化代码、减少计算等,也可以通过提升硬件配置来达到效果。本文中的案例为大家展示了3D粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对本站的支持。