background image

1 

DirectX ▪ X – Files 

Witam, witam. 
Po wielu namowach, prośbach i groźbach w końcu ustąpię i zrobię Wam tutorial o ładowaniu modeli na naszą scenę. Długo 
się z tym nosiłem, chciałem Wam dać jakąś moją wydumaną bibliotekę, ale w końcu stwierdziłem, że jest beznadziejna i 
zrobimy tak, jak należy - posłużymy się dzisiaj znów naszym wspaniałym pakietem, czyli rzecz będzie o plikach *.x (X 
Files). Nie będą one miały jednak nic wspólnego z agentami FBI uganiającymi się za zielonymi ludkami. W końcu będziemy 
mogli się wyrwać ze świata sześcianów i prostokątów w świat trochę bardziej realnych brył trójwymiarowych. 
 
Większość z Was ma zapewne dosyć mojego przykładowego sześcianu, na którym prezentujemy nasze kolejne sztuczki. Jest 
on wprawdzie dosyć wdzięcznym modelem do nauki, no ale bez przesady, ileż można. Mamy tyle wspaniałych programów 
do modelowania scen 3D, że o ilości darmowych modeli nie wspomnę. Aż prosi się o ich wykorzystanie, no a my do tej pory 
nie wiemy jak. Bardziej ambitni na pewno sobie już poradzili. Mając do dyspozycji biblioteki, które wczytują modele z 
plików programów 3D (na sieci błąka się ich całe mnóstwo) ładowali sobie ich dane na przykład do bufora wierzchołków i w 
jakiś tam sposób radzili sobie. Direct3D nie byłby jednak pełną biblioteką gdyby nie umożliwiał ładowania i osadzania na 
scenie modeli wczytywanych z plików. Żeby jednak być trochę oryginalnymi, panowie z Microsoftu wymyślili sobie nie 
wiadomo po co własny format plików z modelami. Nazwali jest właśnie plikami *.x (może byli fanami naszego serialu?). W 
każdym razie my dostaliśmy do ręki format pliku i odpowiednie narzędzia, które posłużą nam do tego szczytnego celu, jakim 
będzie załadowanie modeli na scenę. Powiedzmy sobie zatem kilka słów o plikach *.x. 
 
Pliki *.x zostały wprowadzone w wersji DirectX 2.0 i były one wtedy w formie tekstowej (o tym za chwilę). W wersji 3.0 
dostępne już były pliki w postaci binarnej, ale dopiero w wersji 6.0 dano użytkownikom pakietu do ręki narzędzia (interfejsy 
i metody), które pozwalają odczytywać i co ciekawe również zapisywać do plików w formacie *.x. Jak twierdzą twórcy 
pakietu DirectX, format plików *.x jest formatem bardzo elastycznym, może przechowywać modele (jako siatki), tekstury, 
animacje i obiekty zdefiniowane przez użytkownika. Jak widać, format faktycznie bardzo uniwersalny. Weźmy dla przykładu 
animację. Możliwość przechowania całej animacji daje nam do ręki różnorakie możliwości. Możemy sobie na przykład w 
naszym programie do modelowania i animacji stworzyć jakąś piękną scenę, potem załadować sobie to do DirectX-a i voila! 
Będziemy sobie odtwarzać to w czasie rzeczywistym z możliwością naszej ingerencji. Wyobrażacie sobie na przykład grę z 
grafiką obecną na przykład w filmie "Final Fantasy"? No ale wróćmy może na ziemię ;-), bo nie stać nas jeszcze na 
komputery, które potrafią takie coś policzyć "na żywca", choć podobno są czynione próby :-). Swoją drogą każdy fan grafiki 
komputerowej powinien obejrzeć ten film, bo kto wie, czy za parę lat nie skończy jako producent takich właśnie filmów. Cóż 
jeszcze wynika z uniwersalności plików *.x. Na przykład możemy sobie tworzyć hierarchię obiektów (kto pracował z takimi 
programami jak 3D Studio Max, LightWave itp.), ten wie o czym mówię. Można raz zapisany obiekt wykorzystywać potem 
w pliku wiele razy (odwołując się do niego). 
 
