《Exploring in UE4》物理模塊淺析[原理分析]

目錄

一.Mesh組件與物理

二.物理的創建時機

2.1 UStaticMeshComponent的物理創建

2.2 USkeletalMeshComponent的物理創建

三.物理對象的移動

四.UE4與PhysX

4.1 簡單碰撞的物理創建

4.2 複雜碰撞的物理創建

4.3 物理創建的後續工作

五.物理約束Constraint

5.1 簡單理解物理約束的原理

5.2 物理對象自身約束

5.3 物理約束Actor

5.4 物理約束組件

5.5 SkeletalMesh中的物理約束調整

5.6 UE中的物理約束

六.物理材質

一.Mesh組件與物理

關於UE物理的基本使用,官方文檔以及我之前CSDN博客里已經做了較為詳細的介紹。

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

這篇文章只討論剛體物理。

首先,在遊戲中常見的帶有物理的物體一般有5種,雖然這5種類型本質上產生物理的規則都大同小異,但為了方便我們只針對StaticMesh與SkeletalMesh來總結。

  1. 膠囊體一類(USphereComponent,UBoxComponent,UCapsuleComponent)
  2. 靜態網格物體StaticMesh
  3. 骨骼網格物體SkeletalMesh
  4. Landscape地形
  5. PhysicsVolume(BrushComponent)

對於直接放在場景的石塊等,通常是通過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.1 UStaticMeshComponent的物理創建

首先是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 IVS= InitializationValues())時創建
  3. 是否應該創建 ShouldCreatePhysicsState。很明顯想控制是否給組件創建對應的物理數據,寫在這裡最合適不過了。比如,所有繼承自UActorComponent而且沒有重寫該函數的組件都會直接返回false,而UPrimitiveComponent就重寫了這個函數。

圖2-3 註冊時是否創建物理的條件代碼截圖

2.2 USkeletalMeshComponent的物理創建

USkeletalMeshComponent與其他帶物理組件不同,一般來說我們並不會在玩家一出生就創建出所有的骨骼物理,也不會讓玩家的骨骼物理一直存在著。原因很簡單,就是為了提升性能。對於一般的帶物理的組件,我們只需要給他配置一個簡單的碰撞體既可(包括Sphere,Box,Capsule等)。這樣一個簡單的物理組件在遊戲運行時的開銷是很小的,然而對於一個USkeletalMeshComponent,我們為了精確幾乎需要給所有的骨骼都創建一個基本的物理單位,一旦玩家或者NPC過多,這個消耗是非常可觀的。然而,我們也不能放棄使用USkeletalMeshComponent的物理,因為一旦我們的遊戲想實現精準的打擊,攻擊不同位置的效果不同的時候,就必須要用到骨骼的物理。因此,常見的解決方案就是在需要的時候創建物理,在不需要的時候就拿掉。

默認引擎的SkeletalMesh物理會一直存在 參考圖1-5。實際上,也不是一定要動態創建於刪除skeletalMesh組件的物理,要結合遊戲考慮是否要優化這一部分。

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

圖2-4 重載CreatePhysicsState代碼截圖

可以看到USkeletalMeshComponent創建物理有兩個執行路徑,一種是和其他組件一樣使用基類UPrimitiveComponent的方法創建物理數據,另一種是用USkinnedMeshComponent裡面的PhyiscsAsset數據。(bEnablePerPolyCollision這個變數默認是0,而且引擎沒有修改過)

所以,可以看出,正常的USkeletalMeshComponent初始化物理是通過函數

