基于three.js实现的3D粒子动效实例代码


Posted in Javascript onApril 09, 2019

一、背景

粒子特效是为模拟现实中的水、火、雾、气等效果由各种三维软件开发的制作模块,原理是将无数的单个粒子组合使其呈现出固定形态,借由控制器、脚本来控制其整体或单个的运动,模拟出现真实的效果。three.js是用JavaScript编写的WebGL的第三方库,three.js提供了丰富的API帮助我们去实现3D动效,本文主要介绍如何使用three.js实现粒子过渡效果,以及基本的鼠标交互操作。

(注:本文使用的关于three.js的API都是基于版本r98的。)

基于three.js实现的3D粒子动效实例代码

二、实现步骤

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进行创建,可添加基本几何体,调整几何体的各种参数(位置、颜色、材质等)。

基于three.js实现的3D粒子动效实例代码

使用模型类生成。

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;
 }

基于three.js实现的3D粒子动效实例代码

三、优化方案

1. 减少粒子数量

随着粒子数量的增加,需要的计算每个粒子的位置和大小将会非常耗时,可能会造成动画卡顿或出现页面假死的情况,所以我们在建立模型时可尽量减少粒子的数量,能够有效提升性能。

在以上示例中,我们改变导出模型的精细程度,可以得到不同数量的粒子系统,当粒子数量达到几十万甚至几百万的时候,在动画加载时可以感受到明显的卡顿现象,这主要是由于fps比较低,具体的对比效果如下图所示,左边粒子数量为30万,右边粒子数量为6万,可以明显看出左边跳帧明显,右边基本保持比较流畅的状态。

基于three.js实现的3D粒子动效实例代码

2. 采用GPU渲染方式

编写片元着色器代码,利用webgl可以为canvas提供硬件3D加速,浏览器可以更流畅地渲染页面。目前大多数设备都已经支持该方式,需要注意的是在低端的设备上由于硬件设备原因,渲染的速度可能不及基于cpu计算的方式渲染。

四、总结

综上所述,实现粒子动效的关键在于计算、维护每个粒子的位置状态,而three.js提供了较为便利的方法,可以用于渲染整个粒子场景。当粒子数量极为庞大时,想要实现较为流畅的动画效果需要注意优化代码、减少计算等,也可以通过提升硬件配置来达到效果。本文中的案例为大家展示了3D粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JS 继承实例分析
Nov 04 Javascript
js中parseInt函数浅谈
Jul 31 Javascript
HTML,CSS,JavaScript速查表推荐
Dec 02 Javascript
使用mouse事件实现简单的鼠标经过特效
Jan 30 Javascript
关于js里的this关键字的理解
Aug 17 Javascript
jQuery EasyUI datagrid在翻页以后仍能记录被选中行的实现代码
Aug 15 Javascript
基于JavaScript实现拖动滑块效果
Feb 16 Javascript
vue自定义指令directive实例详解
Jan 17 Javascript
vue cli 全面解析
Feb 28 Javascript
详解vue beforeEach 死循环问题解决方法
Feb 25 Javascript
如何在Vue中使localStorage具有响应式(思想实验)
Jul 14 Javascript
vue实现移动端返回顶部
Oct 12 Javascript
Koa 中的错误处理解析
Apr 09 #Javascript
简单说说如何使用vue-router插件的方法
Apr 08 #Javascript
利用Bootstrap Multiselect实现下拉框多选功能
Apr 08 #Javascript
纯javascript实现选择框的全选与反选功能
Apr 08 #Javascript
详解小程序如何避免多次点击,重复触发事件
Apr 08 #Javascript
「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)
Apr 08 #Javascript
微信小程序BindTap快速连续点击目标页面跳转多次问题处理
Apr 08 #Javascript
You might like
社区(php&amp;&amp;mysql)一
2006/10/09 PHP
浅谈php正则表达式中的非贪婪模式匹配的使用
2014/11/25 PHP
PHP内置的Math函数效率测试
2014/12/01 PHP
C# WinForm中实现快捷键自定义设置实例
2015/01/23 PHP
Laravel下生成验证码的类
2017/11/15 PHP
五个jQuery图片画廊插件 推荐
2011/05/12 Javascript
brook javascript框架介绍
2011/10/10 Javascript
jquery attr方法获取input的checked属性问题
2014/05/26 Javascript
java、javascript实现附件下载示例
2014/08/14 Javascript
Node.js中使用计时器定时执行函数详解
2014/08/15 Javascript
javascript绘制漂亮的心型线效果完整实例
2016/02/02 Javascript
Javascript中判断一个值是否为undefined的方法详解
2016/09/28 Javascript
js实现三级联动效果(简单易懂)
2017/03/27 Javascript
ZeroClipboard.js使用一个flash复制多个文本框
2017/06/19 Javascript
vue两个组件间值的传递或修改方式
2018/07/04 Javascript
js判断非127开头的IP地址的实例代码
2020/01/05 Javascript
js实现div色块拖动录制
2020/01/16 Javascript
浅谈webpack和webpack-cli模块源码分析
2020/01/19 Javascript
webpack 如何同时输出压缩和未压缩的文件的实现步骤
2020/06/05 Javascript
详解Python中的__new__、__init__、__call__三个特殊方法
2016/06/02 Python
Python实现Linux的find命令实例分享
2017/06/04 Python
Python之两种模式的生产者消费者模型详解
2018/10/26 Python
解决django服务器重启端口被占用的问题
2019/07/26 Python
基于Pytorch SSD模型分析
2020/02/18 Python
css3 矩阵的使用详解
2018/03/20 HTML / CSS
英国网上花店:Bunches
2016/11/29 全球购物
澳大利亚时尚前卫设计师珠宝在线:Amber Sceats
2017/10/04 全球购物
windeln官方海外旗舰店:德淘超人气母婴超市
2017/12/15 全球购物
《大自然的语言》教学反思
2014/04/08 职场文书
我的梦中国梦演讲稿
2014/04/23 职场文书
个人债务授权委托书范本
2014/10/05 职场文书
2014年涉外离婚协议书范本
2014/11/20 职场文书
小学英语教师2015年度个人工作总结
2015/10/14 职场文书
导游词之西递宏村
2019/12/10 职场文书
详解Redis基本命令与使用场景
2021/06/01 Redis
Linux在两个服务器直接传文件的操作方法
2022/08/05 Servers