Ten rysunek, jak zwykle skopiowany bezczelnie z DirectX SDK, pokazuje hierarchię interfejsów dotyczących plików *.x i 
ich wzajemne zależności. O niektórych z nich będzie okazja powiedzieć bliżej w naszym tutorialu, o niektórych może 
powiemy sobie trochę później. Napisałem wcześniej o jakiś formatach - tekstowym i binarnym. Otóż pliki *.x mogą właśnie 
istnieć w takich dwóch postaciach. W tekstowym pliku będziemy mieć mnóstwo różnych dziwnych oznaczeń, słów 
kluczowych i danych. My nie będziemy się tym formatem zajmować, bo jakoś nie czuję do niego sentymentu. Zapewniam, 
że używanie plików *.x w formacie binarnym nie różni się niczym od tych w tekstowym i jak ktoś się koniecznie upiera, to 
może sobie używać obydwu równocześnie. Funkcję DirectX-a są na tyle mądre, że nie przejmują się tym kompletnie. Format 
binarny jest dla mnie bardziej zwarty no i przynajmniej nikt nie może nam w nim grzebać bezkarnie, no bo kto się połapie w 
masie krzaczków jakie ujrzy po otwarciu takie pliku... Nie będę tutaj się rozpisywał na temat wszystkich aspektów budowy 
pliku *.x, bo na to znajdziemy jeszcze czas. Pokażę jak załadować plik na scenę i jak otrzymywać w ogóle pliki *.x. I 
zaczniemy właśnie od tego. 
 
Aby użyć pliku *.x trzeba go najpierw mieć. W SDK oczywiście znajdziemy wiele przykładowych plików no ale nam marzy 
się zupełnie co innego. Całe morze Internetu i modele wszelkiego rodzaju - począwszy od prostych sześcianów na modelach 
zawierających po 150 tysięcy wierzchołków. Co w takim razie będzie nam potrzebne? Po pierwsze pliki z modelami - o nie 
nie jest trudno. Wpisujemy w wyszukiwarce "3D models" i jazda, mamy całe mnóstwo. Tutaj jedna, bardzo ważna uwaga! 
Modele muszą być w formacie starego dobrego 3D Studio Max - pliki z rozszerzeniem *.3ds. Oczywiście programów do 
modelowania jest całe mnóstwo i każdy z nich posiada własny format zapisu danych, ale każdy szanujący się powinien mieć 
konwerter do formatu *.3ds. 3D Studio Max był jednym z pierwszych, bardzo zaawansowanych jak na owe czasy 
programów nie tylko do modelowania ale także animacji i pewnie dlatego przez tradycję panowie z Microsoftu zdecydowali 
się właśnie na na stare 3D Studio. W każdym razie dlaczego o tym piszę - żaden program nie zapisuje na razie w formacie 
*.x używanym przez Direct3D i nie wygląda na to, żeby się to miało wkrótce zmienić. Dlatego też potrzebny był konwerter z 
plików 3D Studio do formatu *.x. W pakiecie SDK dostajemy do ręki coś takiego. Program nazywa się "conv3ds.exe" i jak 
dobrze poszukacie, to na pewno znajdziecie. Cóż robi ten program - w zasadzie już napisałem i powinno wystarczyć. No ale 
jak to wiadomo w życiu nie wszystko jest takie proste jakby się zdawało. Wywołanie programu jest zupełnie banalne, linia: 

 
conv3ds file.3ds 
 

spowoduje, że powstanie nam plik o nazwie file.x zawierający wszystko to, co zawierał plik *.3ds. Takie wywołanie 
konwertera powoduje, że plik wynikowy będzie miał format binarny (domyślny) czyli w sumie taki o jaki nam chodzi. 
Ponieważ jednak nasz program zawiera sporo ciekawych opcji, więc przyjrzyjmy mu się bliżej. Przepiszę znowu bezczelnie 
kawał dokumentacji, no ale nigdzie indziej nie znalazłem info na ten temat: 

background image

2 

DirectX ▪ X – Files 

 
conv3ds -A file.3ds 
 

Napisałem wcześniej, że do Direct3D możemy ładować nie tylko modele, ale także całe animacje. I jeśli wiemy, że plik *.3ds 
zawiera taką animację to aby znalazła się ona w pliku *.x, musimy wywołać konwerter w sposób następujący: 

 
conv3ds -m File.3ds 
 

Jeśli na przykład na scenie mamy kilka modeli, lub jeden model złożony z kilku oddzielnych obiektów (na przykład 
samochód = karoseria + koła + wnętrze itp.) a chcemy aby załadował się on nam jako całość, to powinniśmy zrobić tak: 
 

conv3ds -T File.3ds 

 
Pewną ciekwą sztuczkę można zrobić, jeśli plik zawiera animację, ale nie chcemy ładować wszystkich klatek z pliku. 
Możemy "upakować" niejako wszystkie klatki i obiekty do pierwszej klatki animacji, tak że mogą one zostać załadowane 
jednym wywołaniem funkcji. Pierwsza klatka zostanie załadowana z pliku *.x, reszta natomiast zostanie załadowana z pliku 
x3ds_nazwa, który powstanie po użyciu takiej opcji konwersji. Plik ten nie będzie miał rozszerzenia. Jeśli użyjemy takiej 
opcji to nie będzie ona działać jeśli jednocześnie użyjemy opcji -m. 
 

