Three.js 進(jìn)階之旅:物理效果-碰撞和聲音

聲明:本文涉及圖文和模型素材僅用于個(gè)人學(xué)習(xí)、研究和欣賞,請(qǐng)勿二次修改、非法傳播、轉(zhuǎn)載、出版、商用、及進(jìn)行其他獲利行為。

摘要
本文內(nèi)容主要匯總?cè)绾卧?Three.js
?創(chuàng)建的?3D
?世界中添加物理效果,使其更加真實(shí)。所謂物理效果指的是對(duì)象會(huì)有重力,它們可以相互碰撞,施加力之后可以移動(dòng),而且通過(guò)鉸鏈和滑塊還可以在移動(dòng)過(guò)程中在對(duì)象上施加約束。?通過(guò)本文的閱讀,你將學(xué)習(xí)到如何使用?Cannon.js
?在?Three.js
?中創(chuàng)建一個(gè)?3D
?物理世界,并在物理世界更新對(duì)象、聯(lián)系材質(zhì)、施加外力、處理多個(gè)物體中添加物體之間的碰撞效果,通過(guò)檢測(cè)碰撞激烈程度來(lái)添加撞擊聲音等。

效果
本文最終將實(shí)現(xiàn)如下所示的效果,點(diǎn)擊?DAT.GUI
?中創(chuàng)建立方體???
?和球體???
?的按鈕,對(duì)應(yīng)的物體將在擁有重力的三維世界中墜落,物體與地面及物體與物體之間發(fā)生碰撞時(shí)可以產(chǎn)生與碰撞強(qiáng)度匹配的撞擊音頻???
,點(diǎn)擊重置按鈕,創(chuàng)建的物體將被清除。

打開(kāi)以下鏈接,在線預(yù)覽效果,大屏訪問(wèn)效果更佳。
?????
?在線預(yù)覽地址:https://dragonir.github.io/physics-cannon
本專欄系列代碼托管在?Github
?倉(cāng)庫(kù)【threejs-odessey】,后續(xù)所有目錄也都將在此倉(cāng)庫(kù)中更新。
??
?代碼倉(cāng)庫(kù)地址:git@github.com:dragonir/threejs-odessey.git

原理
在專欄之前的原理和示例學(xué)習(xí)中,我們已經(jīng)可以使用光照、陰影、Raycaster
?等特性生成一些簡(jiǎn)單的物理效果,但是如果需要實(shí)現(xiàn)像物體張力、摩擦力、拉伸、反彈等物理效果時(shí),我們可以使用一些專業(yè)的物理特性開(kāi)源庫(kù)來(lái)實(shí)現(xiàn)。
為了實(shí)現(xiàn)物理效果,我們將在?Three.js
?中創(chuàng)建一個(gè)物理世界,它純粹是理論性質(zhì)的,我們無(wú)法直接看到它,但是在其中,三維物體將產(chǎn)生掉落、碰撞、摩擦、滑動(dòng)等物理特性。具體原理是當(dāng)我們?cè)?Three.js
?中創(chuàng)建一個(gè)網(wǎng)格模型時(shí),同時(shí)會(huì)將其添加到物理世界中,在每一幀渲染任何內(nèi)容之前我們會(huì)告訴物理世界如何自行更新,然后我們將獲取物理世界中更新的位移和旋轉(zhuǎn)坐標(biāo)數(shù)據(jù),將其應(yīng)用到?Three.js
?三維網(wǎng)格中。

