Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Bufory wierzchołków w DirectX 9

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest Wachu. Jeżeli został importowany poprawnie, usuń ten szablon!

Wprowadzenie

Poniższy artykuł ma na celu przekonanie młodych programistów gier do DirectX oraz ułatwienie życia tym, którzy zastanawiają się nad przesiadką z OpenGL'a. Przedstawię tutaj parę podstawowych kwestii w programowaniu w DirectX 9 przy okazji pisania swojej własnej kasy do symulacji trybu bezpośredniego znanego z OpenGL.

Implementacja klasy do obsługi buforów wierzchołków

Wydaje mi się, że każdy początkujący programista ma przerażenie w oczach, gdy zobaczy operacje konieczne, aby w ogóle utworzyć bufor wierzchołków, a co dopiero coś narysować na ekranie. Te operacje mogą się początkowo wydawać trudne i trochę niepotrzebne.

Operacje konieczne do zainicjalizowania bufora wierzchołków:

  1. Inicjalizacja DirectX 9
  2. Utworzenie bufora wierzchołków (g_d3d9->CreateVertexBuffer(…))
  3. Ustalenie formatu wierzchołków, które będą przechowywane w buforze
  4. Zablokowanie bufora
  5. Skopiowanie przygotowanych wierzchołków w odpowiednim formacie do bufora
  6. Odblokowanie bufora

Teraz, aby jeszcze cos narysować za pomocą tego bufora konieczne są następujące operacje:

  1. Ustawienie źródła skąd będą pobierane dane o wierzchołkach
  2. Ustawienie formatu wierzchołków
  3. Narysowanie wierzchołków (np. za pomocą g_d3d9->DrawPrimitive(…))
I na koniec po zakończeniu pracy z buforem zwolnienie bufora wierzchołków.

Ufff… aż tyle operacji do zapamiętania dla początkującego w DirectX może być zniechęcające. To nie to samo, co stare dobre glBegin i glEnd. Dlatego, aby uniknąć ewentualnych błędów przy wykonywaniu tych samych czynności zamkniemy je w klasę.

Przykładowa implementacja (musimy wykorzystać szablony, aby nasza klasa była jak najbardziej elastyczna):

// niezbędne nagłówki #include <windows.h> #include <d3dx9.h> // klasa do obsługi "statycznego" bufora wierzchołków template<class T> class DXVertexBuffer { public: // domyslny konstruktor DXVertexBuffer() {} // utworzymy również konstruktor mogący służyć utworzenie bufora DXVertexBuffer(LPDIRECT3DDEVICE9 device, T* data, int count, UINT format) { // sprawdzenie czy przypadkiem bufor już nie jest utworzony if(vb) Release(); uFormat = format; device->CreateVertexBuffer( count * sizeof(T), 0, format, D3DPOOL_DEFAULT, &vb, NULL); VOID* pVertices; vb->Lock( 0, 0, (void**)&pVertices, 0 ) ; memcpy( pVertices, data, sizeof(T) * count); vb->Unlock(); } // domyślny destruktor, jeśli przypadkiem zapomnieliśmy zwolnić // bufora to zwolni go za nas ~DXVertexBuffer() { if(vb) Release(); } // tworzy bufor void Create(LPDIRECT3DDEVICE9 device, T* data, int count, UINT format) { // sprawdzenie czy przypadkiem bufor już nie jest utworzony // jeśli tak to robimy to jeszcze raz(zabezpieczenie…) if(vb) Release(); uFormat = format; device->CreateVertexBuffer( count * sizeof(T), 0, format, D3DPOOL_DEFAULT, &vb, NULL); VOID* pVertices; vb->Lock( 0, 0, (void**)&pVertices, 0 ) ; memcpy( pVertices, data, sizeof(T) * count); vb->Unlock(); } // renderuje bufor void Render(LPDIRECT3DDEVICE9 device, D3DPRIMITIVETYPE type, int count) { if(!vb) return; device->SetStreamSource( 0, vb, 0, sizeof(T) ); device ->SetFVF( uFormat ); device ->DrawPrimitive( type, 0, count); } // funkcja dostępu do bufora wierzchołków, daje możliwość dynamicznej // zmiany wierzchołków w buforze LPDIRECT3DVERTEXBUFFER9 GetVertexBuffer() { return vb; } // zwalnia bufor wierzchołków void Release() { if(vb) vb->Release(); } private: LPDIRECT3DVERTEXBUFFER9 vb; // bufor wierzcholków UINT uFormat; // format wierzcholków };

