前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于 three.js 的 3D 粒子动效实现

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

原创
作者头像
个推
修改2019-04-08 17:56:40
6.8K0
修改2019-04-08 17:56:40
举报
文章被收录于专栏:个推技术实践

*作者:个推web前端开发工程师 梁神*

一、背景

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

二、实现步骤

**1. 创建渲染场景scene**

scene实际上相当于一个三维空间,用于承载和显示我们所定义的一切,包括相机、物体、灯光等。在实际开发时为了方便观察可添加一些辅助工具,比如网格、坐标轴等。

代码语言:txt
复制
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:相对于深度剪切面的远的距离。

代码语言:txt
复制
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(聚光灯)等。配置光源参数时需要注意颜色的叠加效果,如环境光的颜色会直接作用于物体的当前颜色。各种光源的配置参数有些区别,下面是本文案例中会用到的二种光源。

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

使用模型类生成。

代码语言:txt
复制
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格式的模型文件)。

加载并解析模型文件数据。

代码语言:txt
复制
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,目的是把每个粒子位置打乱,设定起始位置。

代码语言:txt
复制
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会使用最新计算的值进行渲染。

代码语言:txt
复制
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方法进行逐帧渲染。

代码语言:txt
复制
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的宽高的一半,当然具体的坐标位置可以根据自己的需求进行计算,具体的效果如下图所示。

代码语言:txt
复制
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粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、实现步骤
  • 三、优化方案
  • 四、总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档