conv3ds -s10 File.3ds 
conv3ds -s0.1 File.3ds 
 

Czasem zdarza się, że pomimo naszych usilnych wysiłków okaże się, że nasz model jest za duży a akurat pożyczyliśmy 
płytkę z programem do modelowania kumplowi. Co wtedy? Nie stoimy jeszcze na straconej pozycji. Możemy sobie przy 
ładowaniu przeskalować nasz model o wybraną, w zasadzie dowolną wartość. Można to robić oczywiście w górę tak, jak w 
pierwszym przykładowym wywołaniu (x10) jak i w dół - drugie wywołanie to pomniejszenie modelu 10 razy. Jeśli po 
konwersji nasz obiekt wyświetlany nie jest do końca taki, jaki sobie wymarzyliśmy - ma dziury, pozmieniane face'y itp., 
możemy spróbować użyć opcji -r, która odwraca porządek ścianek w modelu podczas konwersji. 
 
Opcja -v włącza swego rodzaju tryb debugowania podczas konwersji. W zależności od liczby podanej po literze 'v' możemy 
otrzymać różne poziomy informacji. I tak: 

•  v0 - opcja domyślna, wszelkie informacje wyłączone,  

•  v1 - wyświetla ostrzeżenia o złych obiektach (nieprawidłowo zbudowanych?) oraz ogólne informacje co robi 

konwerter w danej chwili,  

•  v2 - wyświetla podstawowe informacje o klatkach kluczowych, obiektach będących przedmiotem konwersji i 

obiektach, które zostały zapisane,  

•  v3 - najwyższy poziom, mnóstwo informacji, bardzo przydatny do usuwania problemów.  

 

conv3ds -e"ppm" File.3ds 

 
Opcja -e nakazuje konwerterowi zmienić rozszerzenia plików tekstur do których będzie się dowływał model zawarty w pliku 
*.x, w naszym przykładzie *.ppm. Jeśli plik file.3ds zawiera odnośniki do pliku *.gif, konwerter spowoduje, że plik *.x 
będzie zawierał odnośniki do plików *.ppm. Konwerter nie konwertuje samych plików tekstur! Tekstury muszą się 
znajdować w katalogu określonym przez zmienną środowiskową D3DPATH. 
 
Opcja -x nakazuje konwersję do plików tekstowych. Są one zdecydowanie większe od binarnych, ale umożliwiają edycję 
danych z "ręki". 
 
Opcja -X nakazuje właczenie podczas konwersji pików *.x zawierających szablony. O szablonach jednak powiemy sobie 
innym razem. 
 
Opcja -t spowoduje, że plik *.x nie będzie zawierał informacji o teksturach. 
 
Opcja -N nakazuje, aby w pliku wynikowym *.x nie znalazły się informacje o wektorach normalnych. 
 
Opcja -c powoduje to, że plik *.x nie będzie zawierał danych o współrzędnych teksturowania obiektu. Domyślnie, jeśli 
obiekt nie zawiera współrzędnych teksturowania, użycie opcji -m spowoduje, że wszystkie pary uv będą miały wartość 0, 0. 
 
Opcja -f zawiadamia, że plik wyjściowy ma nie zawierać macierzy transformacji. 
 
Opcja -z i -Z umożliwiają dostosowanie wartości kanału alfa koloru materiału, z którego są zbudowane ścianki obiektów. Dla 
przykładu poniższe polecenie powoduje, że konwerter doda wartość 0.1 do wszystkich wartości alfa poniżej 0.2. 

 
conv3ds -z0.1 -Z0.2 file.3ds 

background image

3 

DirectX ▪ X – Files 

 

Następujące polecenie powoduje odjęcie od wszystkich wartości kanału alfa liczby 0.2. 
 

conv3ds -z"-0.2" -z1 file.3ds 
 

Opcja -o umożliwia zmianę nazwy pliku wyjściowego, utworzonego po konwersji. Opcja -h zakazuje pobierania z pliku 
*.3ds jakichkolwiek informacji o znajdujących się tam hierarchiach, zazwyczaj wstawianych tam przez moduł 3D Studio 
zwany keyframerem. Zamiast tego wszystkie obiekty są konwertowane jak w swoich startowych ujęciach, jeśli opcja -m nie 
jest użyta. 
 
Jak już nadmieniłem wcześniej, istnieje wiele programów mogących służyć modelowaniu obiektów i animacji. Jednym z 
nich jest LightWave. Posiada on plugin do eksportu plików w formacie *.3ds potrzebnych konwerterowi. Jednak próba 
konwersji plików pochodzących z LightWave'a może się skończyć obecnością błędów i dlatego w takim przypadku 
wskazane jest użycie następującego polecenia: 
 

