Autor Wątek: Przeliczanie rotacji w Blenderze na rotacje w OpenGL.  (Przeczytany 2230 razy)

Offline jontek

  • Użytkownik

# Kwiecień 27, 2017, 23:26:27
Piszę sobie w Pythonie jakiś prosty exporter scen z myślą o użyciu do OpenGL. Wiem, że trzeba przejść z układu współrzędnych Blendera na OpenGL (right handed). Współrzędne obiektów na scenie konwertuje się łatwo i w OpenGL dostaję to co mam w Blenderze:
x’ = x
y’ = z
z’ = -y

Jednak nie mogę sobie poradzić z obrotami. Nie mam pojęcia jak przeliczyć kąty obrotów w trzech osiach Blendera na kąty obrotów gotowe do użycia w OpenGL (glRotatef). Gdy robię to tak jak przy translacji to jest dobrze ale tylko w pewnym zakresie kątów i gdy nie składam dwóch obrotów.

Niby problem wydaje się całkiem popularny w sieci ale po dłuższym grzebaniu nie znalazłem konkretnej porady jak to zrobić. Może ktoś tutaj przechodził przez ten problem i go rozwiązał. Byłbym bardzo wdzięczny za pomoc. Ręce mi już  tymi obrotami opadły… :)

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline voytech

  • Użytkownik

  • +1
# Kwiecień 29, 2017, 00:42:38
Piszę sobie w Pythonie jakiś prosty exporter scen z myślą o użyciu do OpenGL. Wiem, że trzeba przejść z układu współrzędnych Blendera na OpenGL (right handed).

Ale Blender też ma taki sam układ współrzędnych jak OpenGL. Jak nie wierzysz to zaznacz kamerę i naciśnij Alt-R. Po zresetowaniu rotacji kamery widać, że góra kamery to +Y, prawo +X i patrzy ona w kierunku ujemnych Z. Czyli tak jak w OpenGL.

Jednak nie mogę sobie poradzić z obrotami. Nie mam pojęcia jak przeliczyć kąty obrotów w trzech osiach Blendera na kąty obrotów gotowe do użycia w OpenGL (glRotatef). Gdy robię to tak jak przy translacji to jest dobrze ale tylko w pewnym zakresie kątów i gdy nie składam dwóch obrotów.

obiekty w blenderze zawierają kąty obrotu "rotation_euler" w radianach, glRotate przyjmuje w stopniach więc trzeba sobie przekonwertować:

Kod: (python) [Zaznacz]
import bpy, math

obj = bpy.context.selected_objects[0]
print('Object("%s") Rotation Euler:' % obj.name)
print('  radians: ', [x for x in obj.rotation_euler])
print('  degrees: ', [math.degrees(x) for x in obj.rotation_euler])
print('  euler rotation mode: ', obj.rotation_mode)

ważny jest też tryb obrotu. Jak masz np. XYZ to znaczy, że najpierw jest obracany obiekt wokół osi X, następnie Y a na końcu wokół osi Z. Przekłada się to bezpośrednio na operacje w OGLu (Uwaga! kolejność możenia macierzy są odwrotne do kolejności operacji ):

Kod: (c) [Zaznacz]
for (auto o : scene.objects) {
    glPushMatrix();
         glTranslatef(o.loc_x, o.loc_y, o.loc_z);
         glRotatef(o.deg_z, 0, 0, 1); \\\
         glRotatef(o.deg_y, 0, 1, 0); \\ > XYZ; kolejnosc jest wazna!
         glRotatef(o.deg_x, 1, 0, 0); \\/
         glScalef(o.size_x, o.size_y, o.size_z);
    glPopMatrix();
}

powyższa kolejność się u mnie sprawdzała bo zawsze miałem w Blenderze ustawiony tryb XYZ. Lepiej jednak to wyeksportować i sprawdzać w kodzie programu.

można też odczytać macierz "bpy.context.selected_objects[0].matrix_world" i ładować to bezpośrednio do "glMultMatrixf(m44)"

