iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 21
1

昨天瀏覽過許多 3D 物理引擎後,筆者最後選擇使用 Cannon.js 做為接下來射擊效果的輔助套件,今天先透過練習 Cannon.js 的 Hello world 來理解 Cannon.js 如何與 Three.js 相輔相成。

20_front

Photo by Dawid Zawiła on Unsplash


這是本系列第 20 篇,如果還沒看過第 19 篇可以點以下連結前往:

用 Three.js 來當個創世神 (19):3D 物理引擎初探


Hello Cannon.js

做為熟悉 Cannon.js 語法的第一個練習,當然還是從最基本的 Hello world 開始了:

20_demo

請參考完整原始碼成果展示,可以透過調整 restitution 參數後點擊 resetBall 觀察球體反彈效果的差異。

 

程式碼解析

在程式碼中,筆者將關於 Three.js 的基本設定(場景、渲染器、相機等)統一整理在 initThreeSetting(),而 Cannon.js 所有物理效果相關的內容都放在 initCannonWorld() 中。

然後記得在適當的地方引入 Cannon.js 函式庫:

https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js

1. 建立物理世界

// 建立物理世界
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

2. 建立動態球型剛體

// 建立球剛體
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 則會靜止不動。

3. 建立靜態地板剛體

// 建立地板剛體
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)來處理旋轉,簡單的理解是四元數能更有效地處理旋轉中的任意轉軸與旋轉角。

四元數與旋轉參考文章:

4. 設定碰撞時交互作用屬性

let sphereGroundContact
sphereGroundContact = new CANNON.ContactMaterial(groundCM, sphereCM, {
  friction: 0.5,
  restitution: 0.7 
})
world.addContactMaterial(sphereGroundContact)

這邊可以透過 CANNON.ContactMaterial 來設定在物理世界中的兩個剛體碰撞時的交互作用,設定恢復係數(restitution)可以決定衡量碰撞後的反彈程度,另外也可以設定摩擦力(friction),不過在這個例子感受不太出來摩擦力的差別,猜測是需要有類似車子前進才會有明顯的效果,讀者可以利用範例中的 dat.GUI 調整參數後,使用 resetBall 更新後觀察。

5. 建立球體與地板的網格

// 地板網格
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 中並沒有實際上的畫面,需要建立網格模型才能在場景中看到實體。

6. 更新物理世界動畫

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 的基本語法概念後,明天開始會直接透過專案來試著實現射擊效果,我們明天見!

最後再附上今天的完整原始碼成果展示

 

參考資料


上一篇
用 Three.js 來當個創世神 (19):3D 物理引擎初探
下一篇
用 Three.js 來當個創世神 (21):專案實作#10 - 使用 PointerLockControls 實現射擊遊戲視角 Part.1
系列文
用 Three.js 來當個創世神31

尚未有邦友留言

立即登入留言