conv3ds -r -N -f -h -T|m trans3dfile.3ds 
 

Jeśli już dokonaliśmy konwersji i teraz załadujemy nasze modele do programu i będą problemy, to możecie sprawdzić co 
następuje: 

•  Jeśli w ogóle nie widać obiektów załadowanych po konwersji, spróbujcie skonwertować je jeszcze raz 

przeskalowując jednocześnie (opcja -s) ze współczynnikiem około 100 razy.  

•  Jeśli po załadowaniu obiektu i zmianie cieniowania z płaskiego na Gourauda obiekt nagle ciemnieje, spróbujcie 

konwersji z opcją -N.  

•  Jeśli nie są ładowane tekstury po konwersji, to sprawdźcie, czy nie zmieniliście rozszerzeń plików tekstur, czy 

tekstury znajdują się w katalogu wskazywanym przez zmienną D3DPATH oraz czy wszystkie tekstury mają 
wielkości będące potęgami liczby 2.  

•  Jeszcze jedna uwaga. Konwerter nie potrafi poradzić sobie z obiektami pomocniczymi używanymi w programach do 

animacji, (tzw. dummy objects) i je ignoruje. Jeśli konwertujecie więc animację, weźcie to pod uwagę.  

No i z dokumentacji byłoby na razie tyle a my udamy się w tym momencie do naszego kodu, zobaczyć co tam słychać 
nowego:

 

 
LPD3DXMESH          g_pMesh          = NULL;  // Our mesh object in sysmem 
D3DMATERIAL8*       g_pMeshMaterials = NULL;  // Materials for our mesh 
LPDIRECT3DTEXTURE8* g_pMeshTextures  = NULL;  // Textures for our mesh 
DWORD               g_dwNumMaterials = 0L;    // Number of mesh materials 
 

No i tak. Obiekty Direct3D i urządzenia tradycyjnie już pomijamy, bo znamy je doskonale. W zasadzie obiekty materiału i 
tekstury też znamy i możemy sobie odpuścić ich omawianie. Nowym elementem dla nas jest coś takiego jak obiekt siatki 
(ang. mesh). W naszym programie tworzymy sobie taki obiekt za pomocą wskaźnika do obiektu siatki. Wskaźnik ten jest 
określany przez typ 

LPD3DXMESH

. Jakie metody udostępnia nam taki obiekt? Po zajrzeniu do dokumentacji możemy się 

doskonale przekonać. Nas interesować będzie szczególnie metoda potrafiąca narysować siatkę na ekranie - 

DrawSubset()

Jak ona dokładnie działa omówimy sobie później, no a my zerknijmy jeszcze na dostępne metody. Mamy do dyspozycji 
szereg metod, pozwalających na dostęp do buforów zawierających dane naszej siatki, pozwalające na kopiowanie naszych 
siatek, zwracające ilość różnorakie informacje o siatce, takie jak ilości wierzchołków, face'ów i tym podobne. Czy przydarzy 
się nam jest wykorzystać nie wiem, ale dobrze wiedzieć, gdzie szukać w razie czego. Innymi słowy, chociaż zamkniemy 
sobie nasz model w jakimś tajemniczym obiekcie, dzięki jego metodom będziemy mieć nadal nad naszym modelem kontrolę 
a jak już oswoimy się z jego metodami, to będziemy nim manipulować z łatwością. Ostatnia zmienna typu 

DWORD

 będzie 

określać ilość materiałów, jaką posiada model, co jest dla nas pewną nowością, ponieważ do tej pory każdy model o ile w 
ogóle posiadał materiał jako taki to był on jeden, no a teraz będziemy mogli mieć ich kilka. Wprawdzie poszczególne części 
maszyn czy innych wynalazków przeważnie są zrobione z jednego rodzaju materiału to jednak może nam się na przykład 
zamarzyć, żeby załadować dla przykładu model samochodu w całości (jako jeden obiekt) no i wtedy moglibyśmy mieć 
problem - szyby inne, opony inne, karoseria jeszcze inna. Dzięki możliwości ładowania różnej ilości materiałów przez 
Direct3D ten problem dla nasz nie istnieje. Inicjalizacja urządzenia też nie stanowi dla nas żadnej tajemnicy. Standardowe 
operacje, które wykonujemy chyba codziennie po kilkadziesiąt razy uruchamiając nasze przykłady. Żeby więc niepotrzebnie 
nie przedłużać, zajmijmy się naszym modelem. Załóżmy, że stworzyliśmy w jakimś programie do modelowania piękny 
model powiedzmy naszego przyszłego domu ;-) i chcielibyśmy sobie teraz po nim pochodzić i zobaczyć jak to będzie. 
Napisaliśmy super silnik 3D, który jest w stanie wyrenderować co nam się tylko podoba. Nie mamy tylko czego wyświetlić. 
Zapisaliśmy jakimś cudem nasz model jako plik *.3ds, więc czas przystąpić do konwersji. Uruchamiamy omawiany powyżej 
program z odpowiednimi opcjami, podajemy jako parametr plik *.3ds z modelem naszego domu i odpalamy. Jeśli wszystko 
poszło po naszej myśli to na pewno otrzymaliśmy to, co chcieliśmy - plik moj_dom.x z ewentualnym zestawem tekstur, 
których tam użyliśmy. No i cóż nam pozostało? Popatrzmy. 
 

 

