(转)Unity3D 之插值计算

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

Material.Lerp 插值

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

在两个材质之间插值

Vector2.Lerp 插值

static function Lerp (from : Vector2, to : Vector2, t : float) : Vector2

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

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

Vector3.Lerp 插值

static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3

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

Vector4.Lerp 插值

static function Lerp (from : Vector4, to : Vector4, t : float) : Vector4

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

Mathf.Lerp 插值

static function Lerp (from : float, to : float, t : float) : float

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

Color.Lerp 插值

static function Lerp (a : Color, b : Color, t : float) : Color

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

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

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

Mathf.Lerp 插值

static function Lerp (from : float, to : float, t : float) : float

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

首先,我们来做一个试验,启动Unity3D,任建一个脚本文件,在其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,在控制台将打印出:

这个实验是在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….,因插值是把值插在原来的两数之间,这说明这个函数首先是根据第三个参数所给定的比例算出净增量,再加上起始数,最终算出插值值的。

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

如图,在空间中存在两点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.4*OF=0.4*10=4。

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

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

EO/AO=DF/AF=CB/AC=1-0.4=0.6,则C点的Y坐标值EO=0.6*AO=0.6*10=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(new Vector3(0, 10, 0), new Vector3(10, 0, -10), 0.4f).ToString()); }

其运行结果为:

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

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

我们同样可以得到三角形EGL与三角形EFK,使用同样的方法可计算出HI的长度,再加上OH的长度就是C点的X坐标值了。同样的方法可推演出Y与Z的坐标。

手工计算是很复杂的,而Lerp函数可以高效地为我们返回这个插值的,我们在这里做出的演算,只是帮助我们来推测Lerp这个函数的内部实现机理而也,实际运用中,一切工作都是交于Lerp函数去完成。

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

using UnityEngine;

using System.Collections;

public class example : MonoBehaviour {

public Transform start;

public Transform end;

void Update() {

transform.position = Vector3.Lerp(start.position, end.position, Time.time);

}

}

另一个例子:

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

using UnityEngine;

using System.Collections;

public class example : MonoBehaviour {

public Transform target;

public float smooth = 5.0F;

void Update() {

transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime * smooth);

}

}

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

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

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

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

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

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

具体设计过程如下。导入道路模型,假设命名为forest_1。模型设计时就确定好了其长度为3000、坐标原点在其终端上了的。导入后我们将其沿Z轴正方向放置在场景中,让其Transorm.Position的X、Y值均为0。我们可以导入多段同类型的道路模型,通过控制它们的Z值来把它们拼接成长长的森林道路。在此道路物体上新建一个空物体作为它的子物体,命名为waypoint,再在其下建立多个为空的孙物体,分别命名为waypoint_01、waypoint_02……,把它们放在道路的转折处,并通过放大、旋转场景图后细调这些孙物体的坐标值,使它们与道路路面贴合,如下图所示:说明:图中的绿色按钮状块就是这些孙物体,因它们是空物体,不能显示在场景中,是通过属性面板

给它们设置了一个供编辑时显示使用的图标标示。

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

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

using UnityEngine;

using System.Collections;

public class waypoints : MonoBehaviour {

public Transform[] points;

void OnDrawGizmos(){

iTween.DrawPath (points);

}

}

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

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

using UnityEngine;

using System.Collections;

public class Forest : MonoBehaviour {

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

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

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

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

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

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

void Awake() {

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

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

}

// Use this for initialization

void Start()

{

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

}

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

void Update () {

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

Camera.main.SendMessage("GenerateForest");

GameObject.Destroy(this.gameObject);

}

}

void GenerateObstacle(){

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

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

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

while (true) {

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

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

{

break;

}

else {

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

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

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

}

}

}

Vector3 GetWayPosByz(float z) {

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

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

for (int i = 0; i < points.Length-1; i++) { //根据要插入路障的Z值在集合中寻找在哪两个点之间,找到后记下序数号

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

index = i;

break;

}

}

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

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));

}

}

时间: 2024-09-29 21:27:56

(转)Unity3D 之插值计算的相关文章

【C#】14. printOneExcel在Excel里作图 &amp; 利率插值计算(线性)

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">今天主要写写怎么用Visual Studio在Excel里面做图(chart),网上有很多人都讨论过这个问题,但我觉得这里还是写一下C# for financial markets里面怎么写的,因为我觉得这个做出来的比较好.</span> 首先看一下chart在excel里面