void InitArticulated(FPhysScene* PhysScene,bool 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 result here// - below codes update physics data, so if youdont update pose, the physics wont 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等屬性來控制在伺服器模式下是否創建物理數據。


三.物理對象的移動

在UE裡面,移動邏輯是通過移動組件來完成的。一般來說,一個可移動角色身上要至少掛一個移動組件(控制移動),一個膠囊體組件(也可以是球形等,作為移動組件控制的對象)。

膠囊體身上掛載著簡單類型的物理碰撞,移動時必須要帶著其身上的物理一起移動。圖3-1是移動組件觸發物理移動的調用堆棧。簡單描述一下,移動組件通過調用膠囊體的InternalSetWorldLocationAndRotation來真實的更新其位置與旋轉,更新後會通過函數OnUpdateTransform來觸發其物理對象的更新,即UPrimitiveComponent::SendPhysicsTransform。這一步會執行膠囊體身上對應的BodyInstance位置與旋轉的更新,再深入一些就是更新物理引擎裡面的PxRigidActor對象了。

圖3-1 一般的物理移動調用堆棧

如果你發現你的堆棧是圖3-2樣子的也不用擔心,這是由於移動組件開啟了bEnableScopedMovementUpdates屬性。他等一次移動完全成功後再觸發子對象移動,物理移動等,因為移動過程中可能出現移動不合法重置的情況。這個功能有助於提高移動性能。

圖3-2 開啟延遲更新後的調用堆棧

前面描述的是膠囊體這種簡單類型的物理碰撞,如果是一個SkeletalMeshComponent組件呢?他的身上有與骨骼數量相等的BodyInstance,如何移動?

其實本質上差不多,通過堆棧可以看出USkeletalMeshComponent重寫了函數OnUpdateTransform,隨後會調用UpdateKinematicBonesToPhysics函數更新所有的物理數據。

圖3-3 SkeletalMeshComponent更新移動

在更新動畫的時候也會觸發UpdateKinematicBonesToPhysics函數。

圖3-4 更新動畫時更新物理

如果開啟了物理託管,那麼角色的移動就完全交給物理引擎去處理。通過下面這個介面獲取物理引擎返回的Transform並更新自己的位置。

圖3-5

對於SkeletalMeshComponent,上面的操作只能讓組件與根骨骼位置匹配。其他的骨骼還需要通過USkeletalMeshComponent::BlendInPhysics進一步計算,

圖3-6


四.UE4與PhysX

前面我們已經了解到BodyInstance在UE邏輯里是一個運行時的物理的基本單位。而實際在PhysX引擎中,也同樣存在一個物理基本單位,這個物理單位就PxRigidActor。一個BodyInstance對應一個PxRigidActor(實際上就是BodyInstance::InitBody時創建一個對應的PxRigidActor),這樣我們就可以將UE引擎與PhysX引擎結合起來使用了。

這個時候,我再提出一個問題,真正的物理碰撞是如何檢測的呢?

這個問題確實值得我們深思,而且不同情況下檢測的方法是不一樣的。舉個例子,想知道兩個球是否產生碰撞,那麼只要判斷兩個球心的距離就可以了。而兩個複雜模型的碰撞,可能需要通過判斷兩個三角面是否有交集來判斷。我這裡提出這個問題,只是想提醒大家,物理引擎裡面的Actor也一樣需要知道其本身的形狀,然後進一步來處理碰撞邏輯。所以,在創建一個基本物理單位PxRigidActor之後,我們還需要給其創建基本的幾何形狀(在引擎裡面叫做Shape),這個邏輯的處理就在函數UBodySetup::AddShapesToRigidActor(新版本叫UBodySetup::AddShapesToRigidActor_AssumesLocked)。看到這個函數,我們就知道Shape是通過UBodySetup來創建的,同時這個幾何形狀的數據也是存儲在UBodySetup裡面的。

PhysX裡面提供的類型有下面幾種,官方聲稱前四種是簡單碰撞,第五種是複雜碰撞,而實際上凸面體碰撞的處理與三角面相似,所以也可以理解為複雜碰撞:

  1. PxSphereGeometry 球形
  2. PxBoxGeometry 盒子
  3. PxCapsuleGeometry 膠囊體(SkeletalMesh常用)
  4. PxConvexMeshGeometry 凸面體
  5. PxTriangleMeshGeometry 三角面

4.1 簡單碰撞的物理創建

這5種類型裡面,前4種的生成好的物理數據都存儲在UBodySetup的FKAggregateGeom AggGeom裡面,按照官方文檔的分類,我們稱他們為簡單碰撞類型。實際上,凸面體的碰撞處理並不像前面幾個那樣簡單。

前三種碰撞的添加在代碼實現上也比較簡單,我們在編輯器添加碰撞的時候會通過函數GenerateSphereAsSimpleCollision,GenerateBoxAsSimpleCollision,GenerateSphylAsSimpleCollision分別將碰撞數據添加AggGeom的SphereElems,BoxElems,SphylElems裡面。正如我前面所說的那樣,判斷兩個球體是否碰撞很容易,所以這幾種碰撞類型不需要很複雜的數據來記錄與處理,PhysX引擎可以很容易的獲取到這些碰撞類型對應的數據並做處理。

凸面體與前面三種碰撞類型都不同。由於其可以通過配置生成一個較為複雜的碰撞,而且碰撞體的頂點都是通過演算法生成的,所以他需要經過一個物理Cook的過程。這個過程類似渲染,把所有的三角面的頂點信息和索引提供給PhysX引擎隨後PhysX利用這些數據Cook出一個完整的碰撞模型,不過這個過程需要一定的時間來執行。一般來說,我們在遊戲編輯器添加AutoConvexCollision碰撞並執行Apply的時候,就會執行這個凸面體的Cook過程。Cook的過程與三角面的Cook過程相似,後面再詳細分析。(下圖是簡單類型碰撞的添加)

圖4-1

我們知道SkeletonMeshComponent裡面使用PhysicsAsset來創建骨骼動畫的物理,那PhysicsAsset裡面的BodySetup裡面的數據是如何初始化的?他裡面的shape類型是什麼?看下面的兩個堆棧,當我們導入一個帶有動畫的骨骼資產時,首先會判斷該SkeletalMesh有沒有物理資產。

如果沒有就會調用圖3-2創建物理資產,隨後執行第二步,根據每個骨骼初始化對應的物理。當前引擎中,會針對沒有物理資產的SkeletalMesh的每個骨骼默認初始化一個膠囊體類型的簡單碰撞。這個類型是通過FPhysAssetCreateParams NewBodyData;初始化後作為參數傳遞給物理資產的,所以一般來說我們的角色的物理資產都是膠囊體的。

圖4-2

圖4-3

當然,當你導入模型之後。你也可以根據你的骨骼資產創建一個新的PhysicsAsset,在創建的時候右鍵骨骼資產文件——Create——CreatePhysicsAsset,隨後會彈出下面的界面,可以根據需求針對每個骨骼創建一個指定類型的Shape碰撞。如果想單獨調整個別骨骼的碰撞,就要打開PhysicsAsset在編輯界面里單獨處理了。(SkeletonMeshComponent的物理並不一定就是簡單的膠囊體碰撞,也可能複雜碰撞,下個小結分析)

圖4-4

圖4-5

4.2 複雜碰撞的物理創建

最後一種碰撞類型是複雜類型,那麼有多複雜呢?其實就是根據Mesh的網格信息(也就是三角面的數量)來進行物理的生成,所以模型面數越多那自然就越複雜。生成好的三角面的物理數據都存儲在UBodySetup的TriMesh與TriMeshNegX裡面。

圖4-6

看過官方文檔碰朋友肯定知道,我們可以通過StaticMesh裡面Collision

Complexity設置來改變其碰撞的複雜度,當我們標記為UseComplexAsSimple的時候,其實就會在此時去除簡單碰撞,並給對應的Mesh資產創建一份複雜的物理碰撞,這個時候就會執行三角面的Cook過程。

圖4-7

圖4-8

看上面的函數堆棧,在更新上面的配置的時候需要重新創建PhysicsMeshs並獲取到CookData。GetCookedData就會調用FDerivedDataPhysXCooker裡面的FDerivedDataPhysXCooker:: BuildConvex處理凸面體物理或者FDerivedDataPhysXCooker::BuildTriMesh處理三角面的物理數據。更深入一步,在這個兩個函數里又分別通過PhysX引擎IPhysXFormat介面裡面的PxCooking調用cookConvexMesh函數以及cookTriangleMesh函數。

另外,想要執行Cook,我們一定要準確的獲取到碰撞模型的所有頂點信息,對於StaticMesh三角面碰撞。這個頂點信息就是通過渲染的Lod信息來取到的,具體的操作在函數GetPhysicsTriMeshData裡面,執行堆棧如下:

圖4-9

複雜物理的生成與渲染很像,如果你需要動態的去生成與刪除物理,那麼一定要慎重考慮這個動態創建的過程消耗如何?我們平時對StaticMesh物理的Cook過程都是在編輯器裡面就完成了。如果遊戲中做這個操作很有可能造成卡頓,如果非要這麼做也可以考慮使用非同步線程FNonAbandonableTask來執行這個過程。

官方文檔上可以看到靜態模型是如何創建複雜物理的,但是好像沒有說骨骼資產如何創建,骨骼模型是不是不可以創建?並不是。在4.8以後的版本里,我們打開骨骼資源文件找到bEnablePerPolyCollision屬性並勾選既可。在舊版本4.5裡面需要到角色藍圖找到對應的SkeletalMesh組件里勾選bEnablePerPolyCollision屬性。

註:新版本skeletalMesh組件里也有這個屬性,但是勾選無效,這是引擎的一個Bug

4.3 物理創建的後續工作

前面的操作是將函數UBodySetup::CreatePhysicsMeshes()展開,將物理Cook的過程執行完畢。隨後,UBodySetup::AddShapesToRigidActor函數會獲取AggGeom以及TriMesh裡面已經Cook好的數據,創建對應的物理Shape。

另外,我們在創建物理的時候還分為靜態與動態兩種,他們通過組件上的OwnerComponent->Mobility

!= EComponentMobility::Movable來控制。很明顯,靜態碰撞與動態碰撞的消耗是不同的。

//創建靜態PxRigidActorGPhysXSDK->createRigidStatic(PTransform);//創建動態PxRigidActorGPhysXSDK->createRigidDynamic(PTransform);

截止到這裡,我們已經基本上完成了物理數據的初始化。然而,我們知道在遊戲裡面,還有很多詳細的設置,比如碰撞通道,碰撞類型等。這些數據也必須要及時更新與處理,這些邏輯與相關標記的處理在

FBodyInstance::UpdatePhysicsFilterData>FBodyInstance::UpdatePhysicsShapeFilterData()

UpdatePhysicsFilterData是總的物理數據的處理(因為可能還有其他的物理引擎,如Box2D等)。而UpdatePhysicsShapeFilterData是真正的UE邏輯與PhysX邏輯交互的地方。

實際上我們的平時做的碰撞設置CollisionEnabled對應到Physx裡面就是這兩個操作。

PShape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE,true);PShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);(參考後面的結構體)

