基于 three.js 的 3D 粒子动效实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
getui
V2EX    前端开发

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

  •  
  •   getui 2019 年 4 月 8 日 3380 次点击
    这是一个创建于 2469 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    一、背景

    粒子特效是为模拟现实中的水、火、雾、气等效果由各种三维软件开发的制作模块,原理是将无数的单个粒子组合使其呈现出固定形态,借由控制器、脚本来控制其整体或单个的运动,模拟出现真实的效果。three.js 是用 Javascript 编写的 WebL 的第三方库,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 粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2641 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 11:27 PVG 19:27 LAX 03:27 JFK 06:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86