庫(kù)
已經(jīng)有很多功能完備的物理特性庫(kù),我們就沒(méi)必要重復(fù)造輪子了。物理特性庫(kù)可以分為?2D
?庫(kù)和?3D
?庫(kù),雖然我們是使用?Three.js
?開(kāi)發(fā)三維功能,但是有些?2D庫(kù)
?在三維世界中同樣是適用的而且它們的性能會(huì)更好,如果我們需要開(kāi)發(fā)的物理功能是碰撞類的,則可以使用?2D
?庫(kù),比如Ouigo Let's play就是一個(gè)使用?2D
?庫(kù)開(kāi)發(fā)的優(yōu)秀示例。下面是一些常用的物理特性庫(kù)。
對(duì)于?3D
?物理庫(kù),主要有以下三個(gè):
Ammo.js
官網(wǎng):http://schteppe.github.io/ammo.js-demos/
倉(cāng)庫(kù):https://github.com/kripken/ammo.js/
文檔:https://github.com/kripken/ammo.js/#readme
Bullet
?一個(gè)使用?C++
?編寫(xiě)的物理引擎的?JavaScript
?直接移植包比較重量級(jí),當(dāng)前仍然由社區(qū)更新維護(hù)
Cannon.js
官網(wǎng):https://schteppe.github.io/cannon.js/
倉(cāng)庫(kù):https://github.com/schteppe/cannon.js
文檔:http://schteppe.github.io/cannon.js/docs/
比?
Ammo.js
?更加輕量級(jí),使用起來(lái)更舒服主要由一個(gè)開(kāi)發(fā)者維護(hù),已經(jīng)多年未更新,有一個(gè)維護(hù)的?
fork
?是?cannon-es
Oimo.js
官網(wǎng):https://lo-th.github.io/Oimo.js/
倉(cāng)庫(kù):https://github.com/lo-th/Oimo.js
文檔:http://lo-th.github.io/Oimo.js/docs.html
比?
Ammo.js
?輕量且更容易入手主要由一個(gè)開(kāi)發(fā)者維護(hù),已經(jīng)有兩年沒(méi)有更新
對(duì)于?2D
?物理庫(kù),有很多,下面列出了比較流行的幾個(gè):
Matter.js
官網(wǎng):https://brm.io/matter-js/
倉(cāng)庫(kù):https://github.com/liabru/matter-js
文檔:https://brm.io/matter-js/docs/
主要由一個(gè)開(kāi)發(fā)者維護(hù),目前仍在更新中
P2.js
官網(wǎng):https://schteppe.github.io/p2.js/
倉(cāng)庫(kù):https://github.com/schteppe/p2.js
文檔:http://schteppe.github.io/p2.js/docs/
主要由一個(gè)開(kāi)發(fā)者維護(hù),已經(jīng)有2年沒(méi)有更新
Planck.js
官網(wǎng):https://piqnt.com/planck.js/
倉(cāng)庫(kù):https://github.com/shakiba/planck.js
文檔:https://github.com/shakiba/planck.js/tree/master/docs
主要由一個(gè)開(kāi)發(fā)者維護(hù),目前仍在更新中
Box2D.js
官網(wǎng):http://kripken.github.io/box2d.js/demo/webgl/box2d.html
倉(cāng)庫(kù):https://github.com/kripken/box2d.js/
文檔:無(wú)
主要由一個(gè)開(kāi)發(fā)者維護(hù),目前仍在更新中
本文內(nèi)容及示例將使用?Cannon.js
?庫(kù),因?yàn)樗菀桌斫夂褪褂?,?duì)于其他庫(kù),使用原理基本上是一樣的,大家感興趣的話可以自行嘗試。
Cannon.js
Cannon.js
?是一個(gè)?3D
?物理引擎,通過(guò)為物體賦予真實(shí)的物理屬性的方式來(lái)計(jì)算運(yùn)動(dòng)、旋轉(zhuǎn)和碰撞檢測(cè)。Cannon.js
?相較于其他常見(jiàn)的物理引擎來(lái)說(shuō),比較輕量級(jí)而且完全通過(guò)?JavaScript
?來(lái)實(shí)現(xiàn)。主要有以下特性:
剛體動(dòng)力學(xué)
離散碰撞檢測(cè)
接觸、摩擦和恢復(fù)
點(diǎn)到點(diǎn)約束、鉸鏈約束、鎖緊裝置約束等
Gauss-Seidel
?約束求解器與孤島分割算法碰撞過(guò)濾
剛體休眠
實(shí)驗(yàn)性?
SPH
?流體支持各種形狀和碰撞算法
Cannon-es
Cannon.js
?庫(kù)已經(jīng)多年沒(méi)有更新了,但是另一庫(kù)?Cannon-es
?克隆了原倉(cāng)庫(kù)并致力于長(zhǎng)期更新維護(hù)新的倉(cāng)庫(kù),可以像下面這樣安裝并使用,Cannon-es
?用法和?Cannon.js
?用法是完全一致的。
Git
?倉(cāng)庫(kù):https://github.com/pmndrs/cannon-esNPM
?地址:https://www.npmjs.com/package/cannon-es