background image

4 

DirectX ▪ X – Files 

HRESULT InitGeometry() 

  LPD3DXBUFFER pD3DXMtrlBuffer; 
 
  // Load the mesh from the specified file 
  if( FAILED( D3DXLoadMeshFromX( "Tiger.x", D3DXMESH_SYSTEMMEM, 
                                 g_pd3dDevice, NULL, &pD3DXMtrlBuffer, 
                                 &g_dwNumMaterials, &g_pMesh ) ) ) 
  { 
    return E_FAIL; 
  } 
 
  // We need to extract the material properties and texture names from the 
  // pD3DXMtrlBuffer 
  D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer(); 
  g_pMeshMaterials = new D3DMATERIAL8[g_dwNumMaterials]; 
  g_pMeshTextures = new LPDIRECT3DTEXTURE8[g_dwNumMaterials]; 
 
  for( DWORD i=0; i<g_dwNumMaterials; i++ ) 
  { 
    // Copy the material 
    g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D; 
    // Set the ambient color for the material (D3DX does not do this) 
    g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse; 
 
    // Create the texture 
    if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, 
d3dxMaterials[i].pTextureFilename, &g_pMeshTextures[i]))) 
    { 
      g_pMeshTextures[i] = NULL; 
    } 
  } 
 
  // Done with the material buffer 
  pD3DXMtrlBuffer->Release(); 
 
  return S_OK; 

 

Skoro mamy już nasz model w pliku *.x możemy przystąpić do jego ładowania. Powyższa funkcja ma spowodować, aby 
nasz dom znalazł się gdzieś w pamięci, skąd będziemy mogli go wyświetlać. Czy się jej to uda? Sprawdźmy.

 

 
LPD3DXBUFFER pD3DXMtrlBuffer; 

 
Zmienną bufora już znamy, omawialiśmy sobie ją przy okazji vertex shadera. Cóż ona będzie zawierać w tym przypadku? 
Otóż, jeśli funkcji ładującej dane z pliku *.x uda się wszystko tak, jak powinno, to w buforze tym znajdzie się tablica struktur 
typu D3DXMATERIAL, zawierających dane o materiałach wczytanych z pliku *.x.

 

 
// Load the mesh from the specified file 
if( FAILED( D3DXLoadMeshFromX( "Tiger.x", D3DXMESH_SYSTEMMEM, 
                g_pd3dDevice, NULL, &pD3DXMtrlBuffer, &g_dwNumMaterials, 
                &g_pMesh ) ) ) 

  return E_FAIL; 

 

No i wreszcie długo upragniony moment, czyli ładowanie modelu z pliku. Znowu z pomocą przychodzi nam nieoceniona 
biblioteka 

D3DX

 i jej bardzo pożyteczne funkcje. Użyjemy sobie funkcji 

D3DXLoadMeshFromX()

. Nazwa mówi sama za 

siebie, no ale parametry już mniej. Cóż więc takiego mamy: 

•  "Tiger.x" - pierwszy parametr to jak łatwo się domyślić i zobaczyć nazwa naszego pliku z modelem. Piszę 

modelem, ponieważ nie eksportujemy żadnej animacji ani hierarchii.  

• 

D3DXMESH_SYSTEMMEM

 - Tutaj powinna znaleźć się kombinacja flag określających sposób tworzenia siatki 

na podstawie danych z pliku. Nasza siatka jest zapisana w specyficzny sposób w pliku, o którym już pisałem kiedyś. 
Są to dane wierzchołków i indeksy do nich. Czyli tak naprawdę każdy wierzchołek jest zapisany tylko raz i jeśli 

background image

5 

DirectX ▪ X – Files 

kilka trójkątów się do niego odwołuje to robi to poprzez jego numer w kolejności - czyli tzw. indeks. W ten sposób 
oszczędzamy miejsce i pamięć dla danych. Flaga 

D3DXMESH_SYSTEMMEM

 określa pewien sposób tworzenia 

