插值与平滑
插值的运算规则
- 插值的作用是根据比率
t
返回from
到to
之间的某个值。 - 比率
t
限制在[0, 1]之间。 from
等于0,to
等于100。t
等于0时返回0,t
等于1时返回100,t
等于0.5时返回50,
// 插值的实现代码
float Lerp(float from, float to, float t) {
// 变化率最大为100%
if (t > 1)
t = 1;
else if (t < 0)
t = 0;
float delta = to - from;
return from + delta *t;
}
插值的类型
Unity提供了各种类型插值,用于计算两个数据中间的某个数据。
Mathf.Lerp
计算两个小数间的插值。Vector2.Lerp
计算两个2维向量间的插值。Vector3.Lerp
计算两个3维向量间的插值。Vector4.Lerp
计算两个4维向量间的插值。Quaternion.Lerp
计算两个四元数间的插值。Color.Lerp
计算两个颜色间的插值。Material.Lerp
计算两个材质间的插值。
还用一些插值的扩展方法。
- 上述插值都有一组对应的
LerpUnclamped
方法。LerpUnclamped
中比率t
在小于0或大于1时的返回值不会被限制。 Mathf.LerpAngle
计算两个角度的插值。和Mathf.Lerp
类似,但会限制角度在360之内。Vector3.Slerp
计算两个向量的球形插值。Vector3.Lerp
主要用来计算两个坐标点之间的插值,而Vector3.Slerp
主要用来计算两个方向之间的插值。
插值的作用
插值在项目中的常见用法有4种,后面分别讲解:
- 计算中间量
- 线性平滑
- 非线性趋近平滑
- 游戏淡入
计算中间量
这是插值最基本、最直接的使用方式。比如计算0到100,中间值是多少。
Mathf.Lerp(0, 100, 0.5f);
摄像机跟随玩家时,玩家可能被墙挡住。为了避开墙的遮挡,我们可以在摄像机正常位置到玩家头顶位置之间选择几个点,逐个发射线确定一个没有遮挡的位置。下面的Demo演示了其中最核心的一个算法。
// 把p1点到p2点的线段分成5等分,计算6个等分点的坐标。
Vector3 p1 = new Vector3(1, 2, 3);
Vector3 p2 = new Vector3(4, 5, 6);
for(int i=0; i <= 5; i++) {
var point = Vector3.Lerp(p1, p2, i/5);
Debug.Log(point);
}
线性平滑
- 线性平滑常用于根据某个数据(比如时间)等比例的变化。
// 3秒内将灯的亮度从1变到7
Light light;
float pass;
void Start() {
// 获取Light组件
light = GetComponent<Light>();
}
void Update() {
pass += Time.deltaTime;
light.intensity = Mathf.Lerp(1f, 7f, pass/3f);
}
扩展练习:
- 将灯的亮度从7变到1。
- 将灯的亮度从1到7再从7到1,不断反复。
非线性平滑
- 非线性平滑是一种初期变化快,后期变化极小的平滑算法。
- 前期变化快,变化更明显。后期变化慢,效果更平滑。
// 摄像机平滑跟随
void LateUpdate() {
// 计算当前物体后方5米,上方3米位置的世界坐标。
Vector3 pos = transform.position + transform.rotation * new Vector3(0, 3, 5);
Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, pos, Time.deltaTime * 1.2f);
}
- 可以把这个平滑算法做为一个公式
要修改的数据 = 数据类型.Lerp(要修改的数据, 目标值, Time.deltaTime * 变化率);
- 实践
如果要实现摄像机直接跟随代码如下:
void LateUpdate() {
// 计算当前物体后方5米,上方3米位置的世界坐标。
Vector3 pos = transform.position + transform.rotation * new Vector3(0, 3, 5);
Camera.main.transform.position = pos;
}
重点就是这句话Camera.main.transform.position = pos;
。现将其改为平滑跟随:
- 确定要修改的数据
// 要使用平滑,而不是直接把摄像机位置赋值为目标值pos。 Camera.main.transform.position =
- 确定插值类型
// position属性是Vector3类型,所以我们用Vector3的插值 Camera.main.transform.position = Vector3.Lerp(,,);
- Lerp第一个固定给赋值变量
Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position,,);
- Lerp第二个参数为目标值
Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, pos,);
- Lerp第三个参数为Time.delta*变化率
// 变化率越大,数据变化的越快。 // 变化率一般在0.8 ~ 5之间。 Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, pos, Time.deltaTime * 1.2f);
游戏淡入
- 在游戏启动时经常会使用一些淡入效果。
// 在游戏启动的3秒内灯的亮度从1变到7。
// Time.time表示游戏启动到目前的秒数。
light.intensity = Mathf.Lerp(1, 7, Time.time / 3);