實(shí)現(xiàn)
??
?本文示例及相關(guān)教程翻譯并整理自?three.js journey?相關(guān)課程。
開(kāi)始
安裝并引入
初始化場(chǎng)景是一個(gè)平面???
?和一個(gè)球體???
,為了更好觀察物理特性,已經(jīng)開(kāi)啟了陰影效果。

我們可以使用?WebGL
?創(chuàng)建一個(gè)無(wú)重力的太空?qǐng)鼍?,但是為了模擬地球環(huán)境???
?,就需要添加重力,在?Cannon.js
?中可以通過(guò)修改?gravity
?屬性值來(lái)實(shí)現(xiàn),它是一個(gè)?Cannon.js Vec3
?值,和?Three.js
?中的?Vector3
?一樣,它包含?x
、y
、z
?屬性且擁有一個(gè)?set(...)
?方法
我們使用?-9.82
?作為重力的?y
?值,是因?yàn)樗?strong>地球的重力系數(shù),如果你想讓物理墜落的更慢或者想創(chuàng)建一個(gè)火星重力環(huán)境???
?,就可以把它改為其他數(shù)值。
基礎(chǔ)
世界
首先,我們需要?jiǎng)?chuàng)建一個(gè)?Cannon.js
?世界:
對(duì)象
我們?cè)趫?chǎng)景中已經(jīng)創(chuàng)建了一個(gè)球體,現(xiàn)在來(lái)在?Cannon.js
?世界中創(chuàng)建一個(gè)球體。為了實(shí)現(xiàn)它,我們首先必須創(chuàng)建一個(gè)剛體Body,剛體是一種簡(jiǎn)單的對(duì)象,可以墜落和其他剛體產(chǎn)生碰撞。創(chuàng)建剛提前,我們首先需要決定剛體的形狀,有很多形狀可選,比如?Box
、Cylinder
、Plane
?等,我們創(chuàng)建一個(gè)和?Three.js
?中球體相同半徑的球狀剛體:
然后,創(chuàng)建一個(gè)初始化?mass
?質(zhì)量及?position
?位置的?Body
?剛體:
最后,我們通過(guò)?addBody(...)
?方法將創(chuàng)建的剛體添加到世界中:
此時(shí)查看頁(yè)面可以看到?jīng)]有任何效果,我們還需要更新?Cannon.js
?世界和?Three.js
?球體坐標(biāo)。為更新物理世界world
,我們必須使用時(shí)間步長(zhǎng)step(...)
方法。
更新
現(xiàn)在需要實(shí)現(xiàn)更新?Cannon.js
?世界和?Three.js
?場(chǎng)景。此時(shí)我們需要使用?step(...)
?方法,為了使其生效,必須提供一個(gè)固定時(shí)間步長(zhǎng)、自上次調(diào)用函數(shù)以來(lái)經(jīng)過(guò)的時(shí)間、以及每個(gè)函數(shù)調(diào)用可執(zhí)行的最大固定步驟數(shù)作為參數(shù)。
dt
:固定時(shí)間戳,要使用的固定時(shí)間步長(zhǎng)[timeSinceLastCalled]
:自上次調(diào)用函數(shù)以來(lái)經(jīng)過(guò)的時(shí)間[maxSubSteps=10]
:每個(gè)函數(shù)調(diào)用可執(zhí)行的最大固定步驟數(shù)
??
?關(guān)于時(shí)間步長(zhǎng)原理,可查看此文章
在動(dòng)畫(huà)函數(shù)中,我們希望以?60fps
?運(yùn)行,因此將第一個(gè)參數(shù)設(shè)置為?1/60
,這個(gè)設(shè)置在更高或更低幀率的情況下都能以相同速度運(yùn)行;對(duì)于第二個(gè)參數(shù),我們需要計(jì)算自上一幀以來(lái)經(jīng)過(guò)了多少時(shí)間,通過(guò)將前一幀的?elapsedTime
?減去當(dāng)前?elapsedTime
?來(lái)獲得,不要直接使用?Clock
?類中的?getDelta()
?方法,因?yàn)闊o(wú)法得到預(yù)期的結(jié)果還會(huì)弄亂內(nèi)部邏輯;第三個(gè)迭代參數(shù),可以隨便設(shè)置一個(gè)值,運(yùn)行體驗(yàn)是否絲滑并不重要。
此時(shí)查看頁(yè)面,看起來(lái)仍然沒(méi)有變化,但實(shí)際上物理世界中的球體剛體?sphereBody
?正在不斷下墜,可以通過(guò)如下的打印日志???
?可以觀察到。

