Kolejność przekształceń macierzowych

2008-01-28 20:38

Kiedy uczyłem się biblioteki DirectX, miałem dość spore kłopoty z kwestią właściwej kolejności przekształceń opisanych przez macierze. Jak wiadomo, w grafice każdą transformację możemy opisać macierzą, a złożenie takich przekształceń możemy być reprezentowane przez odpowiedni iloczyn macierzy. Wówczas pomnożenie wektora (odpowiednio rozszerzonego o czwartą współrzędną) przez taką macierz skutkuje zastosowaniem do niego tych wszystkich przekształceń. Może być ich bardzo wiele, lecz wymagana jest tylko jedna macierz i jedno mnożenie przezeń wektora. Jest to więc efektywne, jeśli mamy dużą ilość geometrii do przetworzenia – czyli, co tu ukrywać, w zasadzie zawsze :)

Rzeczone macierze opisujące przekształcenia są kwadratowe; w przypadku grafiki 3D mają rozmiar 4×4. Dlatego też możliwe jest ich mnożenie w dowolnej kolejności. Wiemy jednak, że operacja mnożenia macierzy nie jest przemienna. Odpowiada to zresztą temu, iż przy przekształcaniu punktów w przestrzeni też liczy się kolejność: obrót, a potem przesunięcie to nie to samo, co przesunięcie, a potem obrót.
I tu się zaczyna problem, bowiem w bardzo wielu źródłach wprowadzone jest niezłe zamieszanie, jeśli chodzi o kolejność mnożenia macierzy opisujących geometryczne przekształcenia. Najczęściej pomieszane są konwencje tego, jaki porządek jest poprawny w danej bibliotece graficznej, a jaki “w matematyce”. Ostatecznie więc nie wiadomo, czy trzeba iloczyn macierzy zapisywać w kolejności, w jakiej chcemy aplikować przekształcenia, które reprezentują – czy może na odwrót. Dość prosto można oczywiście sprawdzić, jak to jest akurat w naszej bibliotece graficznej, lecz to nie mówi nic o istocie problemu…

Wektor kolumnowyWłaściwie to dopiero niedawno dowiedziałem się, gdzie jest tu pies pogrzebany. Otóż matematycy z pewnych przyczyn lubią traktować wektory jako kolumnowe, tj. jako macierze Nx1 (N wierszy, 1 kolumna). Przy takiej interpretacji tylko iloczyn w postaci:

macierz1 * wektor_kolumnowy

daje w wyniku wektor (także kolumnowy, rzecz jasna). W tym przypadku będzie on przekształcony przez macierz1. Jeżeli teraz zechcemy dodać drugie przekształcenie, to mnożenie przez odpowiednią macierz również musimy zapisać z przodu:

macierz2 * (macierz1 * wektor_kolumnowy)

Ale mnożenie jest oczywiście łączne, więc:

(macierz2 * macierz1) * wektor_kolumnowy = macierz * wektor_kolumnowy

a wynikowa macierz = macierz2 * macierz1 opisuje złożenie naszych przekształceń. Jak widać wyżej, najpierw jest stosowane to opisane przez macierz1, a dopiero potem to z macierzy2 – mimo że są one mnożone w porządku odwrotnym. Tak bowiem wygląda sprawa kolejności przekształceń dla wektorów kolumnowych.

Twórcy DirectX uznali prawdopodobnie, że jest to nieintuicyjne dla nie-matematyków i dokonali pewnego “triku”. Opiera się on na tym, że gdy w dwóch macierzach zamienimy ze sobą wiersze i kolumny – czyli dokonamy transpozycji – pomnożymy je przez siebie, a następnie transponujemy wynik, to rezultat będzie taki, jakbyśmy mnożyli wyjściowe macierze w odwrotnej kolejności. Wyjątkowo trzeba tutaj przyznać, że wzór mówi więcej niż jego opis, więc spójrzmy na ten wzór :)

(A * B)T = BT * AT

W DirectX dokonano więc transpozycji wszystkich macierzy opisujących przekształcenia. Przykładowo, funkcja D3DXMatrixTranslation zwraca macierz z wartościami przesunięć wpisanych w ostatnim wierszu, podczas gdy w wersji “matematycznej” powinny być one w ostatniej kolumnie. Podobnie jest ze wszystkimi innymi macierzami… ale także z wektorami!
Wektor wierszowyChociaż wektory z programistycznego punktu widzenia to cztery składowe i nic więcej, to w DirectX należy je traktować jako wektory wierszowe, czyli macierze 1xN. Dla nich zaś sensownym sposobem mnożenia przez macierz jest tylko następujący:

wektor_wierszowy * macierz1

Dodając kolejne przekształcenie, mamy:

(wektor_wierszowy * macierz1) * macierz2

i znów opierając się na łączności mnożenia otrzymujemy ostatecznie:

wektor_wierszowy * (macierz1 * macierz2) = wektor_wierszowy * macierz

Tutaj z kolei widać wyraźnie, że przekształcenia są stosowane w takiej samej kolejności, w jakiej odpowiadające im macierze występują w iloczynie.

Ponieważ, jak wspomniałem wyżej, cała sprawa jest kwestią czysto arbitralną (wystarczy transpozycja, aby odwrócić porządek), powinniśmy tym bardziej zwrócić na nią uwagę. A jeśli programujemy w DirectX, nie należy dopuścić do tego, by matematycy wmawiali nam ‘właściwą’ kolejność :P

Tags: , ,
Author: Xion, posted under Programming »


One comment for post “Kolejność przekształceń macierzowych”.
Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.