圖4-10

PhysX裡面的Shape標記。

struct PxShapeFlag{ enum Enum { eSIMULATION_SHAPE = (1<<0), eSCENE_QUERY_SHAPE = (1<<1), eTRIGGER_SHAPE = (1<<2), eVISUALIZATION = (1<<3), ePARTICLE_DRAIN = (1<<4) };};

關於UE與PhysX之間的交互,就簡單介紹到這裡。至於更詳細的內容,大家有興趣的話就去源碼裡面進一步了解吧。


五.物理約束Constraint

物理約束系統作為物理引擎的一項重要組成部分能夠提供更真實與更豐富的模擬表現。

前面通過Mesh,膠囊體等組件總結了物理是以一個什麼形式存在於遊戲世界中的,UE中的物理對象與PhysX的對象是怎樣的關係。但是我們沒有分析遊戲世界是如何模擬真實的物理現象的,比如我們常見的鐘擺,彈簧,關門等。

對於兩個完全分離的剛體,他們之間的作用是比較容易模擬的,只要在發生碰撞的位置給各自施加一個力就可以了,他們會根據受力情況各自進行模擬移動,不會相互影響。不過,如果我們想模擬一個鐘擺的效果,目前的條件就無法完成了。所以,引擎提供了物理約束(Constraint)功能,可以將兩個對象綁定在一起根據參數進行物理模擬,方便我們快速的模擬類似的效果。