Kod wydaje mi się, że jest dość przejrzysty. Jeśli czegoś nie rozumiesz polecam zerknąć do odpowiedniej książki o DirectX lub do SDK. Wadą powyższego kodu jest to, że prawie w każdej funkcji konieczne jest przekazywanie wskaźnika do urządzenia DirectX. Można to rozwiązać poprzez utworzenie globalnego obiektu DirectX.

Teraz czas na przedstawienie wykorzystanie tej klasy w praktyce(na podstawie kodu z SDK):

// obiekt urządzenia DirectX 9 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // struktura przykładowych wierzchołków struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color; }; // nasz format wierzcholkow #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) DXVertexBuffer< CUSTOMVERTEX > g_VertexBuffer; // domyslny konstruktor // inicjalizacja bufora gdzieś w jakiejś funkcji.... CUSTOMVERTEX g_Vertices[] = // przykładowe wierzchołki { { 150.0f, 50.0f, 500.5f, 1.0f, 0x00ff0000, }, { 250.0f, 250.0f, 500.5f, 1.0f, 0x0000ff00, }, { 50.0f, 250.0f, 500.5f, 1.0f, 0x0000ffff, }, }; g_VertexBuffer->Create(pd3dDevice, g_Vertices, 3, D3DFVF_CUSTOMVERTEX); // i teraz użycie w funkcji renderujacej.... g_VertexBuffer->Render(g_pd3dDevice, D3DPT_TRIANGLELIST, 1); // i na koniec zwolnienie bufora g_VertexBuffer->Release();

Jak widać użycie naszej klasy znacznie uprościło nam sprawę ;)

Implementacja klasy symulującej glBegin i glEnd

Naszym celem będzie stworzenie odpowiedników tych funkcji w DirectX 9. Na początek przypomnienie dla tych, którzy nie pamiętają jak te funkcje działają:

glBegin(typ_wielokatow);

typ_wielokatow może być np.: GL_TRIANGLES, GL_LINES, GL_QUADS, GL_TRIANGLESTRIP... itp. Typ wielokątów tak naprawdę określa, w jaki sposób maja być interpretowane wierzchołki znajdujące się w bloku glBegin - glEnd. glEnd() – koniec podawania wierzchołków Pomiędzy tymi funkcjami umieszcza się instrukcje, np. glVertex2f, glColor3f… itp. Określające poszczególne parametry wierzchołków.

Dla tych, którzy nie rozumieją, co oznaczają poszczególne operacje odsyłam do artykułów wyjaśniających podstawy operacji graficznych w OpenGL'u lub jeśli Cię to API nie interesuje możesz je po prostu zignorować (nie powinno ci to przeszkodzić w zrozumienie pozostałej części artykułu).

Zacznijmy od przedstawienie naszej implementacji w DirectX:

// klasa do obsługi „dynamicznego” bufora wierzchołków template<class T> class DXDirectMode { public: // konstruktor i destruktor DXDirectMode() : vertex_count(0) {} ~DXDirectMode() {} // inicjalizacja “dynamicznego” bufora wierzchołków void Init(LPDIRECT3DDEVICE9 _device, UINT format, int max_vertex = 32) { device = _device; max_count = max_vertex; uFormat = format; data = new T[max_vertex]; device ->CreateVertexBuffer(max_count * sizeof(T), 0, uFormat, D3DPOOL_DEFAULT, &vb, NULL ); } // zwalnia dane bufora wierzchołków void Release() { if(data) delete []data; vb->Release(); } // początek określania listy wierzchołków void Begin(D3DPRIMITIVETYPE _type) { type = _type; } // wstawienie pojedynczego wierzchołka void Vertex(const T& vertex) { data[vertex_count] = vertex; ++vertex_count; } // koniec listy wierzchołków, rysowanie, trzeba niestety podać // ilość wielokątów do wyswietlenia(rzecz która można poprawić) void End(int primitive_count) { // kopiujemy wierzcholki do bufora VOID* pVertices; vb->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD) ; memcpy( pVertices, data, sizeof(T) * vertex_count); vb->Unlock(); device ->SetStreamSource(0, vb, 0, sizeof(T)); device ->SetFVF(uFormat); device ->DrawPrimitive(type, 0, primitive_count); // zerujemy ilosc wierzchokow do narysowania vertex_count = 0; } private: int max_count; // maksymalna liczba wierzcholkow int vertex_count; // obecna liczba wierzchołków T* data; // dane wierzcholkow LPDIRECT3DDEVICE9 device; // urządzenie D3D, można usunąć singletona LPDIRECT3DVERTEXBUFFER9 vb; // bufor wierzchołków D3DPRIMITIVETYPE type; // typ wyświetlania wierzchołków UINT uFormat; // format wierzchołków };