現(xiàn)在我們需要使用物理世界的?sphereBody
?剛體坐標(biāo)來(lái)更新?Three.js
?中的球體,可以使用如下兩種方法實(shí)現(xiàn)該功能:
??
?copy方法在 Vector2、Vector3、Euler、Quaternion 甚至 Material、Object3D、Geometry 等類中都是可用的。

此時(shí)就能看到小球???
?墜落的效果,但是它直接穿過(guò)了地面,因?yàn)楝F(xiàn)在僅在?Three.js
?場(chǎng)景中添加了地面,而沒(méi)有在?Cannon.js
?物理世界中創(chuàng)建地面的剛體。
現(xiàn)在我們使用平面形狀?Plane
?來(lái)創(chuàng)建地面剛體,地面不應(yīng)該受到物理世界重力的影響而下沉,它應(yīng)該是保持靜止不動(dòng)的,我們可以通過(guò)如下方法將?mass
?設(shè)置為?0
?來(lái)實(shí)現(xiàn):

此時(shí)你會(huì)發(fā)現(xiàn)小球???
?墜落的方向變了,并不是我們預(yù)期的結(jié)果,它應(yīng)該落到地面上。因?yàn)槲锢硎澜缰刑砑拥钠矫媸敲嫦蛳鄼C(jī)???
?的,我們需要像在?Three.js
?中旋轉(zhuǎn)平面一樣對(duì)它進(jìn)行旋轉(zhuǎn)。在?Cannon.js
?中,我們只能使用四元數(shù)?Quaternion
?來(lái)對(duì)剛體進(jìn)行旋轉(zhuǎn),可以通過(guò)?setFromAxisAngle(...)
?方法:
第一個(gè)參數(shù)是旋轉(zhuǎn)軸
第二個(gè)參數(shù)是角度