bufora indeksów dla naszej siatki. Co to bufor indeksów i z czym to się je, może powiemy sobie kiedy indziej. Teraz 
znowu musicie uwierzyć mi na słowo, że tak będzie ok.  

•  g_pd3dDevice - Trzeciego parametru nie muszę chyba nikomu opisywać? Oczywiście nasze bardzo pożyteczne 

urządzenie.  

• 

NULL

 - Czwarty parametr może zawierać trochę mniej istotną dla nas wartość, a w szczegółach to bufor 

zawierający tablicę numerów trzech sąsiednich trójkątów dla tego, którego numer jest aktualnym indeksem tej 
tablicy. Nas to na razie nie interesuje, więc ustawiamy to sobie na wartość 

NULL

.  

•  pD3DXMtrlBuffer - Ten parametr omawialiśmy przed chwilą na górze. Jest to bufor zawierający tablicę struktur 

typu 

D3DXMATERIAL

, w których zawarte są dane o poszczególnych materiałach użytych w naszym modelu.  

•  g_dwNumMaterials - zmienna przechowuje ilość materiałów jakie zawiera nasza siatka. Do funkcji podajemy ją 

jako wskaźnik.  

•  g_pMesh - czyli to, co interesuje nas najbardziej w tym momencie, czyli obiekt naszej siatki (ang. mesh). Obiektu 

tego użyjemy za chwilę do narysowania naszego modelu na ekranie. Ale zanim to nastąpi musimy jeszcze dokonać 
kilku rzeczy.  

// We need to extract the material properties 
// and texture names from the pD3DXMtrlBuffer 
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer(); 
g_pMeshMaterials = new D3DMATERIAL8[g_dwNumMaterials]; 
g_pMeshTextures = new LPDIRECT3DTEXTURE8[g_dwNumMaterials]; 
 

Jak widać na załączonym fragmencie kodu, będziemy jeszcze potrzebować pewnych namiarów na materiały i tekstury użyte 
w naszym modelu, które to informacje są zapisane w pliku *.x. Po pierwsze opis materiałów. Jak wiemy, funkcja 

D3DXLoadMeshFromX()

 ładuje informacje o materiałach do bufora. W buforze znajduje się tablica ze strukturami 

D3DXMATERIAL

 zawierającymi dane o materiałach i teksturach. Jest to struktura zawierająca strukturę 

D3DMATERIAL8

 oraz nazwę pliku z teksturą. Pamiętamy również, że aby użyć materiału na scenie ustawiamy go metodą 

urządzenia 

SetMaterial()

, tylko że ta metoda potrzebuje wskaźnika do zmiennej będącej strukturą materiału. No a my mamy 

na razie bufor. Cóż więc zrobić? Ano musimy jakoś do tych danych się dobrać. Ponieważ mamy bufor, w buforze tablicę, 
więc zastosujemy tu pewną sztuczkę. Wiadomo, że w buforze nic oprócz naszych danych nie ma. Jest więc w pamięci tablica 
(ciąg) kolejnych struktur zawierających dane materiału. Czyli pobierając wskaźnik do bufora dostaniemy jednocześnie 
wskaźnik na pierwszy element naszej tablicy! Proste i skuteczne, prawda? Na samym początku naszego programu 
zadeklarowaliśmy także wskaźniki (w rzeczywistości będą to tablice) na struktury 

D3DMATERIAL8

 oraz 

LPDIRECT3DTEXTURE8

, powyżej zaś wykorzystując właśnie te wskaźniki i ilość materiałów dla naszego modelu 

tworzymy tablice materiałów i tekstur.

 

 
for( DWORD i=0; i<g_dwNumMaterials; i++ ) 

  // Copy the material 
  g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D; 
 
  // Set the ambient color for the material (D3DX does not do this) 
  g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse; 
 
  // Create the texture 
  if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, 
d3dxMaterials[i].pTextureFilename, &g_pMeshTextures[i]))) 
  { 
    g_pMeshTextures[i] = NULL; 
  } 

 

Spójrzmy bliżej na powyższą pętlę. Kopiujemy z tablicy struktur 

D3DXMATERIAL8

, która to struktura ma dwa pola. Pole 

pierwsze, MatD3D jest strukturą typu 

D3DMATERIAL8

, drugie to pTextureFilename czyli łatwo się domyśleć. Zmienna 

g_pMeshMaterials jest teraz tablicą struktur 

D3DMATERIAL8

 o rozmiarze zawartym w zmiennej g_dwNumMaterials

czyli określającej ilość materiałów na scenie. Jeśli więc chcemy załadować sobie dane z tablicy struktur 

D3DXMATERIAL

 

do tablicy ze strukturami 

D3DMATERIAL8

