Unity3D中的线性插值Lerp()函数解析

经常用线性插值函数Lerp()来在两者之间插值,两者之间可以是两个材质之间、两个向量之间、两个浮点数之间、两个颜色之间,其函数原型如下:

1.Material.Lerp 插值

function Lerp(start : Material, end : Material, t : float) : void

在两个材质之间插值

2.Vector2.Lerp 插值

static functionLerp (from : Vector2, to : Vector2, t : float) : Vector2

两个向量之间的线性插值。按照数字t在form到to之间插值。

t是夹在0到1之间。当t=0时,返回from。当t=1时,返回to。当t=0.5时放回from和to之间的平均数。

3.Vector3.Lerp 插值

static functionLerp (from : Vector3, to :Vector3, t : float) :Vector3

两个向量之间的线性插值。按照数字t在from到to之间插值。

4.Vector4.Lerp 插值

static functionLerp (from : Vector4, to : Vector4, t : float) : Vector4

两个向量之间的线形插值。按照数字t在from到to之间插值。t是夹在[0...1]之间的值。,当t = 0时,返回from。当t = 1时,返回to。当t = 0.5 返回from和to的平均数。

5.Mathf.Lerp 插值

static functionLerp (from : float, to : float, t : float) : float

基于浮点数t返回a到b之间的插值,t限制在0~1之间。当t = 0返回from,当t = 1 返回to。当t = 0.5 返回from和to的平均值。

6.Color.Lerp 插值

static functionLerp (a : Color, b : Color, t : float) : Color

通过t在颜色a和b之间插值。

"t"是夹在0到1之间的值。当t是0时返回颜色a。当t是1时返回颜色b。

插值,从字面意思上看,就是在其间插入一个数值,这种理解是否正确呢?我们先从最简单的浮点数插值函数来分析:

Mathf.Lerp 插值

static functionLerp (from : float, to : float, t : float) : float

基于浮点数t返回a到b之间的插值,t限制在0~1之间。当t = 0返回from,当t = 1 返回to。当t = 0.5 返回from和to的平均值。

首先,我们来做一个试验,启动,任建一个脚本文件,在其Start()中输入内容如下:

void Start () {

    print(Mathf.Lerp(0.0f, 100.0f,0.0f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.1f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.2f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.3f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.4f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.5f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.6f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.7f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.8f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,0.9f).ToString());

    print(Mathf.Lerp(0.0f, 100.0f,1.0f).ToString());

}

运行Unity,在控制台将打印出:

image
   这个实验是在0到100之间插值,插入什么值,取决于第3个参数,从打印结果可看出,第3个参数是个比例因数,是0.1时表示0到100这个长度的十分之一,同理,0.2表示十分之二,依此类推。从这点上看来,我们起初从字面上所理解的插值就是插入一个数值是可以这样理解的。

   如果我们把上面那个脚本里的插值函数里的第一个参数变为100.0f,第二个参数变为110.0f,第三个参数保持不变,大家想想其运行结果该是什么呢?可不要认为是0、1、2、3、4、5、6、7、8、9、10了哟,实际结果是100、101、102、103、104、105、106….,因插值是把值插在原来的两数之间,这说明这个函数首先是根据第三个参数所给定的比例算出净增量,再加上起始数,最终算出插值值的。

在开发中,应用最多的是Vector3.Lerp 向量插值,下面我们以此插值来猜推其内部实现机理以及一些应用。

image

如图,在空间中存在两点A(0,10,0)与B(10,0,-10),我们在A、B两点间插入一C点,假设C点的位置在AB的五分之二处,即AC/AB=0.4,根据相似图形对应边成比例的初中几何知识可知,在⊿ABO中AC/AB=OD/OB,同理在⊿OBF中OD/OB=OE/OF,所以AC/AB=OD/O=OE/OF = 0.4,则C点的X坐标值为:OE=0.4OF=0.410=4。

根据上图,还可知ED/FB=0.4,所以C点的Z坐标值DE=0.4BF=0.4(-10)=-4。

C点的Y坐标值请看下图:

image

EO/AO=DF/AF=CB/AC=1-0.4=0.6,则C点的Y坐标值EO=0.6AO=0.610=6。

综上所述,C点的三维坐标为C(4,6,-4)。

下面我们利用Unity3D中的Vector3.Lerp 插值函数:static function Lerp (from :Vector3, to :Vector3, t : float) :Vector3来计算上面演算的插值。

我们把先前脚本中的Start()函数改写成:

void Start()

{ print(Vector3.Lerp(newVector3(0, 10, 0),newVector3(10, 0, -10), 0.4f).ToString()); }

其运行结果为:

image

这与我们的演算结果是一致的。

