DirectX - oswajamy potwora
Jak od strony technicznej wygląda tworzenie efektów graficznych w najnowszych grach?
W2K 31 lipca 2008, 17:43
Niemal wszystkie najnowsze gry zachęcają nas do zabawy przy pomocy wspaniałych efektów graficznych. Ich rozwój od samego początku związany był z technologią DirectX firmy Microsoft. W tym artykule zaprezentuję zagadnienia związane z tworzeniem grafiki przy wykorzystaniu DirectX. Będziesz miał zatem okazję przekonać się jak wygląda tworzenie oprawy wizualnej gier od strony czysto technicznej, oraz poczuć w niewielkim stopniu wysiłek programistów, by najnowsza produkcja przykuła uwagę graczy.
Z czym walczymy
Pakiet DirectX od początku swojego wydania w 1995 roku jest niemal synonimem współczesnych gier komputerowych. Stał się także potężnym narzędziem marketingowym, a producenci gier oraz sprzętu, zwłaszcza kart graficznych, bez skrępowania umieszczają na opakowaniach swoich produktów ogromne logo kolejnych wersji DirectX obsługiwanych przez ich sprzęt (swoją drogą część tych kart tak naprawdę tylko teoretycznie obsługuje najnowszą jego wersję, bo wydajność np. GeForce’a 8500 w trybie DX10 wypada przez grzeczność przemilczeć...). Kolejne wersje miały wprowadzać znaczną poprawę oprawy audiowizualnej gier, jednak w praktyce sami mieliśmy okazję przekonać się na przykładzie kilku gier, że ta kolosalna różnica jest taka naprawdę chwytem marketingowym. Widać zatem, że z tym słynnym pakietem związanych jest wiele nieporozumień i niedomówień. Pisząc ten artykuł chciałbym zatem dać Tobie, Czytelniku, możliwość samodzielnego sprawdzenia, czym tak naprawdę jest mityczny DirectX.
W artykule skupię się wyłącznie na najważniejszym i najbardziej widocznym dla zwykłego użytkownika elemencie pakietu, a mianowicie Direct Graphics. Jest to element pakietu odpowiedzialny za tworzenie tego, co obecni gracze kochają najbardziej, czyli grafiki 3D. W początkowych wersjach DX-a (tak będę w skrócie nazywał cały pakiet) za tworzenie grafiki odpowiedzialne była dwa moduły: Direct Draw – grafika dwuwymiarowa i Direct 3D – grafika trójwymiarowa. Od wersji 8.0 funkcje Direct Draw zostały przejęte przez Direct 3D, a cały moduł zmienił nazwę na DirectX Graphics (choć wielu programistów z przyzwyczajenia używa nadal określenia Direct 3D albo w skrócie D3D).
W artykule opiszę (korzystając z kodu w języku C/C++) proces tworzenia urządzenia DX 9.0c (jest czym jest urządzenie wyjaśnię potem dalszej części artykułu), zarządzanie nim i to, co najprzyjemniejsze, a zarazem najtrudniejsze, czyli samo renderowanie 3D. Celowo wybrałem starszą wersję i nie opisuję najnowszego DX10, ponieważ spodziewam się, że wielu czytelników (zresztą ja też) z tego czy innego powodu nie ma zainstalowanego Windows Vista oraz karty graficznej zgodnej z DX10, które są wymagane do stworzenia czegokolwiek, ponadto wbrew pozorom przy umiejętnym kodowaniu dziewiątka wcale nie jest gorsza od dziesiątki, wymaga tylko czasami trochę więcej pracy... Po tym ciut dłuższym wstępie możemy zaczynać zabawę.
Co nam będzie potrzebne?
Niestety już na samym początku wymagane jest od nas trochę cierpliwości. Narzędzia programistyczne przygotowane przez Microsoft do pracy z DX-em, mimo iż bardzo dobrej jakości (to chyba jedna z niewielu rzeczy, która im się naprawdę udała) zajmują całkiem sporo. Przede wszystkim potrzebujemy środowiska programistycznego (zakładam, że Czytelnik zna język C++ w stopniu pozwalającym na zrozumienie prezentowanego tu kodu). Mój wybór padł na Microsoft Visual C++ 2008 Express, który nie tylko jest darmowy, ale idealnie sprawdza się w roli narzędzia do pisania pod DX-a. Można go pobrać ze stąd. Ponadto potrzebujemy pakietu DirectX SDK. SDK (z angielskiego Software Developer Kit) to zestaw narzędzi potrzebnych do tworzenia aplikacji z wykorzystaniem danego interfejsu programistycznego (API). DX SDK jest pakietem bardzo obszernym (skompresowany zajmuje ok. 450 MB), ale zawiera wszystko, co może przydać się przy tworzeniu aplikacji pod DX-a: od zestawu bibliotek i nagłówków po rewelacyjne przykłady. Polecam zaraz po zainstalowaniu pakietu przyjrzeć się wszystkim zaprezentowanym tam przykładom, potrafią naprawdę zrobić wrażenie. Pakiet pobieramy z tej strony
Konfiguracja środowiska
Po skompletowaniu wszystkich narzędzi i ich instalacji (ważne, żeby najpierw instalować Visual C++, a dopiero potem SDK – oszczędzi nam to trochę pracy) na dysku, przechodzimy do konfiguracji środowiska.
Na początek sprawdzamy, czy instalator SDK dobrze pododawał ścieżki dostępu do swoich plików. W tym celu po uruchomieniu Visual C++ wybieramy Tools ► Options, a w otwartym okienku przechodzimy do zakładki Projects and Solutions ► VC++ Directories. Powinno to wglądać mniej więcej tak:

W menu rozwijanym (oznaczonym na rysunku czerwoną ramką) wybieramy Include files i sprawdzamy, czy istnieje wpis podobny do tego zaznaczonego na rysunku ramką pomarańczową. Podobnie sprawdzamy dla pozycji Library files menu rozwijanego. Jeśli Visual C++ zostało zainstalowane jako pierwsze, pozycje te zostały automatycznie dodane i ten etap konfiguracji mamy za sobą. Jeśli się tak nie stało, klikamy na ikonę Add Line (druga po lewej), a następnie w podanej do listy ramce na wielokropek. W otwartym oknie dialogowym szukamy miejsca, gdzie zainstalowano SDK i dodajemy folder zgodny z tym, dla którego opcję właśnie wybieramy (tj. dla pozycji Include files dodajemy folder include itp.). W rezultacie wpis powinien wyglądać tak jak ten na rysunku. W ten sposób zakończyliśmy konfigurację środowiska.
Tworzenie projektu
Teraz zabieramy się za właściwe tworzenie projektu aplikacji.Wybieramy File ► New ► Project, a w otwartym okienku kartę Win32 ► Win32 Project. Poniżej w polu Name wpisujemy nazwę dla naszego projektu, a w polu Location zmieniamy ew. miejsce, w którym będzie on zapisany. Klikamy OK i uruchomi się kreator, w którego oknie wybieramy Application Settings i zaznaczamy opcję Empty Project. Po kilknięciu Finish kreator utworzy nam nowy pusty projekt. Teraz pozostaje nam konfiguracja samego projektu. Musimy dodać wykorzystywane przez nas biblioteki. Robimy to w następujący sposób: w lewym panelu zaznaczamy nasz projekt i klikamy na niego prawym przyciskiem, po czym wybieramy Properties. W okienku wybieramy kartę Configuartion Properties ► Linker ► Input. W polu Additional Dependencies klikamy na wielokropek i dodajemy w nowym oknie nazwy bibliotek d3d9.lib i d3dx9.lib (rys). Klikamy OK i Zastosuj .

Teraz dodajemy plik do projektu plik kodu C++. W panelu projektu klikamy prawym przyciskiem na kartę Header Files i wybieramy Add ► New Item. W oknie wybieramy Header File(.h), nadajemy mu nazwę Aplikacja i klikamy ok. Podobnie robimy z kartą Source Files, z tą różnicą, że zamiast Header File, wybieramy C++ File(.cpp) i nadajemy mu nazwę Okno.
Ufff... dużo pracy, ale dzięki temu możemy z powodzeniem przejść do pisania kodu i nie martwić się o niemiłe niespodzianki.
Wreszcie coś się zaczyna dziać...czyli piszemy kod...
Na początku, zanim zaczniemy tworzyć urządzenie DX-a, potrzebujemy czegoś, w czym moglibyśmy rysować (renderować) nasze obrazy. Dlatego też potrzebne będzie nam zwykłe windowsowe okno (bez względu na to, czy rysujemy w tym okienku, czy na pełnym ekranie). Do jego utworzenia wykorzystamy znienawidzone przez większość programistów WinAPI, czyli interfejs służący do programowania pod Windows. Jako że sama procedura tworzenia okienka to właściwie materiał na osobny artykuł, ograniczę się tylko do omówienia ogólnej organizacji tego fragmentu kodu, bez jego dokładnego analizowania. Bardziej dociekliwym polecam lekturę tutoriali na stronie www.winapi.org.
Otwieramy podwójnym kliknięciem w bocznym panelu plik aplikacja.h i w polu edycji wpisujemy następujący kod:
#include LRESULT WINAPI Komunikaty(HWND hWnd,UINT komunikat,WPARAM wParam, LPARAM lParam); static TCHAR lpszAppName[] = TEXT( "Aplikacja D3D" );
W pliku tym umieszczamy nagłówki i prototypy funkcji, jakie będą nam potrzebne globalnie w całym projekcie. W podobny sposób otwieramy plik Okno.cpp i wpisujemy kod:
#include "Aplikacja.h"
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hpInstance,LPSTR, INT)
{
MSG Komunikat;
WNDCLASS wndclass;
HWND hWnd;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = Komunikaty;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = NULL;
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = lpszAppName;
if(RegisterClass(&wndclass) == 0)
{
MessageBox(hWnd,L"Nie udało się zarejestrować klasy okna",L"Błąd",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
hWnd = CreateWindow(lpszAppName,lpszAppName,WS_OVERLAPPEDWINDOW|
WS_CLIPCHILDREN|WS_CLIPSIBLINGS,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
if(hWnd == NULL)
{
MessageBox(hWnd,L"Nie udało się utowrzyć okna",L"Błąd",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
ShowWindow(hWnd,SW_SHOW);
UpdateWindow(hWnd);
PeekMessage(&Komunikat,0,0,0,PM_REMOVE);
while(Komunikat.message != WM_QUIT)
{
TranslateMessage(&Komunikat);
DispatchMessage(&Komunikat);
PeekMessage(&Komunikat,0,0,0,PM_REMOVE);
}
return 0;
}
LRESULT WINAPI Komunikaty(HWND hWnd,UINT Komunikat,WPARAM wParam, LPARAM lParam)
{
switch (Komunikat)
{
case WM_CLOSE:
NiszczUrzadzenie();
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd,Komunikat,wParam,lParam);
}
Zadaniami tego fragmentu są: zarejestrowanie klasy okna, utworzenie samego okna, obsługa pętli komunikatów oraz reagowanie na same komunikaty. Jak już wspomniałem, po szczegóły działania tego kodu odsyłam do strony www.winapi.org. Większość przedstawionego tu kodu, za wyjątkiem funkcji Komunikaty, nie ulegnie zmianie podczas pisania dalszej części kodu, dlatego z powodzeniem można go skopiować, zapisać i zapomnieć o nim.