, to musimy się odwołać do pola MatD3D kolejnych struktur w tablicy 

d3dxMaterials. Ponieważ Direct3D nie nada naszemu materiałowi koloru składowej rozproszonej, musimy jeszcze ten jeden 
element uzupełnić sami. Przepisujemy więc teraz wartość z pola Diffuse do pola Ambient tej samej tablicy. Tak będzie w 
sumie najbezpieczniej. 
Dobra, mamy materiały, więc czas na tekstury. Tablica, z której czytamy nasze dane czyli d3dxMaterials zawiera w polu 
pTextureFilename nazwę pliku określającą naszą teksturę. Więc w czasie działania naszej pętli nie pozostaje nam nic 
innego jak tylko tworzyć nasze tekstury! Tablica g_pMeshTextures powinna zawierać nasze tekstury (obiekty tekstur). Z 
pomocą znowu przyjdzie nam także biblioteka 

D3DX

. Funkcja 

D3DXCreateTextureFromFile()

, którą także dobrze znamy, 

background image

6 

DirectX ▪ X – Files 

będzie nam tworzyć kolejne obiekty tekstury w tablicy wykorzystując pole pTextureFilename kolejnych struktur z tablicy 
d3dxMaterials. Jeśli natomiast nie powiedzie się wywołanie funkcji, tworzącej obiekty tekstur (na przykład nie będzie pliku 
z teksturą), to wtedy do tablicy zapiszemy wartość 

NULL

 i na scenie ujrzymy zapewne trochę zubożony nasz model. No ale 

może nie będzie aż tak źle. Po naszych rozważaniach wszystko powinno się udać ;-).

 

 
// Done with the material buffer 
pD3DXMtrlBuffer->Release(); 
 

Całą naszą zabawę kończymy zwalniając nasz bufor, w którym znajdują się dane dla materiałów i tekstur, które 
skopiowaliśmy do tablic za pomocą omówionej przed chwilą pętli. Wywołujemy jak zwykle metodę interfejsu 

IUnknown

która pozwoli naszemu buforowi odejść w zapomnienie a nam zwróci jakże cenną pamięć.

 

 
VOID Render() 

  // Clear the backbuffer and the zbuffer 
  g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 
D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); 
 
  // Begin the scene 
  g_pd3dDevice->BeginScene(); 
 
  // Setup the world, view, and projection matrices 
  SetupMatrices(); 
 
  // Meshes are divided into subsets, one for each material. 
  // Render them in a loop 
  for( DWORD i=0; i<g_dwNumMaterials; i++ ) 
  { 
    // Set the material and texture for this subset 
    g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] ); 
    g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] ); 
 
    // Draw the mesh subset 
    g_pMesh->DrawSubset( i ); 
  } 
 
  // End the scene 
  g_pd3dDevice->EndScene(); 
 
  // Present the backbuffer contents to the display 
  g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); 

 

No i cóż. Model, tekstury i materiały załadowane na scenę, więc nie pozostało nam już nic innego, jak tylko rozpocząć 
rysowanie! Nasza stara znajoma, czyli funkcja Render(). Prócz standardowej inicjalizacji, czyszczenia tła i ustawienia 
macierzy da się zauważyć brak metody urządzenia dotyczącej rysowania brył 

DrawPrimitive()

. Dlaczego? Otóż my 

wykorzystamy do rysowania nasz obiekt siatki. Jest on na tyle sprytny, że potrafi narysować się sam! Nie potrzebujemy już 
przedzierać się przez wszelkie dane wierzchołków, ilość trójkątów, ustawienia stanów urządzenia. My po prostu każemy się 
naszemu obiektowi narysować i... i w zasadzie już. Ino, że w tym miejscu czeka nas niespodzianka. W zasadzie kto czytał 
uważnie to wcale nie powinien być zaskoczony, no ale dla pewności powiedzmy sobie o tym. Jak napisałem dużo wcześniej, 
możemy mieć model złożony z kilku oddzielnych elementów (dobrym przykładem pozostaje niezmiennie samochód). Każdy 
element może mieć inny materiał (opona, szyba, karoseria itd.). Możemy skonwertować sobie nasz model do pliku *.x w taki 
sposób, że wszystkie elementy zostaną połączone w jedną całość. W tym momencie na pewno powstanie pytanie - co z 
materiałami? Nie możemy oczywiście ich scalić w jedno, bo to zupełnie bez sensu. Podczas konwersji materiały są 
zapisywane oczywiście po staremu - czyli każdy oddzielnie, no a jak mieliśmy okazję się przekonać, podczas ładowania 
otrzymujemy wszystkie ładnie podane na tacy. Jeśli nasz model zostaje niejako "scalony" w jedną wielką bryłę to na pewno 
przy tym nie może zostać zapomniane rozłożenie materiałów na naszym modelu. Model więc zostaje podzielony na pewne 
segmenty, z których każdy charakteryzuje się materiałem, który jest jemu przydzielony. Oczywiście w większości modeli nie 
będzie tego problemu, no ale żeby nie robić sobie na przyszłość kłopotów i wyjaśnić istotę rysowania modelu za pomocą 
obiektu siatki lepiej sobie o tym powiedzieć. Tak więc będziemy mieć nasz model podzielony na pewne fragmenty zależne 
od materiału.

 

 
for( DWORD i=0; i<g_dwNumMaterials; i++ ) 

  // Set the material and texture for this subset 

