做者:個推web前端開發工程師 梁神前端
粒子特效是爲模擬現實中的水、火、霧、氣等效果由各類三維軟件開發的製做模塊,原理是將無數的單個粒子組合使其呈現出固定形態,藉由控制器、腳原本控制其總體或單個的運動,模擬出現真實的效果。three.js是用JavaScript編寫的WebGL的第三方庫,three.js提供了豐富的API幫助咱們去實現3D動效,本文主要介紹如何使用three.js實現粒子過渡效果,以及基本的鼠標交互操做。(注:本文使用的關於three.js的API都是基於版本r98的。) web
1. 建立渲染場景scene算法
scene實際上至關於一個三維空間,用於承載和顯示咱們所定義的一切,包括相機、物體、燈光等。在實際開發時爲了方便觀察可添加一些輔助工具,好比網格、座標軸等。canvas
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(透視相機):ruby
視覺效果是近大遠小。app
配置參數 PerspectiveCamera(fov, aspect, near, far)。dom
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粒子動效如何實現,你們能夠根據本身的實際需求去製做更炫酷的動態效果。