1. 程式人生 > >UE4物理模組分析

UE4物理模組分析

一.Mesh元件與物理之間的關係

         這裡主要是從程式碼方面,簡單分析一下UE4裡面的物理是如何使用與生效的,StaticMesh以及SkeletalMesh對應的物理都是如何產生與作用的。第三部分會涉及到一些PhyX引擎的內容,簡單談談UE與PhysX間的互動。

         首先,在遊戲中常見的帶有物理的物體一般有5種,膠囊體一類、靜態網格物體StaticMesh、骨骼網格物體SkeletalMesh、Landscape地形以及PhysicsVolume(BrushComponent)。這5種類型本質上產生物理的規則都大同小異,為了方便我們只針對StaticMesh與SkeletalMesh來總結。

         對於直接放在場景的石塊等,通常是通過3D建模軟體匯入到引擎中的資產。匯入到引擎資原始檔夾後就石塊模型就以UStaticMesh類存在於引擎中,從資原始檔夾拖到場景中後場景中的該物件就變成了StaticMeshActor。對於帶動畫表現的玩家模型,是通過3D建模軟體匯入的帶骨骼資訊的資產。匯入到引擎資原始檔夾後就以USkeletalMesh類存在於引擎中,從資原始檔夾拖到場景中後場景中的該物件就變成了SkeletalMeshActor。當然,單獨把沒有任何處理的SkeletalMeshActor放在場景中看起來就如一個StaticMeshActor一樣,沒有任何動畫,也可能沒有任何物理。

         由於UE4提倡元件式的開發,Actor身上的很多特性都是通過元件提供的,所以物理資料都是掛在元件上的而不是Actor上。任何Actor上面都可以掛上N多個元件,因此一個玩家身上就可以有多個UStaticMeshCompnent與USkeletalMeshCompnent(一般還有一個膠囊體作為根元件)。舉個簡單的例子,玩家自身的模型是一個USkeletalMeshCompnent,然後身上的衣服裝備就可以用一個UStaticMeshCompnent來表示。二者最大的差別就是SkeletalMesh可以產生動畫。為了表現的更精確,他的每一個骨骼都可以產生對應的物理,而隨著動畫的變化他自身的每個骨骼物理也就需要跟隨動畫而改變,所以相比StaticMesh要複雜不少。

         對於一個靜態網格物體StaticMesh,他的物理一般在建模軟體裡面就應該建立好,匯入到編輯器時UE就會根據匯入的資料建立物理資訊,當然UE4本身也提供了物理碰撞的建立,如圖1-1所示。不過無論哪種做法,本質上都是在編輯器裡給UStaticMeshComponent構建一個UBodySetup,在開始遊戲的時候在建立執行時的基本物理資料UBodyInstance。


圖1-1 UE編輯器新增碰撞

(UBodySetup與UBodyInstance:我個人理解UBodySetup就是一個靜態的物理資料,一般在在遊戲執行前就已經構建好了[當然,你在遊戲執行時建立也沒什麼問題]。你可以理解為一個類,編譯以後就存在了。而UBodyInstance是一個在遊戲時真正起作用的物理資料,可以理解為通過這個類建立的物件,執行時才真正出現。通過一個UBodySetup是可以創建出多個UBodyInstance的)

而對於骨骼網格物體SkeletalMesh,由於資料比較多,他的物理資料儲存在PhysicsAsset裡面。在遊戲執行的時候,SkeletalMeshComponent會讀取物理資產裡面的資料UBodySetup隨後再通過UBodySetup給角色建立對應的基本物理資料UBodyInstance。再進一步深入就是NVIDA的PhysX物理引擎了(當然你也可以採用BOX2D物理引擎),這篇文件後面會有簡單的講解。

(UE4裡面除了SkeletalMeshComponent.cpp以外還有SkeletalMeshComponentPhysics.cpp,PhysAnim.cpp用來專門處理SkeletalMeshComponent物理相關的邏輯)


圖1-2 物理資產

下面的圖片描述了Mesh、 Component與物理基本類的基本關係