background image

7 

DirectX ▪ X – Files 

  g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] ); 
  g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] ); 
 
  // Draw the mesh subset 
  g_pMesh->DrawSubset( i ); 

 

Przypatrzmy się temu fragmentowi funkcji renderującej. Znów mamy pętlę, która będzie się wykonywać tyle razy, ile mamy 
materiałów. Za każdym przebiegiem pętli ustawiony jest materiał i tekstura z tablic, które wypełnialiśmy podczas 
wczytywania modelu z pliku *.x. Następnie mamy wywołanie tajemniczej jeszcze metody obiektu siatki 

DrawSubset()

. Cóż 

robi ta metoda? Wiemy już, że model może się składać z kilku "części". W zasadzie żadnej filozofii tutaj nie ma i widać 
jasno o co chodzi. Jeśli zdarzy się, że model jest złożony z kilku części i każdy z nich ma inny materiał to należy narysować 
nasz model niejako na raty. Najpierw te części, które posiadają określony materiał, potem te które mają inny i tak w kółko aż 
model będzie cały. I to właśnie wykonuje metoda 

DrawSubset()

. Pobiera ona jako parametr numer kolejnego fragmentu 

naszego modelu, który należy narysować. Oczywiście, jak widać, wcześniej ustawiany jest dla danego fragmentu odpowiedni 
dla niego materiał i tekstura. W ten właśnie sposób możemy rysować złożone modele, które zawierają dużo materiałów i 
tekstur. Oczywiście w większości przypadków, jak już nadmieniłem, model będzie zawierał jedną teksturę i jeden materiał. 
No ale rysować należy tak a nie inaczej, żeby nie spotkały nas żadne przykre niespodzianki. No i to w zasadzie tyle, jeśli 
chodzi o samo rysowanie :-).

 

 
if( g_pMeshMaterials != NULL ) 
    delete[] g_pMeshMaterials; 
 
if( g_pMeshTextures ) 

  for( DWORD i = 0; i < g_dwNumMaterials; i++ ) 
  { 
    if( g_pMeshTextures[i] ) 
    g_pMeshTextures[i]->Release(); 
  } 
  delete[] g_pMeshTextures; 

 
if( g_pMesh != NULL ) 
    g_pMesh->Release(); 
 

Jeśli już napatrzymy się na nasz wymarzony dom, czy samochód i zapragniemy wrócić do szarej rzeczywistości, to nie 
zapominajmy o porządkach. Ponieważ stworzyliśmy w naszym programie tablicę obiektów tekstur, więc teraz kultura 
nakazywałaby wszystkie je usunąć. Tak samo będzie w przypadku materiałów i siatki. Ale chyba to już macie wpojone 
bardzo mocno i nie muszę Wam o tym pisać bez końca. Wystarczy, że rzucicie okiem na fragment kodu powyżej i wszystko 
powinno być dla Was całkiem jasne. 
 
No i cóż, obrazek z działania programu mamy bardzo powiedziałbym reprezentacyjny :-). Każdy może oczywiście teraz 
szaleć i wczytywać sobie coraz to inne modele. Oczywiście zwróćcie uwagę na skalę (rozmiary) modeli, bo nasz program nie 
jest zbyt inteligentny i nie będzie sobie ich skalował, tak żeby było je widać. Tutaj macie oczywiście pole do popisu. Nie 
opisałem odtwarzania animacji. Nie chciałem wszystkiego na raz a w sumie wydaje mi się, że wczytywanie animacji jest 
trochę bez sensu. Przecież równie dobrze możemy sobie taką animację odtworzyć w programie do modelowania i animacji 
(choćby 3D Studio Max). Ale zapewniam, że jeśli zajdzie taka potrzeba to opiszemy i ten proces. Dawno nie wracaliśmy do 
przykładów z SDK, no ale pewnie zauważyliście, że w ten pokrętny sposób ukończyliśmy niejako analizę tutoriali 
podstawowych z DirectX SDK. Kod oczywiście wiecie gdzie znaleźć ;-). Do zobaczenia.