上面的演算,我们为了简便,A、B两点取得较特殊,降低了演算的复杂度。而对普通的A、B两点,如下图所示:

image
   我们同样可以得到三角形EGL与三角形EFK,使用同样的方法可计算出HI的长度,再加上OH的长度就是C点的X坐标值了。同样的方法可推演出Y与Z的坐标。手工计算是很复杂的,而Lerp函数可以高效地为我们返回这个插值的,我们在这里做出的演算,只是帮助我们来推测Lerp这个函数的内部实现机理而也,实际运用中,一切工作都是交Lerp函数去完成。

    Lerp函数在游戏开发过程使用较多,在Unity的帮助文档里就有为我们列举了[Vector3](http://blog.csdn.net/LaoJiu_/article/details/50164801).Lerp的两个应用的例子,一个是在1秒时间动画位置移动从start.position开始到end.position结束:

[csharp] view plain copy print?

  1. using UnityEngine;
  2. usingSystem.Collections;
  3. public classexample : MonoBehaviour
  4. {
  5. public Transform start;
  6. public Transform end;
  7. void Update()
  8. {
  9. transform.position =Vector3.Lerp(start.position, end.position, Time.time);
  10. }
  11. }

另一个例子:

//像弹簧一样跟随目标物体

[csharp] view plain copy print?

  1. using UnityEngine;
  2. usingSystem.Collections;
  3. public classexample : MonoBehaviour
  4. {
  5. public Transform target;
  6. public float smooth = 5.0F;
  7. void Update()
  8. {
  9. transform.position =Vector3.Lerp(transform.position, target.position, Time.deltaTime * smooth);
  10. }
  11. }

这个例子中的transform.position是去跟随的那个物体的空间坐标,target.position是目标物体的空间坐标,整句的结果是让跟随物体的坐标不断地变化为它们两者之间的插值,然而随着时间的推移,第三个参数的值最终会为1,所以最终跟随物体的位置会与目标物体重合的。我们以前所玩的游戏中,主人公身上依附着一只宠物如鹰,主人公移动时,鹰会跟随着飞动,主人公移动得快它就飞行跟动得快,始终不会离开主人公,使用Lerp插值函数就可实现。

下面我们来看另一个应用实例。

image
   这是酷跑游戏场景,囚犯沿着一条森林道路向前奔跑,后面有警车追赶,前面有路障,在游戏过程中,我们要在囚犯奔跑的固定路线上随机产生路障,而道路不是平直的,既左右弯曲,又上下起伏,由程序随机生成的路障怎样确定其空间位置呢?这时,Lerp函数就派上了用场。

    先根据道路的弯曲与起伏,在转折处设置一个空物体,此空物体的Position值即空间坐标与此处道路一致,我们把这些空物体所在的点称为道路转折点,这些点连接而成的线段所组成的多段折线贴合在路面上,是这条道路的近似路径,这些点取得越多、越准确,这条路径与道路的相似程度就越高。

现在我们用那条路径来代替那条道路,把随机产生的路障放在这条路径上也就是放在道路上了。

    假设我们想每隔100米至200米之间产生一个路障,用变量z += Random.Range(100, 200)记录下该路障的Z坐标值(因囚犯总体上是沿着Z轴往前跑)然后根据此Z坐标值判断该坐标值在前面所设置的转折点中的哪两个点之间,找到后就在这两个点之间插值,其插值的比例因数(Lerp()函数的第3个参数)可由两个转折点与这个插值点这三个点中已知的Z坐标值算出来,这样[Vector3](http://blog.csdn.net/LaoJiu_/article/details/50164801).Lerp (from : [Vector3](http://blog.csdn.net/LaoJiu_/article/details/50164801), to :[Vector3](http://blog.csdn.net/LaoJiu_/article/details/50164801), t : float)函数中的三个参数值便都是已知的了,它就可计算出这个插值点的空间坐标了,根据前面的设计,这两个转折点之间的线段是贴合在路面上的,那么此插值的坐标也就是在路面上了,根据此插值放置的路障也就不会偏离道路,且会随着道路的左转而左转,右转而右转,上坡而上坡,下坡而下坡了。

具体设计过程如下:

    导入道路模型,假设命名为forest_1。模型设计时就确定好了其长度为3000、坐标原点在其终端上了的。导入后我们将其沿Z轴正方向放置在场景中,让其Transorm.Position的X、Y值均为0。我们可以导入多段同类型的道路模型,通过控制它们的Z值来把它们拼接成长长的森林道路。

    在此道路物体上新建一个空物体作为它的子物体,命名为waypoint,再在其下建立多个为空的空物体,分别命名为waypoint_01、waypoint_02……,把它们放在道路的转折处,并通过放大、旋转场景图后细调这些孙物体的坐标值,使它们与道路路面贴合,如下图所示:
image

说明:图中的绿色按钮状块就是这些空物体,因它们是空物体,不能显示在场景中,是通过属性面板给它们设置了一个供编辑时显示使用的图标标示。

image

这样,我们便把弯弯曲曲的道路分成了一段一段的直路段,并记录下来了各段路段两端的特征点的坐标值。有了这些特征点,也就有了与道路相近的路线了。这是化曲为直的方法,把弯曲、起伏的道路化成了与此相近的一段一段的线段。这样的点越多,其相似程度越高。

在waypionts上创建一个脚本组件waypionts.cs:

[csharp] view plain copy print?

  1. using UnityEngine;
  2. using System.Collections;
  3. public class waypoints : MonoBehaviour
  4. {
  5. public Transform[] points;
  6. void OnDrawGizmos()
  7. {
  8. iTween.DrawPath (points);
  9. }
  10. }

public Transform[]points;该句所定义的points就是存放那些特征点的数组,因它是public,可在Unity编辑界面中为其赋值,其操作方法是先在Hierarchy视图中选中waypoints控件,然后在其Inspector视图中点击图标锁住其Inspector面板,然后在Hierarchy视图中全选waypoint_01至waypiont_11后拖到属性面板上的数组名points上即可完成赋值,如下图:

image

接下来,在这个森林道路上建立的Forestcs.cs脚本组件里添加生成路障的脚本:

[cpp] view plain copy print?

  1. <span style="background-color: rgb(255, 255, 153);">using UnityEngine;

  2. using System.Collections;

  3. public class Forest : MonoBehaviour

  4. {

  5. public GameObject[] obstacles; //路障物体数组

  6. public float startLength = 50; //路障在道路上出现的开始位置

  7. public float minLength = 100; //路障距上一个路障的最小距离

  8. public float maxLength = 200; //路障距上一个路障的最大距离

  9. private Transform player; //游戏主人公-奔跑者的Transform组件

  10. private waypoints wayPoints; //与路面相贴合的路线上的脚本组件

  11. void Awake()

  12. {

  13. player = GameObject.FindGameObjectWithTag(Tags.player).transform; //找到游戏主人公-奔跑者并获得它的Transform组件

  14. wayPoints = transform.Find("waypoints").GetComponent<waypoints>(); //找到与路面相贴合的路线上的脚本组件

  15. }

  16. // Use this for initialization

  17. void Start()

  18. {

  19. GenerateObstacle(); //当森林道路被创建出来时,就会自动调用此Start()方法,从而调用此GenerateObstacle()方法

  20. }

  21. // 如果主人公跑完了这段道路,则通知GenerateForest类开始运行产生新的道路,并销毁已跑完的这条道路

  22. void Update ()

  23. {

  24. if (player.position.z > transform.position.z+100)

  25. {

  26. Camera.main.SendMessage("GenerateForest");

  27. GameObject.Destroy(this.gameObject);

  28. }

  29. }

  30. void GenerateObstacle()

  31. {

  32. float startZ = transform.position.z - 3000; //当前道路在场景中的起始Z坐标

  33. float endZ = transform.position.z; //当前道路在场景中的结束Z坐标

  34. float z = startZ + startLength; //将要产生的路障的Z坐标

  35. while (true)

  36. {

  37. z += Random.Range(100, 200); //每隔100多米的距离产生一个路障

  38. if (z > endZ) //如果将要产生路障的位置超出了这条道路则退出路障产生循环,否则产生路障

  39. {

  40. break;

  41. }

  42. else

  43. {

  44. Vector3 position = GetWayPosByz(z); //调用GetWayPosByz()方法计算路障位置坐标

  45. int obsIndex = Random.Range(0, obstacles.Length); //产生一个从路障数组里取路障的随机序数

  46. GameObject.Instantiate(obstacles[obsIndex], position, Quaternion.identity);//实例化路障

  47. }

  48. }

  49. }

  50. Vector3 GetWayPosByz(float z)

  51. {

  52. Transform[] points = wayPoints.points; //在道路上设置的转折点的集合

  53. int index = 0; //转折点在集合中的序数号

  54. for (int i = 0; i < points.Length-1; i++)

  55. { //根据要插入路障的Z值在集合中寻找在哪两个点之间,找到后记下序数号

  56. if(z<=points[i].position.z && z>=points[i+1].position.z){

  57. index = i;

  58. break;

  59. }

  60. }

  61. //使用Lerp函数计算出插入路障处的空间坐标值

  62. return Vector3.Lerp(points[index + 1].position, points[index].position,(z - points[index + 1].position.z) / (points[index].position.z - points[index +1].position.z));

  63. }

  64. }</span>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容