Зачем использовать Time.deltaTime в функциях Lerping?

12

Насколько я понимаю, функция Lerp интерполирует между двумя значениями ( aи b), используя третье значение ( t) между 0и 1. At t = 0, значение a возвращается, at t = 1, значение bвозвращается. На 0,5 значение на полпути между aи bвозвращается.

(Следующая картинка - плавный шаг, обычно кубическая интерполяция)

введите описание изображения здесь

Я просматривал форумы и в этом ответе нашел следующую строку кода:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

Я подумал про себя: «Что за дурак, он понятия не имеет», но поскольку у него было более 40 голосов, я попробовал и, конечно же, это сработало!

float t = Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, t);
Debug.Log(t);

Я получил случайные значения между 0.01и 0.02для t. Разве функция не должна интерполировать соответственно? Почему эти значения складываются? Что такого в lerp, что я не понимаю?

AzulShiva
источник
1
A - это обычно положение, которое меняется, и поэтому выборка на 1/60 (60 кадров в секунду) будет перемещать объект только путем интерполяции 0,16, непрерывно сужая расстояние между A и B (таким образом, выборка становится все меньше и меньше каждый раз).
Сидар
Вы вошли в систему t и использовали tt ... это разные переменные.
user253751

Ответы:

18

Смотрите также этот ответ .

Есть два распространенных способа использования Lerp:

1. Линейное смешивание между началом и концом

progress = Mathf.Clamp01(progress + speedPerTick);
current = Mathf.Lerp(start, end, progress);

Это версия, с которой вы, вероятно, больше всего знакомы.

2. Экспоненциальная легкость по отношению к цели

current = Mathf.Lerp(current, target, sharpnessPerTick);

Обратите внимание, что в этой версии currentзначение отображается как выход и вход. Она смещает startпеременную, поэтому мы всегда начинаем с того места, куда переместились при последнем обновлении. Это то, что дает этой версии Lerpпамяти от одного кадра к другому. Из этой движущейся начальной точки мы затем перемещаем часть расстояния в направлении, targetопределяемом sharpnessпараметром.

Этот параметр больше не является «скоростью», потому что мы приближаемся к цели в стиле Zeno . Если бы sharpnessPerTickбыли 0.5, то при первом обновлении мы бы пошли на полпути к нашей цели. Затем в следующем обновлении мы переместим половину оставшегося расстояния (то есть четверть нашего начального расстояния). Тогда на следующем мы двинулись бы снова наполовину ...

Это дает «экспоненциальное замедление», когда движение происходит быстро, когда оно далеко от цели, и постепенно замедляется по мере асимптотического приближения (хотя с числами бесконечной точности оно никогда не достигнет его в любом конечном количестве обновлений - для наших целей это подбирается достаточно близко). Это отлично подходит для погони за движущимся целевым значением или для сглаживания шумового ввода с использованием « экспоненциального скользящего среднего », обычно с использованием очень маленького sharpnessPerTickпараметра, например 0.1или меньше.


Но вы правы, есть ошибка в ответе, на который вы ответили. Это не deltaTimeправильно для правильного пути. Это очень распространенная ошибка при использовании этого стиля Lerp.

Первый стиль Lerpлинейный, поэтому мы можем линейно регулировать скорость, умножая на deltaTime:

progress = Mathf.Clamp01(progress + speedPerSecond * Time.deltaTime);
// or progress = Mathf.Clamp01(progress + Time.deltaTime / durationSeconds);
current = Mathf.Lerp(start, end, progress);

Но наше экспоненциальное ослабление нелинейно , поэтому умножение нашего sharpnessпараметра на deltaTimeне даст правильной коррекции времени. Это будет проявляться как дрожь в движении, если наша частота кадров колеблется, или изменение резкости ослабления, если вы последовательно переходите от 30 до 60.

Вместо этого нам нужно применить экспоненциальную коррекцию для нашей экспоненциальной простоты:

blend = 1f - Mathf.Pow(1f - sharpness, Time.deltaTime * referenceFramerate);
current = Mathf.Lerp(current, target, blend);

Вот referenceFramerateтолько константа, как 30сохранить единицы для sharpnessтого же, что мы использовали, прежде чем исправлять на время.


В этом коде есть еще одна спорная ошибка Slerp- сферическая линейная интерполяция полезна, когда мы хотим точно одинаковую скорость вращения на протяжении всего движения. Но если мы все равно будем использовать нелинейную экспоненциальную простоту, Lerpэто даст почти неразличимый результат и будет дешевле. ;) Кватернионы выглядят гораздо лучше, чем матрицы, поэтому обычно это безопасная замена.

ДМГригорий
источник
1

Я думаю, что основная концепция отсутствует в этом сценарии А не является фиксированной. A обновляется с каждым шагом, насколько бы ни была интерполяция Time.deltaTime.

Таким образом, когда A становится ближе к B с каждым шагом, общее пространство интерполяции меняется с каждым вызовом Lerp / Slerp. Не занимаясь реальной математикой, я подозреваю, что эффект не такой же, как ваш график Smoothstep, но это дешевый способ приблизить замедление, когда A приближается к B.

Кроме того, это часто используется, потому что B также не может быть статическим. Типичным случаем может быть камера, следующая за игроком. Вы хотите избежать рывков, заставив камеру перейти на локацию или вращение.

Крис
источник
1

Вы правы, метод Quaternion Slerp(Quaternion a, Quaternion b, float t)интерполирует между aи bпо сумме t. Но следите за первым значением, это не начальное значение.

Здесь первое значение, данное методу, является текущим вращением объекта transform.rotation. Таким образом, для каждого кадра он интерполирует между текущим и целевым вращением _lookRotationпо величине Time.deltaTime.

Вот почему он производит плавное вращение.

Людовик Фельц
источник
2
Теперь я чувствую себя идиотом
АзулШива
@AzulShiva Не волнуйтесь, это случается со всеми;)
Людовик Фельц