我們先從一個簡單的問題入手。
圖1
如圖,橫座標 t 是時間,縱座標 v 是速度,圖中圖像表示為 v = 2 ,意義為以恆定速度 2 (這裡省略了單位)前進 2 秒。那麼這 2 秒內,移動的距離是多少呢?
很簡單,我們可以直接得到 2 x 2 = 4,稍作觀察我們就會發現下圖:
圖2
路程的值就是 v = 2 與 t 軸組成的矩形的面積。
我們再看下一個。
圖3
有了前面的例子,這個圖像理解起來就更簡單了:路程的值為圖像中紅色陰影部分的面積,即 2 x 2 / 2 = 2。
上述兩個例子中,我們所要求得的路程的值,均為規整圖形的面積,那麼如果 v 值隨著 t 而做非勻速變化呢?如下圖:
圖4
這次並不能簡單的求得陰影部分面積了。但是我們可以用一種取巧的方法:
圖5
我們將陰影部分分割為一個一個的矩形,從圖像上可以看出,我們只要把所有矩形的面積求出並相加,就能得到一個近似值,也就是我們要求得的路程值。此求值過程,就是所謂的積分過程。
上述三個例子均是極其簡單的運動問題,同時也是一個簡單的積分過程。在遊戲引擎物理相關的模擬中,大多也是如此。
比如,給某物體施加一個恆定的力,隨著時間變化,這個力會推動物體行進多少距離?
再比如,給某物體施加一個扭矩,隨著時間變化,這個物體會旋轉多少度?
這個值的精確度取決於矩形的寬,即 t 值的間隔大小:
結合圖5、6 來看我們不難發現,如果矩形的寬度越小,分割出的矩形越多,我們得到的值就越精確。帶入具體的座標含義來說就是,如果我們計算的時間間隔(矩形的寬)越小,那麼我們得到的最終路程值就越精確。
同理我們不難得出一個重要結論:在遊戲引擎的物理計算中,如果我們能保證每次計算的時間間隔足夠小,那麼物理計算的精確性也就越高
物理引擎的計算結果決定了最終的渲染結果。例如一個在某個力的作用下運動的球,在經過兩秒的運動後,此時它在遊戲場景的位置,就決定了渲染結果中這個球所在畫面中的位置。
遊戲引擎會以某種幀率來渲染畫面,這貌似可以用來驅動物理引擎的運作。但是問題的關鍵在於,我們無法保證渲染幀率的穩定性。這一秒渲染幀率可能還是 60fps,下一秒可能就因為場景的複雜度提高而降低至30fps。如果我們的項目需要物理引擎保持 50fps 的計算頻率,那 30fps 的渲染幀率就無法驅動物理引擎做出正確的模擬了,自然渲染結果也會變得不正常。
那麼如何保證物理引擎能夠不受渲染幀率影響,同時能以一個足夠精確的時間間隔持續運行呢?
通常有我們有兩種解決方案可供選擇,其一為 ue 所採用的半固定物理幀(semi-fixed),其二為 unity 採用的固定物理幀(fixed)。
半固定物理幀(semi-fixed)
半固定物理幀有兩個關鍵的參數,如下圖所示:
其中,Max Substeop Delta Time 最好理解,它代表著我們希望物理引擎以哪種幀率運行,如圖上數值 0.016667 即代表著為我們希望物理引擎能以每秒 60 幀的速度運行(1 / 0.016 = 60),從而保證在遊戲中的物理計算能夠符合我們對物理模擬精度的要求。當然,這個值也可以是 0.033 ,即 30FPS,這個值的選取就是看我們對遊戲中物理計算的精度有何種要求。Max substeps 我們放到最後解釋。
接下我們進行一次演繹推理,來看看半固定物理幀到底是怎麼運行的。
如上圖所示:
1. 我們先設定兩個關鍵屬性的值,其中 max substep delta time = 0.016 (後簡稱 MDST),max substeps = 6
2. 我們假設 frame 0 耗時為 0.016 ,這與我們設定的 MDST 值相等,所以 frame 1 我們的物理計算只需一次
3. 要理解第三步推理,我們還是要強調上文中提到過的一個最基本的原則,渲染幀率有可能隨著畫面變動而變動,但是,不論是半固定還是固定物理幀,都是為了保證物理引擎的運算頻率不受渲染幀率的影響。在此基礎上我們可以從圖中觀察第三步的推理。假設 frame 1 耗時 0.033,而我們期望的物理幀運算間隔為 MDST = 0.016,即每秒60幀的物理計算頻率(1 / 0.016 = 60),也就是說,為了滿足這個要求,我們需要彌補第 1 幀所耽誤的時間,第 2 幀的物理幀就要運行 0.033 / MDST = 2 次。並且物理幀運算時將得到 0.033 / 2 = 0.0165 的時間間隔值,作為物理計算的必要參數。
4. 跳出每幀計算的細節,我們可以從更高的角度來觀察。如果我們設定 MDST = 0.016,也就是說我們要求物理計算頻率為 60fps。觀察圖中第四步,我們不難發現,前一幀多消耗的時間,會在後一幀補以多次物理計算的形式補回來,這個算法的確能保證物理計算的評率符合我們的設定頻率。
下面是這個算法的具體代碼,可以與上圖中的推理過程相互印證:
從代碼中可以看到,在計算 substeps 時,採取了向上取整的方法,這是為了保證我們的物理幀儘可能的多,而不是少。同時我們也可以從代碼中看出 Max Substeps 參數的含義,即如果系統運行的速度低過一定的閾值,並且我們無限制的增加物理幀運算,就會導致整個系統的惡性循環。所以一旦系統運行的速度降低到一定程度,半固定物理幀策略就會做出適當的取捨,放棄彌補物理計算的不足。
固定物理幀(Fixed)
下圖所示是 unity 中關於固定物理幀的參數設置:
此策略的關鍵參數為 Fixed Timestep,如圖所示 Fixed Timestep 值為 0.02,即我們要求物理計算以 50 fps (1 / 0.02 = 50) 的頻率進行。
如上圖所示:
1. 假設在第 1 幀時我們消耗的時間為 0.024,這比我們設置的 Fixed Timestep 的值大了 0.008
2. 第 2 幀時我們消耗的時間同樣為 0.024, 與 Fixed Timestep 的值相比還是大了 0.008
3. 這兩幀耗多消耗的時間總和為 0.016,正好與我們的 Fixed Timestep 值相等,即第 2 幀我們可以多做一次物理計算,以彌補我們多消耗的時間帶來的物理模擬的延遲
下面是這個算法的具體代碼,可以與上圖中的所示過程相互印證:
最後還要多補充的幾點是,當我們在引擎中做物理相關的方法調用時,通常都被要求放在關於物理的幀回掉中,例如 unity 物理幀回調位 fixedupdate,ue 則為 substep 回調並需要寫特定的代碼進行綁定。這個要求其實過於籠統。準確的說,只有我們需要進行某種“恆定的物理作用”時,才需要遵守這裡條原則,例如以一個恆定的力持續推進一個物體。
原因是為了保證我們前文一直強調的物理計算的準確性,物理引擎在模擬計算時,一直在物理幀中進行。如果我們在代碼中進行的物理相關操作,沒有放到物理幀中,這就會導致遊戲中有兩種物理計算,一種是精確地在物理幀中的,一種是不精確的在非物理幀中的。如果這兩個物理操作均在非物理幀中,那麼最起碼也還保證了一致性,不會因為處在不同類型幀中從而導致的調用次數不一致(例如 unity 的updata 方法回調頻率可能是 200fps,fixedupdate 僅為 50fps,物理引擎內部模擬一個力只在 50fps 下,而用戶在 update 中手動調用施加力的方法則 1 秒鐘會調用 200次,這顯然會出現不協調的物理結果)。但是現實狀況是引擎內部的物理模擬本就在物理幀,用戶沒有其他選擇,如果我們的調用不在物理幀,那連一致性都無法保證了,就更別提準確性了。
對於那些一次性的物理操作,並且不涉及的時間間隔相關的物理值計算,例如在某幀施加一個衝擊力,則可以再非物理幀中調用。
兩種物理幀的策略都有其優劣。
非固定物理幀的參數設置不當,可能會導致遊戲幀率陷入惡性循環,永遠無法跳出低幀率狀態。
而固定物理幀則會由於前一幀的耗時和 Fixed Timestep 相差不多,導致累積結果漫長,從而導致畫面延遲。
關於 ue 為和決定選擇使用非固定物理幀,官方論壇上有如下回答:
We actually had a debate about these two techniques [fixed and semi-fixed timestep] and eventually decided on semi-fixed, and here’s why: If you use [fixed timestep] you have to use a timebank. If you want to tick physics at 60fps and you have a frame that took a little bit more than 1/60 you will need to have some left over time. Then you will tick the physics engine with the perfect 1/60 delta time, leaving the remainder for the next frame. The problem is that the rest of the engine still uses the original delta time. This means that things like blueprint will be given a delta time of 1/60 + a bit.
引用:
Everything you always wanted to know about Unreal Engine physics (but were afraid to ask)
Update vs. FixedUpdate vs. LateUpdate in Unity