基于Unity3D三维模型的动作插值(空间关键帧动画实现)

1.引言 最近在Unity3D中实现一个基于自定义Mesh网格的骨骼动画.存储关键帧信息,然后通过插值形成中间动画.网格GameObject之间存在父子关系.插值动画对模型骨骼的Position.Sclae.Rotation三个部分分别混合插值. 并且注意,一般选取的是子骨骼相对父骨骼的Transform信息,即上述关键帧信息具体应该为Transform.localPosition.localScale.localRotation. 并且这个系统的关键帧是定义在三维空间中的,用户在一系列关键帧点

Unity3D - 详解Quaternion类(二)

OK,不做引子了,接上篇Unity3D - 详解Quaternion类(一)走起! 四.Quaternion类静态方法 Quaternion中的静态方法有9个即:Angle方法.Dot方法.Euler方法.FromToRotation方法.Inverse方法.Lerp方法.LookRotation方法.RotateToWards方法和Slerp方法.关于静态的方法的使用就是直接用类名调用其静态方法,例如Quaternion.Angle(q1,q2);下面对这些静态方法做下分析. 1.Angle方

时光煮雨 Unity3D实现2D人物移动-总结篇

系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率 背景 最近研究Unity3d,2d寻路的实现.所以又一次涉及到了角色坐标位移的问

Unity3D 骨骼动画原理学习笔记

最近研究了一下游戏中模型的骨骼动画的原理,做一个学习笔记,便于大家共同学习探讨. ps:最近改bug改的要死要活,博客写的吭哧吭哧的~ 首先列出学习参考的前人的文章,本文较多的参考了其中的表述: 1.骨骼动画详解 :http://blog.csdn.net/ccx1234/article/details/6641944,不过这篇文章的原文已经被csdn封了:D,可以看看对应的转载的文章也行 2.OpenGL10-骨骼动画原理篇:http://www.cnblogs.com/zhanglitong

插值技术之Bezier插值(1) -- Bezier Curve

作者:i_dovelemon 来源:CSDN 日期:2015 / 7 / 11 主题:Interpolate,Bezier Curve 引言 在游戏开发中,诸如动画系统,路径计算等等操作,都会遇到对数值进行插值的问题.从今天开始,将会陆陆续续的向大家介绍什么是插值技术?以及在计算机视频游戏开发中经常使用的插值技术有哪些. 插值技术(Interpolate Technology),是通过数学计算的方式,将两个值之间的部分进行平滑过渡的一种技术方案.这样的技术可以在诸如动画系统等游戏内容中得到使用.

Tetrahedron based light probe interpolation(基于四面体的Light Probe插值)

在当前的游戏引擎中,使用Light Probe来计算全局环境光对于动态物体的影响是一种很主流的方法.在预处理阶段生成完场景的Light Probe之后,传统的方法采用查找最近的8个相邻的Probe然后使用三线性的方式(Trilinear Interpolation)进行插值,但是这样的插值代价稍大,不过一个可行的优化就是尽可能地减少插值中使用的Probe的数量,比如由8个减少到4个不等.但是这时就不能再用三线性插值,而需要用其它的插值方法比如Inverse Distance等,不过这样就会带来另

自学Unity3D 之 贪吃蛇 添加摄像机跟随

在Unity的世界中, 物体的位置都是由向量构成的. 今天所需要做的就是让摄像机保持跟蛇头的相对距离. 首先  设蛇头的位置在A 点  , 摄像机的位置在B 点 则  我们可以知道  他们的offset = B - A; 所以  新的摄像机位置应该为 B =  A + offset: 令 :  摄像机的移动是根据帧走的  所以是抖动的 , 我们应该做一个平滑处理 恰好Vector 给我们提供了一个方法 利用插值做到平滑移动 Vector3.lerp(B,A + offset,0.1f)  也就是

Swift字符串插值

1 字符串插值是一种全新的构建字符串的方式,可以在其中包含常量.变量.字面量和表达式.您插入的字符串字面量的每一项都被包裹在以反斜线为前缀的圆括号中: 2 let multiplier = 3 3 let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" 4 // message is "3 times 2.5 is 7.5" 5 在上面的例子中,multiplier 作为 \(mu