
1. 項目概述從“黑盒”到“白盒”的物理系統探索如果你在Unity里做過一個簡單的盒子從斜坡上滾下來的效果或者實現過一個復雜的布娃娃系統那你一定和物理系統打過交道。大多數時候我們把它當作一個“黑盒”設置剛體Rigidbody、碰撞體Collider調整幾個參數然后物理引擎就會自動處理碰撞、重力、關節約束這些復雜的事情。但當你遇到一些棘手的問題比如物體莫名穿透、關節抖動、或者性能突然下降時僅僅調整參數往往無濟于事。這時深入引擎源碼理解物理系統內部的運作機制就成了解決問題的關鍵。這也是我們繼上一篇基礎架構分析后繼續深入“Unity引擎源碼-物理系統詳解”的原因。這次我們把焦點從宏觀架構轉向更核心的模擬流程與算法實現。物理系統的核心任務是在每一幀將場景中所有物理實體的狀態位置、旋轉、速度向前推進一小步。這個過程看似簡單實則內部包含了碰撞檢測Broad Phase Narrow Phase、碰撞求解Constraint Solver和積分Integration等多個精密環節。理解這些環節不僅能幫你精準定位“物體為什么穿墻了”、“關節為什么抖個不停”這類問題更能讓你在開發賽車游戲、物理解謎或復雜的角色交互時擁有優化性能和穩定性的主動權。無論是使用內置的PhysX還是探索基于DOTS的Unity Physics或Havok Physics其底層邏輯是相通的。我們將剝開這層外殼看看Unity是如何將物理世界的法則轉化為一行行可執行的代碼。2. 物理模擬的核心循環拆解Unity的物理模擬無論是基于GameObject的傳統系統還是基于ECS的DOTS物理都遵循一個經典的“模擬循環”。這個循環是物理引擎的心臟驅動著整個虛擬世界的運動。理解這個循環是理解一切物理現象和調試問題的基石。2.1 傳統物理系統PhysX的幀內流程在傳統的、基于MonoBehaviour的系統中物理模擬獨立于渲染幀運行其頻率由Time.fixedDeltaTime控制。一個完整的FixedUpdate物理幀內引擎內部大致執行以下順序應用力與扭矩Apply Forces/Torques在FixedUpdate函數調用后物理引擎會收集所有作用于剛體上的力如AddForce、扭矩AddTorque和沖量AddImpulse。這是改變物體運動狀態的輸入階段。寬階段碰撞檢測Broad Phase這是性能優化的第一道關卡。引擎不會愚蠢地讓場景中每一個物體都去和另一個物體檢測碰撞。寬階段的目標是快速找出可能發生碰撞的物體對Pair。Unity PhysX通常使用軸對齊包圍盒AABB層次結構如動態AABB樹來實現。它會遍歷所有碰撞體用它們的AABB進行快速的重疊測試將大量不可能碰撞的組合剔除生成一個“潛在碰撞對”列表。你可以通過Physics類的設置來調整寬階段算法例如在復雜場景中合理的AABB膨脹Physics.defaultContactOffset能平衡精度和性能。窄階段碰撞檢測Narrow Phase對上一步篩選出的每個潛在碰撞對進行精確的幾何相交測試。這是計算密集型操作。例如一個BoxCollider和一個MeshCollider的檢測就需要進行多邊形層面的精確計算。此階段會計算出碰撞的詳細信息接觸點Contact Point、接觸法線Contact Normal和穿透深度Penetration Depth。這些數據是后續求解的基礎。求解器準備Solver Setup將碰撞信息、關節約束等全部轉化為物理引擎內部求解器能處理的數學形式——通常是約束方程。例如一個碰撞約束可以理解為“兩個物體在接觸點法線方向上相對速度不能為負即不能繼續穿透”。關節則更復雜可能包含位置、旋轉、角度限制等多個約束。約束求解Constraint Solver這是物理模擬中最核心、最復雜的步驟。求解器如PhysX使用的PGS迭代求解器的任務是解算所有約束方程計算出為了滿足這些約束不穿透、關節連接每個剛體所需要的速度或沖量調整量。這個過程是迭代的迭代次數Physics.defaultSolverIterations直接影響模擬的穩定性和精度。迭代次數少計算快但可能不穩定關節松散、堆疊晃動迭代次數多更穩定但更耗時。積分Integration根據求解器計算出的最終速度已考慮了碰撞和約束的反作用結合上一幀的位置通過積分公式如半隱式歐拉法更新每個剛體的位置Position和旋轉Rotation。同時也會更新一些內部狀態如用于睡眠判斷的速度緩存。觸發與回調Triggers Callbacks如果發生碰撞的物體設置了isTrigger或者腳本中監聽了OnCollisionEnter等消息物理引擎會在此階段組織并派發這些回調事件到游戲邏輯層。睡眠管理Sleep Management為了節省性能幾乎靜止的物體會進入“睡眠”狀態。引擎會檢查剛體的動能如果低于某個閾值Physics.sleepThreshold且一段時間內沒有受到外力則讓其“入睡”在后續幀中跳過該物體的絕大部分物理計算直到它被碰撞或外力喚醒。注意這個順序是邏輯上的實際在PhysX原生代碼中可能交錯或并行。但作為使用者建立這個順序模型對調試至關重要。例如在FixedUpdate中修改剛體位置會影響寬/窄階段檢測而在OnCollisionEnter中施加力則要等到下一幀的“應用力”階段才會生效。2.2 DOTS物理Unity Physics/Havok Physics的并行化革新基于ECS架構的DOTS物理系統核心目標是將上述流程并行化、批量化以榨干多核CPU的性能。其模擬循環在概念上相似但數據組織和執行方式有本質區別數據布局所有物理組件如PhysicsVelocity,PhysicsCollider,PhysicsMass都是IComponentData存儲在緊密排列的Chunk內存中。這種布局對CPU緩存極其友好是高性能的基石。作業系統調度模擬循環中的每一個階段如碰撞檢測、求解器、積分都被封裝成一個或多個IJobEntity或IJob。Unity的作業系統Job System會自動將這些作業調度到多個CPU核心上并行執行。例如計算所有剛體下一幀速度的積分作業可以輕松地并行處理成千上萬個實體。碰撞檢測的并行化DOTS物理使用全新的、為并行計算設計的碰撞檢測算法。寬階段可能使用并行化的Sweep and Prune或并行邊界體積層次結構BVH構建。窄階段的幾何測試也被設計成可并行執行。求解器的差異Unity Physics作為“無狀態”物理其求解器設計更傾向于避免依賴上一幀的緩存狀態這使得它在網絡同步和確定性模擬方面有潛在優勢。而Havok Physics for Unity則集成了經過工業驗證的、帶智能緩存的求解器在復雜堆疊和穩定性上表現更佳但其底層核心是閉源的C引擎。同步點盡管作業可以并行但階段之間存在依賴關系。比如必須等所有碰撞檢測作業完成才能開始求解器作業。ECS通過System的[UpdateBefore/After]屬性或EntityCommandBuffer來管理這些依賴和同步。實操心得從傳統PhysX轉向DOTS物理時最大的思維轉變是從“面向對象”到“面向數據”。你不再是通過GetComponentRigidbody來操作單個物體而是通過Entities.ForEach來批量處理所有具備特定物理組件的實體。這種范式對于大規模、同質化的物理對象如成千上萬的子彈、碎片、粒子性能提升是顛覆性的但對于少量、異質性強的復雜交互如一個主角與復雜環境的多種交互傳統模式在開發效率上可能仍有優勢。3. 碰撞檢測的深度解析從AABB到接觸流形碰撞檢測是物理系統中最耗時的部分之一也是bug的高發區。我們深入看看Unity是如何實現它的。3.1 寬階段Broad Phase的策略與優化寬階段的目標是快速縮減需要精確檢測的物體對數量。Unity PhysX主要采用動態AABB樹Dynamic Bounding Volume Hierarchy Tree。工作原理引擎為場景中每個活動的碰撞體維護一個軸對齊包圍盒AABB。這些AABB被組織成一棵二叉樹。當物體移動時其AABB更新樹的結構也會進行局部調整重插或重構。每一幀通過遍歷這棵樹可以高效地找出所有AABB重疊的葉子節點對。關鍵參數影響Physics.defaultContactOffset這個值不僅用于解決浮點誤差在寬階段中它會被用來“膨脹inflate”物體的AABB。更大的值意味著AABB更大能更早地觸發碰撞檢測避免高速物體穿透但也會導致更多的潛在碰撞對進入窄階段增加計算量。這是一個典型的精度與性能的權衡。Physics.broadphaseType在某些版本或自定義中你可以選擇寬階段算法如Sweep and Prune。對于動態物體多、運動頻繁的場景動態AABB樹通常綜合性能更好。調試技巧在Scene視圖開啟Gizmos - Physics下的Colliders顯示你可以看到碰撞體的線框。高速移動的物體如果發生穿透可以嘗試適當增大defaultContactOffset。同時觀察Profiler窗口的Physics.Processing部分如果Broadphase耗時異常高可能是場景中動態物體過多或AABB設置不合理。3.2 窄階段Narrow Phase的幾何對決窄階段接收寬階段傳來的物體對進行精確的幾何相交測試。不同類型的碰撞體組合測試算法完全不同基礎圖元對圖元如Sphere-Sphere, Box-Box, Capsule-Capsule。這些有直接的數學公式速度最快。圖元對凸包Convex Hull凸包碰撞體Convex Hull Collider是性能與精度折中的選擇。檢測算法通常使用分離軸定理Separating Axis Theorem, SAT或Gilbert–Johnson–Keerthi (GJK) 算法配合擴張多面體/平移向量EPA算法。GJK用于快速判斷是否相交EPA則在相交時計算穿透深度和方向。網格碰撞體Mesh Collider這是性能殺手。當Mesh Collider特別是非凸的參與檢測時引擎通常需要將其三角面片與另一個碰撞體進行逐一或分區測試。務必勾選Convex選項將其轉換為凸包性能會提升數個數量級。對于靜態的環境網格使用Mesh Collider并標記為Static引擎會為其生成優化的空間數據結構如BVH但動態物體與之碰撞的成本依然很高。一個關鍵概念接觸流形Contact Manifold窄階段輸出的不是單個接觸點而是一個接觸流形——即一組通常是1-4個能最好地描述兩個碰撞體接觸區域的接觸點。例如一個立方體平面放在地上理想的接觸流形是四個角點。使用流形而非單點能使后續的求解更穩定防止物體在接觸邊緣搖擺或旋轉。在Unity PhysX中你可以通過Physics.Contact相關的API部分需通過底層接口來獲取這些信息這對于實現自定義的碰撞效果如根據接觸點播放音效非常有用。注意事項Mesh Collider的Cooking Options在導入設置或組件上對性能和穩定性有巨大影響。Use Fast Midphase和Cook For Faster Simulation通常應該開啟。對于移動平臺要嚴格控制網格碰撞體的面數并積極使用凸包近似或層次化簡單碰撞體組合來代替單一復雜網格碰撞體。4. 約束求解器物理穩定的幕后功臣求解器是物理引擎的“大腦”它負責解決所有沖突的約束。想象一下有十個盒子堆成一個塔每個盒子都受到重力同時盒子之間又有碰撞約束防止相互穿透。求解器的任務就是計算出每個盒子在這一幀應有的移動讓所有約束都盡可能得到滿足。4.1 迭代求解速度與穩定的平衡Unity PhysX默認使用順序沖量法Sequential Impulse這是一種投影高斯-賽德爾Projected Gauss-Seidel, PGS迭代求解器。它的工作方式很直觀列出所有約束方程碰撞、關節等。遍歷每一個約束獨立地計算滿足該約束所需的沖量調整并立即應用到相關物體上。由于物體可能同時參與多個約束一次調整可能會破壞之前已滿足的約束。因此需要重復步驟2多次即迭代。經過多次迭代后所有約束會趨于一個近似解。關鍵參數Physics.defaultSolverIterations全局迭代次數。增加此值可以提高堆疊穩定性、關節剛性但代價是CPU時間線性增加。對于簡單的場景4-6次可能就夠了對于復雜的布娃娃或車輛可能需要10-20次。Physics.defaultSolverVelocityIterations速度約束的迭代次數主要影響接觸和關節的反彈、摩擦力求解。通常比位置迭代次數設置得高一些效果更好。實操心得不要盲目增加迭代次數。首先應該優化碰撞體確保沒有過于細長或尺度差異巨大的碰撞體這會導致數值病態檢查是否有持續深度穿透的情況可能是defaultContactOffset太小或物體生成位置重疊。對于特定的、需要高穩定性的關節如角色鉸鏈可以使用ConfigurableJoint的solverIterationCount屬性進行單獨覆蓋而不是全局提高開銷。4.2 關節約束的內部實現關節Joint是比碰撞約束更復雜的約束類型。以最靈活的ConfigurableJoint為例在求解器內部它會被分解為多個簡單的線性約束和角約束線性限制Linear Limit相當于在特定軸上設置了一個“不可逾越”的位置范圍約束。角度限制Angular Limit限制了繞特定軸旋轉的角度。彈簧Spring和阻尼Damper這些是“軟約束”求解器會將其轉化為試圖達到目標位置或速度的力而不是硬性限制。在源碼層面每個關節類型都會在初始化時向物理場景注冊對應的約束器Constraint。在每幀求解階段這些約束器會將其當前的物理狀態如連接的兩物體的位置差、角度差轉化為求解器能處理的線性互補問題LCP形式。常見問題排查關節抖動或“爆炸”突然產生巨大力量飛出去通常源于數值誤差累積嘗試稍微增加關節的breakForce一個不可能達到的值或調整massScale改變連接體的有效質量比。約束沖突例如同時鎖定了某個方向的移動和在該方向設置了彈簧可能導致求解器振蕩。時間步長不穩定確保Time.fixedDeltaTime是固定的且Time.maximumAllowedTimestep能防止卡頓幀導致過大的物理步進。5. 性能優化與調試實戰指南理解了原理最終要服務于實踐。以下是一些基于源碼邏輯的實戰優化和調試技巧。5.1 性能剖析與瓶頸定位首先必須善用Unity Profiler打開Profiler窗口切換到Physics或Physics2D子面板。觀察Physics.Processing的總耗時以及其下的子項Broadphase耗時高說明動態物體太多或AABB更新頻繁。考慮將靜止物體設為Static或使用Rigidbody的Sleep模式。Narrowphase耗時高說明復雜碰撞檢測多。檢查是否大量使用了非凸MeshCollider或碰撞體三角面片過多。Solver耗時高說明約束復雜堆疊多、關節多。嘗試優化迭代次數或簡化物理場景。使用Physics DebuggerWindow - Analysis - Physics Debugger 需安裝Package。它可以可視化物理世界的狀態如顯示碰撞體、接觸點、剛體睡眠狀態等是定位問題區域的利器。5.2 針對性的優化策略分層碰撞Layer Collision Matrix這是最有效的優化手段之一。在Edit - Project Settings - Physics中精心設計碰撞矩陣。讓不需要相互碰撞的物體層徹底忽略對方如子彈和子彈、遠處的裝飾物之間能直接減少寬階段和窄階段的工作量。剛體屬性優化Interpolate只在視覺抖動時才開啟它會消耗額外內存和計算進行插值。Collision Detection對于高速物體Continuous或Continuous Dynamic可以防止穿透但性能開銷巨大。Continuous Dynamic只對標記為動態的物體進行連續檢測是較好的折中。盡可能使用Discrete并通過合理設計游戲規則如限制速度來避免穿透。將不需要受物理驅動的物體如跟隨玩家的攝像機、粒子效果發射器的Rigidbody設為Kinematic。碰撞體優化用簡單形狀復合一個復雜物體用多個Box、Sphere、Capsule組合遠比一個Mesh Collider高效。簡化網格碰撞體如果必須用Mesh Collider使用簡化后的低模網格。合理使用TriggerTrigger不參與物理求解開銷小于普通碰撞體。對于僅需檢測重疊的區域使用Trigger。5.3 高級調試深入引擎內部當你遇到無法用常規手段解釋的物理bug時可能需要更深入的洞察可視化接觸與法線可以編寫一個調試腳本在OnCollisionStay或通過Physics.Contact查詢用Debug.DrawLine或Gizmos.DrawRay繪制出每個接觸點的位置和法線方向。這能幫你判斷碰撞檢測是否準確法線方向是否符合預期。模擬確定性測試對于需要網絡同步或錄像回放的游戲物理的確定性至關重要。確保所有物理相關的計算包括隨機數都在FixedUpdate中進行并使用固定的Time.fixedDeltaTime。可以記錄關鍵剛體幾幀內的位置/旋轉數據在相同輸入下反復運行檢查輸出是否一致。源碼輔助理解雖然看不到PhysX的完整C源碼但Unity提供了部分封裝層的C#源碼通過Unity源碼訪問或反編譯工具。閱讀Rigidbody、Collider等類的底層接口調用以及Physics、RaycastHit等結構的定義能幫助你理解參數是如何傳遞到底層引擎的。對于DOTS物理其C#源碼是開放的直接閱讀Unity.Physics包中的SimulationStep.cs、CollisionQuery.cs等文件是理解其并行化實現的最佳途徑。我個人在實際項目中的一個深刻教訓曾有一個賽車游戲車輛在特定彎道偶爾會莫名彈飛。通過Profiler發現該幀Solver耗時激增。用Physics Debugger可視化后發現是賽道邊緣的一個復雜裝飾物Mesh Collider未勾選Convex與車輪的多個膠囊碰撞體產生了大量非預期的接觸點導致求解器在該處“卡住”并產生巨大沖量。解決方案是將那個裝飾物的碰撞體替換為一個簡單的Box Collider問題立即消失。這個案例讓我明白物理性能問題往往不是均勻分布的而是由場景中少數幾個“熱點”引起的精準定位這些熱點是關鍵。