Tak na marginesie, jeżeli masz animacje opartą na kątach Eulera to może się okazać, że interpolacja liniowa z jednego kąta Eulera do innego powoduje czasami bardzo dziwny ruch obiektu. Warto wtedy bawić się z ustawieniami kolejności rotacji, czyli XYZ zamienić na inny. Nie zawsze to pomoże, wtedy kwaterniony mogą jedynie pomóc.

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 12:01:23
Stokrotne dzięki za odpowiedź. Męczę się z tymi obrotami trzeci dzień. Jestem już zrezygnowany. Myślałem, że nikt się nie odezwie. :) W Google jak coś wyszukuję na ten temat to na pięciu pierwszych podstronach z wynikami mam już wszystko "kliknięte". :)

Najpierw sprawa z kolejnością rotacji. Próbowałem różnie ale teraz potwierdziłeś mi, że ma być ZYX. Za chwilę to pozmieniam i tego się będę trzymał. Dzięki!

Druga sprawa to układ współrzędnych Blendera i OpenGL. Po Alt+R faktycznie X jest w prawo i Y do góry. Ale kamera w Blenderze patrzy w kierunku dodatnich Z, a nie ujemnych (jak w OpenGL). Więc tu jest pewna różnica. :(

A jeszcze mam pytanie o kamerę. Jakiej kolejności transformacji używać dla kamery? Rotacja też ZYX? Pozycję monożę przez -1.

------- po chwili --------

Na razie kamerę ustawiłem prosto i sprawdzam obroty obiektu. Gdy tylko jeden z kątów XYZ nie jest zerowy to widok w Blenderze i OpenGL mam taki sam. Ale gdy w dwóch albo trzech osiach ustawiam kąt != 0 to już jest różnica.
Czy tu przypadkiem nie jest potrzebny jakiś warunek? Mam wrażenie, że powyżej jakiegoś kąta (90? 180?) pojawia się problem ze składaniem rotacji.
Już powoli świruję...
« Ostatnia zmiana: Kwiecień 29, 2017, 14:47:41 wysłana przez jontek »

Offline DezerteR

  • Użytkownik

# Kwiecień 29, 2017, 17:58:05
Do wykonywania i przechowywania orientacji polecę kwaterniony. Obliczenia są dużo prostsze i nie trzeba się męczyć z kolejnością przekształceń. Do tego kwaternion da się jednoznacznie przekształcić na macierz więc możesz używać ich tylko przy eksporcie z blendera.

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 18:06:42
Do wykonywania i przechowywania orientacji polecę kwaterniony. Obliczenia są dużo prostsze i nie trzeba się męczyć z kolejnością przekształceń. Do tego kwaternion da się jednoznacznie przekształcić na macierz więc możesz używać ich tylko przy eksporcie z blendera.
Trochę nie ogarniam kwaternionów. :| Poza tym wymagałoby to sporych przeróbek, bo mocno się już "przykleiłem" do kątów obrotu w trzech osiach. Kolejność przekształceń to nie jest męczarnia. Raz to sobie ustalę i będzie ok. :) Nie mogę tylko zrozumieć dlaczego nie dostaję tego samego efektu w OpenGL i Blenderze. Mam wrażenie, że nie da się tego zrobić. :(

A kwaterniony w przyszłości faktycznie będę musiał poznać, bo coraz częściej to pojęcie pojawia się w moim życiu. :)

Offline DezerteR

  • Użytkownik

# Kwiecień 29, 2017, 18:18:38
Pamiętam że też próbowałem eksportować kąty z blendera, ostatecznie mam kwaterniony :D Katy bywają bardzo błędogenne i źle się interpolują.

