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

前一篇在学习使用UnReal的时候,了解到了一个非常好的概念 spring arm, 给相机加上这个组件后,能防止相机同步目标物体时,过于频繁的移动导致的抖动。于是乎,想到 天眼3d地图中由于数据频繁的位移抖动导致的视觉效果不佳,遂动手实践一番,写一个PigeonGL的防抖功能。

弹簧 - 拟物

说到防抖,最常见的就是电流按键防抖,简单暴力的去除按键接触电极时频繁的接通断开的电流毛刺。在web领域对应的就是搜索时,延时处理,防止频繁处理搜索请求。而在3d世界中的防抖,则是真真正正的要把抖动的毛刺变成一条平滑曲线而不是去除,所以我就yy了一下,想起来我的破旧小电驴过减震带的颠簸,轮子上的那个巨大的废物减震弹簧应该关系巨大,干脆造一个虚拟弹簧来实现spring arm,固定在相机和目标物体的上面连接起来,计算出由于物体移动位移造成弹簧末端的移动速度,做出类似于css中的ease-out的效果。

计算

弹簧高中物理学过一个 虎克公式 ,既弹簧绳长量和产生的拉力成正比
屏幕快照 2019-01-03 下午4.55.42.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到小车位置

最后

实用主义者认为 有物理意义的 数学知识才是值得学习的!

猜你喜欢

转载自yq.aliyun.com/articles/685478