Warum Time.deltaTime in Lerping-Funktionen verwenden?

12

Nach meinem Verständnis interpoliert eine Lerp-Funktion zwischen zwei Werten ( aund b) unter Verwendung eines dritten Werts ( t) zwischen 0und 1. Bei t = 0wird der Wert a zurückgegeben, bei t = 1wird der Wert bzurückgegeben. Bei 0,5 wird der Wert auf halbem Weg zwischen aund bzurückgegeben.

(Das folgende Bild ist ein sanfter Schritt, normalerweise eine kubische Interpolation.)

Geben Sie hier die Bildbeschreibung ein

Ich habe die Foren durchsucht und bei dieser Antwort die folgende Codezeile gefunden:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

Ich dachte mir, "was für ein Idiot, er hat keine Ahnung", aber da es mehr als 40 Upvotes hatte, habe ich es ausprobiert und sicher hat es funktioniert!

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

Ich habe zufällige Werte zwischen 0.01und 0.02für t. Sollte die Funktion nicht entsprechend interpolieren? Warum stapeln sich diese Werte? Was verstehe ich an Lerp nicht?

AzulShiva
quelle
1
A ist normalerweise eine Position, die sich ändert, und daher würde eine Abtastung mit 1/60 (60 fps) das Objekt nur durch Interpolation von 0,16 bewegen, wodurch der Abstand zwischen A und B kontinuierlich verringert wird (daher ist die Abtastung jedes Mal kleiner und kleiner).
Sidar
Sie haben t protokolliert und mit tt lerped ... das sind verschiedene Variablen.
user253751

Antworten:

18

Siehe auch diese Antwort .

Es gibt zwei gängige Verwendungsmöglichkeiten Lerp:

1. Lineare Überblendung zwischen Anfang und Ende

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

Dies ist die Version, mit der Sie wahrscheinlich am besten vertraut sind.

2. Exponentielle Leichtigkeit in Richtung eines Ziels

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

Beachten Sie, dass in dieser Version der currentWert sowohl als Ausgabe als auch als Eingabe angezeigt wird . Die startVariable wird verschoben , sodass wir immer dort beginnen, wo wir beim letzten Update umgezogen sind. Dies gibt dieser Version Lerpeines Speichers von einem Frame zum nächsten. Von diesem beweglichen Startpunkt aus bewegen wir uns dann einen Bruchteil der Entfernung in Richtung des targetdurch einen sharpnessParameter vorgegebenen .

Dieser Parameter ist keine "Geschwindigkeit" mehr, da wir uns dem Ziel auf Zeno-ähnliche Weise nähern . Wenn sharpnessPerTickja 0.5, dann würden wir beim ersten Update auf halbem Weg zu unserem Ziel gehen. Beim nächsten Update würden wir dann die Hälfte der verbleibenden Distanz zurücklegen (also ein Viertel unserer ursprünglichen Distanz). Dann würden wir uns beim nächsten Mal wieder halb bewegen ...

Dies führt zu einer "exponentiellen Lockerung", bei der die Bewegung weit vom Ziel entfernt schnell ist und sich bei asymptotischer Annäherung allmählich verlangsamt (obwohl sie mit Zahlen mit unendlicher Genauigkeit niemals in einer endlichen Anzahl von Aktualisierungen erreicht wird - für unsere Zwecke kommt nah genug heran). Es eignet sich hervorragend zum Verfolgen eines sich bewegenden Zielwerts oder zum Glätten einer verrauschten Eingabe mithilfe eines " exponentiellen gleitenden Durchschnitts ", normalerweise unter Verwendung eines sehr kleinen sharpnessPerTickParameters wie 0.1oder kleiner.


Aber Sie haben Recht, es gibt einen Fehler in der von Ihnen verlinkten Antwort. Es korrigiert nicht deltaTimeden richtigen Weg. Dies ist ein sehr häufiger Fehler bei der Verwendung dieses Stils von Lerp.

Der erste Stil von Lerpist linear, daher können wir die Geschwindigkeit linear anpassen, indem wir multiplizieren mit deltaTime:

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

Unsere exponentielle Lockerung ist jedoch nicht linear . Wenn Sie also nur unseren sharpnessParameter mit multiplizieren deltaTime, erhalten Sie keine korrekte Zeitkorrektur. Dies zeigt sich als Ruckeln in der Bewegung, wenn unsere Framerate schwankt, oder als Änderung der Lockerungsschärfe, wenn Sie konstant von 30 auf 60 gehen.

Stattdessen müssen wir eine exponentielle Korrektur für unsere exponentielle Leichtigkeit anwenden:

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

Hier referenceFramerateist nur eine Konstante 30, um die Einheiten auf sharpnessdem gleichen Stand zu halten, den wir vor der Zeitkorrektur verwendet haben.


Es gibt einen weiteren möglichen Fehler in diesem Code, der verwendet wird Slerp: Die sphärische lineare Interpolation ist nützlich, wenn wir eine genau konsistente Rotationsrate über die gesamte Bewegung wünschen. Aber wenn wir sowieso eine nichtlineare exponentielle Leichtigkeit verwenden, Lerpwird dies ein fast nicht unterscheidbares Ergebnis liefern und es ist billiger. ;) Quaternionen lerp viel besser als Matrizen, daher ist dies normalerweise eine sichere Substitution.

DMGregory
quelle
1

Ich denke, das fehlende Kernkonzept wäre in diesem Szenario A nicht festgelegt. A wird mit jedem Schritt aktualisiert, unabhängig davon, wie viel Time.deltaTime interpoliert.

Wenn sich A mit jedem Schritt B nähert, ändert sich der Gesamtraum der Interpolation mit jedem Lerp / Slerp-Aufruf. Ohne die eigentliche Berechnung würde ich vermuten, dass der Effekt nicht mit Ihrem Smoothstep-Diagramm identisch ist, aber eine kostengünstige Methode ist, um eine Verzögerung zu approximieren, wenn A sich B nähert.

Dies wird auch häufig verwendet, da B möglicherweise auch nicht statisch ist. Ein typischer Fall könnte eine Kamera sein, die einem Spieler folgt. Sie möchten ein Ruckeln vermeiden, indem die Kamera zu einem Ort oder einer Drehung springt.

Chris
quelle
1

Sie haben Recht, die Methode Quaternion Slerp(Quaternion a, Quaternion b, float t)interpoliert zwischen aund bum den Betrag t. Aber achten Sie auf den ersten Wert, es ist nicht der Startwert.

Hier ist der erste Wert, der der Methode gegeben wird, die aktuelle Objektdrehung transform.rotation. Für jeden Frame wird also zwischen der aktuellen Drehung und der Zieldrehung _lookRotationum den Betrag interpoliert Time.deltaTime.

Deshalb erzeugt es eine gleichmäßige Rotation.

Ludovic Feltz
quelle
2
Jetzt fühle ich mich wie ein Idiot
AzulShiva
@AzulShiva Keine Sorge, es passiert jedem;)
Ludovic Feltz