Co ci mogę poradzić i co zadziała najszybciej to wyeksportować kwaternion i odczytać z niego kąty.
Jeśli jeszcze nie używasz to polecę ci GLM, standard jeśli chodzi o algebrę.

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 18:19:55
Czy ja dobrze zaobserwowałem, że kolejność rotacji powinna być inna dla innych kątów? Jeszcze nie udało mi się ustalić jakiejś powtarzalnej zasady ale po "milionie" prób zaczynam w tym widzieć jakąś dziwną prawidłowość. Przy niektórych translacjach dobrze jest gdy kolejność jest XYZ, a przy innych jest dobrze gdy kolejność jest ZYX. Głupieję już. Od czego to zależy? Jakie warunki powinienem tu zastosować?

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 18:23:56
Cytuj
Co ci mogę poradzić i co zadziała najszybciej to wyeksportować kwaternion i odczytać z niego kąty.
A może ja bym sobie wyciągnął te kąty z kwaternionów w czasie eksportu (jeszcze w skrypcie Pythona) i je zapisał w wyeksportowanym pliku. Byłoby to możliwe?
« Ostatnia zmiana: Kwiecień 29, 2017, 18:30:06 wysłana przez jontek »

Offline DezerteR

  • Użytkownik

# Kwiecień 29, 2017, 18:33:32
To widziałeś? https://blender.stackexchange.com/questions/34975/getting-euler-rotations-for-an-object-and-exporting-them
Może masz coś namieszane w samych obiektach? Sprawdź ich rotation mode.
---
A może ja bym sobie wyciągnął te kąty z kwaternionów w czasie eksportu (jeszcze w skrypcie Pythona) i je zapisał w wyeksportowanym pliku. Byłoby to możliwe?

Próbuj, powinno się dać.

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 18:40:33
To widziałeś? https://blender.stackexchange.com/questions/34975/getting-euler-rotations-for-an-object-and-exporting-them
Dzięki za link. Już na to patrzę. Chociaż nie wiem jaka miałaby być różnica między obj.matrix_world.to_euler(), a obj.rotation_euler (którym wyciągam kąty do eksportu)

A na te kwaterniony to mnie namówiłeś. Teraz jeszcze nie mogę sobie powzwolić na takie przeróbki ale jak tylko ugaszę "pożar w stodole" to na pewno spróbuję, bo przy tych kątach można dostać świra. :)

Offline jontek

  • Użytkownik

# Kwiecień 29, 2017, 18:47:57
Obiekt w Blenderze jest obrócony:
X = 199 stopni
Y = 123 stopnie
Z = 0 stopni

W Python Console Blendera sprawdzam:

C.selected_objects[0].matrix_world.to_euler()
Euler((0.3316124677658081, 0.9948376417160034, 3.1415927410125732), 'XYZ')

C.selected_objects[0].rotation_euler
Euler((3.473205089569092, 2.1467549800872803, 0.0), 'XYZ')

Wobec tego mi chodzi raczej o ten drugi sposób, bo to są te moje kąty (w radianach), które chcę wyeksportować.

Offline voytech

  • Użytkownik

  • +1
# Kwiecień 30, 2017, 07:27:25
Najpierw sprawa z kolejnością rotacji. Próbowałem różnie ale teraz potwierdziłeś mi, że ma być ZYX. Za chwilę to pozmieniam i tego się będę trzymał. Dzięki!

