天天看點

3D世界相機防抖杆的機制探究

前一篇在學習使用UnReal的時候,了解到了一個非常好的概念 spring arm, 給相機加上這個元件後,能防止相機同步目标物體時,過于頻繁的移動導緻的抖動。于是乎,想到 天眼3d地圖中由于資料頻繁的位移抖動導緻的視覺效果不佳,遂動手實踐一番,寫一個PigeonGL的防抖功能。

彈簧 - 拟物

說到防抖,最常見的就是電流按鍵防抖,簡單暴力的去除按鍵接觸電極時頻繁的接通斷開的電流毛刺。在web領域對應的就是搜尋時,延時處理,防止頻繁處理搜尋請求。而在3d世界中的防抖,則是真真正正的要把抖動的毛刺變成一條平滑曲線而不是去除,是以我就yy了一下,想起來我的破舊小電驢過減震帶的颠簸,輪子上的那個巨大的廢物減震彈簧應該關系巨大,幹脆造一個虛拟彈簧來實作spring arm,固定在相機和目标物體的上面連接配接起來,計算出由于物體移動位移造成彈簧末端的移動速度,做出類似于css中的ease-out的效果。

計算

彈簧高中實體學過一個 虎克公式 ,既彈簧繩長量和産生的拉力成正比

3D世界相機防抖杆的機制探究
F = k * X            

(F: 拉力 牛 , X: 位移 米)

彈簧的拉力和被拉開的位移成正比;

求出拉力F可以反向通過加速度公式;

F = m * a;           

得出加速度 a = k * X / m;

另外又有速度公式

Vt = V0 + a * t;           

最終得出

Vt = V0 + k * X / m;           

映射速度

經過一系列的計算,得出了速度的最終函數,經過簡化系數,可以抽象成下列函數

let v += speedRatio * x           

其中x 可以了解為,目标物體從某個位置的到另一個位置間的距離 差 , 這裡我設

X = targetCenter[0] - nowCenter[0];
Y = targetCenter[1] - nowCenter[1];           

OK,這樣我們可以計算出速度了!速度這個概念 對經常做js動畫的人來說,可以了解為每一幀的運動距離,我們的速度機關為 m/frame (1frame = 1/fps s)是以可以得出每一幀的距離,接下來隻需要設定定時函數,把地圖的每一幀加上這個距離

this.eachX += this.speedRatio*(this.targetCenter[0] - this.nowCenter[0])*1/30;//30幀的時間 約1s
 this.eachY += this.speedRatio*(this.targetCenter[1] - this.nowCenter[1])*1/30;
 this.timeout = setTimeout(()=>{
        this.nowCenter[0] += this.eachX;
        this.nowCenter[1] += this.eachY;
       
      },30);//每幀30ms           

每次判斷是否已經移動到目标點

if( (this.eachX>0?-1:1)*(this.nowCenter[0] - this.targetCenter[0])<0){
           this.nowCenter = this.targetCenter;
           this.eachX = this.eachY = 0;//到終點後,速度歸零
           this.pigeonMap.cameraControl.setCenter(this.nowCenter);
           this.pigeonMap.cameraControl.updateCamera();
           return;
        }
        //沒有歸零則繼續加速
        this.pigeonMap.cameraControl.setCenter(this.nowCenter);
        this.pigeonMap.cameraControl.updateCamera();
        this.toCenter();           

然後要支援,彈簧伸縮到一半的時候,目标物體又發生移動,此時要根據目前的位移距離重新計算出加速度,然後累計到目前速度上。

this.nowCenter = this.pigeonMap.cameraControl.map.center;
      this.targetCenter = newPosition;
      this.eachX += this.speedRatio*(this.targetCenter[0] - this.nowCenter[0])*1/30;//30幀的時間
      this.eachY += this.speedRatio*(this.targetCenter[1] - this.nowCenter[1])*1/30;
      if(this.timeout)clearTimeout(this.timeout);           

現在每當我重新設定目标位置時,就會重新獲得拉力,把相機拉向小車,并且ease-out到小車位置

最後

實用主義者認為 有實體意義的 數學知識才是值得學習的!