在Unity中,物理約束更多的是以關節joint的概念存在。本質上物理約束的概念更大一些,關節是針對兩個物體的約束,而物理約束可能只對一個物體產生約束。物理約束通常以關節的方式存在於引擎中。

5.1 簡單理解物理約束的原理

物理約束顧名思義,是通過對剛體的各個自由度的移動限制來實現特殊的模擬效果。一個普通的剛體運動通過6個自由度來控制,分別是3個位置方向線性位移與3個軸方向的旋轉。我們可以分別或者組合的對每個自由度進行控制,比如限制對象只能沿著XOZ平面移動,就可以實現類似摩天輪運動,鐘擺的基本效果。

不過在遊戲中,我們更對的是對兩個對象進行物理約束(也就是關節),那麼對兩個物體產生物理約束有什麼特點?答案是多個Actor的約束需要有特定的參照對象。一旦我們對兩個對象進行約束,那麼二者就必須有一個統一的約束參照對象,然後根據參照對象的坐標系來進行模擬。通常來說,這個參照對象就是ConstraintActor或者ConstraintComponent。

比如說,我用一個ConstraintActor對兩個Actor進行約束,限制他們只能繞X軸旋轉。不過,這兩個Actor繞誰的X軸旋轉?難道是世界坐標系的X軸?顯然我們應該選擇一個合適的可以配置的參考對象,這個對象就是上面的ConstraintActor,完成配置後Actor就會繞著ConstraintActor的X軸旋轉了。

