插值与平滑

插值的运算规则

  • 插值的作用是根据比率t返回fromto之间的某个值。
  • 比率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;。现将其改为平滑跟随:

  1. 确定要修改的数据
    // 要使用平滑,而不是直接把摄像机位置赋值为目标值pos。
    Camera.main.transform.position =
    
  2. 确定插值类型
    // position属性是Vector3类型,所以我们用Vector3的插值
    Camera.main.transform.position = Vector3.Lerp(,,);
    
  3. Lerp第一个固定给赋值变量
    Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position,,);
    
  4. Lerp第二个参数为目标值
    Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, pos,);
    
  5. 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);