Wstęp do grafiki 2D/3D  Udostępnij na: Facebook

Autor: Dawid Pośliński

Opublikowano: 2011-02-16

Programowanie aplikacji graficznych wymaga poznania podstawowych elementów związanych z reprezentacją graficzną 2D oraz 3D i tego jak są realizowane przez API XNA. 

Rysowanie w przestrzeni 3D

Punkt

Rysowanie w przestrzeni, a właściwie zrozumienie istoty umieszczania obiektów w przestrzeni, jest kluczowe przy tworzeniu aplikacji 3D. Pozycja punktu w przestrzeni opisywana jest przez trzy podstawowe składowe – x, y, z. Pierwszy i ostatni parametr określają położenie punktu na płaszczyźnie, natomiast drugi parametr określa wysokość, na jakiej ma znaleźć się punkt.

Tworząc osie współrzędnych x, y, z, sprawiamy, że cała sytuacja staje się bardziej klarowna:

Rys. 1. Punkt w przestrzeni trójwymiarowej

 

Wektor

XNA oferuje gotową klasę Vector3, pozwalającą w bardzo prosty sposób tworzyć wektory, które mogą być wykorzystywane do różnego rodzaju transformacji obiektów w przestrzeni. Jako punkt zaczepienia przyjmuje się początek układu współrzędnych, zatem pomija się podawanie punktu początkowego  (zaczepienia) wektora. Podaje się jedynie współrzędne x, y, z punktu, na który zwrócony jest wektor. Opisaną sytuację przedstawia rysunek poniżej:

Vertex

Wszystkie obiekty w przestrzeni 3d tak naprawdę składają się z, przekształconych w różny sposób, obiektów dwuwymiarowych. Elementarną figurą, która jest tutaj wykorzystywana, będzie trójkąt. Trójkąt został wybrany ze względu na możliwość „poskładania” z niego dowolnej bryły przestrzennej, a dokładność, z jaką zostanie ona odwzorowana, zależy od ilości trójkątów wykorzystanych przy jej tworzeniu. Trójkąt składa się z trzech punktów. W przypadku Verteksów tworzy się specjalne struktury, które oprócz współrzędnych punktów przyjmują również jako parametr informację o kolorze punktu bądź o teksturach, a także o tak zwanych wektorach normalnych.

Rys. 2. Vertex w przestrzeni trójwymiarowej

Wektor normalny

Wektory normalne to wektory jednostkowe (czyli współrzędne przyjmują wartości 0 lub 1 ), które są niezbędne, aby DirectX poprawnie mógł obliczyć natężenie światła padającego na vertex. Wektor normalny musi być prostopadły do verteksa, do którego jest przypisany, co jest niezbędne, aby tzw. dotProduct w jednostkach cieniowania karty graficznej został poprawnie obliczony (na bazie tego wektora jest obliczony kąt padania światła na vertex, od którego zależy natężenie światła padającego na dany vertex).

Całą sytuację obrazuje następujący rysunek:

Rys. 3. Wektor normalny verteksa

 

Macierz

Macierze w XNA należy rozumieć jako zbiór punktów w przestrzeni trójwymiarowej. Kolejne wiersze oznaczają kolejne współrzędne (x, y, z), natomiast kolumny oznaczają kolejne punkty.

Typowa macierz wygląda zatem następująco:

Ponieważ obiekt w przestrzeni (np. Model 3d lub inny dowolny zbiór verteksów) jest opisywany przez wiele punktów, więc aby dokonać specyficznych typów transformacji, np. obrotu, przy zachowaniu stałych odległości między punktami należącymi do jednego obiektu, niezbędne są macierze. Oczywiście nic nie stoi na przeszkodzie, aby stworzyć macierz o dowolnej strukturze, dowolnej ilości kolumn czy też wierszy, co tak naprawdę sprowadzałoby się do stworzenia tablicy dwuwymiarowej, jednak w naszym przypadku omawiane są macierze w kontekście rysowania w przestrzeni w XNA i na nich skupi się ten opis.

Teraz należy wspomnieć o trzech podstawowych macierzach, których określenie jest niezbędne w XNA. Są to: worldMatrix (Macierz świata), viewMatrix (Macierz widoku) oraz projectionMatrix (Macierz projekcji).

Macierz świata (worldMatrix)

                Wewnątrz tej macierzy zawarte są informacje dotyczące położenie wszystkich punktów dla danego obiektu bądź też zbioru obiektów.

Macierz widoku (viewMatrix)

                Macierz ta określa kierunek, w jakim zwrócony jest obserwator (punkt w przestrzeni, na który spogląda). Macierz ta jest niezbędna, aby karta graficzna „wiedziała”, który fragment sceny i w jaki sposób ma renderować (rysować).

Macierz projekcji (projectionMatrix)

                Macierz ta określa obszar, który ma zostać zaprezentowany. Można to porównać do sytuacji, gdzie oczy są bardziej lub mniej zmrużone. Im bardziej otwarte powieki, tym większe pole widzenia, im bardziej powieki są przymknięte, tym mniejsze pole jest widoczne. Może ta analogia nie jest do końca zrozumiała, ale rysunek poniżej powinien wyjaśnić całą koncepcję.

W powyższym przykładzie na scenie znajdują się dwa obiekty, o czym świadczą czerwone punkty na ich krańcach, fioletowy vertex oraz szara płaszczyzna, która została narysowana dla łatwiejszego zrozumienia problemu. Macierz widoku wskazuje na vertex. W przyszłości, jeżeli w metodzie Update() każdorazowo macierz widoku będzie aktualizowana i będzie wskazywać na obiekt, który się porusza, zaobserwować będzie można ruch kamery (zawsze wskazywany punkt – znajduje się w środku kadru).