5.2 物理對象自身約束

物理自身約束比較容易理解,他只是單純的限制自身的物理模擬的位置,不會對其他對象產生影響。所有包含物理數據的對象都可以進行設置,根據配置限制對象只能在某個軸或某個面產生模擬移動。如圖5-1

圖5-1

5.3 物理約束Actor

他本身是一個Actor對象,利用它可將兩個Actors 連接起來(假定成一個物理模擬體),並應用限制和力度。引擎擁有一些默認關節類型 (球窩式ball-and-socket、鉸鏈式hinge、稜柱式prismatic),區別只存在於它們的對Actor的6個自由度的限制差異。可任選一種關節開始,自行進行調整試驗。裡面的參數比較多,除了基本的限制操作外還可以通過Motor參數添加驅動力,後面會對一些常見的應用場景進行簡單進行分析,其他的建議大家查閱相關資料後多去嘗試。

圖5-2

上圖就是物理約束Actor的配置,一個物理約束Actor能且只能綁定兩個Actor對象,這兩個對象至少有一個要開啟物理模擬。如果需要的話,我們也可以將一個SkeletalMesh的骨骼與另一個Actor綁定,甚至我們還可以指定一個Actor的某個組件與另一個Actor的某個組件綁定。具體的操作草考官方文檔。

我們還可以對一個Actor進行多次約束綁定來模擬更多效果,舉個例子:假如我要模擬一個鞦韆,剪斷一邊的繩子後,這邊就會下落向另一邊旋轉。我們可以用兩個ConstraintActor綁定鞦韆上面的支架與下面的鞦韆板,然後設置鞦韆支架不可移動。兩個ConstraintActor都設置允許3個自由度的旋轉(Angular Limits都設置為free),Z值做30cm限制(Linear limit的Zmotion設置為limited),分別放在繩子與木板相連的位置(很重要,因為兩個ConstraintActor方向不同,導致其兩個方向都不能旋轉)。當玩家用道具砍掉一邊的繩子後,對一個ConstraintActor解綁,就可以模擬一邊被砍斷的情形了。

5.4 物理約束組件

物理約束組件(Physics Constraint Components)的使用方法和 物理約束 Actors 相同,不同之處是其在藍圖中使用,可在 C++ 中進行創建。物理約束組件結合了藍圖的靈活和 C++ 的強大,您可利用它對項目中的任意物理形體設置約束。官方文檔也有案例,不再贅述。

5.5 SkeletalMesh中的物理約束調整

打開物理資源文件(PhysicsAsset),默認是Body模式,點擊按鈕我們會看到有一個Constraint Mode,點擊就會進入物理約束模式。

圖5-3

圖5-4

進入物理約束模式後,首先注意紅色標記,我們有19個骨骼18個關節,同時也有18個物理約束。其實,這裡可以猜出來,UE在創建對應的PhysicsAsset的同時會對每一個骨骼關節創建一個對應的物理約束,這正好符合我們的常識。

那是不是說,我們每次導入一個SkeletalMesh就會完美的創建一個帶有物理約束的PhysicsAsset?並不是,每當我們根據一個SkeletalMesh創建一個物理體的時候,是會創建對應的物理資產,但是這個資產往往問題很多,無法使用,如圖5-5。可以看到他的膠囊體數量很多,重疊嚴重,而且物理約束都是默認的值。

圖5-5 默認創建的物理資產

在上面的菜單位置,我們可以看到4個默認的物理約束方式,下面一一描述。