現(xiàn)在可以看到小球???
?從高處下落并且停在地面上,因?yàn)榈孛媸庆o止不動(dòng)的,因此我們不需要使用?Cannon.js
?中的地面來(lái)更新?Three.js
?中的地面。
聯(lián)系材質(zhì)
從上圖可以觀察到,小球???
?墜落到地面后并沒(méi)有反復(fù)彈跳,我們可以通過(guò)修改設(shè)置?Cannon.js
?中的?Material
?和?ContactMaterial
?來(lái)添加摩擦和彈跳效果。
一個(gè)?Material
?僅僅是一個(gè)類,你可以用它創(chuàng)建一種材質(zhì)并命名后將它關(guān)聯(lián)到?Body
?剛體上,對(duì)于場(chǎng)景中所有的材質(zhì),都可以通過(guò)此方法進(jìn)行創(chuàng)建。比如,假設(shè)世界中的所有物體都是塑料材質(zhì)的,此時(shí)你只需創(chuàng)建一種材質(zhì)即可,可以將它命名為?default
?或?plastic
;如果場(chǎng)景中地面和小球是不同材質(zhì)的,就需要根據(jù)它們的類型創(chuàng)建多種材質(zhì)。下面我們?yōu)槭纠械膬深愇矬w分別創(chuàng)建名為混凝土?concrete
?和 塑料?plastic
?的材質(zhì):
接下來(lái),我們使用創(chuàng)建的兩種材質(zhì)來(lái)創(chuàng)建聯(lián)系材質(zhì)?ContactMaterial
,它是兩種材質(zhì)的組合,包含對(duì)象碰撞時(shí)的屬性。然后使用?addContactMaterial(...)
?方法將它添加到世界中:
前兩個(gè)參數(shù)是材質(zhì)
第三個(gè)參數(shù)是碰撞屬性對(duì)象,包含摩擦系數(shù)和恢復(fù)系數(shù),兩者的默認(rèn)值均為?
0.3
接著我們將創(chuàng)建好的?Material
?應(yīng)用到?Body
?上,可以在實(shí)例化主體時(shí)直接傳遞材質(zhì),也可以在實(shí)例化之后使用材質(zhì)屬性傳遞材質(zhì)?,F(xiàn)在可以看到小球???
?下落后在停止之前會(huì)返回彈跳多次:

場(chǎng)景中一般會(huì)有多種材質(zhì)?Materials
?的物體,為每種兩兩組合創(chuàng)建?ContactMaterial
?會(huì)費(fèi)時(shí)費(fèi)解,為了簡(jiǎn)化這一操作,我們來(lái)使用一種默認(rèn)材質(zhì)來(lái)替換創(chuàng)建聯(lián)系材質(zhì)時(shí)的兩種材質(zhì),并將它應(yīng)用到所有剛體上:

可以觀察到效果是相同的?;蛘呶覀冎苯釉O(shè)置世界的默認(rèn)聯(lián)系材質(zhì)defaultContactMaterial
?屬性,然后移除?sphereBody
?和?floorBody
?的?material
?屬性,這樣世界中的所有材質(zhì)就都是相同的默認(rèn)材質(zhì)。
施加外力
對(duì)一個(gè)剛體?Body
?有以下幾種施加外力的方法:
applyForce(force, worldPoint)
:從空間中的一個(gè)特殊點(diǎn)對(duì)剛體施加力(不一定在剛體的表面),比如就像風(fēng)推動(dòng)所有物體一樣,或微弱但突然的力推向多米諾骨牌,或者像強(qiáng)烈且突然的力把憤怒的小鳥(niǎo)推向城堡一樣。force
:力的大小?Vec3
worldPoint
:施加力的世界點(diǎn)?Vec3
applyImpulse
:類似于?applyForce
,但它不是因?yàn)樵黾訉?dǎo)致加速度改變,而是直接作用于加速度。applyLocalForce(force, localPoint)
:與?applyForce
?相同,但是坐標(biāo)系是剛體的局部坐標(biāo),即?(0, 0, 0)
?將是剛體的中點(diǎn),從物體的內(nèi)部施力。force
:要應(yīng)用的力向量?Vec3
localPoint
:剛體中中要施加力的局部點(diǎn)?Vec3
applyLocalImpulse
:與?applyImpulse
?相同,但是坐標(biāo)系是剛體的局部坐標(biāo),即從物體的內(nèi)部施力。
現(xiàn)在我們使用?applyLocalForce(...)
?來(lái)為小球剛體?sphereBody
?開(kāi)始時(shí)施加一個(gè)小沖擊力:
可以看到小球???
?向右彈跳并滾動(dòng)。