圖1-3 物理相關類圖

         如果對BodyInstance還是覺得比較陌生的話,不妨結合下面我們熟悉的類圖來理解。我們知道在給Mesh設定物理的時候需要設定準確的碰撞通道,才能讓不同的物理之間有碰撞效果。仔細看一下,CollisionResponses,ObejctType這些其實都是FBodyInstance裡面的成員,我們在編輯器裡面設定的這些屬性其實就是在給BodyInstance設定(進一步還會去給到PhysX裡面的PxRigidActor,後面講)。

圖1-4

         如果我們想在編輯器裡直觀的看到是否建立了物理就呼叫控制檯命令Show Collision既可。下圖就顯示了角色的膠囊體碰撞以及對應的骨骼物理碰撞(多個膠囊體組合)。


圖1-5

二.物理的建立流程

         前面大致的描述了UE4裡面基本元件與物理之間的邏輯關係,我們看到上面無論是Staticmesh還是SkeletalMesh都會通過BodySetup來建立物理,而BodySetup最終又會呼叫BodyInstance來產生真正的物理。下面我們從遊戲內具體的物理初始化流程分析一下。

2.1UStaticMeshComponent物理的建立

         首先是UStaticMeshComponent,可以看到在場景裡面載入Actor並註冊UActorComponent的時候會對UPrimitiveComponent元件進行物理資訊建立。其實除了UStaticMeshComponent以外,所有繼承自UPrimitiveComponent的元件(第一部分提到的那5種都是)都會在註冊後就建立物理資料(對於直接繼承自UActorComponent的元件,如移動元件就不會執行該操作)。因此除了SkeletalMeshComponent以外(這個後面再分析),其他繼承自UPrimitiveComponent的元件物理建立的時機都很明確,也就是UActorComponent被註冊的時候建立物理。(當然還有一些特殊情況也需要更新物理,比如更換模型的時候)

圖2-1 載入場景StaticMesh物理的建立堆疊圖


圖2-2 玩家出生時膠囊體物理的建立堆疊圖

在註冊元件時是否要建立物理資料?可以參考下面程式碼。其實很明顯的有三個條件,

1.      是否已經建立過了

