Macierze, czyli kręcimy, przesuwamy i skalujemy.

Hej! Kompletny brak talentu pisarskiego, a więc i kwiecistych wstępów, zaczyna mi doskwierać. Jednak na razie tego nie zmienię, więc zacznijmy lekcję.

Macierz jaka jest – każdy widzi.  Niekażdy jednak zdaje sobię sprawę, jak wiele ów twór nam pomoże w naszej podróży po świecie grafiki 3D. Mówiąc prosto, macierz to tablica (najczęściej 4×4) liczb. Za jej pomocą możemy przechowywać przekształcenia (transformacje). Aby zrozumieć ideę, musimy popatrzeć na sposób w jaki z wierzchołka dostarczonego przez aplikację powstaje gotowy obraz. Musimy więc wrócić się do czegoś, co omawialiśmy w pierwszej lekcji tego tutoriala, a więc do potoku (pipeline). Obiecywałem wtedy, iż zajmiemy się nim dokładniej – oto przyszła na to pora. Nie będziemy jednak narazie rozszerzać potoku na dalsze etapy, a wgryziemy się w sposób reprezentacji wierzchołka i jego związek z macierzami.

Ustwiając w naszej aplikacji, w poprzednim przykładzie, pozycje naszych wierzchołków, używaliśmy układu współrzędnych (przestrzeni) ekranu.

Układ ekranu jest układem, jak widzimy, dwuwymiarowym. Początek jego jest w lewym górnym rogu „monitora”, oś y rośnie „w dół”, oś x – w prawo. O ile układ ten jest dosyć wygodny do reprezentowania wierzchołków w przestrzeni 2D, to nie uda nam się stworzyć żadnego trójwymiarowego kształu w tymże! Dlatego właśnie zazwyczaj vertexy przedstawia się w przestrzeni trójwymiarowej.

W takim systemie możemy już spokojnie podawać nasze współrzędne wierzchołków, jest to intuicyjne i proste, ale coś nie gra – skoro my podajemy koordynaty w układzie 3D, a ekran jest układem 2D, to gdzieś musi zajść konwersja. No i tutaj do akcji wkraczają macierze; pozwalają nam one na dowolne „przemieszczanie się” po różnych układach współrzędnych dzięki mnożeniu odpowiednio skonstruowanej macierzy z pozycją (wektorem czterowymiarowym) w układzie A, dzięki czemu otrzymamy pozycję w układzie B. Niestety, aby nie było zbyt prosto, pewien (zapewne) mądry człowiek dawno dawno temu wymyślił, iż należy stworzyć podział, na tak zwane przestrzenie modelu, świata, widoku i przycięcia.

 

Stworzone zostały one po to, by uporządkować sposób wykonywania przekształceń. Wierzchołki każdego obiektu podajemy w jego własnym, lokalnym układzie współrzędnych (przestrzeń modelu). Aby ukazać położenie owego modelu w świecie, przekształcamy go właśnie do przestrzeni świata. Przekształcenie widoku (a.k.a  kamery) przekształca świat do pola widzenia ustawionej przez nas kamery. Ostatnim krokiem jest przekształcenie rzutowania.

Jak pisałem wcześniej, jeśli zaczeliśmy działać w przestrzeni 3D, a monitor jest płaski, będziemy musieli kiedyś coś z tym zrobić. Właśnie tutaj wkracza przekształcenie rzutowania do akcji – przekształca ono obraz z przestrzeni widoku do przestrzeni przycięcia (clip space) – dwuwymiarowej.

Zdaję sobie sprawę, że nie brzmi to dobrze – tym niemniej, zrozumienie idei jest bardzo ważne. Prawdopodobnie pomogą nam w tym następne lekcje oraz kod, który pora obejrzeć.

   1: struct OurVertex

   2: {

   3:     float x, y, z; // pozycja

   4:     D3DCOLOR color; // kolor

   5: };

   6:  

   7: const DWORD OURVERT_FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;

   8:  

   9: // ............. 

  10:  

  11: D3DXMATRIX MatWorld;

  12: D3DXMATRIX MatView;

  13: D3DXMATRIX MatProj;

 

Widzimy, że zmieniła się nasza struktura wierzchołka – nie mamy już rhw, który to teraz nie jest nam zupełnie potrzebny. Również FVF się zmienił. Deklarujemy sobie także trzy zmienne reprezentujące nasze macierze – świata, widoku i projekcji. Dodajemy także następującą linijkę:

   1: pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

Nasze obiekty składają się z trójkątów (no dobra, na razie jednego :P ). Jeśli więc mamy, dajmy, boxa na ekranie, składającego się z sześciu ścian, to w danym momencie mogą być widoczne z naszej perspektywy jedynie trzy. Jednak dla całej reszty trójkątów, nadal wszystkie wierzchołki będą musiały przejść cały pipeline! Aby zapobiec tej sytuacji, wymyślono coś takiego jak backface culling. Skoro nasz trójkąt składa się z trzech wierzchołków, to kiedyś ktoś mądry wymyślił, iż wierzchołki można podawać w odpowiedniej kolejności.

I tak oto, wierzchołki w Direct3D przyjęło się podawać w kolejności zgodnej z ruchem wskazówek zegara (clockwise). Teraz, jeśli taki trójkąt obrócimy wokół dowolnej osi, to kolejność jego wierzchołków w stosunku do ekranu zmieni się – i pyk: wiemy który trójkąt jest z tył. Dzięki temu pozbywamy się dużej części niepotrzebnych trójkątów. Stała D3DCULL_NONE mówi zaś D3D, aby wyłączyć culling zupełnie. Dzięki temu nasz trójkąt widać nadal, nawet kiedy obróci się tyłem. Dociekliwi mogą spróbować zmienić tę wartość na jedną z pozostałych dwóch:

  • D3DCULL_CCW – odrzucane są trójkąty, których wierzchołki są ponumerowane odwrotnie do kierunku ruchu wskazówek zegara (counter clockwise)
  • D3DCULL_CW – odrzucamy trójkąty, których vertexy ponumerowane są zgodnie z tą kolejnością.

 

   1: D3DXMatrixPerspectiveFovLH(&MatProj, D3DXToRadian(90), 4.0f/3.0f, 1, 100);

   2: D3DXMatrixLookAtLH(&MatView, 

   3:     &D3DXVECTOR3(0,0,-3),

   4:     &D3DXVECTOR3(0,0,0),

   5:     &D3DXVECTOR3(0,1,0));

 

Ważnym krokiem jest utworzenie macierzy – na razie projekcji i widoku. Na szczęście biblioteka D3DX udostępnia nam funkcje robiące to za nas. Funkcja tworząca macierz MatProj przyjmuje jako parametry tzw. Field of View (FOV) – pole widzenia, Aspect Ratio ekranu (czyli szerokość podzielona przez wysokość), przednią oraz tylną płaszczyznę przycięcia – nic co jest dalej od Far Clipping Plane nie zostanie narysowane, podobnie jak nic, co jest bliżej Near Clipping Plane.

 

Następna funkcja tworzy nam macierz widoku – kamery. Przyjmuje ona jako parametry trzy wektory, pozwalające określić orientację danego przedmiotu w przestrzeni trójwymiarowej.

Wektory te wskazują kolejno pozycję kamery w przestrzeni świata, „w górę” od jej położenia (czyli jesli wektor ten będzie wskazywał w dół, powstanie efekt wiszenia do góry nogami) oraz wektor wskazujący w którą stronę patrzymy – view.

   1: // gdzieś w środku pętli programu

   2: static float angle = 0.0f;

   3: D3DXMatrixRotationY(&MatWorld, angle);

   4: angle += 0.01f;

Macierz świata tworzymy zaś przez utworzenie macierzy obrotu. Tutaj także wspiera nas D3DX, dając nam parę funkcji do tworzenia różnych przekształceń. Mamy więc funkcje do przesuwania świata (D3DXMatrixTranslation), obracania, czy skalowania (D3DXMatrixScaling).

   1: D3DXMATRIX mat_worldViewProj = MatWorld * MatView * MatProj; 

   2: pEffect->SetMatrix("worldViewProj", &mat_worldViewProj);

W tym snippecie widzimy dwie operacje; proste przekazanie macierzy shaderowi oraz tę ważniejszą – składanie przekształceń. Czynimy to przez proste mnożenie. Jednak nie jest tak zielono jak by się mogło wydawać – jako, że mnożenie ich nie jest przemienne, musimy pamiętać o prawidłowej kolejności przekształceń – a więc World View Proj. Również kolejność składania przekształceń zazwyczaj używanych do świata jest ważna – najczęściej stosuje się kolejność SRT Scale, Rotate, Translate.

   1: float4x4 worldViewProj;

   2:  

   3: void vsmain(float3 pos : POSITION,

   4:             float4 color : COLOR,

   5:             out float4 oPos : POSITION,

   6:             out float4 oColor : COLOR)

   7: {

   8:     oPos = mul(float4(pos,1), worldViewProj);

   9:     oColor = color;

  10: }

Obejrzmy kod naszego shadera. Jak widać, nie zmienił się on dużo – doszła zmienna reprezentująca naszą macierz, oraz przemnożenie pozycji (dodajemy czwarty komponent – w – o wartości 1) przez macierz reprezentującą nasze poskładane przekształcenia.

Nareszcie dobrnęliśmy do końca tej lekcji. Zdaję sobię jednak sprawę, że część rzeczy nadal pozostała niewyjaśniona. Stało się tak, ponieważ matematyka stojąca za macierzami nie jest prosta – tym niemniej, myślę, że nauczyliśmy się używać tego niezbędnego narzędzia wystarczająco dobrze jak na początek. Dobrze byłoby również przeanalizować kod źródłowy

image


komentarzy 7 to “Macierze, czyli kręcimy, przesuwamy i skalujemy.”

  1. D3DXMatrixPerspectiveFovLH(&MatProj, D3DXToRadian(90), 4/3, 1, 100);
    Chyba powinno być 4.0f/3.0f co? ;)

    // EDIT By Charibo:
    Dzięki, z rozpędu napisałem jak napisałem. Poprawione. :)

  2. Interesujące

  3. Chciałbym zauważyć że w kodach źródłowych błąd nadal istnieje.

  4. Ano :)

  5. Wyskakuje mi Access Violation w Ln 115 kodu źródłowego. Używam VS2003. Sam na nic nie wpadłem.

    • Dobra, jestem kretynem. Od razu po napisaniu komentarza zobaczyłem, że zła ścieżka do shadera. Sorry za bałagan.

  6. Gdzie jest powiązanie w kodzie pDev lub wierzchołków z macierzami lub obrotem i jak mam zrobić jesli na przykład mam 2 trójkąty i jeden chce żeby obracał się w jedną stronę a drugi w drugą

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

 
%d bloggers like this: