一、背景
粒子特效是為模擬現實中的水、火、霧、氣等效果由各種三維軟體開發的製作模組,原理是將無數的單個粒子組合使其呈現出固定形態,藉由控制器、指令碼來控制其整體或單個的運動,模擬出現真實的效果。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粒子動效如何實現,大家可以根據自己的實際需求去製作更炫酷的動態效果。