昨天瀏覽過許多 3D 物理引擎後,筆者最後選擇使用 Cannon.js 做為接下來射擊效果的輔助套件,今天先透過練習 Cannon.js 的 Hello world 來理解 Cannon.js 如何與 Three.js 相輔相成。
Photo by Dawid Zawiła on Unsplash
這是本系列第 20 篇,如果還沒看過第 19 篇可以點以下連結前往:
用 Three.js 來當個創世神 (19):3D 物理引擎初探
做為熟悉 Cannon.js 語法的第一個練習,當然還是從最基本的 Hello world 開始了:
請參考完整原始碼及成果展示,可以透過調整 restitution
參數後點擊 resetBall
觀察球體反彈效果的差異。
在程式碼中,筆者將關於 Three.js 的基本設定(場景、渲染器、相機等)統一整理在 initThreeSetting()
,而 Cannon.js 所有物理效果相關的內容都放在 initCannonWorld()
中。
然後記得在適當的地方引入 Cannon.js 函式庫:
https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js
// 建立物理世界
world = new CANNON.World()
// 設定重力場為 y 軸 -9.8 m/s²
world.gravity.set(0, -9.8, 0)
// 碰撞偵測
world.broadphase = new CANNON.NaiveBroadphase()
首先需建立一個 CANNON.World
物件,用它作為物理世界的容器來管理世界中的剛體與模擬行為,在這裡簡單設定物理世界的重力場為 y
軸向下 9.8 m/s²
,並設定碰撞偵測的模式,預設就是 CANNON.NaiveBroadphase
。
// 建立球剛體
let sphereShape = new CANNON.Sphere(1)
let sphereCM = new CANNON.Material()
let sphereBody = new CANNON.Body({
mass: 5,
shape: sphereShape,
position: new CANNON.Vec3(0, 10, 0),
material: sphereCM
})
world.add(sphereBody)
在物理世界中會創建剛體(rigid body)
來代表場景中物體的各種物理特性。
創建剛體的步驟跟在 Three.js 中創建網格模型很像,設定形狀與材質,組成一個剛體後加到世界中。而這裡需注意的是球體的 mass(質量)
設為 5
kg,才會有重力的效果,若是設成 0
則會靜止不動。
// 建立地板剛體
let groundShape = new CANNON.Plane()
let groundCM = new CANNON.Material()
let groundBody = new CANNON.Body({
mass: 0,
shape: groundShape,
material: groundCM
})
// setFromAxisAngle 旋轉 x 軸 -90 度
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
world.add(groundBody)
建立地板的剛體跟上面差不多,差別在於這裡將質量設為 0
讓地板是靜止狀態。另外預設的地板在 x-y
平面,一樣需要將地板沿著 x
軸逆時針轉 90
度攤平在 x-z
平面上。
在 Cannon.js 範例中會常看到使用四元數(Quaternion)來處理旋轉,簡單的理解是四元數能更有效地處理旋轉中的任意轉軸與旋轉角。
四元數與旋轉參考文章:
let sphereGroundContact
sphereGroundContact = new CANNON.ContactMaterial(groundCM, sphereCM, {
friction: 0.5,
restitution: 0.7
})
world.addContactMaterial(sphereGroundContact)
這邊可以透過 CANNON.ContactMaterial
來設定在物理世界中的兩個剛體碰撞時的交互作用,設定恢復係數(restitution)
可以決定衡量碰撞後的反彈程度,另外也可以設定摩擦力(friction)
,不過在這個例子感受不太出來摩擦力的差別,猜測是需要有類似車子前進才會有明顯的效果,讀者可以利用範例中的 dat.GUI 調整參數後,使用 resetBall
更新後觀察。
// 地板網格
let groundGeometry = new THREE.PlaneGeometry(20, 20, 32)
let groundMaterial = new THREE.MeshLambertMaterial({
color: 0xa5a5a5,
side: THREE.DoubleSide
})
let ground = new THREE.Mesh(groundGeometry, groundMaterial)
ground.rotation.x = -Math.PI / 2
ground.receiveShadow = true
scene.add(ground)
// 球網格
let sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
let sphereMaterial = new THREE.MeshStandardMaterial({ color: 0x33aaaa })
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true
scene.add(sphere) = true
scene.add(sphere)
前面創建的剛體在 Three.js 中並沒有實際上的畫面,需要建立網格模型才能在場景中看到實體。
const timeStep = 1.0 / 60.0 // seconds
function render() {
world.step(timeStep)
if (sphere) {
sphere.position.copy(sphereBody.position)
sphere.quaternion.copy(sphereBody.quaternion)
}
requestAnimationFrame(render)
renderer.render(scene, camera)
}
最後是為物理世界設定持續更新的動畫效果,這邊簡單設定 timeStep 為 60Hz,並且同步更新網格模型的位置需與剛體同步。
今天練習利用 Cannon.js 建立第一個擁有物理效果的 3D 世界,可以發現任何新技術從 Hello world 開始學起就是這麼直白簡單,瞭解了 Cannon.js 的基本語法概念後,明天開始會直接透過專案來試著實現射擊效果,我們明天見!