Three.js實(shí)現(xiàn)簡單開門動畫
先來看一下簡單的開門動畫實(shí)現(xiàn)效果寓涨,如下圖所示:
可以看得出這個開門動畫還是比較簡單的盯串,主要關(guān)鍵點(diǎn)有2個地方:
- 實(shí)現(xiàn)門圍繞右門框旋轉(zhuǎn)。
- 利用關(guān)鍵幀實(shí)現(xiàn)動畫戒良。
1.實(shí)現(xiàn)門圍繞右門框旋轉(zhuǎn)
Three.js中物體的默認(rèn)旋轉(zhuǎn)中心為物體自身的中心体捏,例如有一扇門(實(shí)際上是一個寬為10,高為20糯崎,深度為0.5的立方體)几缭,如果讓它繞Y軸旋轉(zhuǎn)90度,也就是設(shè)置rotation.y
屬性為Math.PI/2
沃呢,結(jié)果如下圖所示:
很顯然年栓,大多數(shù)門不應(yīng)該這樣旋轉(zhuǎn)。那么我們應(yīng)該怎么做才能讓門繞著指定軸旋轉(zhuǎn)呢樟插?這時候我們可以在
門
這個對象外面包一層Object3D
對象韵洋,讓外層物體的旋轉(zhuǎn)中心位置在原本想要旋轉(zhuǎn)的指定位置上,再調(diào)整門在外層物體中的相對位置黄锤,使門的右邊緣在外層物體的中心位置上搪缨。
// 創(chuàng)建門
const door = new THREE.BoxBufferGeometry(10, 20, 0.5);
const doorMaterial = new THREE.MeshLambertMaterial({ color: 0xd88c00 });
const doorMesh = new THREE.Mesh(door, doorMaterial);
// 實(shí)現(xiàn)門圍繞特定軸旋轉(zhuǎn)
const group = new THREE.Group(); // 外層對象
group.position.set(5, 10, 0); // 設(shè)置外層對象的中心為原本想要旋轉(zhuǎn)的位置
group.add(doorMesh); // 把'門'添加進(jìn)外層對象中
doorMesh.position.set(-5, 0, 0); // 調(diào)整門在外層對象中的相對位置
Group
對象和Object3D
對象幾乎是相同的,其目的是使得組中對象在語法上的結(jié)構(gòu)更加清晰鸵熟。
2.利用關(guān)鍵幀實(shí)現(xiàn)動畫
先給用于關(guān)鍵幀動畫的網(wǎng)格模型命名副编。
group.name = 'door'; // 外層網(wǎng)格模型命名為door
動畫將作用于外層對象
group
上,因?yàn)?code>group包含doorMesh
對象流强,所以group
旋轉(zhuǎn)時doorMesh
也會旋轉(zhuǎn)痹届。
接著需要編輯關(guān)鍵幀,需要用到關(guān)鍵幀軌道(KeyframeTrack)
API打月。
const times = [0, 3]; // 關(guān)鍵幀時間數(shù)組队腐,單位'秒'
const rotationValues = [0, -Math.PI / 2]; // 需要轉(zhuǎn)動的角度
// 創(chuàng)建關(guān)鍵幀軌道
const rotationTrack = new THREE.KeyframeTrack(
'door.rotation[y]', // 指定對象中的變形目標(biāo)為Y軸旋轉(zhuǎn)屬性
times, // 關(guān)鍵幀的時間數(shù)組
rotationValues // 與時間數(shù)組中的時間點(diǎn)相關(guān)的值組成的數(shù)組
);
然后使用AnimationClip
API剪輯動畫。
const duration = 3; // 持續(xù)時間奏篙,單位'秒'
// 動畫剪輯
const clip = new THREE.AnimationClip(
'open', // 此剪輯的名稱
duration, // 如果傳入負(fù)數(shù)柴淘,持續(xù)時間將會從傳入的數(shù)組中計(jì)算得到
[rotationTrack] // 一個由關(guān)鍵幀軌道(KeyframeTracks)組成的數(shù)組迫淹。
);
duration
參數(shù)決定了動畫的播放時間,偏小則編輯的幀動畫將不能完全播放为严,偏大則幀動畫播放完后會繼續(xù)空播放敛熬,所以一般設(shè)置為關(guān)鍵幀時間數(shù)組的最大值。
第三個參數(shù)為數(shù)組第股,可以剪輯多個關(guān)鍵幀軌道应民,意思是可以同時進(jìn)行多個不同的動畫,比如在物體旋轉(zhuǎn)的同時可以改變材質(zhì)的顏色或者改變幾何體的大小等夕吻。
最后就是播放編輯好的關(guān)鍵幀數(shù)據(jù)诲锹,需要用到動畫混合器AnimationMixer
API。
const mixer = new THREE.AnimationMixer(group); // 動畫混合器
const AnimationAction = mixer.clipAction(clip); // 返回所傳入的剪輯參數(shù)的AnimationAction
AnimationAction.timeScale = 1; // 可以調(diào)節(jié)播放速度梭冠,默認(rèn)是1辕狰。為0時動畫暫停。值為負(fù)數(shù)時動畫會反向執(zhí)行控漠。
AnimationAction.play(); // 開始播放
AnimationMixer
需要傳入的參數(shù)為混合器播放的動畫所屬的對象,在這里也就是外層對象group
悬钳。
clipAction
方法返回所傳入的剪輯參數(shù)的 AnimationAction盐捷,第一個參數(shù)可以是動畫剪輯(AnimationClip)對象或者動畫剪輯的名稱,所以這里也可以傳入剪輯的名稱“open
”默勾。
設(shè)置完以上所有內(nèi)容后碉渡,你會發(fā)現(xiàn)動畫還是沒有變化,那是因?yàn)檫€有一個很重要的步驟沒有做母剥。我們需要在渲染函數(shù)中render()
調(diào)用動畫混合器mixer
的update
方法滞诺,update
方法需要傳入兩幀渲染的間隔時間,需要用到Clock
對象的getDelta
方法环疼。
const clock = new THREE.Clock(); // 創(chuàng)建時鐘
在render方法中添加:
mixer.update(clock.getDelta()); // 更新動畫
現(xiàn)在我們的動畫就已經(jīng)動了起來习霹。
以下是完整代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js實(shí)現(xiàn)簡單開門動畫</title>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
const stats = new Stats(); // 性能監(jiān)控器,用來查看Three.js渲染幀率
// 創(chuàng)建div
const container = document.createElement('div');
document.body.appendChild(container);
// 創(chuàng)建場景
const scene = new THREE.Scene();
// 創(chuàng)建時鐘
const clock = new THREE.Clock();
// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera( // 透視投影相機(jī)
40, // 視場炫隶,表示能夠看到的角度范圍
window.innerWidth / window.innerHeight, // 渲染窗口的長寬比淋叶,設(shè)置為瀏覽器窗口的長寬比
0.1, // 從距離相機(jī)多遠(yuǎn)的位置開始渲染
2000 // 距離相機(jī)多遠(yuǎn)的位置截止渲染
);
camera.position.set(-20, 60, 30); // 設(shè)置相機(jī)位置
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 是否執(zhí)行抗鋸齒
});
renderer.setPixelRatio(window.devicePixelRatio); // 設(shè)置設(shè)備像素比率。通常用于HiDPI設(shè)備伪阶,以防止輸出畫布模糊煞檩。
renderer.setSize(window.innerWidth, window.innerHeight); // 設(shè)置渲染器大小
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// 創(chuàng)建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 創(chuàng)建平面
const planeGeometry = new THREE.PlaneGeometry(300, 300); // 生成平面幾何
const planeMaterial = new THREE.MeshLambertMaterial({
// 生成材質(zhì)
color: 0xcccccc,
});
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); // 生成平面網(wǎng)格
planeMesh.rotation.x = -Math.PI / 2; //繞X軸旋轉(zhuǎn)90度
scene.add(planeMesh); // 添加到場景中
// 創(chuàng)建平行光源
const light = new THREE.DirectionalLight(0xffffff, 1); // 平行光,顏色為白色栅贴,強(qiáng)度為1
light.position.set(-40, 40, 20); // 設(shè)置燈源位置
scene.add(light); // 添加到場景中
// 創(chuàng)建門框
const doorFrameSide = new THREE.BoxBufferGeometry(1, 20, 2); // 側(cè)邊門框
const doorFrameTop = new THREE.BoxBufferGeometry(12, 1, 2); // 上門框
const doorFrameMaterial = new THREE.MeshLambertMaterial({
color: 0xad4800,
});
const doorFrameLeftMesh = new THREE.Mesh(
doorFrameSide,
doorFrameMaterial
);
const doorFrameTopMesh = new THREE.Mesh(doorFrameTop, doorFrameMaterial);
const doorFrameRightMesh = doorFrameLeftMesh.clone();
doorFrameLeftMesh.position.set(-5.5, 10, 0);
doorFrameRightMesh.position.set(5.5, 10, 0);
doorFrameTopMesh.position.set(0, 20.5, 0);
scene.add(doorFrameLeftMesh);
scene.add(doorFrameRightMesh);
scene.add(doorFrameTopMesh);
// 創(chuàng)建門
const door = new THREE.BoxBufferGeometry(10, 20, 0.5);
const doorMaterial = new THREE.MeshLambertMaterial({ color: 0xd88c00 });
const doorMesh = new THREE.Mesh(door, doorMaterial);
// 實(shí)現(xiàn)門圍繞特定軸旋轉(zhuǎn)
const group = new THREE.Group();
group.position.set(5, 10, 0); // 設(shè)置外層對象的中心為原本想要旋轉(zhuǎn)的位置
group.add(doorMesh);
group.name = 'door';
doorMesh.position.set(-5, 0, 0);
scene.add(group);
const times = [0, 3]; // 關(guān)鍵幀時間數(shù)組
const rotationValues = [0, -Math.PI / 2];
const rotationTrack = new THREE.KeyframeTrack(
'door.rotation[y]',
times,
rotationValues
); // 關(guān)鍵幀軌道
const duration = 3; // 持續(xù)時間 (單位秒)
const clip = new THREE.AnimationClip('open', duration, [rotationTrack]); // 動畫剪輯
// 播放編輯好的關(guān)鍵幀數(shù)據(jù)
const mixer = new THREE.AnimationMixer(group); // 動畫混合器
const AnimationAction = mixer.clipAction(clip); // 返回所傳入的剪輯參數(shù)的AnimationAction
AnimationAction.timeScale = 1; // 可以調(diào)節(jié)播放速度斟湃,默認(rèn)是1。為0時動畫暫停檐薯。值為負(fù)數(shù)時動畫會反向執(zhí)行凝赛。
AnimationAction.play(); // 開始播放
render();
function render() {
requestAnimationFrame(render);
stats.begin();
renderer.render(scene, camera);
mixer.update(clock.getDelta());
stats.end();
}
</script>
</body>
</html>