2.       是否能獲取到當前的物理場景(物理場景的變數為FPhysScene*    PhysicsScene,理解為與遊戲世界同時存在的一個物理世界。這個PhysicsScene一般是在初始化World資訊,也就是在void InitWorld(const InitializationValues IVSInitializationValues())時建立

3.      是否應該建立ShouldCreatePhysicsState。很明顯想控制是否給元件建立對應的物理資料,寫在這裡最合適不過了。比如,所有繼承自UActorComponent而且沒有重寫該函式的元件都會直接返回false,而UPrimitiveComponent就重寫了這個函式。


圖2-3 註冊時是否建立物理的條件程式碼截圖

2.2 USkeletalMeshComponent物理的建立

         USkeletalMeshComponent與其他帶物理元件不同,由於他的物理比較複雜,涉及到PhysicsAsset,所以他需要重寫基類的物理建立流程。另外,一般來說我們並不想會讓玩家的骨骼物理一直存在著。原因很簡單,就是為了提升效能。對於一般的帶物理的元件,我們只需要給他配置一個簡單的碰撞體既可(PhysX裡面就包括了Sphere,Box,Capsule這幾種)。這樣一個簡單的物理元件在遊戲執行時的開銷是很小的,然而對於一個USkeletalMeshComponent,我們為了精確幾乎需要給所有的骨骼都建立一個基本的物理單位,一旦玩家或者NPC過多,這個消耗是非常可觀的。然而,我們也不能放棄使用USkeletalMeshComponent的物理,因為一旦我們的遊戲想實現精準的打擊,攻擊不同位置的效果不同的時候,就必須要用到骨骼的物理。因此,常見的解決方案就是在需要的時候建立物理,在不需要的時候就拿掉。(預設引擎的SkeletalMesh物理會一直存在參考圖1-5,需要我們自己處理,後面會簡單給出一個解決思路)

         我們還是從元件的註冊說起,USkeletalMeshComponent的物理的初始化與前面的元件不同,他首先過載了void USkeletalMeshComponent::CreatePhysicsState()函式。並通過呼叫InitArticulated函式來對所有的骨骼來進行物理的初始化,這是元件初始化時的邏輯程式碼。我們簡單分析一下,


圖2-4 過載CreatePhysicsState程式碼截圖

         可以看到USkeletalMeshComponent建立物理有兩個執行路徑,一種是和其他元件一樣使用基類UPrimitiveComponent的方法建立物理資料,另一種是用USkinnedMeshComponent裡面的PhyiscsAsset資料。(bEnablePerPolyCollision這個變數預設是0,而且引擎沒有修改過)所以,可以看出,正常的USkeletalMeshComponent初始化物理是通過函式void InitArticulated(FPhysScene*PhysScenebool bForceOnDedicatedServer false);來對每一個骨骼來初始化物理的(Articulate表示關節連線的)。如果開發者不做任何處理的話,那麼USkeletalMeshComponent的物理資料就會在註冊時建立並且在遊戲過程中一直存在著。

一般來說,USkeletalMeshComponent在每幀TickComponent的時候都會呼叫到USkeletalMeshComponent::RefreshBoneTransforms函式,顧名思義就是更新骨骼的座標旋轉等。


圖2-5 Tick更新骨骼Transform堆疊圖

RefreshBoneTransforms函式裡面,可以根據CPU核數等相關引數來決定是否開一個執行緒來單獨更新動畫以及相關物理資料(最後還是呼叫InitArticulated函式建立物理)。


圖2-6 開啟單獨執行緒來處理動畫物理資料

下面的堆疊圖就是引擎通過單獨開一個執行緒來處理物理等資料。


圖2-7 單獨執行緒來處理動畫物理資料呼叫堆疊圖

         前面我們提到要選擇讓物理在需要的時候去生成,而在一般狀態下要拿掉。那這是如何做到的?其實我們可以在USkeletalMeshComponent::UpdateKinematicBonesToAnim 去處理,這個函式意義是根據動畫的變換去更新當前的物理資料,每一幀都需要執行。基本思路就是,每幀都去檢測是否需要骨骼物理資料,如果需要我們建立對應的物理資料(已經建立過了就直接返回)。如果檢測到當前不再需要更新物理,就呼叫USkeletalMeshComponent::TermArticulated()刪除物理資料。我們已經知道,執行中的物理資料全部儲存在BodyInstance裡面,而這個函式就會把我們當前儲存在Bodies裡面的所有BodyInstance資料全部清除。忘記Bodies的朋友可以回頭看一下USkeletalMeshComponent的類圖。

         同時這裡還有一段註釋可以參考一下:

// This below code produces some interesting resulthere

// - below codes update physics data, so if youdon't update pose, the physics won't have the right result

// - but if we just update physics bone withoutupdate current pose, it will have stale data

// If desired, pass the animation data to thephysics joints so they can be used by motors.

// See if we are going to need to update kinematics

const bool bUpdateKinematics = (KinematicBonesUpdateType != EKinematicBonesUpdateToPhysics::SkipAllBones); 可以把是否更新物理放到這個位置去處理。

       另外,對於USkeletalMeshComponent,初始化物理時會有bPhysicsRequiredOnDediServer等屬性來控制在伺服器模式下是否建立物理資料。

三.BodyInstance與PhysX物理引擎

         前面我們已經瞭解到BodyInstance在UE邏輯裡是一個執行時的物理的基本單位。而實際在PhysX引擎中,也同樣存在一個物理基本單位,這個物理單位就PxRigidActor。一個BodyInstance對應一個PxRigidActor(實際上就是BodyInstance::InitBody時建立一個對應的PxRigidActor),這樣我們就可以將UE引擎與PhysX引擎結合起來使用了。
         這個時候,我再提出一個問題,真正的物理碰撞是如何檢測的呢?
         這個問題確實值得我們深思,而且不同情況下檢測的方法是不一樣的。舉個例子,想知道兩個球是否產生碰撞,那麼只要判斷兩個球心的距離就可以了。而兩個複雜模型的碰撞,可能需要通過判斷兩個三角面是否有交集來判斷。我這裡提出這個問題,只是想提醒大家,物理引擎裡面的Actor也一樣需要知道其本身的形狀,然後進一步來處理碰撞邏輯。所以,在建立一個基本物理單位PxRigidActor之後,我們還需要給其建立基本的幾何形狀(在引擎裡面叫做Shape),這個邏輯的處理就在函式UBodySetup::AddShapesToRigidActor_AssumesLocked。看到這個函式,我們就知道Shape是通過UBodySetup來建立的,同時這個幾何形狀的資料也是儲存在UBodySetup裡面的。

PhysX裡面提供的型別有下面幾種:

1.       PxSphereGeometry球形

2.      PxBoxGeometry盒子

3.      PxCapsuleGeometry膠囊體(SkeletalMesh常用)

4.      PxConvexMeshGeometry凸面體

5.      PxTriangleMeshGeometry 三角面

這5種類型裡面,前4種的資料都儲存在UBodySetup的FKAggregateGeom AggGeom;裡面。

而最後三角面的物理資料則儲存在UBodySetup的TriMesh與TriMeshNegX裡面。

         凸面體與三角面物理資料特別的是,他需要經過一個物理Cook的過程,這個過程類似渲染,把所有的三角面的頂點資訊和索引提供給PhysX引擎隨後PhysX利用這些資料Cook出一個完整的碰撞模型,不過這個過程需要一定的時間來執行(在我們建立凸面體碰撞盒或者設定簡單碰撞為複雜碰撞的時候)。所以如果想針對自己的一個特殊的ACtor來採用第5種方法,也應該更傾向於在遊戲開始前就完成Cook過程,這樣就不會影響遊戲時的效能與體驗。

         另外,我們在建立物理的時候還分為靜態與動態兩種,他們通過元件上的OwnerComponent->Mobility!= EComponentMobility::Movable來控制。很明顯,靜態碰撞與動態碰撞的消耗是不同的。

         //建立靜態PxRigidActor

         GPhysXSDK->createRigidStatic(PTransform);

         //建立動態PxRigidActor

GPhysXSDK->createRigidDynamic(PTransform);

         截止到這裡,我們已經基本上完成了物理資料的初始化。然而,我們知道在遊戲裡面,還有很多詳細的設定,比如碰撞通道,碰撞型別等。這些資料也必須要及時更新與處理,這些邏輯與相關標記的處理在FBodyInstance::UpdatePhysicsFilterData->FBodyInstance::UpdatePhysicsShapeFilterData裡。UpdatePhysicsFilterData是總的物理資料的處理(因為可能還有其他的物理引擎,如Box2D等)。而UpdatePhysicsShapeFilterData是真正的UE邏輯與PhysX邏輯互動的地方。實際上我們的平時做的碰撞設定CollisionEnabled對應到Physx裡面就是這兩個操作。PShape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPEtrue);PShape->setFlag(PxShapeFlag::eSIMULATION_SHAPEfalse);(參考後面的結構體)。

下面是PhysX裡面的Shape標記

structPxShapeFlag

{

           enumEnum

         {

                   eSIMULATION_SHAPE                              =(1<<0),

                   eSCENE_QUERY_SHAPE                                   =(1<<1),

                   eTRIGGER_SHAPE                                               =(1<<2),

                   eVISUALIZATION                                       =(1<<3),

                   ePARTICLE_DRAIN                                             =(1<<4)

         };

};

    關於UEPhysX之間的互動,就簡單介紹到這裡。至於更詳細的內容,大家有興趣的話就去原始碼裡面進一步瞭解吧。

    最後,再推薦一篇UE物理相關的文件   

原文連結(轉載請標明):http://blog.csdn.net/u012999985/article/details/78242493