Modele 3D, czyli żegnaj boxie.

Hej!

Muszę się z czegoś zwierzyć. Pisząc dwie poprzednie części tego tutoriala dostawałem białej gorączki. Denerwowałem się okrutnie, a męczyłem się przez dobre parę minut – nad czym? Nad wymyśleniem współrzędnych, a potem koordynatów tekstur boxa, którego sobie rysowaliśmy, a miał on ich jedynie 8 zestawów. Przeciętny model w przeciętnej grze ma ich między 150 a 15000! Również graficy zarabiają często więcej niż my, programiści. Dlatego dzisiaj poznamy nową, lepszą metodę definiowana wierzchołków naszych siatek – a mianowicie, wczytamy je z pliku.

Opowiedzmy więc trochę o tym, jakie pliki i jak będziemy wczytywać. Za czasów DirectX 2 mądrzy ludzie z Microsoftu wymyślili, iż D3DX będzie posiadał „własny” format siatek. Przez parę lat zyskał on wiele na elastyczności i zawartości, dzisiaj wspierając animację szkieletową, instancje shaderów, podział na submeshe, zestawy texcoordów, oraz wiele innych, ciekawych rzeczy. Jest to format .x, którego to będziemy używać w tej i następnych lekcjach.

Zobaczmy kod, który się pojawia:

   1: ID3DXMesh* pMesh;
   2: IDirect3DTexture9** pMeshTex = NULL;
   3: DWORD numMaterials;
   4:  
   5: void CreateMesh(char* path)
   6: {
   7:     D3DVERTEXELEMENT9 VertexDecl[] =
   8:     {
   9:         {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
  10:         {0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
  11:         D3DDECL_END()
  12:     };
  13:  
  14:     LPD3DXBUFFER materialsBuffer;
  15:     D3DXLoadMeshFromX(path, D3DXMESH_MANAGED, pDev, NULL, 
  16:         &materialsBuffer, NULL, &numMaterials, &pMesh);
  17:  
  18:     pMesh->CloneMesh(D3DXMESH_MANAGED, VertexDecl, pDev, &pMesh);
  19:  
  20:     D3DXMATERIAL* pMeshMaterials = (D3DXMATERIAL*)materialsBuffer->GetBufferPointer();
  21:     pMeshTex = new LPDIRECT3DTEXTURE9[numMaterials];
  22:  
  23:     for( DWORD i = 0; i < numMaterials; i++ )
  24:     {
  25:         if (FAILED(D3DXCreateTextureFromFile(pDev,
  26:             pMeshMaterials[i].pTextureFilename, &pMeshTex[i])))
  27:                 pMeshTex[i] = NULL;
  28:     }
  29:     materialsBuffer->Release();
  30: }

Widzimy deklaracje paru zmiennych, w tym naszej zmiennej reprezentującej siatkę – ID3DXMesh, tablicy wskaźników na tekstury, oraz ilość materiałów. Dalej, definiujemy funkcję wczytującą siatkę z pliku. Zacznijmy więc od góry:

   1: D3DVERTEXELEMENT9 VertexDecl[] =
   2: {
   3:     {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
   4:     {0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
   5:     D3DDECL_END()
   6: };

Ten dziwny twór, to tak zwana Vertex Declaration. Jest to nowocześniejszy odpowiednik FVF, o którym wiemy już co nieco. Składnia deklarowania VD jest następująca:

   1: { Strumien, Offset, Rozmiar, SposobDeklarowania, SemantykaUzycia, IndeksSemantyki },

„Strumien” to numer strumienia (Stream), którym przesyłane będą dane. Karty Shader Model 2 wspierają jedynie 1 strumień przekazywania wierzchołków (o indeksie 0), więc narazie możemy spokojnie wpisywać tam null. Drugi parametr to przesunięcie w bajtach względem początku wierzchołka, trzeci to sposób, w jaki ma zostać zadeklarowany dany parametr (typowo D3DDECLMETHOD_DEFAULT, jest to najczęściej używana flaga). „SemantykaUzycia” to poprostu odpowiednik HLSL-owej Semantyki. W przykładzie używamy Usages: Position oraz Texcoord. Vertex Declaration kończymy zaś makrem D3DDECL_END().

   1: LPD3DXBUFFER materialsBuffer;
   2: D3DXLoadMeshFromX(path, D3DXMESH_MANAGED, pDev, NULL, 
   3:     &materialsBuffer, NULL, &numMaterials, &pMesh);

To wywołanie nie jest skomplikowane. Tworzy ono obiekt o interface ID3DXMesh z pliku .x. Pierwszym parametrem jest ścieżka do pliku, drugim – flagi typu D3DXMESH. W tym przypadku używamy flagi D3DXMESH_MANAGED, która oznacza, iż mamy utworzyć naszą siatkę w pamięci zarządzanej przez urządzenie Direct3D i my nie musimy się martwić zarządzaniem takim zasobem. Kolejnym parametrem jest obiekt urządzenia, a następnie taki twór jak Adjacency Buffer – co to i do czego służy przekonamy się być może w przyszłych lekcjach, narazie zostawiamy null. Zmienna materialsBuffer, którą tworzymy w kolejnym parametrze oznacza bufor materiałów siatki – materiały to takie twory, które określają teksturę i kolor powierzchni, do której są przypisane. Kolejny parametr (NULL) to tablica plików efektów – zazwyczaj jednak jest to rzadko używany parametr. My z resztą sami zarządzamy naszymi shaderami, więc z czystym sumieniem zostawiamy ten parametr pusty.

   1: pMesh->CloneMesh(D3DXMESH_MANAGED, VertexDecl, pDev, &pMesh);

Zadeklarowaliśmy sobie piękne Vertex Declaration, całe nawet omówiliśmy, ale w żadnym wywołaniu jak dotąd nie „powiedzieliśmy” buforom wierzchołków obiektu ID3DXMesh, że mamy zamiar używać takiego-a-takiego sposobu ułożenia danych! Dlatego też musimy to zrobić. Służy temu metoda CloneMesh interfejsu ID3DXMesh. Tworzy ona nową siatkę, na bazie starej oraz podanego VD. Niestety, jeśli stworzymy nowe pola (takie, których jeszcze nie ma w starej siatce, a które chcielibyśmy mieć wypełnione), musimy wypełnić je sami. Jeśli zaś pola sobie odpowiadają, sprawa jest prosta i CloneMesh ładnie sobie z tym radzi.

   1: D3DXMATERIAL* pMeshMaterials = (D3DXMATERIAL*)materialsBuffer->GetBufferPointer();
   2: pMeshTex = new LPDIRECT3DTEXTURE9[numMaterials];
   3:  
   4: for( DWORD i = 0; i < numMaterials; i++ )
   5: {
   6:     if (FAILED(D3DXCreateTextureFromFile(pDev,
   7:         pMeshMaterials[i].pTextureFilename, &pMeshTex[i])))
   8:             pMeshTex[i] = NULL;
   9: }
  10: materialsBuffer->Release();

Dalej sprawa jest trywialna. Tworzymy tablicę materiałów, a następnie tablicę tekstur. Potem wypełniamy tablicę tekstur, w pętli. Tekstury wczytujemy znaną nam funkcją D3DXCreateTextureFromFile, nazwę tekstury pobieramy z materiału o odpowiednim indeksie. Na koniec „sprzątamy” po sobie.

Ostatnim krokiem jest wyrenderowanie naszego modelu 3D:

   1: D3DXMATRIX matRot, matTrans, matWorld;
   2: D3DXMatrixTranslation(&matTrans, 0,0,5);
   3: D3DXMatrixRotationAxis(&matRot, &D3DXVECTOR3(1,1,0), angle);
   4:  
   5: matWorld = matRot * matTrans;
   6:  
   7: D3DXMATRIX mat_worldViewProj = matWorld * MatView * MatProj; 
   8: pEffect->SetMatrix("worldViewProj", &mat_worldViewProj);
   9:  
  10: UINT passes;
  11: D3DXHANDLE tech;
  12: pEffect->FindNextValidTechnique(0, &tech);
  13: pEffect->SetTechnique(tech);
  14: pEffect->Begin(&passes,0);
  15: for (UINT pass = 0; pass < passes; pass++)
  16: {
  17:     pEffect->BeginPass(pass);
  18:  
  19:     for (int i = 0; i < numMaterials; i++)
  20:     {
  21:         pEffect->SetTexture("tex0", pMeshTex[i]);
  22:         pEffect->CommitChanges();
  23:         pMesh->DrawSubset(i);
  24:     }
  25:  
  26:     pEffect->EndPass();
  27: }
  28: pEffect->End();

Na początek obracamy nasz model wzdłuż osi (1,1,0), dzięki funkcji D3DXMatrixRotationAxis, następnie ustawiamy wszystko tak jak w poprzednich przykładach. Nowością natomiast jest sposób renderowania:

   1: for (int i = 0; i < numMaterials; i++)
   2: {
   3:     pEffect->SetTexture("tex0", pMeshTex[i]);
   4:     pEffect->CommitChanges();
   5:     pMesh->DrawSubset(i);
   6: }

Na początku iterujemy się po wszystkich materiałach. Ustawiamy teksturę, wywołaniem SetTexture. Nowością natomiast jest metoda CommitChanges ID3DXEffect. Służy ona do wprowadzania w życie stanów zmienionych między wywołaniami pEffect->Begin() a pEffect->End. Metoda pMesh->DrawSubset(.) zaś odpowiada za narysowanie konkretnego subseta (submesha) siatki, Subset to taki fragment modelu, do którego przypisany jest jeden, konkretny materiał (tekstura), przy czym indeks materiału odpowiada odpowiedniemu indeksowi subsetu.

 

Dzisiejsza lekcja (wraz z kodami źródłowymi) była dosyć prosta i szybka, a jednak ważna – w końcu nasz przykładowy samolocik z Direct3D SDK wygląda znacznie lepiej niż prosty box!

image

Reklamy

Komentarzy 8 to “Modele 3D, czyli żegnaj boxie.”

  1. shader.fx – nie chce się załadować… :( błąd wywala

  2. wywala sie na linii z pMesh->CloneMesh(D3DXMESH_MANAGED, VertexDecl, pDev, &pMesh); :/ jest szansa na wrzucenie poprawionej wersji?

    • właśnie zauważyłem, ze plik 1.exe odpalony przed kompilacja (w stanie jaki jest do ściągnięcia) działa poprawnie, ale po przekompilowaniu wyskakuje i debuger wskazuje na ww linie. co może być przyczyną takiego zachowania?

  3. Visual Studio ustawia jako working directory programu folder z projektem, a ścieżka do pliku .x ustawiona jest relatywnie do .exe. Można albo zmienić ścieżkę, albo wejść w opcje projektu, zakładka Debugger i w pole Working Dir wpisać $(OutDir). :)

  4. Dziękuje za odpowiedź. Postąpiłem według instrukcji i wszystko działa idealnie :) Ustawiając pole Working Dir na $(OutDir) ustawiłem, że ścieżki będą taktowane podając pozycje w stosunku do położenia pliku .exe, a domyślenie położenie jest liczone od pliku projektu? Czy dobrze rozumiem?

    PS. Bardzo dobry tutorial, najlepszy jaki znalazłem w języku polskim.

  5. Tak, właśnie o to chodzi :)

    Ciesze się, że kurs się podoba :)

  6. Witaj Charibo,
    Potrzebuję pilnej pomocy przy swoim projekcie z DX, utknąłem w momencie, gdzie muszę wczytać 27 modeli sześcianu, który jest w pliku .x. Projekt powinien być oddany w ubiegły piątek ale dalej leży w powijakach :(

    Mam już gotowy model i szkice klas. Proszę odezwij się do mnie mailowo, przesłałbym Ci to co mam zrobione z prośbą o ocenę i wskazówki. Nie mam zbyt wiele kasy, ale postaram się Ci zapłacić godne pieniążki. Błagam Cię bardzo o pomoc. Jesteś mogą ostatnią nadzieją.

  7. Widzę, że trzeba czytać komentarze, ja szukam już odp. od kilku dni :) a wszystko jest pod kursem :)

    Kurs jest b. dobry :)

Skomentuj

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

Logo WordPress.com

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

Zdjęcie z Twittera

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

Zdjęcie na Facebooku

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

Zdjęcie na Google+

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

Connecting to %s

 
%d blogerów lubi to: