另请参阅此答案。
有两种常见的使用方式Lerp
:
1.起点和终点之间的线性融合
progress = Mathf.Clamp01(progress + speedPerTick);
current = Mathf.Lerp(start, end, progress);
这可能是您最熟悉的版本。
2.实现目标的指数轻松
current = Mathf.Lerp(current, target, sharpnessPerTick);
请注意,在此版本中,current
值既显示为输出,也显示为输入。它替换了start
变量,因此我们总是从上次更新的位置开始。这就是这种版本的Lerp
存储器从一帧到下一帧的原因。然后,从这个移动的起点开始,将距离的一小部分移向参数target
所指定的位置sharpness
。
该参数不再是“速度”,因为我们以类似于Zeno的方式接近目标。如果sharpnessPerTick
为0.5
,则在第一次更新中,我们将移到目标的一半。然后,在下一次更新中,我们将移动剩余距离的一半(即初始距离的四分之一)。然后在下一个我们再移动一半...
这提供了“指数缓和”,其中,运动远离目标时运动很快,并且随着运动渐近而逐渐减慢(尽管对于无穷精度数字,它在任何有限数量的更新中都永远不会达到它的目的-对于我们而言,足够近)。对于追踪移动目标值或使用“ 指数移动平均值 ”(通常使用非常小的sharpnessPerTick
参数,例如0.1
或更小)来平滑噪声输入非常有用。
但是您是对的,您所链接的答案中有一个错误。这不是deltaTime
正确的方法。使用这种样式的时,这是一个非常常见的错误Lerp
。
的第一种样式Lerp
是线性的,因此我们可以乘以以线性调整速度deltaTime
:
progress = Mathf.Clamp01(progress + speedPerSecond * Time.deltaTime);
// or progress = Mathf.Clamp01(progress + Time.deltaTime / durationSeconds);
current = Mathf.Lerp(start, end, progress);
但是我们的指数缓和是非线性的,因此仅将我们的sharpness
参数乘以deltaTime
将不会给出正确的时间校正。如果我们的帧率波动,这将显示为运动中的抖动,或者如果您将帧率从30持续更改为60,则会显示缓和锐度的变化。
取而代之的是,为了方便指数计算,我们需要进行指数校正:
blend = 1f - Mathf.Pow(1f - sharpness, Time.deltaTime * referenceFramerate);
current = Mathf.Lerp(current, target, blend);
这referenceFramerate
只是一个常数,就像在校正时间之前30
保持单位sharpness
不变。
该代码中还有一个可争论的错误,那就是使用Slerp
-当我们想要整个运动过程中的旋转速度完全一致时,球面线性插值很有用。但是,无论如何,如果我们要使用非线性指数缓和,Lerp
将得到几乎无法区分的结果,并且价格便宜。;)四元数比矩阵好得多,因此通常这是一个安全的替代方法。