Babylon.js:如何與3D場景中的物體交互?
圖片
開發棋類游戲,需要實現鼠標控制棋子的功能,使得游戲能夠將鼠標在屏幕上的位置準確地映射到棋盤上的對應落子點。
如果開發的是2D游戲的話,實現就比較簡單,鼠標屏幕坐標和棋盤都是二維平面。就像下圖圍棋所示,可以根據xy坐標表示鼠標的位置,同時圍棋落子點的坐標也可以計算得出xy坐標,這樣很輕松地就能實現鼠標位置和圍棋落子點坐標的映射和距離比較。
圖片
如果開發的是3D游戲,這個問題就不那么好處理了,增加一個維度意味著復雜度的增加。玩家可以通過變換攝像機從不同角度觀察場景中的物體,這樣也會導致物體在觀察空間下的坐標發生變化,那么在3D場景中怎么樣才能從二維屏幕中的鼠標位置得出鼠標點中哪個物體以及點中物體的具體坐標呢?對于圍棋游戲來說就是怎么判斷鼠標點中位置最接近圍棋盤中具體哪個落子位置呢?
先來梳理一下思路,圍棋棋盤放置到3D空間中的位置是固定的,也就是說圍棋棋盤每個交叉點的xyz坐標可以認為是已知的。那么如果能得到鼠標屏幕位置對應3D空間中棋盤的坐標,那么判定鼠標在棋盤落子點的哪個位置就自然不是問題了。
下圖是計算機圖形學中的視錐體模型,描述的是觀察者(攝像機)在視角下可見的空間范圍,前后兩個平面分別是近平面和遠平面,這里可以近似認為近平面就是計算機屏幕,如果從觀察者到屏幕鼠標做一條射線,通過計算射線和圍棋平面的交點就可以得出鼠標位置點擊到的棋盤坐標,這樣就可以拿射線交點坐標和圍棋棋盤坐標一一比較得出更接近哪個落子點了。
圖片
關于射線和平面的求交算法這里不做講解,如果有興趣請查閱圖形學相關的資料進行查閱。像Babylon.js這樣的3D框架已經實現了創建射線、求射線和網格交點這些算法,這里介紹如何使用Babylon.js的提供的API來實時跟蹤鼠標位置,并獲取觀察者到鼠標射線與場景中物體的交點坐標。
圖片
主要用到的API是scene.pick方法,scene是空間場景Scene類的實例。關于pick方法及參數說明如下:
pick(x, y, predicate?, fastCheck?, camera?, trianglePredicate?) 方法用于從屏幕上的指定位置(x, y)拾取對象。以下是該方法參數的詳細解釋:
x, y:這兩個參數指定了屏幕上的坐標,用于確定拾取操作的起點。這些坐標通常是鼠標事件中獲取的,表示用戶點擊或觸摸屏幕的位置。
predicate?(可選):這是一個過濾函數,允許你自定義拾取邏輯。它接受一個AbstractMesh對象作為參數,并返回一個布爾值。如果返回true,則該網格會被考慮在內進行拾取;如果返回false,則該網格會被忽略。這個參數可以用來排除某些對象,或者只拾取特定類型的網格。例如,你可以設置一個predicate來忽略不可見的網格或者只拾取特定標簽的網格。
fastCheck?(可選):這是一個布爾值參數,用于優化拾取性能。如果設置為true,Babylon.js將跳過一些檢查,從而加快拾取速度,但可能會降低拾取的準確性。默認值為false,意味著執行完整的拾取檢查。
camera?(可選):這個參數允許你指定一個特定的相機來進行拾取操作。如果不提供,將使用場景中的默認相機。這在多相機場景中特別有用,你可以指定哪個相機的視角用于拾取。
trianglePredicate?(可選):這是一個更細粒度的過濾函數,用于在三角形級別上控制拾取邏輯。它接受四個參數:三個表示三角形頂點的向量和一個射線對象。返回一個布爾值,決定是否拾取該三角形。這個參數可以用來實現更復雜的拾取邏輯,比如只拾取面向攝像機的三角形。
pick方法返回一個PickingInfo對象,其中包含了拾取操作的結果,例如拾取的網格、距離、撞擊點等信息。如果沒有拾取到任何對象,則返回null。
下面的示例代碼展示了監測鼠標的實時移動,通過pick方法獲取射線與網絡的相交信息,還可以判斷相交的網絡名稱,如果相交的網格是圍棋棋盤則簡單打印了交點的坐標信息。可見通過Babylon.js提供的API可以非常輕松地實現鼠標和3D場景的交互操作,激發自己的創意并借助框架提供的能力可以輕松地開發出有趣的3D應用。
// 監測鼠標的實時移動
scene.onPointerMove = function castRay() {
// (scene.pointerX, scene.pointerY)為鼠標實時屏幕坐標
var hit = scene.pick(scene.pointerX, scene.pointerY)
// hit.pickedMesh表示射線與場景的物體有相交
// hit.pickedMesh.name表示相交物體的name屬性值
if (hit && hit.pickedMesh && hit.pickedMesh.name == "goboard"){
//hit.pickedPoint表示射線與場景中物體相交點的坐標
console.log(hit.pickedPoint.x, hit.pickedPoint.y, hit.pickedPoint.z)
}
}
參考文獻
[1]. https://doc.babylonjs.com/typedoc/classes/BABYLON.Scene#pick
[2]. https://doc.babylonjs.com/features/featuresDeepDive/mesh/interactions/picking_collisions/