Sprawdzenie swojej wiedzy

  1. Z ilu punktów składa się vertex?
  2. Pod jakim kątem do verteksa musi znajdować się wektor normalny?
  3. W jakich strukturach matematycznych przechowywane są dane o grupie punktów?
  4. Jakie dane są przechowywane w macierzy świata?

Transformacje w przestrzeni

Temat transformacji w przestrzeni nawiązuje bezpośrednio do teorii przedstawionej w rozdziale „Rysowanie w przestrzeni”.

W przypadku transformacji wszystkie obliczenia odbywają się na macierzach.

Przesunięcie

Przesunięcie stosuje się bardzo często, a sama koncepcja jest bardzo prosta do zrozumienia, co przedstawia rysunek poniżej:

Rys. 4. Przesunięcie punktu w przestrzeni

 

Na przykład zakładamy, że pozycja początkowa obiektu wynosi:

Vector3Position = new Vector3(2, 0, 2);

Aby zmienić pozycję obiektu o jedną jednostkę w lewo i jedną jednostkę w dół (x, z) w przestrzeni, wystarczy dodać następujący wektor:

Position += new Vector3(-1, 0, -1);

Pozycja końcowa przesuniętego punktu wyniesie:

Position = new Vector3(1, 0, 1);

Jak widać, zmiana pozycji sprowadza się jedynie do dodania do aktualnej pozycji (dowolnej, niekoniecznie 0,0,0) wektora, o który ma zostać przesunięty obiekt.

Ponieważ przy rysowaniu obiektu potrzebne będzie stworzenie macierzy świata danego obiektu, trzeba do tego zastosować następujący zapis:

Matrix wMatrix = Matrix.CreateTranslation(Position);

… dzięki czemu w macierzy wMatrix znajdą się wszystkie informacje o położeniu punktów danego obiektu.

Skalowanie

Użyjemy teraz macierzy w celu wykonania transformacji skali obiektu. Co należałoby zrobić, aby przeskalować obiekt? Wystarczyłoby przemnożyć macierz, która reprezentuje położenie punktów tego obiektu, przez określoną liczbę.

Sama koncepcja może być zobrazowana następująco:

Rys. 5. Skalowanie macierzy

Co ciekawe, wszystkie operacje na macierzach realizowane są przez sam Framework, zatem nie trzeba pisać złożonego kodu, który odpowiednio przemnoży wszystkie punkty. Do takich operacji służy metoda CreateScale klasy Matrix, która jako parametry przyjmuje omawiany wcześniej vector3 (x ,y, z).

Następujący przykład:

MatrixScale = Matrix.CreateScale(1, 0.5f, 1);

stworzy w zmiennej Scale macierz, przez którą pomnożenie spowoduje, iż obiekt zachowa swoje rozmiary dotyczące szerokości i długości, jednak zostanie „spłaszczony” o połowę, na co wskazuje drugi parametr. Innymi słowy, wszystkie punkty w macierzy obiektu zostaną przemnożone przez:

  • Dla parametru x – przez 1,
  • Dla parametru y – przez 0,5,
  • Dla parametru z – przez 1.

Matematycznie całe skalowanie przedstawia się następująco (dla vertexa), macierz skalowania:

Sama transformacja wykona się według następującego schematu:

Nic nie stoi na przeszkodzie, aby samemu napisać metodę, która to zrobi, ale ten przykład znakomicie pokazuje, jak wiele pracy zostaje zaoszczędzone przez XNA, dzięki zastosowaniu w nim większości niezbędnych metod wykorzystywanych przy tworzeniu gier.

Obracanie

W przypadku obracania wzajemne odległości między punktami muszą zostać zachowane. Zobrazować tę transformację można następująco:

Rys. 6. Obracanie grupy punktów w przestrzeni

Cała koncepcja matematyczna opisująca transformację w przestrzeni sprowadza się do przemnożenia macierzy danej (w tym przypadku będzie to punkt) przez macierz rotacji, w której umieszczone zostaną odpowiednie wartości funkcji trygonometrycznych dla kątów, o jaki użytkownik chce obrócić punkt.

Dla przykładu, obrót o 45˚ wzdłuż osi z, punktu (3, 2, 1) można matematycznie przedstawić w następujący sposób:

Metody służące do przekształceń obrotu na macierzach, które oferuje nam XNA:

Matrix.CreateRotation()

Matrix.CreateRotationX()

Matrix.CreateRotationY()

Matrix.CreateRotationZ()

Matrix.CreateFromQuaternion()

Praktyczne ich użycie sprowadza się do podania odpowiedniego parametru (kąta w radianach bądź odpowiedniej macierzy) i przemnożenie aktualnej pozycji przez macierz zwróconą przez jedną z powyższych metod.

Bibliografia

  1. http://en.wikipedia.org/wiki/Microsoft_XNA
  2. https://creators.xna.com
  3. http://ilovevb.net/Web/blogs/vbxna/default.aspx
  4. http://forums.xna.com/forums/t/1464.aspx
  5. https://msdn.microsoft.com/en-us/vstudio/default.aspx
  6. http://pl.wikipedia.org/wiki/Wektor
  7. http://www.riemers.net/eng/ExtraReading/matrices_geometrical.php
  8. Learning XNA 3.0, Aaron Reed; O’Reilly; 2009; ISBN: 978-0-596-52195-0