現(xiàn)在我們使用?applyForce(...)
?方法來(lái)施加一點(diǎn)風(fēng)力???
?,因?yàn)轱L(fēng)是永久性的,因此在更新?World
?之前,我們需要將這種力施加到每一幀。要正確應(yīng)用此力,受力點(diǎn)應(yīng)該是小球的位置?sphereBody.position
:

處理多個(gè)物體
對(duì)一個(gè)或兩個(gè)物體添加物理效果比較簡(jiǎn)單,但是為很多個(gè)物體都按上述方法添加就會(huì)非常復(fù)雜,我們需要添加一個(gè)自動(dòng)化處理方法。
自動(dòng)處理函數(shù)
首先,移除或注釋掉?Cannon.js
?世界和?Three.js
?中的球體,還有動(dòng)畫(huà)函數(shù)?tick()
?中球體的設(shè)置,然后創(chuàng)建一個(gè)?createSphere
?方法來(lái)生成小球:
接著使用如下方法來(lái)創(chuàng)建一個(gè)小球???
?,其中?position
?參數(shù)不必是?Three.js
?中的?Vector3
?或者?Cannon.js
?中的?Vec3
,只需使用?x, y ,z
?即可:
可以看到地面頂部的創(chuàng)建的小球,但是由于我們移除了將?Cannon.js
?世界中小球的?position
?拷貝到?Three.js
?中的方法,現(xiàn)在的小球暫時(shí)沒(méi)有物理下墜效果。

使用一個(gè)對(duì)象數(shù)組
為了使批量創(chuàng)建的小球得到更新,我們使用一個(gè)數(shù)組?objectsToUpdate
?在創(chuàng)建函數(shù)中保存它們:
然后在動(dòng)畫(huà)方法?tick()
?中批量將小球的?body.position
?拷貝到?mesh.position
:
此時(shí),批量創(chuàng)建的小球???
?也有物理效果了。

添加Dat.GUI
為了方便調(diào)試,我們給頁(yè)面按如下方式添加?Dat.GUI
?調(diào)試工具,并添加一個(gè)?createSphere
?來(lái)在場(chǎng)景中創(chuàng)建多個(gè)小球:


優(yōu)化
因?yàn)?Three.js
?網(wǎng)格?Mesh
?的?geometry
?和?material
?都是一樣的,我們應(yīng)該將其移出?createSphere
?方法,由于我們使用?radius
?來(lái)創(chuàng)建幾何體的,為了兼容之前的方法,我們可以按如下方式將?SphereGeometry
?半徑設(shè)置為?1
,并使用?scale
?來(lái)調(diào)整幾何體的大小,得到的結(jié)果和上面是一致的,但是性能得以提升:
添加立方體
現(xiàn)在我們使用相同的流程添加一個(gè)創(chuàng)建立方體???
?的方法?createBox
,其中傳入的參數(shù)將是?width
,height
,depth
,position
。需要注意的是,Cannon.js
?中創(chuàng)建Box
?與?Three.js
?創(chuàng)建?Box
?不同,在?Three.js
?中,創(chuàng)建幾何體BoxBufferGeometry
?只需要直接提供立方體的寬高深就行,但是在Cannon.js中,它是根據(jù)立方體對(duì)角線距離的一半來(lái)計(jì)算生成形狀,因此其寬高深必須乘以0.5。
先移除創(chuàng)建小球的方法,頁(yè)面運(yùn)行可以得到如下的結(jié)果:

現(xiàn)在可以創(chuàng)建隨機(jī)的立方體了,但是看起來(lái)有點(diǎn)奇怪不太逼真是不是?因?yàn)榱⒎襟w掉下來(lái)后沒(méi)有翻轉(zhuǎn),原因是?Three.js
?中的網(wǎng)格沒(méi)有像?Cannon.js
?中的剛體一樣旋轉(zhuǎn),在球體的示例中我們沒(méi)有發(fā)現(xiàn)是因?yàn)闊o(wú)論球體是否旋轉(zhuǎn)都是和原來(lái)一樣的,而在立方體中不一樣。我們可以通過(guò)如下將剛體的?quaternion
?屬性拷貝到網(wǎng)格的?quaternion
?屬性來(lái)實(shí)現(xiàn),就像之前拷貝位置屬性?position
?一樣:
現(xiàn)在立方體???
?墜落時(shí)的旋轉(zhuǎn)也正常了。


性能優(yōu)化
Broadphase ?
測(cè)試物體之間的碰撞時(shí),一種方法是檢測(cè)一個(gè)剛體與另外所有其他剛體之間的碰撞,雖然這一操作很容易實(shí)現(xiàn),但是非常耗費(fèi)性能。此時(shí)就需要?Broadphase
,它會(huì)在測(cè)試之前對(duì)剛體進(jìn)行粗略的分類,想象一下,兩堆相距很遠(yuǎn)的立方體,為什么要用一堆立方體來(lái)測(cè)試另一堆立方體之間的碰撞關(guān)系能,它們相距很遠(yuǎn),不會(huì)發(fā)生碰撞,因此就沒(méi)必要測(cè)試來(lái)耗費(fèi)性能。
在?Cannon.js
?中共有?3
?種?Broadphase
?算法:
NaiveBroadphase
:測(cè)試每個(gè)剛體與其他所有剛體之間的碰撞,默認(rèn)算法。GridBroadphase
: 使用四邊形柵格覆蓋?world
,僅針對(duì)同一柵格或相鄰柵格中的其他剛體進(jìn)行碰撞測(cè)試。SAPBroadphase
:掃描剪枝算法,在多個(gè)步驟的任意軸上測(cè)試剛體。
NaiveBroadphase
?是默認(rèn)檢測(cè)方法,但是推薦使用?SAPBroadphase
?算法,雖然這種算法有時(shí)可能會(huì)產(chǎn)生檢測(cè)不會(huì)發(fā)生碰撞的錯(cuò)誤,但是它的檢測(cè)速度非???。通過(guò)如下方式,簡(jiǎn)單設(shè)置?world.broadphase
?屬性即可修改碰撞檢測(cè)算法:
Sleep ??
即使我們使用改進(jìn)的?Broadphase
?碰撞檢測(cè)算法,有可能所有的剛體都會(huì)被檢測(cè),即使是那些不再發(fā)生移動(dòng)的剛體。此時(shí)我們可以使用稱為?Sleep
?的特性,當(dāng)剛體的速度逐漸變小不再發(fā)生移動(dòng),它就會(huì)進(jìn)入睡眠狀態(tài),此時(shí)就不會(huì)對(duì)它進(jìn)行碰撞檢測(cè),除非使用代碼讓其施加一個(gè)足夠的力再次運(yùn)動(dòng)或有其它的剛體擊中它??梢酝ㄟ^(guò)對(duì)?world
?設(shè)置?allowSleep
?屬性為?true
?來(lái)實(shí)現(xiàn):
你也可以使用?sleepSpeedLimit
、sleepTimeLimit
?屬性對(duì)睡眠速度和時(shí)間進(jìn)行詳細(xì)設(shè)置,但是一般不會(huì)改變默認(rèn)值。
事件
可以對(duì)剛體的事件進(jìn)行監(jiān)聽(tīng),比如你想在物體發(fā)生碰撞時(shí)播放呻吟或者在射擊游戲中檢測(cè)是否命中敵人等情況下是非常有用的。你可以在剛體上監(jiān)聽(tīng)?colide
、sleep
、wakeup
?等事件。
現(xiàn)在,我們來(lái)實(shí)現(xiàn)一下當(dāng)場(chǎng)景中的小球???
?和立方體???
?互相之間發(fā)生碰撞時(shí)播放聲音???
?的功能。首先在?JavaScript
?中創(chuàng)建音頻,并添加一個(gè)方法來(lái)播放它。
??
?有些瀏覽器比如?Chrome
?默認(rèn)會(huì)靜音???
?除非用戶與頁(yè)面發(fā)生交互,例如點(diǎn)擊任意區(qū)域,所以不要擔(dān)心首次加載時(shí)不播放聲音的問(wèn)題
然后在創(chuàng)建立方體方法?createBox
?中調(diào)用:

此時(shí),當(dāng)立方體???
?撞擊到地面或相互碰撞時(shí)可以聽(tīng)到撞擊聲音???
,看起來(lái)似乎是正確的,但是當(dāng)添加多個(gè)立方體時(shí),我們會(huì)聽(tīng)到很多立方體之間相互撞擊的聲音是一樣的,而現(xiàn)實(shí)中的聲音應(yīng)該是根據(jù)聲音隨著立方體之間的撞擊程度而不同,撞擊程度足夠小的話就聽(tīng)不到聲音。為了獲取撞擊的強(qiáng)度,我們需要獲取撞擊信息,可以通過(guò)如下給?playHitSound
?方法添加參數(shù)?collision
?的方式來(lái)獲取撞擊信息:
然后在創(chuàng)建球體的方法?createSphere
?中同樣調(diào)用播放撞擊音頻方法:
移除物體
當(dāng)頁(yè)面上添加過(guò)多物體時(shí),我們可以通過(guò)在?Dat.GUI
?添加一個(gè)重置按鈕來(lái)移除已添加的物體,通過(guò)遍歷?objectsToUpdate
?數(shù)組,將每個(gè)數(shù)組項(xiàng)對(duì)應(yīng)的的?object.body
?從 物理世界?world
?中移除,將?object.mesh
?從?Three.js
?場(chǎng)景中移除,并清除?collide
?碰撞事件的?eventListener
:

總結(jié)
本文中主要包含的知識(shí)點(diǎn)包括:
Three.js
?中添加物理效果基本原理常用?
3D
?與?2D
?物理物理引擎匯總Cannon.js
?與?Cannon-es
?安裝與引用物理世界創(chuàng)建、對(duì)象更新、聯(lián)系材質(zhì)、施加外力、處理多個(gè)物體
碰撞事件監(jiān)聽(tīng)、音頻添加
性能優(yōu)化、物理世界移除物體等
想了解其他前端知識(shí)或其他未在本文中詳細(xì)描述的Web 3D開(kāi)發(fā)技術(shù)相關(guān)知識(shí),可閱讀我往期的文章。如果有疑問(wèn)可以在評(píng)論中留言,如果覺(jué)得文章對(duì)你有幫助,不要忘了一鍵三連哦 ??。

附錄
[1].??? Three.js 打造繽紛夏日3D夢(mèng)中情島
[2].??? Three.js 實(shí)現(xiàn)炫酷的賽博朋克風(fēng)格3D數(shù)字地球大屏
[3].??? Three.js 實(shí)現(xiàn)2022冬奧主題3D趣味頁(yè)面,含冰墩墩
[4].??? Three.js 實(shí)現(xiàn)3D開(kāi)放世界小游戲:阿貍的多元宇宙
[5].??? 掘金1000粉!使用Three.js實(shí)現(xiàn)一個(gè)創(chuàng)意紀(jì)念頁(yè)面
…
【Three.js 進(jìn)階之旅】系列專欄訪問(wèn) ??
更多往期【3D】專欄訪問(wèn) ??
更多往期【前端】專欄訪問(wèn) ??
參考
[1].?three.js journey(https://threejs-journey.com/)
[2].?threejs.org(https://threejs.org)
