基于three.js实现的3D粒子动效实例代码
一、背景
粒子特效是为模拟现实中的水、火、雾、气等效果由各种三维软件开发的制作模块,原理是将无数的单个粒子组合使其呈现出固定形态,借由控制器、脚本来控制其整体或单个的运动,模拟出现真实的效果。three.js是用javascript编写的webgl的第三方库,three.js提供了丰富的api帮助我们去实现3d动效,本文主要介绍如何使用three.js实现粒子过渡效果,以及基本的鼠标交互操作。
(注:本文使用的关于three.js的api都是基于版本r98的。)
二、实现步骤
1. 创建渲染场景scene
scene实际上相当于一个三维空间,用于承载和显示我们所定义的一切,包括相机、物体、灯光等。在实际开发时为了方便观察可添加一些辅助工具,比如网格、坐标轴等。
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粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。