找回密码
 立即注册
查看: 292|回复: 0

可能在Unity中,你一直用错了Lerp线性插值

[复制链接]
发表于 2022-10-31 19:52 | 显示全部楼层 |阅读模式
前言

线性插值是一个在游戏中经常使用的数学,但是在Unity社区,经常被错误的使用,并且传播。追溯其渊源,就连官方文档和官方视频也在错误的使用,这令初学者非常的困惑。



官方文档错误的示范。箭头标注的地方,完全违背了线性插值正确的使用意图

首先我们来看一下Lerp函数的三个参数都是什么。a是起始,b是目标,t是一个0到1的进度。也就是说a如果为0,b如果为10,t如果为0.5,那么这个函数就会返回5,t如果为0就返回0,t如果为1就返回10,仅此而已,非常简单。但是在Unity社区当中,非常流行用 Vector3.Lerp() 函数来做一些摄像机跟随之类的需求,例如将这段代码放置在摄像机上:
void Update()
{
    transform.position = Vector3.Lerp(transform.position, playerPosition, Time.deltaTime);
}
这时,摄像机会跟随玩家,所形成的视觉是,摄像机先迅速的移动,后来越接近玩家越慢,形成了看似非常完美的缓动效果。但是这种缓动效果是“伪造”的,这种缓动效果是怎么来的呢,因为摄像机作为Lerp的起始点a,一直在改变位置,最开始时摄像机与玩家距离最远,所以插值的距离也最大,视觉上运动最迅速,接着距离越来越近,插值的距离也越来越短,视觉上就显示出了越来越慢的缓动效果。这在功能上可能已经工作了,但是完全违背了插值的使用意图。第三个参数t,应该是一个随时间变动的0到1的进度值,或者说百分比,这里用的是Time.deltaTime,这是毫无意义的,因为它根本和进度或百分比没有关系。参数a与参数b也不应该变动。这种Lerp的错误使用方式会造成副作用,例如它永远不可能到达目标,浮点数越来越不精确,摄像机会发生抖动。
那么摄像机想要平滑的跟随要用什么函数呢?Vector3.SmoothDamp是专门为这个需求而设计的,而不是这种Lerp的错误使用方式。Vector3.SmoothDamp函数的原理可以翻阅《Game Programming Gems 4》的第一节,有详细的叙述。Unity中SmoothDamp函数的源码也是完全依照这节所实现的。
1.Lerp的正确用法

在正确使用Lerp之前,我们先看一下Lerp的Unity源码:
public static float LerpUnclamped(float a, float b, float t)
{
    return a + (b - a) * t;
}这就是Lerp的原理了,非常简单。例如我们要实现在3秒内从0递增到10:
float duration = 3f;
float num = 0f;
float timer = 0f;

private void Update()
{
    if (timer < duration)
    {
        float t = timer / duration;
        num = Mathf.LerpUnclamped(0f, 10f, t);
        timer += Time.deltaTime;
    }
    else
    {
        num = 10f;
    }

    Debug.Log(num);
}就是这样,这就是Lerp的真正数学意图,a与b都是固定的,t是流逝的。而不是错误的使用方式,将Lerp用作制造“伪缓动”效果的技巧。在游戏当中可以使用Lerp来做一些动画,将一个图像过渡到另一个图像来创造运动的感觉,而不是僵硬的闪现过去。这就是Lerp的最基础使用了。



用Lerp来实现简单的移动

2.Lerp的实际应用(DoTween,iTween等补间库的原理)

在上一节我们知道了Lerp的正确使用方式,在真正的游戏当中,Lerp较常用的地方是做一些动画效果,但是单调的移动并没有太大的吸引力,这时我们可以为Lerp加上easing(缓动曲线),例如下方的的曲线:


立即就可以实现果汁感十足的动画:


那么原理是怎样的呢?注意看,这个曲线,x轴代表进度,是0到1,y轴代表数值,也是0到1,这时我们就可以将上一节在Lerp函数中使用的参数t这个进度值放入此曲线中,二次加工,得到一个新的t(也就是y的值),这时这个t值就不是像上一节那种单调的直线递增了,而是像这个曲线一样来回变化的,这个进度t一旦随着这个曲线变化了,动画效果也就诞生了。这也是Unity流行的那些补间库,例如DoTween,iTween的最底层原理。一旦知道这个原理后,其实如果你的项目只有少量的这种动画需求,完全可以自己实现,自己定制曲线,无需引入这种庞大的库。
这里我会展示相关的代码进一步注释:
[SerializeField] private AnimationCurve easeCurve; //Unity自带的动画曲线类,声明后即可编辑。

private void PanelAnimation(float duration)
{
    IEnumerator C()
    {
        Vector3 startPos = new Vector3(-1000, 0, 0); //UI面板的开始位置
        Vector3 endPos = new Vector3(-500, 0, 0); //UI面板的结束位置
        _panel.anchoredPosition = startPos; //初始化UI面板的位置

        float timer = 0; //计时器
        while (timer < duration)
        {
            float t = timer / duration; //进度 0~1,完全的直线。
            t = easeCurve.Evaluate(t); //根据进度,再在曲线上加工一个值,以实现在不同的进度间摇摆,形成缓动动画。                                    
            _panel.anchoredPosition = Vector3.LerpUnclamped(startPos, endPos, t);
            timer += Time.deltaTime;
            yield return null;
        }

        _panel.anchoredPosition = endPos;
    }

    StartCoroutine(C());
}如果是在别的引擎中,例如MonoGame,并没有Unity这种可编辑的动画曲线,或者你并不喜欢手动编辑这些曲线,你想直接使用,那么如何实现这种缓动曲线呢?我们可以完全使用数学方式。将这种曲线数学公式放入一个方法库中,将它当作黑箱来使用。在这里可以直接使用我的缓动数学库放入你的游戏,立即享有这种动感十足的动画效果。
这种方式可能并不直观,因为你不知道这个数学式对应的是什么动画效果,我推荐查看这个网站,挑选好动画效果后,再调用对应的数学方法即可。
3.曲线还可以做什么?

这种曲线在UI和物体的动画效果之外,也可以做广泛的其它应用。例如:
应用到游戏的概率:



这条曲线在开始和结束时都很平缓,因此这些值接近极值的几率较高,而中间的陡峭部分表示得到这些值的几率较低。

应用到载具的发动机:



具有多个档位的载具,给人一种不使用变速箱发动机系统的情况下拥有自动档位的感觉:该曲线显示了第二、第三和第四的大部分功率,其中第三、第四和第四第五档是更长的档位,第五档在达到最大速度之前会持续一段时间。 不同的切线用于创建所需的结果。

更丰富的UI动画:


除此之外,粒子效果,角色的经验值升级曲线,敌人每关的产卵数量递增,关卡中的平台,人物的动作等等,都可以进行有效的应用。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-21 19:28 , Processed in 0.097783 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表