前一篇在學習使用UnReal的時候,了解到了一個非常好的概念 spring arm, 給相機加上這個元件後,能防止相機同步目标物體時,過于頻繁的移動導緻的抖動。于是乎,想到 天眼3d地圖中由于資料頻繁的位移抖動導緻的視覺效果不佳,遂動手實踐一番,寫一個PigeonGL的防抖功能。
彈簧 - 拟物
說到防抖,最常見的就是電流按鍵防抖,簡單暴力的去除按鍵接觸電極時頻繁的接通斷開的電流毛刺。在web領域對應的就是搜尋時,延時處理,防止頻繁處理搜尋請求。而在3d世界中的防抖,則是真真正正的要把抖動的毛刺變成一條平滑曲線而不是去除,是以我就yy了一下,想起來我的破舊小電驢過減震帶的颠簸,輪子上的那個巨大的廢物減震彈簧應該關系巨大,幹脆造一個虛拟彈簧來實作spring arm,固定在相機和目标物體的上面連接配接起來,計算出由于物體移動位移造成彈簧末端的移動速度,做出類似于css中的ease-out的效果。
計算
彈簧高中實體學過一個 虎克公式 ,既彈簧繩長量和産生的拉力成正比
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuATZwITOwIWZyYmY4YmM1gzYjZWZjJzMwYGO1ImN2MzMvwVbvNmLj5Wat4Wd5lGbh5iY1BXLn1WauU3bop3ZuFGat42YucWbp1iMhRXYvw1LcpDc0RHaiojIsJye.png)
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到小車位置
最後
實用主義者認為 有實體意義的 數學知識才是值得學習的!