從零開始手敲次世代遊戲引擎(四十三)

接從零開始手敲次世代遊戲引擎(四十二),我們繼續我們的物理引擎探險之路。

之前我們都是在OGEX文件當中通過硬性編碼來指定物體的碰撞盒,現在讓我嘗試根據場景物體的頂點數據自動生成碰撞盒。我們首先依然是從生成AABB碰撞盒開始。

首先,讓我們在我們的MyPhysicsManager的CreateRigidBody的代碼當中,加入當場景物體沒有顯式指定碰撞盒時自動生成碰撞盒的代碼。也就是下面所示的swtich-case的default分支部分:

switch(geometry.CollisionType()) { case SceneObjectCollisionType::kSceneObjectCollisionTypeSphere: { auto collision_box = make_shared<Sphere>(param[0]); const auto trans = node.GetCalculatedTransform(); auto motionState = make_shared<MotionState>( *trans ); rigidBody = new RigidBody(collision_box, motionState); } break; case SceneObjectCollisionType::kSceneObjectCollisionTypeBox: { auto collision_box = make_shared<Box>(Vector3f(param[0], param[1], param[2])); const auto trans = node.GetCalculatedTransform(); auto motionState = make_shared<MotionState>( *trans ); rigidBody = new RigidBody(collision_box, motionState); } break; case SceneObjectCollisionType::kSceneObjectCollisionTypePlane: { auto collision_box = make_shared<Plane>(Vector3f(param[0], param[1], param[2]), param[3]); const auto trans = node.GetCalculatedTransform(); auto motionState = make_shared<MotionState>( *trans ); rigidBody = new RigidBody(collision_box, motionState); } break; default: { // create AABB box according to Bounding Box auto bounding_box = geometry.GetBoundingBox(); auto collision_box = make_shared<Box>(bounding_box.extent); const auto trans = node.GetCalculatedTransform(); auto motionState = make_shared<MotionState>( *trans, bounding_box.centroid ); rigidBody = new RigidBody(collision_box, motionState); } } node.LinkRigidBody(rigidBody);

我們通過獲取場景物體的Bounding Box,也就是包圍盒,來生成場景物體的碰撞盒。場景物體的包圍盒則是通過遍歷場景物體的所有頂點數據,記錄下X/Y/Z三個軸上的最大最小值(也就是AABB方法)來得到的。

BoundingBox SceneObjectMesh::GetBoundingBox() const { Vector3f bbmin (numeric_limits<float>::max()); Vector3f bbmax (numeric_limits<float>::min()); auto count = m_VertexArray.size(); for (auto n = 0; n < count; n++) { if (m_VertexArray[n].GetAttributeName() == "position") { auto data_type = m_VertexArray[n].GetDataType(); auto vertices_count = m_VertexArray[n].GetVertexCount(); auto data = m_VertexArray[n].GetData(); for (auto i = 0; i < vertices_count; i++) { switch(data_type) { case VertexDataType::kVertexDataTypeFloat3: { const Vector3f* vertex = reinterpret_cast<const Vector3f*>(data) + i; bbmin.x = (bbmin.x < vertex->x)? bbmin.x : vertex->x; bbmin.y = (bbmin.y < vertex->y)? bbmin.y : vertex->y; bbmin.z = (bbmin.z < vertex->z)? bbmin.z : vertex->z; bbmax.x = (bbmax.x > vertex->x)? bbmax.x : vertex->x; bbmax.y = (bbmax.y > vertex->y)? bbmax.y : vertex->y; bbmax.z = (bbmax.z > vertex->z)? bbmax.z : vertex->z; break; } case VertexDataType::kVertexDataTypeDouble3: { const Vector3* vertex = reinterpret_cast<const Vector3*>(data) + i; bbmin.x = (bbmin.x < vertex->x)? bbmin.x : vertex->x; bbmin.y = (bbmin.y < vertex->y)? bbmin.y : vertex->y; bbmin.z = (bbmin.z < vertex->z)? bbmin.z : vertex->z; bbmax.x = (bbmax.x > vertex->x)? bbmax.x : vertex->x; bbmax.y = (bbmax.y > vertex->y)? bbmax.y : vertex->y; bbmax.z = (bbmax.z > vertex->z)? bbmax.z : vertex->z; break; } default: assert(0); } } } } BoundingBox result; result.extent = (bbmax - bbmin) * 0.5f; result.centroid = (bbmax + bbmin) * 0.5f; return result; }}

這裡需要特別注意的就是centroid,也就是場景物體的包圍盒的幾何中心所在的位置。我們在DCC工具當中創建3D模型的時候,根據我們所採用的創建手法,模型的零點(也就是旋轉中心,Pivot)並不一定是位於模型的包圍盒的幾何中心。比如在我們本篇所使用的場景當中,桌腿的Pivot就不是在其幾何中心,而是在檯面的中心。

桌腿的Pivot並不在其包圍盒的幾何中心

而我們的碰撞盒以及之後的物理相關計算,都是假設碰撞盒的幾何中心為場景物體本地坐標零點進行的(可以最大限地利用幾何體的對稱性優化計算,而且可以減少碰撞盒存儲的內存開銷,因為我們只要存儲其大小即可)。所以我們在物理引擎的MotionState當中,除了需要記錄場景物體的姿態,還需要記錄Pivot與幾何中心的偏差。(在這裡我們假定Pivot就是場景物體的質心。在真實遊戲開發當中,這種約定對於美術團隊製作模型是有影響的,需要事先規範好)

另外需要特別注意的是,這個偏差我們記錄的是原始的3D模型當中的值(在物體本地坐標系裡的值)。我們的場景管理是通過場景節點來保存場景物體在場景當中的實際位置,即使是同一個場景物體,也可能會通過綁定不同的場景節點出現在場景的不同位置,並且有著不同的縮放。

因為這個縮放的存在,Pivot與幾何中心的偏差也是同比縮放的,我們需要在繪製碰撞盒的時候考慮到這個因素,否則繪製出來的結果就會不對:

void MyPhysicsManager::DrawAabb(const Geometry& geometry, const Matrix4X4f& trans, const Vector3f& centerOfMass) { Vector3f bbMin, bbMax; Vector3f color(0.7f, 0.6f, 0.5f); Matrix4X4f _trans; BuildIdentityMatrix(_trans); _trans.data[3][0] = centerOfMass.x * trans.data[0][0]; // scale by x-scale _trans.data[3][1] = centerOfMass.y * trans.data[1][1]; // scale by y-scale _trans.data[3][2] = centerOfMass.z * trans.data[2][2]; // scale by z-scale MatrixMultiply(_trans, trans, _trans); geometry.GetAabb(_trans, bbMin, bbMax); g_pGraphicsManager->DrawBox(bbMin, bbMax, color); }

下圖??線條顯示的是自動根據場景數據生成的碰撞盒

這樣我們就可以比較簡單地獲得擁有大量碰撞盒的場景,為我們進一步進行物理引擎的開發奠定了測試環境的基礎。

本文所用場景文件相關版權聲明

VERY IMPORTANT LICENSE INFORMATION:

This file has been released by oldtimer under the following license:

Creative Commons Attribution 3.0

You can use this model for any porposes according to the following conditions:

  • You MUST give attribution/credit to oldtimer.

推薦閱讀:

[GDC17] Ghost Recon Wildlands:Terrain Tools and Technology
Dirty Game Engine
[翻譯]Metal Gear Solid V – Graphics Study
Matrix and Transform Conversion 1/3
[GDC16] Assassins Creed Syndicate: London Wasnt Built in a Day

TAG:物理引擎 | 遊戲引擎 | 編程 |