Интерполяция
Интерполяция — распространённая операция в графическом программировании, используемая для смешивания или перехода между двумя значениями. Интерполяция также может использоваться для сглаживания движения, вращения и т. д. Полезно ознакомиться с ней, чтобы расширить свой кругозор как разработчика игр.
Основная идея состоит в том, что вы хотите перейти из точки А в точку В. Значение 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.
Вот пример псевдокода для перехода из точки А в точку В с использованием интерполяции:
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().
Вот пример трансформирования обезьяны с первой позиции (Position1) на вторую (Position2):
Используя следующий псевдокод:
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);
}
И снова, это произведет следующее движение:
Сглаженное движение
Интерполяцию можно использовать для плавного отслеживания движущегося целевого значения, например, положения или поворота. В каждом кадре функция lerp() перемещает текущее значение к целевому на фиксированный процент от оставшейся разницы между значениями. Текущее значение будет плавно двигаться к цели, замедляясь по мере приближения. Вот пример круга, следующего за мышью с использованием интерполяционного сглаживания:
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);
}
Вот как это выглядит:
Это полезно для сглаживания движения камеры, для союзников, следующих за игроком (гарантируя, что они остаются в пределах определенного диапазона), а также для многих других распространенных игровых схем.
Примечание
Несмотря на использование delta, формула, использованная выше, зависит от частоты кадров, поскольку параметр weight (вес) функции lerp() представляет собой процент оставшейся разницы значений, а не абсолютную величину изменения. В _physics_process() это обычно приемлемо, поскольку физика должна поддерживать постоянную частоту кадров, и, следовательно, delta также должна оставаться постоянной.
Для версии интерполяционного сглаживания, не зависящей от частоты кадров, которую также можно использовать в process(), используйте следующую формулу:
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);
}
Вывод этой формулы выходит за рамки данной страницы. Подробнее см. Improved Lerp Smoothing или видео Lerp smoothing is broken.