插值
Interpolation is a common operation in graphics programming, which is used to blend or transition between two values. Interpolation can also be used to smooth movement, rotation, etc. It's good to become familiar with it in order to expand your horizons as a game developer.
基本思想是从 A 转换到 B。t
值是介于两者之间的状态。
举个例子,如果 t
是 0,那么他的状态是 A。如果 t
是 1,那么它的状态是 B。任何介于两者之间的状态都是插值。
两个实数(浮点数)之间的插值可以描述为:
interpolation = A * (1 - t) + B * t
通常简化为:
interpolation = A + (B - A) * t
这种以恒定速度将一个值转换为另一个值的插值被称为“线性”。因此,当你听到线性插值时,你就知道他们指的是这个公式。
还有其他类型的插值, 这里将不做讨论. 建议之后阅读 Bezier 页面.
向量插值
向量类型(Vector2 和 Vector3)也可以插值,向量自带了相关的便捷函数 Vector2.lerp() 和 Vector3.lerp()。
对于三次插值,还有 Vector2.cubic_interpolate() 和 Vector3.cubic_interpolate() ,它们执行 Bezier 式插值。
下面是从 A 点插值到 B 点的示例伪代码:
var t = 0.0
func _physics_process(delta):
t += delta * 0.4
$Sprite2D.position = $A.position.lerp($B.position, t)
private float _t = 0.0f;
public override void _PhysicsProcess(double delta)
{
_t += (float)delta * 0.4f;
Marker2D a = GetNode<Marker2D>("A");
Marker2D b = GetNode<Marker2D>("B");
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Position = a.Position.Lerp(b.Position, _t);
}
它将产生以下运动:

变换插值
也可以对整个变换进行插值(确保它们具有均一缩放,或者至少有相同的非均一缩放)。为此,可以使用函数 Transform3D.interpolate_with()。
下面是将猴子从位置1转换为位置2的例子:

使用以下伪代码:
var t = 0.0
func _physics_process(delta):
t += delta
$Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t)
private float _t = 0.0f;
public override void _PhysicsProcess(double delta)
{
_t += (float)delta;
Marker3D p1 = GetNode<Marker3D>("Position1");
Marker3D p2 = GetNode<Marker3D>("Position2");
CSGMesh3D monkey = GetNode<CSGMesh3D>("Monkey");
monkey.Transform = p1.Transform.InterpolateWith(p2.Transform, _t);
}
又会产生下面的动作:

平滑运动
Interpolation can be used to smoothly follow a moving target value, such as a
position or a rotation. Each frame, lerp()
moves the current value towards
the target value by a fixed percentage of the remaining difference between the values.
The current value will smoothly move towards the target, slowing down as it gets
closer. Here is an example of a circle following the mouse using interpolation smoothing:
const FOLLOW_SPEED = 4.0
func _physics_process(delta):
var mouse_pos = get_local_mouse_position()
$Sprite2D.position = $Sprite2D.position.lerp(mouse_pos, delta * FOLLOW_SPEED)
private const float FollowSpeed = 4.0f;
public override void _PhysicsProcess(double delta)
{
Vector2 mousePos = GetLocalMousePosition();
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Position = sprite.Position.Lerp(mousePos, (float)delta * FollowSpeed);
}
如下:

This is useful for smoothing camera movement, for allies following the player (ensuring they stay within a certain range), and for many other common game patterns.
备注
Despite using delta
, the formula used above is framerate-dependent, because
the weight
parameter of lerp()
represents a percentage of the remaining
difference in values, not an absolute amount to change. In _physics_process()
,
this is usually fine because physics is expected to maintain a constant framerate,
and therefore delta
is expected to remain constant.
For a framerate-independent version of interpolation smoothing that can also
be used in process()
, use the following formula instead:
const FOLLOW_SPEED = 4.0
func _process(delta):
var mouse_pos = get_local_mouse_position()
var weight = 1 - exp(-FOLLOW_SPEED * delta)
$Sprite2D.position = $Sprite2D.position.lerp(mouse_pos, weight)
private const float FollowSpeed = 4.0f;
public override void _Process(double delta)
{
Vector2 mousePos = GetLocalMousePosition();
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
float weight = 1f - Mathf.Exp(-FollowSpeed * (float)delta);
sprite.Position = sprite.Position.Lerp(mousePos, weight);
}
Deriving this formula is beyond the scope of this page. For an explanation, see Improved Lerp Smoothing or watch Lerp smoothing is broken.