¿Por qué usar Time.deltaTime en las funciones de Lerping?

12

A mi entender, una función Lerp interpola entre dos valores ( ay b) usando un tercer valor ( t) entre 0y 1. En t = 0, se devuelve t = 1el valor a , en , bse devuelve el valor . A 0.5 se devuelve el valor a mitad de camino entre ay b.

(La siguiente imagen es un paso suave, generalmente una interpolación cúbica)

ingrese la descripción de la imagen aquí

He estado navegando por los foros y en esta respuesta encontré la siguiente línea de código:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

Pensé para mí mismo, "qué tonto, no tiene idea", pero como tenía más de 40 votos a favor, lo probé y, efectivamente, ¡funcionó!

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

Tengo valores aleatorios entre 0.01y 0.02para t. ¿No debería la función interpolar en consecuencia? ¿Por qué se acumulan estos valores? ¿Qué hay en lerp que no entiendo?

AzulShiva
fuente
1
A generalmente es la posición, que cambia y, por lo tanto, el muestreo a 1/60 (60 fps) solo movería el objeto mediante la interpolación de 0.16 estrechando continuamente la distancia entre A y B (por lo tanto, la muestra es cada vez más pequeña).
Sidar
Usted ingresó t y comenzó a escribir tt ... esas son diferentes variables.
user253751

Respuestas:

18

Ver también esta respuesta .

Hay dos formas comunes de usar Lerp:

1. Mezcla lineal entre un inicio y un final

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

Esta es la versión con la que probablemente estés más familiarizado.

2. Facilidad exponencial hacia un objetivo

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

Tenga en cuenta que en esta versión el currentvalor aparece como salida y como entrada. Desplaza la startvariable, por lo que siempre comenzamos desde donde nos mudamos en la última actualización. Esto es lo que le da a esta versión de Lerpuna memoria de un cuadro a otro. Desde este punto de partida en movimiento, luego movemos una fracción de la distancia hacia lo targetdictado por un sharpnessparámetro.

Este parámetro ya no es una "velocidad", porque nos acercamos al objetivo de una manera similar a Zeno . Si así sharpnessPerTickfuera 0.5, en la primera actualización nos moveríamos a la mitad de nuestro objetivo. Luego, en la próxima actualización, moveríamos la mitad de la distancia restante (un cuarto de nuestra distancia inicial). Luego, en el siguiente, nos moveríamos la mitad otra vez ...

Esto proporciona una "facilidad exponencial" en la que el movimiento es rápido cuando está lejos del objetivo y se ralentiza gradualmente a medida que se acerca asintóticamente (aunque con números de precisión infinita nunca lo alcanzará en un número finito de actualizaciones, para nuestros propósitos se acerca lo suficiente). Es ideal para perseguir un valor objetivo en movimiento o para suavizar una entrada ruidosa usando un " promedio móvil exponencial ", generalmente usando un sharpnessPerTickparámetro muy pequeño como 0.1o más pequeño.


Pero tienes razón, hay un error en la respuesta votada que vinculas. No está corrigiendo de deltaTimela manera correcta. Este es un error muy común al usar este estilo de Lerp.

El primer estilo de Lerpes lineal, por lo que podemos ajustar linealmente la velocidad multiplicando por deltaTime:

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

Pero nuestra flexibilización exponencial no es lineal , por lo que simplemente multiplicando nuestro sharpnessparámetro por deltaTimeno dará la corrección de tiempo correcta. Esto se mostrará como un factor decisivo en el movimiento si nuestro framerate fluctúa, o un cambio en la nitidez de flexibilización si pasa de 30 a 60 de manera constante.

En cambio, necesitamos aplicar una corrección exponencial para nuestra facilidad exponencial:

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

Aquí referenceFrameratehay una constante como 30mantener las unidades para sharpnesslo mismo que estábamos usando antes de corregir el tiempo.


Hay otro error discutible en ese código, que está utilizando Slerp: la interpolación lineal esférica es útil cuando queremos una velocidad de rotación exactamente consistente a través de todo el movimiento. Pero si vamos a utilizar una facilidad exponencial no lineal de todos modos, Lerpdará un resultado casi indistinguible y es más barato. ;) Los cuaterniones son mucho mejores que las matrices, por lo que esta suele ser una sustitución segura.

DMGregory
fuente
1

Creo que el concepto central que falta sería en este escenario A no está arreglado. A se actualiza con cada paso, independientemente de la interpolación que tenga Time.deltaTime.

Entonces, con A acercándose a B con cada paso, el espacio total de la interpolación cambia con cada llamada Lerp / Slerp. Sin hacer los cálculos reales, sospecharía que el efecto no es el mismo que el gráfico Smoothstep, pero es una forma económica de aproximar una desaceleración a medida que A se acerca a B.

Además, esto se usa con frecuencia porque B puede no ser estático tampoco. El caso típico podría ser una cámara que sigue a un jugador. Desea evitar sacudidas, haciendo que la cámara salte a una ubicación o rotación.

Chris
fuente
1

Tienes razón, el método Quaternion Slerp(Quaternion a, Quaternion b, float t)interpola entre ay bpor la cantidad t. Pero mire el primer valor, no es el valor inicial.

Aquí el primer valor dado al método es la rotación actual del objeto transform.rotation. Entonces, para cada cuadro, se interpola entre la rotación actual y la rotación objetivo _lookRotationpor la cantidad Time.deltaTime.

Por eso produce una rotación suave.

Ludovic Feltz
fuente
2
Ahora me siento como un idiota
AzulShiva
@AzulShiva No te preocupes, le pasa a todos;)
Ludovic Feltz