Notifications
Article
Struktura Quaternion – Lerp(), Slerp(), SlerpUnclamped(), LerpUnclamped(), RotateTowards()
Published 5 months ago
80
0
Struktura Quaternion – Lerp(), Slerp(), SlerpUnclamped(), LerpUnclamped(), RotateTowards()

Lerp / Slerp

Metoda Lerp() / Slerp() służy do interpolowania między dwoma rotacjami. Te metody są bardzo do siebie podobne więc pierw opowiem wam o ich wspólnych cechach a potem pokaże wam różnice między tymi metodami.

Definiowanie parametrów Lerp() / Slerp()

Metoda Lerp() / Slerp() pozwala uzyskać pośrednią rotacje między rotacjami podanymi jako parametry (czyli a i b), przez podanie wartości procentowej, która wskazuję pośrednią rotacje.
// Definiujemy dwa quaterniony, między którymi interpolujemy Quaternion start; Quaternion end; // wskazujemy wartość z przedziału od 0 do 1 (czyli 0% - 100%) // wartości poniżej 0 są traktowane jako 0, wartości powyżej 1 są traktowane jako 1 float percentage = 0.5f; // wartość procentowa wskazuje pośrednią rotacje między a i b Quaternion chosenRotation = Quaternion.Lerp(a: start, b: end, t: percentage); LUB // wartość procentowa wskazuje pośrednią rotacje między a i b Quaternion chosenRotation = Quaternion.Slerp(a: start, b: end, t: percentage);
Poniżej rysunek pokazujący jak wartość procentowa (czyli parametr t) wpłynie na zwracaną wartość przez metodę Lerp() / Slerp().

Poniżej film pokazujący jak wartość parametru t wpływa na wartość uzyskanej rotacje.
Fioletowy wektor i łuk reprezentuje interpolowaną rotacje między rotacją a i b.

Jeśli ustawimy wartość parametru "t" na 0 uzyskamy rotacje "a".
Jeśli ustawimy wartość parametru "t" na 1 to uzyskamy rotacje "b".
Parametr "t" określa jaką wartość rotacji otrzymamy.

Do czego możemy wykorzystać metodę Lerp()/Slerp() ?

Te metody są przydatne gdy chcemy obrócić jakiś obiekt.
Uzyskaną rotacje przypisujemy do komponentu Transform i zmieniamy wartość parametru "t" z poziomu Inspectora.
// rotacje między, którymi obracamy Quaternion startRotation, endRotation; // wartość procentowa określająca jaką wartość pośredniej rotacji chcemy uzyskać // zmieniając tą wartość wpływamy na końcową rotacje float percentage; // uzyskaną rotacje przypisujemy do komponentu transform tym samymm nim obracamy transform.rotation = Quaternion.Lerp(a: startRotation, b: endRotation, t: percentage);

Jak obrócić ciało w określonym czasie?