圖5-6

球窩式ball-and-socket:類似上面圖片的效果,一個球狀的骨骼塞到凹槽裡面,可以在一定空間內旋轉,類似人的肘關節。下面完全開放了3個旋轉自由度,但通常情況我們經常要對各個方向做一定的限制。

圖5-7

鉸鏈式hinge:類似上面圖片的效果,兩個物體通過鉸鏈相連,只能繞著固定方向旋轉,如門的開關。這裡完全打開了繞著X軸方向的旋轉。

圖5-8

稜柱式prismatic:兩個剛體間的角度是固定的,只能在一個固定的軸上滑動。這裡我們看到只能沿著X軸產生位移。

圖5-9

角色關節 Skeletal:其實與球窩式很相似,但是在各個旋轉方向上都有限制。

圖5-10

下圖是官方根據實際情況調整過後的物理約束:可以看出來他把沿著X軸方向的旋轉給禁掉了,這樣更符合現實情況(可以自己試一下)。

當然,我們可以根據自己的情況做特殊處理,比如角色的骨骼上可能綁定了一個武器,那麼這個武器的物理約束就可以設置成6個自由度的。不過這些全都是我們在開啟角色物理模擬時才會出現的效果。

在實際應用中,我們很多情況下需要固定一個物體,然後另一個物體相對進行移動或者旋轉,比如門,鐘擺等的實現。所以只需要開啟一個物體的simulates即可。

圖5-11

關於約束限制的驅動力,可以通過Motor來添加,有位移驅動與旋轉驅動。具體可以參考官方的ContentExample的PhysicsMap。

圖5-12

5.6 UE中的物理約束

在UE源碼裡面,物理約束Actor的類是APhysicsConstraintActor函數,物理約束組件是UPhysicsConstraintComponent。APhysicsConstraintActor本身也是使用約束組件的功能。

約束組件裡面最重要的數據就是FConstraintInstance ConstraintInstance,該對象包含了我們在編輯器中所見的各項參數,同時會將相關的約束數據保存到PhysX引擎中的PxD6Joint類型的數據裡面。具體細節請查看源碼分析。

圖5-13

六.物理材質

物理材質在官方上這樣定義:用於定義當物理對象和世界進行動態交互時它所做出的反應。

說白了,就是他在遊戲世界應該是什麼材料的,雖然我們通過材質的表現可以看出來他是一塊木頭還是一塊鐵,但實質上普通的材質只是在視覺上達到了效果。真正要在木頭上跑步,敲擊,採集等等,你肯定還需要其他的邏輯去處理。UE裡面提供了物理材質便於你去定義你的遊戲世界裡面的物理類型(即物理材質),物理材質的創建很簡單,編輯器——AddNew——Physics——PhysicalMaterial。創建之後打開就是這樣的,參數很少,基本上就是設置一下摩擦係數和物理表面類型,具體可以參考官方文檔的介紹。

圖6-1

關於表面類型,可以打開編輯器Edit——ProjectSettings——Physics——PhysicalSurface來查看與添加。

圖6-2

物理材質添加完之後,需要賦給對應的材質、材質實例、StaticMesh、SkeletonMesh等等,在各個藍圖搜索physicalMaterial既可。另外,對於每一個Mesh(實際上BodyInstance)都存在一個PhysMaterialOverride,可以覆蓋你前面設置的物理材質。

最後,簡單說一下一般遊戲裡面的使用場景。

  • 第一點是走路與落地的音效,在PrimalCharacter(玩家與動物通用)裡面會通過GetFootPhysicalSurfaceType函數獲取腳下地面的材質類型,進而根據檢測到的類型播放事先預製好的音效。
  • 第二點是武器擊中不同物體對應的特效與音效,思路基本上相同。

推薦閱讀:

《InsideUE4》UObject(六)類型系統代碼生成重構-UE4CodeGen_Private
[UE4]資源非同步載入(Assets Asynchronous Loading)與內存釋放(Free Memory)
[UE4]Generate Visual Studio工程時如何指定VS版本
技術美術職能概述(Unreal Engine 4)
[UE4]Indirect Lighting Cache(間接光照緩存)

TAG:遊戲 | 遊戲引擎 | 虛幻引擎 |