Unity 繩子插件,是一個基于物理的、高度逼真且可交互的繩索模擬解決方案。
其性能良好,能夠運行在小游戲平臺。
一、插件基本
插件資源商店地址:
Obi Rope | Physics | Unity Asset Store
官方文檔(手冊):
Obi Physics for Unity - Rope Setup
官方文檔(API):
Obi - Unified Particle Physics for Unity 3D
1、導入插件
2、按照URP和插件文檔說明,修復材質為URP材質
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@7.1/manual/upgrading-your-shaders.html
3、安裝Burst?、Collections?、Mathematics?包(unity2021.3.42f1c1)
二、ObiSolver 及相關設置
可以添加到任何GameObject上;
在同一個場景中可以有多個解算器同時工作;
每個Actor都必須在一個 ObiSolver?層級之下才能工作
每個ObiSolver獨立,由不同 ObiSolver更新的 Actor 不會產生相互影響/碰撞。
Solver 設置
Backends
使用哪個模擬后端。Brust性能更好,如果任何給定的后端不可用,Obi 將嘗試回退到 Oni 后端。
Mode
2D 模式通常與 2D 碰撞器一起使用。
Interpolation
用于渲染粒子的插值模式。None計算速度更快,而插值將提供更美觀的結果.插值會引入 1 幀延遲。
模擬設置
Gravity Space
如果設置為“Self”,重力方向將與解算器一起旋轉。
Gravity
應用于此ObiSolver中粒子的重力方向和大小,以ObiSolver的局部空間表示。
Sleep threshold都將凍結
任何動能低于此值的粒子在原地。(忽略微小抖動)
注意,休眠粒子并不會從模擬中移除,也不會節省性能。
Damping
應用于粒子速度的速度阻尼。增加速度阻尼可降低粒子的動能
World linear inertia scale
應用于粒子的世界空間線性(移動)慣性,范圍0~1
控制 運動時突然停止/減速;停止時突然運動或加速的表現
World angular inertia scale
應用于粒子的世界空間角慣性(旋轉),范圍0~1
Max Anisotropy
最大各向異性
Obi 中的流體粒子可以是橢圓形,而不是完美的球形。這用于更好地使其形狀適應它們所代表的物體的表面,從而實現更準確的碰撞檢測和更平滑的渲染。最大各向異性可讓您確定橢圓半徑之間的最大比率:值為 1 將強制所有粒子為球形(停用各向異性),大于 1 的值將允許粒子變得更橢圓,最大各向異性越高。
Simulate when invisible
隱藏時模擬?
當所有相機都看不到該ObiSolver時,該ObiSolver是否應保持模擬繼續進行?
如果您的模擬需要始終更新,請保持啟用此功能;
當場景中存在多個ObiSolver但它們并非始終可見時,請禁用此功能以提高性能。
平流設置(平流粒子(泡沫)的全局設置)(暫跳過)
碰撞設置
Collider CCD:(Continuous collision detection)
連續碰撞檢測
在碰撞檢測期間用于擴展碰撞體邊界框的剛體速度百分比。值為 1 將使用 100% 的速度,從而產生完全的連續碰撞檢測。值為 0 將導致純靜態碰撞檢測。
Particle CCD (Continuous collision detection)
粒子連續碰撞檢測
在碰撞檢測期間用于擴展其邊界框的粒子速度百分比。值為 1 將使用 100% 的速度,從而產生完全的連續碰撞檢測。值為 0 將導致純靜態碰撞檢測。
Collision margin??Collision margin
碰撞邊距
添加到粒子邊界框的邊距(CCD 擴展后),在生成觸點時使用。粒子邊界框內的任何碰撞體/粒子都將被考慮用于接觸生成。此值應保持相對較低。較大的值將生成更多的接觸,這可以提高非常復雜場景中的穩定性,但會對性能產生負面影響。
Max depenetration??
最大穿透力
使粒子脫離碰撞盒的最大速度(以米/秒為單位)。
Shock propagation??沖擊傳播
較高的值將人為地增加支持其他粒子的粒子的質量。
使用它來獲得更好的堆疊穩定性。
Surface collision iterations
表面碰撞迭代
為優化表面碰撞而執行的最大迭代次數。
Surface collision tolerance
曲面碰撞容差
容差閾值,低于該閾值時,曲面碰撞優化將停止。
稍微增加它有助于獲得與平面更穩定的表面碰撞。
約束設置
您可以為解算器管理的所有角色全局啟用/禁用每種約束類型。以這種方式禁用約束(與禁用單個角色的約束組件相反)將允許解算器完全跳過與該特定約束類型相關的任何計算。
例如,如果禁用碰撞約束和粒子碰撞約束,則會跳過整個碰撞檢測管道。這使您可以自定義在后臺執行的作,并消除不必要的開銷。
默認情況下,所有約束類型都處于啟用狀態,盡管這很少是生產就緒模擬所需的。您應該禁用任何對模擬的最終外觀不重要的約束。
Iterations??
迭代
每個子步驟應該評估這些約束多少次?高迭代計數將使模擬更接近真實解決方案。如果這些約束對于您的特定目的不是很重要,并且您希望獲得更好的性能,請保持較低的值。默認值為 3。
Evaluation mode??
評估模式
在?Sequential (順序) 模式下,所有約束都按照它們的創建順序 (由每個特定的 ObiActor 確定) 進行評估,并且每個約束 “看到” 所有先前的約束所做的調整。這確保了快速收斂,因此您的約束只需要很少的迭代即可看起來不錯。但是,當多個約束爭奪控制權時,它不是很穩定 - 它會引入抖動 - 因此在某些用例中,這種模式不是一個好的選擇。它與順序相關,因此在低預算情況下(少量粒子、少量迭代和/或大時間步長),這可能會導致粒子排列中出現可見的圖案。對于那些稍微懂技術的人來說,這是一個?Gauss-Seidel?類型的求解器。
在?Parallel?模式下,將評估所有約束,但不會立即將其調整應用于粒子。相反,它們被存儲、平均,然后使用最終結果來調整粒子位置。即使同時應用了大量約束,這也能產生非常穩定的模擬,但是“硬”約束需要更多的迭代,因為它的收斂速度更慢。它也是與階次無關的,因此可以確保顆粒的平滑排列。如果您想用性能(高迭代次數)或質量(低迭代次數)來換取穩定性和平滑度,請使用此模式。同樣,對于技術用戶來說:這是類似?Jacobi?的求解。
Relaxation Factor??
松弛因子
連續過度弛豫 (SOR) 因子。當嘗試滿足約束時,提高收斂性的一種方法是 “過度放寬” 約束。也就是說,如果將粒子向左移動 2 個單位現在就滿足約束,為什么不將其移動 3 個單位呢?這正是這個因子的用途。1 是默認值,它根本不執行過度松弛。2 是最大值,它允許您對約束執行兩倍 (200%) 的松弛。高值可用于幫助加快兩種模式(順序或并行)中的任何一種的收斂速度,但請記住,仿真穩定性可能會降低。小于 1 的值將僅部分強制執行約束。例如,當松弛因子為 0.25 時,約束將僅具有其正常效果的 25%。
一塊布、一根繩子、一個流體發射器或一個軟體,它們都是Actor。
所有 Actor 都以藍圖作為輸入。
Actor 將實例化藍圖中包含的信息(粒子和約束),以便解算器可以對其進行模擬。
您可以在多個 Actor 之間重復使用同一個藍圖。
如果希望將 Actor 包含在模擬中,則Actor 必須是Resolver的子項。
三、原理
本質:
模擬原理,重要!!!
若自己有手寫碰撞需求,亦可參考其實現原理
Obi 的工作原理
物理更新循環
Obi 將萬物建模為一組粒子和約束。
(約束,即為:預測后的校正)
注意點:
????????順序模式和并行模式
????????更高次數的迭代
????????時間步長
????????較重的物體不會下落得更快
????????物體的絕對質量并不重要,它們的相對質量才是關鍵,通常稱為質量比。
????????警惕較大的質量比,需要增加解算器的預算(迭代/子步)以確保滿足約束。
約束類型一覽
Obi Physics for Unity - Scripting Particles
注意:每個ObiSolver都有一個數組用于這些屬性中的每一個,該數組存儲由解算器管理的任何參與者正在使用的所有粒子的當前數據。所有空間屬性(位置、方向、速度、渦度等)都在解算器的局部空間中表示。
即:繩子的位置基于Solver,會跟著Solver移動,不會受中間層父物體位置的影響
四、具體操作或功能
繩子編輯
Obi Physics for Unity - Rope Setup
將繩子固定在某個點
動態設置繩子位置(移動后同步粒子)
public void SyncParticlesToTransform()
{// 計算當前 GameObject 位置與繩子實際位置的偏移量Vector3 positionOffset = transform.position - m_ObiRope.transform.position;// 遍歷所有粒子并應用偏移for (int i = 0; i < m_ObiRope.particleCount; i++){m_ObiRope.solver.positions.SetVector3(i, m_ObiRope.solver.positions.GetVector3(i) + positionOffset);}// 更新粒子位置并重置物理狀態m_ObiRope.UpdateParticleProperties();m_ObiRope.ResetParticles();
}
繩子撕裂/剪斷
Obi Physics for Unity - Cloth Tearing
Obi Physics for Unity - Scripting Ropes
//從鼠標點擊處切斷
public void Cut(Vector2 mousePosition)
{//不再允許點擊到m_BoxCollider2D.enabled = false;float minDistance = float.MaxValue;int nearestSegmentIndex = -1;// 遍歷所有線段尋找最近點for (int i = 0; i < m_ObiRope.elements.Count; i++){var element = m_ObiRope.elements[i];// 獲取當前元素的兩個粒子的索引int particleIndex1 = element.particle1;int particleIndex2 = element.particle2;// 獲取兩個粒子的世界坐標Vector3 worldPos1 = m_ObiRope.solver.positions.GetVector3(particleIndex1);Vector3 worldPos2 = m_ObiRope.solver.positions.GetVector3(particleIndex2);worldPos1 = m_ObiRope.transform.TransformPoint(worldPos1);worldPos2 = m_ObiRope.transform.TransformPoint(worldPos2);if (!Mathf.Approximately(worldPos1.z, worldPos2.z)) { continue; } //忽略非XY平面的線段// 計算點擊點與線段的最近點Vector2 clickScreenPos = mousePosition;Vector2 screenPos1 = Camera.main.WorldToScreenPoint(worldPos1);Vector2 screenPos2 = Camera.main.WorldToScreenPoint(worldPos2);Vector2 closestPoint = ClosestPointOnLineSegment(screenPos1, screenPos2, clickScreenPos);float distance = Vector2.Distance(clickScreenPos, closestPoint);// 更新最近記錄if (distance < minDistance){minDistance = distance;nearestSegmentIndex = i;}//繩子線段位置調試//GameObject go1 = new GameObject($"Test{i}_1");//go1.transform.position = worldPos1;//GameObject go2 = new GameObject($"Test{i}_2");//go2.transform.position = worldPos1;}// 獲取控制點 start 和 end 的索引//限制(即:不允許從兩頭斷開)int startIndex = 2;int endIndex = m_ObiRope.elements.Count - 2;int cutIndex = Mathf.Clamp(nearestSegmentIndex, startIndex, endIndex);// 執行切割m_ObiRope.Tear(m_ObiRope.elements[cutIndex]);m_ObiRope.RebuildConstraintsFromElements();//光標設為切割處m_CursorMu = CalculateCursorMu(cutIndex);// 獲取剪開處的2個粒子int particle1_1 = m_ObiRope.elements[cutIndex - 1].particle2;int particle2_1 = m_ObiRope.elements[cutIndex].particle1;// 施加一個指向繩子左側/右側的力(使搖擺)// 施加一個指向屏幕外的力(使看起來像向外崩開)// 施加一個沿著軸向兩端的力(使看起來像扯開)//todo:考慮受 m_CursorMu 的影響float sideForce = 50f;float backForce = 50f;float axisForce = 100f;Axis axis = m_RopeData.GetAxis();float xForce = axis == Axis.Horizontal ? axisForce : -sideForce;float yForce = axis == Axis.Horizontal ? 0 : axisForce;m_ObiRope.solver.velocities[particle1_1] += new Vector4(xForce, yForce, -backForce, 0);m_ObiRope.solver.velocities[particle2_1] += new Vector4(-xForce, -yForce, -backForce, 0);
}private Vector2 ClosestPointOnLineSegment(Vector2 A, Vector2 B, Vector2 P)
{Vector2 AP = P - A;Vector2 AB = B - A;float magnitudeAB = AB.sqrMagnitude;float ABAPproduct = Vector2.Dot(AP, AB);float distance = ABAPproduct / magnitudeAB;if (distance < 0) return A;else if (distance > 1) return B;else return A + AB * distance;
}
繩子收縮
//從光標處收縮(每幀更新)
private void UpdateToShrink()
{float minLength = 0.2f;//修改長度float newLength = m_ObiRope.restLength - ShowConfig.kRopeShrinkSpeed * Time.deltaTime * m_RopeData.GetLength();float safeNewLength = Mathf.Max(newLength, minLength);m_ObiRopeCursor.cursorMu = m_CursorMu;m_ObiRopeCursor.ChangeLength(safeNewLength);if (newLength < minLength) {m_OnShrinkCompleted();}
}
五、性能相關
1、繩子的解算器應開啟 Burst。
2、繩子的解算器,可分為全局大量靜止時(極低消耗設置),剪斷收縮動態表現時(流暢表現設置)
3、繩子的解算器,要關閉不必要的項,數值設置夠用即可。
4、可減少繩子 截面N邊型 和 繩子PathSmooting(縱向網格密),以減少模型面數
5、繩子在2D視角大量靜止時,其?Render Face 可使用 Front(Cull Front) 而非 Both(Cull Off)僅當剪斷收縮表現時,改為Both。