为什么在Lerping函数中使用Time.deltaTime?


12

据我了解,两个值(之间的线性插值内插功能ab使用第三值()t之间)01。在t = 0处返回值a,在处返回t = 1b。值为0.5时,返回a与之间的中间值b

(下图是一个平滑的步骤,通常是三次插值)

在此处输入图片说明

我一直在浏览论坛,在这个答案上,我找到了以下代码行:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

我对自己说:“真是个傻瓜,他不知道”,但是因为它有40多个投票,所以我尝试了一下,果然成功了!

float t = Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, t);
Debug.Log(t);

我之间的随机值0.01,并0.02t。函数不应该相应地插值吗?为什么这些值会堆叠?我不明白勒普是什么意思?


1
A通常是位置,它会发生变化,因此以1/60(60 fps)进行采样只能通过插值0.16来移动对象,从而不断缩小A和B之间的距离(因此,每次采样都越来越小)。
锡达

您记录了t并使用了tt ...这些是不同的变量。
user253751 '10

Answers:


18

另请参阅此答案

有两种常见的使用方式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的方式接近目标。如果sharpnessPerTick0.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将得到几乎无法区分的结果,并且价格便宜。;)四元数比矩阵好得多,因此通常这是一个安全的替代方法。


1

我认为在这种情况下,缺少的核心概念是不固定的。A随每一步更新,但是沿着Time.deltaTime的插值进行了很多修改。

因此,随着A在每一步上都越来越接近B,内插的总空间会随着每次Lerp / Slerp调用而改变。如果不做实际的数学运算,我会怀疑效果与您的Smoothstep图不一样,但是当A越来越接近B时,这是一种廉价的近似减速方法。

另外,由于B也不是静态的,因此经常使用。典型的情况可能是摄像机跟随玩家。您想使相机跳到某个位置或旋转以避免晃动。


1

你是对的,方法Quaternion Slerp(Quaternion a, Quaternion b, float t)之间进行插值ab通过量t。但是请注意第一个值,而不是起始值。

在此,给该方法的第一个值是当前对象旋转transform.rotation。因此,对于每个帧,它在当前旋转量和目标旋转_lookRotation量之间进行插值Time.deltaTime

这就是为什么它会产生平滑的旋转。


2
现在我觉得自己像个白痴
AzulShiva '17

@AzulShiva别担心每个人都会发生这种情况;)
Ludovic Feltz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.