Tak dla potomności, żeby zrozumieć kolejność operacji wektor-macierz w układzie prawoskrętnym:
[vx']   [a b c d]   [vx]
[vy'] = [e f g h] * [vy]
[vz']   [i j k l]   [vz]
[ 1 ]   [m n o p]   [1 ]
albo inaczej tak jak w matematyce z grubymi literkami v'=M*v

1. Przekształcamy wierzchołki macierzą Mx (obrót wokół osi X): v1=Mx*v
2. Następnie to co już żeśmy przekształcili obracamy wokół osi Y: v2=My*v1
3. Na końcu wokół osi Z: v'=Mz*v2

Teraz sobie to podstawimy:
v' = Mz*v2 = Mz * (My * v1) = Mz * (My * (Mx * v))

nawiasy można przemieścić:
v' =  (Mz * My * Mx) * v

widać z tego że macierz złożona jest z mnożeń macierzy w odwrotnej kolejności do poszczególnych operacji:
Mxyz = Mz * My * Mx
v' = Mxyz * v

Druga sprawa to układ współrzędnych Blendera i OpenGL. Po Alt+R faktycznie X jest w prawo i Y do góry. Ale kamera w Blenderze patrzy w kierunku dodatnich Z, a nie ujemnych (jak w OpenGL). Więc tu jest pewna różnica. :(

to co mówisz nie może być prawdą:



jak widać kamera jest na osi OZ na [0,0, +6], ma wyzerowaną rotację i patrzy w kierunku -Z.

Cytuj
A jeszcze mam pytanie o kamerę. Jakiej kolejności transformacji używać dla kamery? Rotacja też ZYX? Pozycję monożę przez -1.

Kamerę zawsze ustawiałem na początku, czyli glLoadIdentity, potem gluLookAt kamery a następnie rysowanie sceny (rotacje, obroty,..., glDrawElements).

Jeżeli chcesz, żeby twoja kamera patrzyła tak samo jak ta w blenderze to może spróbuj wyeksportować matrix_world kamery i na początku renderowania sceny załadować ją komendą glLoadMatrixf(). Z macierzy można odczytać jej pozycję (4ta kolumna) a także prawo(X), góra(Y) i kierunek patrzenia kamery(-Z), są one w pierwszych trzech kolumnach.

Dodawanie do pozycji kamery wektor X kamery (pierwsza kolumna macierzy) spowoduje, że kamera będzie przesuwać się w bok, coś podobnego jak w grach klawisze A/D (strafe). W/S (przód tył) to zmiana pozycji kamery o wartość wektora Z kamery, oczywiście z odpowiednio przeskalowanego.

Przy skręcaniu kamery wokół osi Y, coś jak głową lewo-prawo, trzeba obrócić wektor X i Z kamery o tę oś.

Trzeba pamiętać o tym, że te trzy wektory tworzą bazę ortonormalną, czyli mają długość 1 i są do siebie wzajemnie prostopadłe, gluLookAt zawsze tego pilnuje. Podobnie patrzenie góra-dół, czyli wokół osi X kamery obrócić wektor Y i Z. Po każdej takiej operacji warto znormalizować te wektory i sprawić żeby ponownie były prostopadłe (cross product), bo operacje błędy numeryczne mogą się skumulować i wektory rozjechać.

Wydaje mi się, że to powinno zadziałać, ale nie mam pewności i nie pamiętam jak ja to robiłem dawno temu u siebie


Obiekt w Blenderze jest obrócony:
X = 199 stopni
Y = 123 stopnie
Z = 0 stopni

W Python Console Blendera sprawdzam:

C.selected_objects[0].matrix_world.to_euler()
Euler((0.3316124677658081, 0.9948376417160034, 3.1415927410125732), 'XYZ')

C.selected_objects[0].rotation_euler
Euler((3.473205089569092, 2.1467549800872803, 0.0), 'XYZ')

Wobec tego mi chodzi raczej o ten drugi sposób, bo to są te moje kąty (w radianach), które chcę wyeksportować.

Po skonwertowaniu na stopnie pierwszy kąt to  [199,123,0] a drugi [19, 57, 180]. Zauważ, że oba zestawy dają w efekcie takie samo ustawienie obiektów. Dwa różne zestawy kątów eulera jednocześnie taka sama macierz, <devils laugh mode>bwhahaha</devils laught mode>. Jeżeli oba obiekty animujemy od rotacji [0,0,0] do wcześniej podanych, to w połowie czasu animacji jedno będzie [99.5,61.5,0] a drugie [9.5,28.5,90] a to są całkiem różne orientacje. Tak więc tu też trzeba uważać. Mam nadzieję że nie zwiększyłem tym poziom ześwirowania ;-)


Offline jontek

  • Użytkownik

# Maj 02, 2017, 03:48:23
Uuuaaaaa. Jaki wykład! Człowieku, stokrotne dzięki! Przeczytałem tak na szybko i muszę powierzieć, że pięknie to wyjaśniłeś. Na pewno się to przyda nie tylko mi. Teraz sobie na spokojnie przeczytam raz jeszcze całość żeby wszystko wyłowić.
Dziękuję za pomoc. No i przede wszystkim że Ci się chciało. :)

Offline voytech

  • Użytkownik

# Maj 03, 2017, 01:03:32
Pogrzebałem jeszcze w moich archiwach (nudzi mi się), ciężko było ale udało mi się znaleźć prostą kamerkę którą sobie zaimplementowałem chyba przeszło 10 lat temu. Oto screen najważniejszych funkcji:

http://i.imgur.com/wkCUlce.png


Obsługuje się nią klawiszami WSAD i strzałkami. Kamera porusza się po płaszczyźnie ZX, można się obracać lewo-prawo bez ograniczeń, góra-dół od -89 do 89 stopni (przy 90 w gluLookAt będzie dzielenie przez 0 i program się wysypie, poza tym nie chciałem mieć gimbal-locka).

Nie ma tutaj żadnych klas i encapsulacji, więc trochę to wygląda chaotycznie, ale można nią oblatywać swobodnie scenę. Kodu jest mało (300 linijek), żadnych skomplikowanych rzeczy, parę funkcji na iloczyn wektorowy, normalizację i to wszystko. Jedyne co może być niezrozumiałe:
Kod: (c) [Zaznacz]
        static const float rad = M_PI / 180.0f;
        camera.dir[0] =  sinf(camera.lewoprawo * rad) * cosf(camera.goradol * rad);
        camera.dir[1] = -sinf(camera.goradol   * rad);
        camera.dir[2] = -cosf(camera.lewoprawo * rad) * cosf(camera.goradol * rad);

jest to kierunek kamery [0,0,-1] pomnożony przez macierz Rx a następnie przez Ry (https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations), czyli zwykły zestaw kątów Eulera (goradol,lewoprawo,0).

Zasada działania jest taka, że kamera jest stale zorientowana w kierunku -Z, góra +Y. To dla takiej orientacji są wyprowadzone te wzory. W programie strzałki modyfikują kąty góra-dół i lewo-prawo, więc wektory kamery up, dir, right są redundantne, ale je sobie trzymam i obliczam, żeby je później użyć w gluLookAt i przy przemieszczaniu kamery. Lepiej by je było zrobić polami prywatnymi a obliczenia wsadzić w gettery klasy.

Moja kamera to w zasadzie jest tylko pozycja i dwa kąty Eulera. Proste to jak drut :)

Jedynie co bym zmienił to opakował w klasę i zamiast strzałek skorzystał z myszki. Można by też dodać klawisze Q/Z do podnoszenia się i opadania (pozycja += wektor(0,1,0)*scale).

Inna opcja to skorzystać np. z biblioteki bullet phisics i przyczepić kamerkę to jakiegoś cylindra, wtedy kolizje można wykorzystać, grawitację i inne cuda.

Ps. gdyby twoja scena była w innej niż ZX płaszczyźnie to po gluLookAt można dać glRotafe, wtedy obróci to cały świat o zadany kąt i oś obrotu.

ps2. w załączniku cały kod wraz binarką prostej sceny, kompilacja i uruchamianie:
bash:~$ g++ -o main main.cpp -lSDL -lSDLmain -lGL -lGLU -lGLEW && ./main
« Ostatnia zmiana: Maj 03, 2017, 01:06:05 wysłana przez voytech »

Offline jontek

  • Użytkownik

# Maj 06, 2017, 18:55:20
Fajna sprawa. Na pewno prześledzę implementację. I pewnie nie tylko ja. :) Dzięki!