Unity物理系統由淺入深第一節:Unity 物理系統基礎與應用
Unity物理系統由淺入深第二節:物理系統高級特性與優化
Unity物理系統由淺入深第三節:物理引擎底層原理剖析
Unity物理系統由淺入深第四節:物理約束求解與穩定性
Unity 物理系統底層使用的是 NVIDIA PhysX 引擎。理解 PhysX 或任何其他實時物理引擎(如 Havok, Bullet)的工作方式,對于一名進階的 Unity 開發者來說,是極其寶貴的知識。本篇我們將從宏觀到微觀,逐步了解物理引擎的核心概念和主要算法。
1. 物理引擎概述:一個模擬真實世界的機器
物理引擎本質上是一個復雜的軟件系統,它接收場景中所有具有物理屬性(剛體、碰撞體等)的物體信息,然后在一個固定時間步(Fixed Timestep)內,迭代計算這些物體在各種力(重力、推力、摩擦力等)和約束(碰撞、關節)作用下的位置、速度和旋轉,從而模擬出真實的物理行為。
一個典型的物理引擎循環(在每個 FixedUpdate
中執行)大致遵循以下步驟:
- 積分器 (Integrators): 根據當前的速度和受到的力,預測物體在下一個時間步的位置和速度。
- 寬相碰撞檢測 (Broad-Phase Collision Detection): 快速找出場景中所有可能發生碰撞的物體對。
- 窄相碰撞檢測 (Narrow-Phase Collision Detection): 對上一步篩選出的可能碰撞對,進行精確的碰撞檢測,計算出詳細的碰撞信息(接觸點、法線、穿透深度等)。
- 接觸生成 (Contact Generation): 根據窄相檢測的結果,生成接觸點(Contact Points)和接觸法線(Contact Normals)。
- 約束求解器 (Constraint Solver): 處理所有碰撞(以及關節)產生的約束。這是物理引擎最復雜的部分,它通過迭代計算來解決物體之間的相互作用,確保它們不會相互穿透,并且能正確反彈、滑動或連接。
- 更新狀態: 根據求解器的結果,更新所有剛體的位置和速度。
2. 剛體動力學基礎:物理引擎的語言
在深入算法之前,我們需要回顧一下物理學中的一些基本概念,它們是物理引擎的“語言”。
- 力 (Force):改變物體運動狀態的原因。單位牛頓 (N)。
- 質量 (Mass):物體慣性的量度。單位千克 (kg)。
- 慣性 (Inertia):物體保持其運動狀態的特性。質量越大,慣性越大。
- 慣性張量 (Inertia Tensor):描述剛體旋轉慣性的一個 3x3 矩陣。它表示了物體在不同軸上抵抗旋轉的能力。對于非球對稱的物體,其旋轉慣性在不同方向上是不同的。在 Unity 中,對于常見的原始碰撞體,PhysX 會自動計算好。對于 Mesh Collider,可能需要外部工具或手動計算。
- 線速度 (Linear Velocity):物體直線運動的速度向量。
- 角速度 (Angular Velocity):物體旋轉的速度向量。
- 動量 (Momentum):物體的質量與其線速度的乘積。
- P=mvP = mvP=mv
- 角動量 (Angular Momentum):描述物體旋轉運動的量。
- L=IωL = I\omegaL=Iω (其中 III 是慣性張量,ω\omegaω 是角速度)
- 沖量 (Impulse):在短時間內施加的巨大力。它直接導致動量的瞬間變化。
- ΔP=FΔt\Delta P = F \Delta tΔP=FΔt (力乘以時間,或直接視為動量變化量)
- 扭矩 (Torque):使物體產生旋轉的力矩。它直接導致角動量的瞬間變化。
- τ=r×F\tau = r \times Fτ=r×F (力臂與力的叉乘)
物理引擎通過這些概念來計算和更新物體的運動狀態。例如,當施加一個力時,引擎會根據牛頓第二定律 F=maF = maF=ma 來計算加速度,然后用積分器更新速度和位置。當發生碰撞時,引擎會計算一個沖量,施加到碰撞的物體上,使其反彈或滑動。
3. 積分器(Integrators):連接現在與未來
積分器是物理引擎的“時間旅行者”,它們負責根據當前的速度和力,預測物體在下一個微小時間步長后的位置和速度。這是物理模擬的核心,因為我們需要連續地更新物體的狀態。
-
歐拉積分 (Euler Integration):
- 原理: 最簡單也是最直觀的積分方法。它假設在整個時間步長內,速度或力是恒定的,并直接通過當前的速度和加速度來更新位置和速度。
- 公式:
- vnew=vcurrent+a?Δtv_{new} = v_{current} + a \cdot \Delta tvnew?=vcurrent?+a?Δt
- xnew=xcurrent+vcurrent?Δtx_{new} = x_{current} + v_{current} \cdot \Delta txnew?=xcurrent?+vcurrent??Δt
- 優點: 實現簡單,計算速度快。
- 缺點: 精度最低,容易產生累積誤差,導致能量損失或增加(物體可能會越彈越低或越彈越高),特別是在較大的時間步長下,可能導致數值不穩定。
- 示例: 在簡單場景中可以接受,但在需要高精度模擬(如車輛、關節鏈)時則不夠理想。
-
Verlet 積分 (Verlet Integration):
- 原理: 一種更穩定且能更好地保持能量的積分方法。它不直接使用速度,而是利用當前位置和上一幀位置來計算新位置。速度是隱式包含的。
- 公式(簡化版):
- xnew=2xcurrent?xprevious+a?Δt2x_{new} = 2x_{current} - x_{previous} + a \cdot \Delta t^2xnew?=2xcurrent??xprevious?+a?Δt2
- 優點: 能量守恒性好,數值穩定性強,即使在較大時間步長下也不太容易發散。
- 缺點: 無法直接訪問速度,如果需要速度信息(例如用于碰撞響應),需要額外計算。
- 示例: 常用于粒子系統、布料模擬等對穩定性要求高的場景。
-
龍格-庫塔積分 (Runge-Kutta Integration - RK4):
- 原理: 一系列更高階的積分方法,通過在時間步長內采樣多個點來計算加權平均值,從而提高精度。RK4 是最常用的一種。
- 優點: 精度高,穩定性好,能夠更準確地模擬復雜的物理系統。
- 缺點: 計算量遠大于歐拉積分,因為需要在每個時間步內進行多次求值。
- 示例: 理論上最接近真實物理的積分方法,但在實時游戲中因性能開銷大而較少直接用于大量剛體,更常見于需要高精度模擬的特定場景或離線物理模擬。
Unity PhysX 采用的積分器: 盡管 PhysX 的具體實現是專有的,但它通常使用經過優化的隱式歐拉積分(Implicit Euler)或類似的半隱式積分。這種方法結合了歐拉的簡潔和一些穩定性優勢,通過在迭代求解器中同時考慮當前和下一幀的狀態來提高穩定性。它并不是單純的顯式歐拉積分。
4. 碰撞檢測(Collision Detection):尋找接觸點
碰撞檢測是物理引擎最核心、最耗性能的部分之一。它分為兩個階段:寬相檢測和窄相檢測。
4.1. 寬相檢測(Broad-Phase Collision Detection)
- 目的: 快速排除場景中絕大多數不可能發生碰撞的物體對。這一步不需要精確的計算,只需要找出“可能”碰撞的候選對。
- 核心思想: 使用粗略的包圍體(Bounding Volume),如 AABB (Axis-Aligned Bounding Box),來近似表示物體的空間范圍。如果兩個 AABB 不重疊,那么它們內部的物體肯定不碰撞。
- 常用算法:
- AABB 樹 (AABB Tree):一種層次化的數據結構。將場景中的所有 AABB 組織成一棵樹。檢測時,只需遍歷樹,如果父節點的 AABB 不重疊,則其子節點也不可能重疊,從而快速排除大量物體。這是 PhysX 廣泛使用的技術。
- SAP (Sweep and Prune / Sort and Sweep):對所有物體在每個軸上的 AABB 投影進行排序。只有當兩個物體的 AABB 在所有軸上都重疊時,才可能發生碰撞。通過維護排序列表,可以高效地找出重疊對。適用于物體數量不太多且運動方向較固定的場景。
- 網格/格子(Grids/Spatial Hashing):將空間劃分為規則的網格單元。每個單元存儲其中包含的物體。檢測時只檢查相鄰或同一單元格內的物體。適用于物體均勻分布的場景。
4.2. 窄相檢測(Narrow-Phase Collision Detection)
- 目的: 對寬相檢測篩選出的“可能”碰撞對,進行精確的幾何測試,判斷它們是否真的發生了碰撞,并計算出詳細的碰撞信息(接觸點、法線、穿透深度等)。
- 核心思想: 使用更精確的幾何算法來處理復雜形狀。
- 常用算法:
- GJK (Gilbert-Johnson-Keerthi Distance Algorithm):
- 原理: GJK 算法并不是直接找到碰撞點,而是用來判斷兩個凸包(Convex Hull)是否相交。它通過迭代尋找兩個物體之間的最短距離向量。如果這個最短距離為零或指向內,則表示它們相交。
- 優點: 效率高,適用于任意凸包形狀,是現代物理引擎的核心。
- 缺點: 只能判斷是否相交,不能直接得到碰撞點和法線。
- EPA (Expanding Polytope Algorithm):
- 原理: EPA 通常緊接著 GJK 之后使用。當 GJK 判斷兩個物體相交后,EPA 會從 GJK 找到的“穿透方向”開始,在閔可夫斯基差集(Minkowski Difference)中擴展一個多面體,直到找到最接近原點的那個面。這個面就定義了最小穿透向量(法線和深度)。
- 優點: 與 GJK 結合,可以準確地計算穿透深度和碰撞法線。
- SAT (Separating Axis Theorem - 分離軸定理):
- 原理: 對于兩個凸體,如果它們不相交,那么一定存在一個軸,使得它們在這條軸上的投影互不重疊。反之,如果找不到這樣的分離軸,則它們相交。對于多邊形,需要測試所有邊以及垂直于這些邊的軸。
- 優點: 簡單易懂,對于多邊形(2D)和多面體(3D)非常有效。
- 缺點: 對于復雜形狀,需要測試的軸可能很多,效率不如 GJK/EPA 組合。
- GJK (Gilbert-Johnson-Keerthi Distance Algorithm):
4.3. 接觸生成 (Contact Generation)
- 一旦窄相檢測確定了碰撞,下一步就是生成一個或多個接觸點 (Contact Point)。每個接觸點包含了:
- 位置 (Position):碰撞發生在世界空間中的坐標。
- 法線 (Normal):指向從一個物體到另一個物體的方向。這決定了碰撞后的反彈方向。
- 深度 (Separation/Penetration Depth):物體之間相互穿透的距離。這個信息對于約束求解器至關重要,因為它需要將物體推開。
- 對于復雜的碰撞,可能會生成多個接觸點(例如,一個盒子平躺在地面上)。這些接觸點會被收集起來,傳遞給下一步的約束求解器。
總結
至此,我們已經基本了解了 Unity 物理引擎的底層工作原理:從宏觀的物理循環,到驅動物體運動的剛體動力學基礎,再到連接過去與未來的積分器,以及尋找和確定碰撞點的寬相和窄相碰撞檢測算法。
我們現在應該對物理引擎如何“看”到和“理解”我們的游戲世界有了更清晰的認識。下一篇,我們將繼續深入物理引擎最復雜也是最精妙的部分:物理約束求解與穩定性,了解它是如何解決碰撞穿透、摩擦和關節限制等問題的。
Unity物理系統由淺入深第一節:Unity 物理系統基礎與應用
Unity物理系統由淺入深第二節:物理系統高級特性與優化
Unity物理系統由淺入深第三節:物理引擎底層原理剖析
Unity物理系統由淺入深第四節:物理約束求解與穩定性