Dlaczego warto korzystać z Time.deltaTime w funkcjach Lerping?

12

W moim rozumieniu funkcja Lerp interpoluje dwie wartości ( ai b) przy użyciu trzeciej wartości ( t) pomiędzy 0i 1. At t = 0zwracana jest wartość a t = 1, zwracana bjest wartość . Przy 0,5 zwracana jest wartość w połowie między ai b.

(Poniższy obrazek przedstawia płynny krok, zwykle interpolację sześcienną)

wprowadź opis zdjęcia tutaj

Przeglądałem fora i na tej odpowiedzi znalazłem następujący wiersz kodu:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

Pomyślałem sobie: „co za głupiec, on nie ma pojęcia”, ale ponieważ miał ponad 40 głosów pozytywnych, spróbowałem i na pewno zadziałało!

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

Mam losowe wartości pomiędzy 0.01i 0.02dla t. Czy funkcja nie powinna interpolować odpowiednio? Dlaczego te wartości się stosują? Co takiego jest w Lerp, czego nie rozumiem?

AzulShiva
źródło
1
A jest zwykle pozycją, która zmienia się i dlatego próbkowanie przy 1/60 (60 fps) przesuwałoby obiekt jedynie przez interpolację 0,16 stale zawężając odległość między A i B (a zatem próbka jest za każdym razem coraz mniejsza).
Sidar
Zalogowałeś t i przeszedłeś na tt ... to różne zmienne.
user253751

Odpowiedzi:

18

Zobacz także tę odpowiedź .

Istnieją dwa typowe sposoby użycia Lerp:

1. Liniowe mieszanie między początkiem a końcem

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

Jest to wersja, którą prawdopodobnie znasz.

2. Wykładnicza łatwość w kierunku celu

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

Zauważ, że w tej wersji currentwartość pojawia się zarówno jako dane wyjściowe, jak i dane wejściowe. Wypiera startzmienną, więc zawsze zaczynamy od miejsca, w którym przeprowadziliśmy się przy ostatniej aktualizacji. To daje tej wersji Lerppamięci od jednej klatki do drugiej. Następnie z tego ruchomego punktu początkowego przesuwamy ułamek odległości w kierunku targetpodyktowanym sharpnessparametrem.

Ten parametr nie jest już „szybkością”, ponieważ zbliżamy się do celu w sposób podobny do Zeno . Gdyby sharpnessPerTickbyły0.5 , to przy pierwszej aktualizacji przenieślibyśmy się w połowie drogi do celu. Następnie przy następnej aktualizacji przesunęlibyśmy się o połowę pozostałej odległości (czyli o jedną czwartą początkowej odległości). Następnie w następnym przejdziemy ponownie o połowę ...

Daje to „wykładniczą ulgę”, w której ruch jest szybki, gdy daleko od celu, i stopniowo zwalnia, gdy zbliża się asymptotycznie (choć przy liczbach o nieskończonej precyzji nigdy go nie osiągnie w żadnej skończonej liczbie aktualizacji - dla naszych celów jest to wystarczająco blisko). Jest świetny do ścigania ruchomej wartości docelowej lub wygładzania głośnego sygnału wejściowego przy użyciu „ wykładniczej średniej ruchomej ”, zwykle przy użyciu bardzo małego sharpnessPerTickparametru, takiego jak 0.1lub mniejszego.


Ale masz rację, w podanej wyżej linku znajduje się błąd. To nie poprawia deltaTimewe właściwy sposób. Jest to bardzo częsty błąd podczas korzystania z tego stylu Lerp.

Pierwszy styl Lerpjest liniowy, więc możemy liniowo dostosować prędkość, mnożąc przez deltaTime:

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

Ale nasze łagodzenie wykładnicze jest nieliniowe , więc pomnożenie naszego sharpnessparametru przez deltaTimenie da poprawnej korekty czasu. Będzie to widoczne jako drżenie w ruchu, jeśli nasze tempo klatek będzie się zmieniać, lub zmiana łagodnej ostrości, jeśli konsekwentnie będziesz przechodził z 30 do 60.

Zamiast tego musimy zastosować wykładniczą korektę dla naszej wykładniczej łatwości:

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

Oto referenceFrameratepo prostu stałe 30zachowanie jednostek sharpnesstak, jak używaliśmy przed korektą czasu.


W tym kodzie jest jeszcze jeden możliwy do uzasadnienia błąd Slerp- sferyczna interpolacja liniowa jest przydatna, gdy chcemy dokładnie spójnego tempa obrotu w całym ruchu. Ale jeśli i tak będziemy korzystać z nieliniowej łatwości wykładniczej, Lerpda to prawie nie do odróżnienia wynik i jest tańszy. ;) Quaternions lerp są znacznie lepsze niż macierze, więc jest to zwykle bezpieczna zamiana.

DMGregory
źródło
1

Myślę, że brakująca podstawowa koncepcja byłaby w tym scenariuszu A nie jest naprawiona. Wartość A jest aktualizowana z każdym krokiem, niezależnie od interpolacji, jaką ma Time.deltaTime.

Zatem, gdy A zbliża się do B z każdym krokiem, całkowita przestrzeń interpolacji zmienia się z każdym wywołaniem Lerp / Slerp. Podejrzewam, że bez robienia prawdziwej matematyki efekt nie jest taki sam jak na wykresie Smoothstep, ale jest tanim sposobem przybliżenia opóźnienia, gdy A zbliża się do B.

Jest to również często używane, ponieważ B może również nie być statyczny. Typowym przypadkiem może być kamera śledząca gracza. Chcesz uniknąć szarpnięć, skoku kamery w wybrane miejsce lub obrotu.

Chris
źródło
1

Masz rację, metoda Quaternion Slerp(Quaternion a, Quaternion b, float t)interpoluje pomiędzy ai bwedług kwoty t. Ale patrz pierwsza wartość, to nie jest wartość początkowa.

Tutaj pierwszą wartością podaną dla metody jest bieżący obrót obiektu transform.rotation. Tak więc dla każdej ramki interpoluje między obrotem bieżącym a docelowym _lookRotationo wartośćTime.deltaTime .

Dlatego zapewnia płynny obrót.

Ludovic Feltz
źródło
2
Teraz czuję się jak idiota
AzulShiva
@AzulShiva Nie martw się, to zdarza się każdemu;)
Ludovic Feltz