Teraz pokaże wam jak korzystać z metody Lerp() / Slerp() aby obrócić ciałem w ciągu zdefiniowanego czasu. W tym przykładzie skorzystam z coroutyny.
Poniżej znajduję się fragment kodu, który powoduję obrót ciała między dwoma rotacjami. Obrót zostanie wykonany w czasie zdefiniowanym przez zmienną lerpTime. W tym przykładzie definiuję działanie metody Lerp() w coroutinie.
public IEnumerator Lerp( Quaternion startRotation, Quaternion endRotation, float lerpTime) { // czas trwania lerpa float currentTime = 0; // procent przebytej drogi float percentage = 0; while (currentTime < lerpTime) { // zwiększamy wartość czasu trwania lerpa currentTime += Time.deltaTime; // dzielimy aktualny czas trwania lerpa przez całkowity czas trwania lerpa // aby wiedzieć ile procent obrotu zostało wykonane percentage = currentTime / lerpTime; // uzyskujemy pośrednią rotacje i przypisujemy ją do komponentu transform transform.rotation = Quaternion.Lerp(startRotation, endRotation, percentage); yield return null; } }
Poniżej fragment kodu pozwalający wywołać wcześniej pokazaną coroutine Lerp().
// definiujemy początkową i końcową rotacje Quaternion startRotation,endRotation; // definiujemy czas trwania lerpa float timeOfLerp; // wywołanie tej części kodu spowoduje wywołanie coroutyny a tym samym obrót ciała. IEnumerator lerp = Lerp(startRotation, endRotation, timeOfLerp); StartCoroutine(lerp);
Poniżej możecie zobaczyć jak te fragmenty kodu działają w praktyce.
Obrót ciała między dwoma rotacjami zostanie wykonany w ciągu 15 sekund (taką wartość zdefiniowałem w Inspectorze)

Czym różni się metoda Lerp() od Slerp()?

Te dwie metody różni sposób interpolacji. Metoda Lerp() reprezentuję liniową interpolacje, Slerp() reprezentuję sferyczną interpolacje liniową.

Czym te dwie interpolacje się różnią?

Na początek obejrzyjcie film pokazujący różnice w zwracanych wartościach rotacji w przypadku Lerpa() i Slerpa().
Wewnętrzny łuk reprezentuję Lerp() a zewnętrzny Slerp(). Rotacje zwracane przez dwie metody nieznacznie się różnią.

Jak zdefiniowany jest Lerp() i Slerp()?

Załóżmy, że mamy kule i dwa punkty (a i b) na tej kuli. Chcemy aby pewien punkt przemieścił się od punktu a do punktu b.
Punkt możemy przemieścić albo po powierzchni kuli (po czerwonej linii) albo po linii łączącej punkt a i b (niebieskiej).
Jeśli będziemy się poruszać po najkrótszej drodze (czyli linii) ze stałą prędkością to ten ruch będzie zdefiniowany z pomocą interpolacji liniowej.
Jeśli będziemy poruszać się po powierzchni kuli ze stałą prędkością kątową to ten ruch opiszemy z pomocą sferycznej interpolacji liniowej.

Na poniższym filmie możecie zobaczyć ruch punktu z pomocą Lerpa i Slerpa.

Slerp a obracanie ciała

W przypadku Slerpa obrót będzie równomierny przez cały czas trwania bo poruszamy się ze stałą prędkością kątową . Punkt będzie przesuwać się o te same odległości na łuku w stałych odstępach czasu.

Lerp a obracanie ciała

W przypadku Lerpa będziemy poruszać się będzie o stałe odstępy po (niebieskiej) linii . Jak wpłynie to na obrót ciała?
Wyprowadźmy z centrum kuli linie przechodzące przez równomiernie rozmieszczone punkty rozmieszczone na linii (łączącej pozycje a i b na kuli) i kończące się na łuku.
Te linie podzieliły łuk na fragmenty. Zauważcie, że fragmenty łuku są różnej długości. Gdy poruszamy się z pomocą Lerpa to przebyta odległość na łuku będzie ciągle się zmieniać w zależności o odległości od punktów orientacyjnych. Prędkość kątowa w przypadku Lepra nie będzie stała. Im większy dystans do przebycia na łuku tym większa prędkość kątowa.

Porównajcie ponownie obrót z pomocą Lerpa() i Slerpa() i przemyślcie chwilę powyższe opisy. Zwróćcie też uwagę na zmiany prędkości kątowej Lerpa.

Kiedy korzystać z Slerpa? Kiedy korzystać z Lerpa.

W większości przypadków powinniście korzystać z Slerpa do obracania ciała. Prędkość kątowa w przypadku tej funkcji jest stała więc uzyskacie równomierny obrót. (Przy założeniu, że zmieniacie w równomierny sposób wartość parametru t.
Jeśli chodzi o Lerpa to jest uproszczeniem Slerpa. Zakładamy, że dla bardzo małych kątów łuk możemy potraktować jako prosta linia, dzięki czemu obliczenia są prostsze i zyskujemy trochę na wydajności.
Minusem tego uproszczenia jest dokładność obrotu dla większych różnic między bazowymi rotacjami a tym samym zmiany prędkości kątowej podczas obrotu.
Zalecam korzystać z Slerpa w 99% przypadków.

SlerpUnclamped() i LerpUnclamped()

Te dwie metody działają bardzo podobnie do metody Lerp() / Slerp(). Definiujemy parametry w ten sam sposób co w przypadku Lerp() / Slerp().
// Definiujemy dwa quaterniony, między którymi interpolujemy Quaternion start; Quaternion end; // wskazujemy wartość z dowolnego przedziału wartości // jeśli podamy wartość z przedziału 0-1 to ta metody będą działać jak Lerp() /Slerp() float percentage = 5f; // wartość procentowa wskazuje rotacje interpolowaną na podstawie a i b Quaternion chosenRotation = Quaternion.Lerp(a: start, b: end, t: percentage); LUB // wartość procentowa wskazuje rotacje interpolowaną na podstawie a i b Quaternion chosenRotation = Quaternion.Slerp(a: start, b: end, t: percentage);

Czym różni się LerpUnclamped()/SlerpUnclamped() od Lerp()/ Slerp()?

W przypadku Lerp()/ Slerp() mogliśmy definiować wartość parametru "t" tylko w przedziale od 0 do 1, co było równe przedziałowi wartości od 0% do 100% między pierwszą a drugą rotacją (aby uzyskać wartość procentową wystarczy wymnożyć parametr t przez 100%).
Wartości parametru "t" z poza przedziału będą traktowane jako wartość 0 bądź 1 (t>1 traktujemy jako wartość 1, t<0 traktujmy jako wartość 0)
W przypadku LerpUnclamped() / SlerpUnclamped() możemy podać dowolne wartości parametru "t".
Poniżej film pokazujący co się dzieje, gdy wybierzemy wartości parametru "t" z i poza zakresu 0 - 1.
Zauważcie, że dzięki metodzie LerpUnclamped()/ SlerpUnclamped() mogę uzyskiwać dowolne rotacje.
Wartość parametru "t" to wartość procentowa więc:
  • t = 1 oznacza, że uzyskaliśmy 100% czyli końcową rotacje


  • t = 2 oznacza, że uzyskaliśmy 2 razy większą rotacje niż końcowa czyli 200%
  • t = 3.5 oznacza 350% różnicy między rotacją a i b

  • t = -1 oznacza, że cofnęliśmy się o kąt równy równicy rotacji a i b


Porównanie uzyskanych rotacji dla t >1

Poniżej przygotowałem wizualizacje zwracanych wartości przez metody LerpUnclamped() i SlerpUnclamped()
Wewnętrzny okrąg reprezentuję rotacje zwracaną przez LerpUnclamped() a zewnętrzny przez SlerpUnclamped()

Jak można zauważyć na filmie metoda LerpUnclamped() słabo sobie radzi gdy parametr t >1. Im większa wartość parametru "t" tym wolniej następuję przyrost rotacji. Dlatego też nie korzystajcie z LerpUnclamped().
Korzystajcie z SlerpUnclamped() do obracania bo niezależnie od wartości parametru "t" zawsze poprawnie interpoluje wartości rotacji.

RotateTowards()

Ta metoda służy do obracania ciała od "jednej do drugiej rotacji" z określoną prędkością kątową. Ciężko opisać działanie tej metody słowami więc skupię się na pokazaniu jej działania. Poniżej przykładowy kod służący do obracania ciała z pomocą metody RotateTowards().
// definiujemy dwa wektory reprezentujace kąty Eulera, z ich pomocą utworzymy quaterniony public Vector3 fromAsEulerAngles = Vector3.zero; public Vector3 toAsEulerAngles= new Vector3(0,0,90); // definiujemy maksymalną prędkość kątową z jaką będzie się zmieniać uzyskana rotacja z metody RotateTowards() // tą prędkość defiuniujemy w stopniach/sekunde public float maxDegreesVelocity = 5; void Update () { //obliczamy rotacje na bazie kątów Eulera Quaternion from = transform.rotation; Quaternion to = Quaternion.Euler (toAsEulerAngles); // obliczamy o ile stopni obróciliśmy się w danej klatce float maxDegreesDelta = maxDegreesVelocity * Time.deltaTime; // obliczamy aktualną rotacje Quaternion actualRotation = Quaternion.RotateTowards (from:from, to:to, maxDegreesDelta: maxDegreesDelta); // przypisuję ją do komponentu Transform transform.rotation = actualRotation; }

Parametr maxDegreesDelta

Parametr maxDegressDelta określa o ile stopni będziemy się obracać przy każdym wywołaniu metody RotateTowards().
W skrypcie parametr maxDegressDelta definiuję w następujący sposób:
float maxDegreesDelta = maxDegreesVelocity * Time.deltaTime;
maxDegreesVelocity - jest to prędkość kątową wyrażona w stopniach na sekundę. O tyle stopni chce obracać ciało w każdej sekundzie.
maxDegreesVelocity wymnażam przez Time.deltaTime aby w każdym Update() uzyskać wartość obrotu proporcjonalną do czasu trwania Update(). Uzyskaną wartość podaję jako parametr metody RotateTowards().
Teraz wystarczy zmieniać wartość parametru maxDegreesVelocity aby ustalić prędkość obrotu.

Gdy prędkość kątowa maxDegreesDelta > 0

Teraz pokaże wam kilka przykładów abyście lepiej zrozumieli działanie parametru "from" i "to" jak i całej metody RotateTowards().

Przykład 1

Obracamy czerwoną lokalną oś komponentu Transform. Czerwona oś początkowo jest obrócona o rotacje "from"

Czerwona oś obraca się od kierunku "from" do "to" a gdy osiąga rotacje zdefiniowana w parametrze "to", ruch ustaję.
Parametr "to" definiuję końcową rotacje, do której dążymy.

Przykład 2

Ustawiłem czerwoną oś obrotu poza przedziałem zdefiniowanym z pomocą parametru "from" i "to". Zobaczmy jak będzie wyglądać obrót w takiej sytuacji.
Jak widać ciało może być dowolnie obrócone, gdy zmieniamy jego rotacje z pomocą metody RotateTowards().
Ciało nie musi być obrócone w przedziale od "from" do "to".
Parametr "from" w połączeniu z "to" wyznacza oś i płaszczyznę obrotu ciała.

Przykład 3

Metoda RotateTowards() wyznacza zawsze najkrótszą drogę do rotacji "to" w płaszczyźnie obrotu.
Na poniższym rysunku możecie zobaczyć dwa kierunki obrotu czerwonej osi układu współrzędnych ciała. Trasy obrotu zostały wybrane w taki sposób aby były jak najkrótsze.

Gdy prędkość kątowa maxDegreesDelta < 0 (wartości ujemne)

Przykład 4

Teraz zobaczymy co się stanie gdy ustawimy ujemną wartość parametru maxDegreesDelta.
Na poniższym filmie możecie zobaczyć jaki jest kierunek końcowego obrotu, gdy maxDegreesDelta <0.
W takim przypadku obracamy się do rotacji będącej całkowicie naprzeciwko rotacji "to".
Końcowa rotacja w tej sytuacji to tak naprawdę rotacja "to" po obrocie o 180° .

Przykład 5

Teraz ponownie sprawdzimy, czy metoda RotateTowards() obróci ciało po najkrótszej trasie gdy maxDegreesDelta < 0.

Metoda RotateTowards() znowu wybiera najkrótszą drogę do rotacji naprzeciw "to".


Potencjalne problemy

Teraz pokaże wam jeszcze dwa ciekawe przypadki związane z parametrem maxDegreesDelta.
  • Gdy maxDegreesDelta>0 i uzyskamy już rotację "to" to zmiana maxDegreesDelta na wartości ujemne nie spowoduję obrotu w przeciwnym kierunku. Ciało będzie skierowane w kierunku "to" i nie będzie już obliczana rotacja. Jeśli chcecie aby ujemne wartości maxDegreesDelta ponownie wywoływały obrót w przeciwnym kierunku, to musicie zmienić w jakiś sposób wartość rotacji komponentu Transform bądź wartości parametru "from" lub "to".

  • Gdy maxDegreesDelta < 0 i uzyskamy końcową rotacje, to metoda RotateTowards() nadal będzie próbować obliczać końcową rotacje czego skutkiem będzie ciągły szarpany ruch ciała w końcowej pozycji.


Mini podsumowanie

Przemyślcie chwilę działanie metody RotateTowards() i wypróbujcie przykłady bo nie jest to zbyt intuicyjna metoda i chwilami jej działanie może się wydawać dziwne.
Tags:
Rafał Tadajewski
I haven't duck. I took dino. - Programmer
10
Comments