Powyższa klasa jest dość prosta i z pewnością ma wiele niedoskonałości. Chociaż osobiście nie widzę zbyt wielu możliwości na jej rozbudowę. Jej implementacja stawia jednak pewne wymaganie odnośnie struktury przechowującej wierzchołki:

  1. Klasa reprezentująca wierzchołek musi mieć publiczny konstruktor domyślny
  2. Dla wygody przydałby się również konstruktor do inicjalizacji wszystkich składowych klasy jakimiś wartościami

Dlaczego, jest tak a nie inaczej postaram się przedstawić na poniższym przykładzie:

// to ten sam przykład co powyżej tylko z małymi zmianami LPDIRECT3DDEVICE8 g_pd3dDevice = NULL; // struktura przykładowych wierzchołków struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color; // konstruktor domyślny CUSTOMVERTEX() {} // konstrukor do inicjalizacji wartosciami wierzchołka CUSTOMVERTEX(FLOAT _x, FLOAT _y, FLOAT _z, FLOAT _rhw, DWORD color) : x(_x), y(_y), z(_z), rhw(_rhw), color(_color) {} }; // nasz format wierzcholkow #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) DXDirectMode<CUSTOMVERTEX> g_DirectMode; // podczas inicjalizacji programu, korzystamy z domyślnej maksymalnej // ilości wierzchołków bloku begin - end g_DirectMode.Init(g_pd3dDevice, D3DFVF_CUSTOMVERTEX); // w tym miejscu rysujemy nasza scenę // do wykorzystanie tej klasy polecam napisać makro #define VERTEX(x, y, z, rhw, color) g_DirectMode->Vertex(CUSTOMVERTEX(x, y, z, rhw, color)); // i teraz podajemy wierzchołki g_DirectMode.Begin(); VERTEX(150.0f, 50.0f, 500.5f, 1.0f, 0x00ff0000); VERTEX(250.0f, 250.0f, 500.5f, 1.0f, 0x0000ff00); VERTEX( 50.0f, 250.0f, 500.5f, 1.0f, 0x0000ffff); // podajemy ilos wielokątów do narysowania g_DirectMode.End(1); // i na koniec g_DirectMode.Release();

Powyższy przykład spowoduje narysowanie na ekranie takiego samego trójkącika jak ten narysowany powyżej z „zwykłego” bufora wierzchołków.

Od Autora

Mam nadzieję, że powyższy artykuł okaże się dla kogoś przydatny. Wszelkie pytania i uwagi proszę kierować na adres: [email protected]

Tekst dodał:
Artur Poznański
22.03.2006 22:15

Ostatnia edycja:
Artur Poznański
22.03.2006 22:15

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#1 edytuj 22.03.2006 22:15 Artur Poznański 11.91 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • ~Dexio 13 września 2007 23:40
    Prezentowane tutaj metoda uzywania vertex buffera jest metoda bardzo zla, malo wydajna i wyrabiajaca zle nawyki:

    1) uzywanie FVF jest juz prezytkiem, powinno sie uzywac Vertex Declarations.

    2) tworzenie i usuwanie bufora tak czesto jest bardzo nieefektywne, wszelkie zasoby DirectX'owe w tym takze bufory wierzcholkow powinno sie tworzyc wczesniej (np podczas inicjalizacji enginu, ladowania mapy etc) a nie podczas normalnej pracy aplikacji.

    3) Autor w ogóle nie ma pojęcia do czego służą takie flagi jak D3DLOCK_DISCARD. Używa jej do bufora statycznego na którym jest to niedozwolone.

    4) Implementacja glBegin/glEnd nie powinna tak wyglądać. Bufor do takich operacji powinien byc najlepiej jeden na cala aplikacje, wrzucanie wierzcholkow powinno sie odbywac inteligentnym Lockiem z odpowiednimi flagami ( glownie nooverwrite ).

    5) Sam kod jest napisany niestarannie, brakuje inicjalizacji pol klas, latwo doprowadzic do stanu w ktorym kod poprostu sie wykrzaczy.

    W skrocie: dopuszczajacy z minusem i nie bierzcie przykładu z kodu tego pana.
  • ~Wachu 14 września 2007 09:46
    bylem mlody i glupi ;)
  • ~Dexio 14 września 2007 21:07
    Hehehe, no to teraz nie pozostaje Ci nic innego jak podzielic sie nowymi przemysleniami na temat VB :)
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)