Direct3D X Files

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.


Wyszukiwarka

Podobne podstrony:
Lab 02 UNIX files and directories management
Direct3D 11 Tessellation
Premier Press Beginning DirectX 9
Active Directory
5. Prensa, Hiszpański, Kultura, España en directo
Active Directory
Direct3D Vertex shader 1
C Program Files Opera profile cache4 opr0260A
Intermediate Short Stories with Questions, Driving Directions
Directional Movement Index, giełda(3)
directx
Komunikacja rynkowa direct response marketing
Aplikacje Direct3D
23 directionsforuse
Direct3D Blending

więcej podobnych podstron