» »

DirectX 9.0 - Osnove programiranja

V prejšnjih dveh člankih smo si ogledali vse novosti, ki jih je Microsoft uvedel v DirectX. Ker verjamem, da mnogi med vami niso ravno programerji, vas pa vseeno zanima, kako izgleda življenje na drugi strani, sem se odločil, da vam to vsaj malo približam. V tem članku bo tako govora o osnovah programiranja s knjižnico Direct3D. Ogledali si bomo, kako narediti enostavno rotirajočo se kocko. Načeloma se bodo v tem članku najbolje znašli tisti, ki znajo nekaj C/C++, vendar upam, da se boste tudi vsi ostali v njem dovolj dobro znašli in iz njega potegnili kaj novega in zanimivega.


Začetek

Če hočemo kar koli programirati s knjižnico DirectX, potrebujemo SDK (Software Development Kit), ki ga lahko vsak najde na tem spletnem mestu. Druga stvar, za katero se moramo odločiti je, kateri programski jezik bomo uporabili. S pomočjo vsega, kar dobimo v DirectX 9.0 SDKju, lahko programiramo v C/C++ in .NET jezikih. Tukaj nastopijo prve težave, saj boste, če nimate ravno Microsoftovega Visual C++, naleteli na težave s statičnimi knjižnicami. Prav tako boste morali knjižnice poiskati sami, če hočete programirati v Delphiju. Za ta članek bo uporabljen kar "dobri stari" Visual C++ 6.0 in, ker se konec koncev hočemo naučiti nekaj o Direct3D, ni ravno pomembno, kateri jezik uporabljamo. Iz tega razloga tudi ne boste slišali nič o Win32 APIju in sistemu sporočil, na katerem delujejo okna. Naprej k zanimivim rečem!


Zagon Direct3D

Celoten DirectX je grajen po COM arhitekturi, vendar nič bati! Vse kar morate vedeti o COMu v Direct3D 9 je to, da je treba vse objekte, ki jih ne potrebujemo več, "releaseati" (sprostiti). Če hočemo uporabljati Direct3D oziroma izvedeti kaj o grafični kartici, moramo kreirati osnovni Direct3D objekt (LPDIRECT3D9 ali IDirect3D9* ali kakorkoli se že pač imenuje v drugih jezikih). To storimo takole:

 
lp3D=Direct3DCreate9(D3D_SDK_VERSION);
S pomočjo tega objekta lahko poizvedujemo po vseh mogočih lastnostih grafične kartice (velikosti tekstur, formati tekstur, grafični načini, podporo shaderjem,...). Ena izmed najbolj zanimivih metod je gotovo GetDeviceCaps, ki nam vrne ogromno podatkov o zmogljivostih grafične kartice ali pa morebitnih softverskih izrisovalnikih (najpogosteje referenčni način). Če nas torej zanimajo lastnosti grafične kartice:
 
D3DCAPS9 caps;
lp3D->GetDeviceCaps(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,&caps);
Ker imamo lahko v računalniku lahko več grafičnih kartic, je prvi parameter številka grafične kartice (v tem primeru D3DADAPTER_DEFAULT ali 0). Drugi parameter pove, da nas zanimajo lastnosti načina HAL (Hardware Abstraction Layer) oziroma grafične kartice. Tretji parameter pa je kazalec na strukturo D3DCAPS9, kamor nam metoda spravi vse podatke. Med podatki, ki jih dobimo v tej strukturi (in jih je ogromno) dobimo tudi: največjo širino tekstur (caps.MaxTextureWidth), največjo višino tekstur (caps.MaxTextureHeigh), največjo globino 3D tekstur (caps.MaxVolumeExtent), verzijo pixel in vertex shaderjev (caps.PixelShaderVersion in caps.VertexShaderVersion),... Dobimo pa tudi precej podatkov v obliki zastavic. To pomeni, da dobimo neko 32-bitno število, ki ima nekatere bite nastavljene (1) in nekaterih ne (0). To število nato primerjamo z neko masko in če se vsi nastavljeni biti v maski in številu ujemajo, potem je to znak, da je tisto, kar maska opisuje, podprto. Tako lahko preverimo naprimer: podporo TruFormu (caps.DevCaps & D3DDEVCAPS_NPATCHES), podporo strojnemu pospeševanju T&L (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT), podporo volumetričnim (3D) teksturam (caps.TextureCaps & D3DPTEXTURECAPS_VOLUMEMAP), ...

Ker pa samo ogledovanje vseh mogočih lastnosti grafične kartice kmalu postane dolgočasno, je počasi čas, da izrabimo nekaj 3D zmogljivosti grafične kartice. Ker osnovni Direct3D objekt že imamo, moramo le še nastaviti svoje zahteve za 3D nastavitve in kreirati Direct3DDevice objekt (LPDIRECT3DDEVICE9 ali IDirect3DDevice9* ali ekvivalentno v drugih jezikih):

 
D3DPRESENT_PARAMETERS param;
ZeroMemory(&param,sizeof(D3DPRESENT_PARAMETERS));
param.BackBufferCount=1;
param.BackBufferWidth=1024;
param.BackBufferHeight=768;
param.BackBufferFormat=D3DFMT_X8R8G8B8;
param.EnableAutoDepthStencil=true;
param.AutoDepthStencilFormat=D3DFMT_D24X8;
param.hDeviceWindow=window;
param.SwapEffect=D3DSWAPEFFECT_FLIP;
 
lp3D->CreateDevice(
	D3DADAPTER_DEFAULT,
	D3DDEVTYPE_HAL,
	window,
	D3DCREATE_MIXED_VERTEXPROCESSING,
	&(param),
	&lpDevice);
Strukturo param najprej izpraznimo, nato pa s param.BackBufferCount=1 povemo, da hočemo samo en back buffer, kamor bomo izrisovali, param.BackBufferWidth=1024 in param.BackBufferHeight=768 povesta željeno resulucijo, param.BackBufferFormat=D3DFMT_X8R8G8B8 pa željeno barvno globino (32bpp) in format, param.EnableAutoDepthStencil=true in param.AutoDepthStencilFormat=D3DFMT_D24X8 povesta Direct3D-ju, da naj sam kreira še z-buffer in da naj bo ta 24-biten brez dela za stencil test. Na koncu nastavimo še param.hDeviceWindow=window in param.SwapEffect=D3DSWAPEFFECT_FLIP, ki povesta, v katero okno želimo renderirati, in pa da naj grafična kartica, če je le možno, za prikaz slike izvede "flip" (torej zamenja le naslova, od koder grafična kartica izrisuje sliko na zaslon in naslovom, kamor mi rišemo našo sliko). Ko imamo strukturo tipa D3DPRESENT_PARAMETERS izpolnjeno, moramo le še klicati lp3D->CreateDevice, ki nam kreira objekt s katerim bomo lahko risali, kreirali teksture in tako naprej. Metoda CreateDevice mora vedeti, katero grafično kartico želimo uporabiti (v našem primeru D3DADAPTER_DEFAULT ali 0), ter v katerem načinu želimo, da grafična kartica deluje (v našem primeru strojno pospeševanje, torej: D3DDEVTYPE_HAL). Četrti parameter pa poleg nekaterih stvari, ki jih tukaj ne potrebujemo, pove tudi kakšen način procesiranja ogljišč hočemo (D3DCREATE_MIXED_VERTEXPROCESSING pomeni, da lahko preklapljamo med programsko in strojno obdelavo ogljišč, če imamo recimo GeForcea 256, ki podpira strojni T&L, ne podpira pa vertex shaderjev, lahko tako strojno pospešujemo stvari, za katere ne potrebujemo vertex shaderjev, kjer pa so vertex shaderji potrebni, pa lahko preklopimo na programsko obdelavo).

Pri tem je potrebno povedati še, da moramo izrecno zahtevati, če ne želimo renderirati v celozaslonskem načinu (param.Windowed=true) in da je v tem primeru najbolje pustiti dimenzije back bufferja (param.BackBufferWidth in param.BackBufferHeight) kar nastavljene na 0, saj bo Direct3D sam ugotovil dimenzije okna. Pomembno je tudi vedeti, da moramo posebej zahtevati, če nočemo čakati na VSYNC (da grafična kartica zamenja sliki samo takrat, ko se katodni žarek vrača na vrh zaslona). To lahko storimo z ukazom param.PresentationInterval=D3DPRESENT_INTERVAL_IMMEDIATE.


Kreiranje sredstev

Sedaj, ko imamo objekta Direct3D in Direct3DDevice, se lahko lotimo kreiranja sredstev (Vertex bufferji, Index bufferji, teksture, ...). Ker hočemo narediti le enostavno kocko, ki se bo rotirala na zaslonu, potrebujemo le en vertex buffer (pomnilnik, kamor bomo shranjevali oblišča) in en index buffer (pomnilnik, kamor bomo shranjevali indekse oglišč, ki bodo sestavljali trikotnike). Vertex buffer kreiramo in napolnimo takole:

 
struct Vertex{
	float x,y,z;
	DWORD color;
};
 
lpDevice->CreateVertexBuffer(
	sizeof(Vertex)*8,
	D3DUSAGE_WRITEONLY,
	D3DFVF_XYZ | D3DFVF_DIFFUSE,
	D3DPOOL_DEFAULT,
	&lpVertices,
	NULL);
 
Vertex *pVertices;
lpVertices->Lock(0,sizeof(Vertex)*8,(void**)&pVertices,0);
pVertices[0].x=-1.0f;	pVertices[0].y=-1.0f;	
pVertices[0].z=1.0f;	pVertices[0].color=0xFF0000FF;
pVertices[1].x=1.0f;	pVertices[1].y=-1.0f;	
pVertices[1].z=1.0f;    pVertices[1].color=0xFF00FF00;
pVertices[2].x=-1.0f;	pVertices[2].y=1.0f;	
pVertices[2].z=1.0f;	pVertices[2].color=0xFFFF0000;
pVertices[3].x=1.0f;	pVertices[3].y=1.0f;	
pVertices[3].z=1.0f;	pVertices[3].color=0xFFFFFFFF;
pVertices[4].x=-1.0f;	pVertices[4].y=-1.0f;	
pVertices[4].z=-1.0f;	pVertices[4].color=0xFF000080;
pVertices[5].x=1.0f;	pVertices[5].y=-1.0f;	
pVertices[5].z=-1.0f;	pVertices[5].color=0xFF008000;
pVertices[6].x=-1.0f;	pVertices[6].y=1.0f;	
pVertices[6].z=-1.0f;	pVertices[6].color=0xFF800000;
pVertices[7].x=1.0f;	pVertices[7].y=1.0f;	
pVertices[7].z=-1.0f;	pVertices[7].color=0xFF808080;
lpVertices->Unlock();
Metoda CreateVertexBuffer sprejme šest parametrov. S prvim parametrom povemo, koliko pomnilnika želimo v bajtih. Drugi parameter pove, da bomo ta vertex buffer uporabljali samo za pisanje (lahko povemo še, da želimo N-zlepke D3DUSAGE_NPATCHES in še nekaj stvari). S tretjim parametrom povemo željen format, ki v našem primeru vsebuje 3D vektor (točka oglišča) in 32-bitno število (barva oglišča). Četrti parameter pa pove še, v katerem "bazenu" želimo naš vertex buffer (D3DPOOL_DEFAULT pomeni, da se grafična kartica sama odloči, kam ga bo postavila in tako običajno konča v video pomnilniku). Šesti parameter pa je vedno neuporabljen. Sedaj moramo vertex buffer le še napolniti s podatki. Ker je delo z grafično kartico zelo paralelizirano, moramo del pomnilka, kjer se vertex buffer nahaja, "zakleniti" (Lock), saj bi se sicer lahko zgodilo, da nam grafična kartica ravno v trenutku, ko bi pisali v vertex buffer, tega premakne. V kakšni drugi situaciji bi se lahko tudi zgodilo, da bi grafična kartica kar izrisala naš vertex buffer, čeprav mi še nebi zaključili s postavljanjem naših ogljišč. Metodi Lock podamo štiri parametre. Prvi pove, pri kateri lokaciji začnemo, drugi pa koliko bajtov želimo zakleniti (v našem primeru zaklenemo ves vertex buffer). Tretji parameter je kazalec, ki nam ga metoda vrne in kamor nato pišemo naša ogljišča. Ta kazalec je veljaven le dokler vertex bufferja ne odklenemo (Unlock), saj se lahko vertex buffer takoj zatem prestavi na povsem drugo lokacijo. Zadnji parameter pa signalizira grafični kartici kaj se bo z vertex bufferjem dogajalo, da se gonilnik lahko bolje znajde. Sedaj moramo kreirati še index buffer, ki pove katera ogljišča bomo povezali v trikotnike:
 
lpDevice->CreateIndexBuffer(
	sizeof(unsigned short)*36,
	D3DUSAGE_WRITEONLY,
	D3DFMT_INDEX16,
	D3DPOOL_DEFAULT,
	&lpIndices,
	NULL);
 
unsigned short *pIndices;
lpIndices->Lock(0,sizeof(unsigned short)*36,(void**)&pIndices,0);
pIndices[0]=1;	pIndices[1]=2;	pIndices[2]=0;
pIndices[3]=3;	pIndices[4]=2;	pIndices[5]=1;
pIndices[6]=0;	pIndices[7]=2;	pIndices[8]=4;
pIndices[9]=2;	pIndices[10]=6;	pIndices[11]=4;
pIndices[12]=5;	pIndices[13]=3;	pIndices[14]=1;
pIndices[15]=5;	pIndices[16]=7;	pIndices[17]=3;
pIndices[18]=5;	pIndices[19]=4;	pIndices[20]=6;
pIndices[21]=5;	pIndices[22]=6;	pIndices[23]=7;
pIndices[24]=2;	pIndices[25]=3;	pIndices[26]=6;
pIndices[27]=6;	pIndices[28]=3;	pIndices[29]=7;
pIndices[30]=1;	pIndices[31]=0;	pIndices[32]=4;
pIndices[33]=1;	pIndices[34]=4;	pIndices[35]=5;
lpIndices->Unlock();
Sintaksa je praktično identična. Edina razlika je, da tukaj namesto formata oglišč podamo format indeksov, ki so v našem primeru 16-bitna cela števila (lahko so še 32-bitna cela števila) in lahko torej uporabimo do 65536 različnih ogljišč v vertex bufferju. V kakšni bolj resni aplikaciji se običajno vso to vsebino prebere iz kakšne datoteke, ki jo naredimo s pomočjo kakšnega izmed 3D modelirnih programov.


Še zadnje nastavitve

Sedaj je potrebno poskrbeti le še za nekaj nastavitev in že bomo lahko zaželi izrisovati naše slike. Ker smo v 3D svetu, moramo postaviti kamero (oziroma opazovalca):

 
D3DXMATRIX view;
 
D3DXMatrixLookAtLH(
	&view,
	&D3DXVECTOR3(0.0f,0.0f,-1.0f),
	&D3DXVECTOR3(0.0f,0.0f,0.0f),
	&D3DXVECTOR3(0.0f,1.0f,0.0f));
lpDevice->SetTransform(D3DTS_VIEW,&view);
Funkcija D3DXMatrixLookAtLH je iz pomožne knjižnice Direct3DX in nam zgradi matriko, ki predstavlja usmerjenost naše kamere. Kamera je postavljena v točki 0.0f,0.0f,-1.0f in gleda proti 0.0f,0.0f,0.0f. Pomemben je še en vektor, ki pove v kateri smeri je "gor" in sicer 0.0f,1.0f,0.0f. Ker se kamera ne bo premikala (bomo raje premikali svet okrog nje) jo lahko kar pošljemo grafični kartici, kar storimo z metodo SetTransform. Sedaj moramo nastaviti še prespektivo:
 
D3DXMatrixPerspectiveFovLH(
	&projection,
	DegToRad(60.0f),
	1.3333f,
	0.1f,
	100.0f);
lpDevice->SetTransform(D3DTS_PROJECTION,&projection);
Zopet uporabimo funkcijo iz pomožne knjižnice Direct3DX in sicer D3DXMatrixPerspectiveFovLH. Drugi parameter je vidni kot, ki je v našem primerju 60 stopinj. Tretji parameter je razmerje med širino in višino back bufferja (v bistvu resulucije). Ker pa delamo v celozaslonskem načinu z znano ločljivostjo (1024x768), je to razmerje že znano. Ta parameter v bistvu skrbi, da so enote v smeri X enake tistim v smeri Y. Če bi ta parameter zmanjšali, bi sliko raztegnili v X smeri, če pa bi ga povečali, bi sliko raztegnili v Y smeri. Zadnje dva parametra sta še near clipping plane in far clipping plane, ki povesta, da ne rišemo ničesar, kar je po z-ju bliže od 0.1f in bolj oddaljeno od 100.0f. Ker se tudi perspektiva ne spreminja več, jo lahko nastavimo kar tukaj z metodo SetTransform. Sedaj grafični kartici le še povemo, iz katerega vertex bufferja in index bufferja naj riše, nastavimo format oglišč in izklopimo senčenje, ki ga ne potrebujemo.
 
lpDevice->SetStreamSource(0,lpVertices,0,sizeof(Vertex));
lpDevice->SetIndices(lpIndices);
 
lpDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
lpDevice->SetRenderState(D3DRS_LIGHTING,false);
Metoda SetStreamSource za prvi parameter vzame številko toka (streama), kateremu želimo nastaviti vertex buffer (drugi parameter). Tretji parameter je odmik od začetka vertex bufferja do lokacije, kjer se začnejo oglišča. Ker pa tega v našem primeru ne potrebujemo, ga pustimo kar na 0. Zadnji parameter pove še koliko bajtov zasede vsako oglišče. To, da imamo lahko več tokov (streamov), je zelo pomembno, če vedno spreminjamo samo določen del vertex bufferja. V tem primeru lahko ta velik vertex buffer razbijemo na dva (ali več) manjših in spreminjamo samo tiste, ki jih moramo, statičnih delov pa se ne dotikamo. Z metodo SetIndices nastavimo še indekse za naše trikotnike. Kljub temu, da smo format oglišč že enkrat nastavili (pri kreiranju vertex bufferja), ga moramo sedaj še enkrat, saj se je morda spremenil s tem, ko smo več različnih vertex bufferjev sestavili v enega s pomočjo tokov (stremov). To storimo z metodo SetFVF, ki obenem tudi pove, da uporabljamo običajen T&L (Transformation & Lighting) in ne vertex shaderjev. Metoda SetFVF tudi ni edina metoda s katero lahko, na tem mestu, povemo format oglišč. Če bi uporabljali kakšne bolj zapletene formate in vertex shaderje, bi se morali zateči k SetVertexDeclaration, za katero pa je potrebno najprej kreirati format podobno kot vertex buffer in index buffer. Metoda SetRenderState skrbi za marsikatero lastnost renderiranja. Z njeno pomočjo lahko nastavimo renderiranje žičnega modela, glajenje črt, operacijo med trenutno renderiranimi pikami in tistimi, ki so že v pomnilniku (blending),... Med drugim lahko izklopimo tudi senčenje, ker ga ne potrebujemo, saj nimamo nobene luči.


Izrisovanje

No, sedaj pa še najbolj zanimiv del - risanje! Ta del kode se bo dejansko izvajal v zanki, s čimer dosežemo animacijo. Ker želimo, da se naša kocka vrti in ker je nekatere stvari v računalništvu lažje narediti, če zavrtimo "svet" namesto kamere, moramo sedaj pripraviti še eno matriko:

 
count+=0.8f;
D3DXMatrixIdentity(&world);
D3DXMatrixMultiply(&world,&world,
	D3DXMatrixRotationX(&temp,DegToRad(count)));
D3DXMatrixMultiply(&world,&world,
	D3DXMatrixRotationY(&temp,DegToRad(count*1.25f)));
D3DXMatrixMultiply(&world,&world,
	D3DXMatrixTranslation(&temp,0.0f,0.0f,3.0f));
Najprej v matriko world zapišemo identiteto (malo matematike: identiteta je preslikava ki vsak element preslika sam vase). Nato matriko množimo z matriko, rotirano okrog osi X, dobljeno matriko zopet množimo še z matriko, rotirano okrog osi Y. To matriko pa nato množimo še enkrat s translacijsko matriko (povedano po domače: matrika ("svet"), ki je prestavljena za nek vektor). Vrstni red operacij je zelo pomemben! V zgornjem primeru si je najbolje predstavljati stvar takole: najprej se prestavimo za 3 enote naprej (v zaslon), potem se po osi Y zavrtimo za nek kot, ter nazadnje še po osi X zopet za nek kot. Sedaj pa še zadnji del do vrteče se kocke:
 
lpDevice->BeginScene();
lpDevice->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,0,1.0f,0);
 
lpDevice->SetTransform(D3DTS_WORLD,&world);		
lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,8,0,12);
 
lpDevice->EndScene();
lpDevice->Present(NULL,NULL,NULL,NULL);
Metodi BeginScene in EndScene signalizirata grafični kartici, da bomo začeli oziroma končali z izrisovanjem. Metoda Clear nam izprazne back buffer (ga napolni z vrednostjo 0 - črna) in izprazne z-buffer (vrednost 1.0f - največja globina). Sedaj moramo na grafično kartico poslati še našo matriko, ki bo skrbela za obračanje kocke, kar zopet storimo z metodo SetTransform. Ker smo vse potrebno za izris naše kocke že nastavili (v našem primeru le vertex buffer in index buffer, ter nekaj nastavitev za renderiranje), je počasi že čas, da jo tudi izrišemo. To storimo z metodo DrawIndexedPrimitive, ki ji v prvem parametru povemo, da rišemo seznam trikotnikov. Drugi parameter pove odmik od začetka index bufferja do prvega indeksa v bajtih, tretji parameter pa kateri je najmanjši indeks, ki ga uporabimo med trikotniki, ki jih izrišemo (ni nujno, da izrišemo vse trikotnike iz index bufferja). Četrti parameter je število oglišč uporabljenih v tem klicu metode. Peti parameter je zopet povezan z index bufferjem in tokrat pove lokacijo, kjer začne metoda brati indekse. Zadnji parameter pa pove še koliko primitivov (v našem primeru trikotnikov) bomo izrisali. Drugi, tretji in peti parameter so si navidez zelo podobni in trije so tu predvsem zato, da se lahko izognemo odvečnim transformacijam pri softwareskem T&L, ki bi sicer predelal celoten vertex buffer, ne glede na to, koliko oglišč je bilo dejansko indeksiranih (in posledično uporabljenih). Za konec moramo našo sliko še prikazati na zaslon, kar pa storimo z metodo Present (tisti štirje parametri so pomembni, če hočemo v okenskem načinu izrisovati na več različnih oken. V celozaslonskem načinu pa nimajo kakšnega uporabnega pomena).

Zna se zgoditi, v primeru risanja več kot le nekaj trikotnikov in beležinja časovne zahtevnosti metod z preprosto "štoparico", da nas bo "štoparica" hotela prepričati, da je časovno najpožrešnejši del klic metode EndScene ali Present, kar pa je le delno res. Kot sem že dostikrat omenil, so grafične kartice zelo paralelizirane in ob klicu metode DrawPrimitive ali DrawIndexedPrimitive začnejo izrisovati, vendar se metoda TAKOJ vrne in lahko tako CPU pride do metode EndScene ali Present precej preden je grafična kartica dejansko končala z izrisom. Ker mora sedaj svoje delo dokončati, bo ena izmed teh dveh metod čakala, dokler grafična kartica ne konča. Priporočljivo je, da vsaka resna aplikacija pred klicem EndScene in Present naredi čim več opravil za katera potrebuje CPU (Ta enostaven programček ni tak).


Vse skupaj

Celotna izvorna koda naše enostavne aplikacije (260 vrstic):

 
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
 
// Objekti Direct3D
LPDIRECT3D9 lp3D;
LPDIRECT3DDEVICE9 lpDevice;
LPDIRECT3DVERTEXBUFFER9 lpVertices;
LPDIRECT3DINDEXBUFFER9 lpIndices;
 
bool Keys[256];
 
// Pomožni makroti
#define SAFE_RELEASE(a)	if (a) { (a)->Release(); (a)=NULL; }
#define DegToRad(a) (0.017453292519943295769236907684886f*a)
 
// Struktura, ki predstavlja naše ogljišče
struct Vertex{
	float x,y,z;
	DWORD color;
};
 
// Funkcija, ki procesira sporočila za okno
long WINAPI WindowProc(HWND window,
	UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch (msg)
	{
	case WM_SETCURSOR:
		SetCursor(NULL);
		break;
 
	case WM_KEYUP:
		Keys[wParam]=false;
		break;
 
	case WM_KEYDOWN:
		Keys[wParam]=true;
		break;
 
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
 
	default:
		return DefWindowProc(window,msg,wParam,lParam);
		break;
	}
	return 0;
}
 
// Glavna funkcija programa
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int)
{
	WNDCLASS wc;
	HWND window;
	MSG msg;
 
	// Registriramo razred za naše okno
	wc.style=CS_VREDRAW | CS_HREDRAW;
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)COLOR_WINDOW;
	wc.hCursor=LoadCursor(NULL,IDC_ARROW);
	wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	wc.hInstance=hInstance;
	wc.lpfnWndProc=WindowProc;
	wc.lpszClassName="Sample";
	wc.lpszMenuName=NULL;
	RegisterClass(&wc);
 
	// Kreiramo naše okno
	window=CreateWindowEx(
		0,"Sample","Osnove Direct3D9",WS_OVERLAPPED,
		CW_USEDEFAULT,CW_USEDEFAULT,400,400,
		NULL,NULL,hInstance,NULL);
	ShowWindow(window,SW_NORMAL);
	UpdateWindow(window);
 
	// Kreiramo Direct3D
	lp3D=Direct3DCreate9(D3D_SDK_VERSION);
	if (!lp3D)
	{
		MessageBox(NULL,"Napaka pri kreiranju Direct3D!",
				"Napaka!",MB_OK | MB_ICONWARNING);
		UnregisterClass("Sample",hInstance);
		return 0;
	}
 
	// Nastavimo željene 3D lastnosti okna
	D3DPRESENT_PARAMETERS param;
	ZeroMemory(&param,sizeof(D3DPRESENT_PARAMETERS));
	param.BackBufferCount=1;
	param.BackBufferWidth=1024;
	param.BackBufferHeight=768;
	param.BackBufferFormat=D3DFMT_X8R8G8B8;
	param.EnableAutoDepthStencil=true;
	param.AutoDepthStencilFormat=D3DFMT_D24X8;
	param.hDeviceWindow=window;
	param.SwapEffect=D3DSWAPEFFECT_FLIP;
 
	/* Kreiramo objekt, ki komunicira 
	   neposredno z grafično kartico */
	lp3D->CreateDevice(
		D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,
		window,
		D3DCREATE_MIXED_VERTEXPROCESSING,
		&param,
		&lpDevice);
	if (!lpDevice)
	{
		MessageBox(NULL,"Ni bilo možno zagnati 3D načina!",
				"Napaka!",MB_OK | MB_ICONWARNING);
		SAFE_RELEASE(lp3D);
		UnregisterClass("Sample",hInstance);
		return 0;
	}
 
	// Rezerviramo pomnilnik za 8 ogljišč na grafični kartici
	lpDevice->CreateVertexBuffer(
		sizeof(Vertex)*8,
		D3DUSAGE_WRITEONLY,
		D3DFVF_XYZ | D3DFVF_DIFFUSE,
		D3DPOOL_DEFAULT,
		&lpVertices,
		NULL);
 
	// V rezerviran pomnilnik zapišemo 8 ogljišč kocke
	Vertex *pVertices;
	lpVertices->Lock(0,sizeof(Vertex)*8,(void**)&pVertices,0);
	pVertices[0].x=-1.0f;	pVertices[0].y=-1.0f;	
	pVertices[0].z=1.0f;	pVertices[0].color=0xFF0000FF;
	pVertices[1].x=1.0f;	pVertices[1].y=-1.0f;	
	pVertices[1].z=1.0f;	pVertices[1].color=0xFF00FF00;
	pVertices[2].x=-1.0f;	pVertices[2].y=1.0f;	
	pVertices[2].z=1.0f;	pVertices[2].color=0xFFFF0000;
	pVertices[3].x=1.0f;	pVertices[3].y=1.0f;	
	pVertices[3].z=1.0f;	pVertices[3].color=0xFFFFFFFF;
	pVertices[4].x=-1.0f;	pVertices[4].y=-1.0f;	
	pVertices[4].z=-1.0f;	pVertices[4].color=0xFF000080;
	pVertices[5].x=1.0f;	pVertices[5].y=-1.0f;	
	pVertices[5].z=-1.0f;	pVertices[5].color=0xFF008000;
	pVertices[6].x=-1.0f;	pVertices[6].y=1.0f;	
	pVertices[6].z=-1.0f;	pVertices[6].color=0xFF800000;
	pVertices[7].x=1.0f;	pVertices[7].y=1.0f;	
	pVertices[7].z=-1.0f;	pVertices[7].color=0xFF808080;
	lpVertices->Unlock();
 
	// Rezerviramo pomnilnik za 36 indeksov
	lpDevice->CreateIndexBuffer(
		sizeof(unsigned short)*36,
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_DEFAULT,
		&lpIndices,
		NULL);
 
	// Sestavimo trikotnike v kocko iz 8 ogljišč
	unsigned short *pIndices;
	lpIndices->Lock(0,sizeof(unsigned short)*36,
		(void**)&pIndices,0);
	pIndices[0]=1;	pIndices[1]=2;	pIndices[2]=0;
	pIndices[3]=3;	pIndices[4]=2;	pIndices[5]=1;
	pIndices[6]=0;	pIndices[7]=2;	pIndices[8]=4;
	pIndices[9]=2;	pIndices[10]=6;	pIndices[11]=4;
	pIndices[12]=5;	pIndices[13]=3;	pIndices[14]=1;
	pIndices[15]=5;	pIndices[16]=7;	pIndices[17]=3;
	pIndices[18]=5;	pIndices[19]=4;	pIndices[20]=6;
	pIndices[21]=5;	pIndices[22]=6;	pIndices[23]=7;
	pIndices[24]=2;	pIndices[25]=3;	pIndices[26]=6;
	pIndices[27]=6;	pIndices[28]=3;	pIndices[29]=7;
	pIndices[30]=1;	pIndices[31]=0;	pIndices[32]=4;
	pIndices[33]=1;	pIndices[34]=4;	pIndices[35]=5;
	lpIndices->Unlock();
 
	D3DXMATRIX view,world,projection,temp;
 
	// Nastavimo matriko za kamero
	D3DXMatrixLookAtLH(
		&view,
		&D3DXVECTOR3(0.0f,0.0f,-1.0f),
		&D3DXVECTOR3(0.0f,0.0f,0.0f),
		&D3DXVECTOR3(0.0f,1.0f,0.0f));
 
	// Nastavimo matriko za perspektivo
	D3DXMatrixPerspectiveFovLH(
		&projection,
		DegToRad(60.0f),
		1.3333f,
		0.1f,
		100.0f);
 
	// Pošljemo obe matriki grafični kartici
	lpDevice->SetTransform(D3DTS_VIEW,&view);
	lpDevice->SetTransform(D3DTS_PROJECTION,&projection);
 
	float count=0.0f;
 
	// Nastavimo vir ogljišč
	lpDevice->SetStreamSource(0,lpVertices,0,sizeof(Vertex));
 
	// Nastavimo vir indeksov
	lpDevice->SetIndices(lpIndices);
 
	// Nastavimo format ogljišč
	lpDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
 
	// Izključimo senčenje
	lpDevice->SetRenderState(D3DRS_LIGHTING,false);
 
	while (1)
	{
		// Procesiramo sporočila, ki jih dobi naše okno
		while (PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
		{
			if (GetMessage(&msg,NULL,0,0))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			} else
			{
				UnregisterClass("Sample",hInstance);
				return 0;
			}
		}
 
		/* Zarotiramo in prestavimo naše izhodišče, 
		   kjer bomo risali kocko */
		count+=0.8f;
		D3DXMatrixIdentity(&world);
		D3DXMatrixMultiply(&world,&world,
			D3DXMatrixRotationX(&temp,
			DegToRad(count)));
		D3DXMatrixMultiply(&world,&world,
			D3DXMatrixRotationY(&temp,
			DegToRad(count*1.25f)));
		D3DXMatrixMultiply(&world,&world,
			D3DXMatrixTranslation(&temp,
				0.0f,0.0f,3.0f));
 
		// Začnemo risati novo sliko
		lpDevice->BeginScene();
 
		// Izpraznemo back buffer in z buffer
		lpDevice->Clear(0,NULL,
			D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
			0,1.0f,0);
 
		// Nastavimo naše izhodišče
		lpDevice->SetTransform(D3DTS_WORLD,&world);		
 
		// Izrišemo 12 trikotnikov
		lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
			0,0,8,0,12);
 
		// Končamo z risanjem slike
		lpDevice->EndScene();
 
		// Prikažemo rezultat, ki smo ga narisali
		lpDevice->Present(NULL,NULL,NULL,NULL);
 
		/* V primeru, da je uporabinik pritisnil ESC, 
 	           zaključimo */
		if (Keys[VK_ESCAPE])
		{
			SAFE_RELEASE(lpVertices);
			SAFE_RELEASE(lpIndices);
			SAFE_RELEASE(lpDevice);
			SAFE_RELEASE(lp3D);
			PostMessage(window,WM_DESTROY,0,0);
		}
	}
 
	return 0;
}

Ter za vse, ki jih zanima kako zadeva izgleda na zaslonu


Zaključek

Pa je še en članek za nami. Upam, da mi je uspelo dokazati, da Direct3D ni tak bav-bav za programiranje in morda koga celo navdušiti, da se poda v te vode. Za vse druge pa: sedaj veste, kako in kje se začne. Morda se komu zdi izdelava preproste kocke preveč enostavna, vendar je dosti bolje začeti in končati lažje stvari kot pa začeti in obupati pri težjih stvareh.

Mimogrede, delujoča verzija programa je na voljo ob članku, prav tako tudi izvorna koda.

Čudežno popotovanje skozi grafični cevovod

Čudežno popotovanje skozi grafični cevovod

Trenutno očitno živimo v obdobju, ko razni *PU-ji (Processing Unit) rastejo kot gobe po dežju. Nedolgo tega je tržne police ugledal prvi pospeševalnik fizikalnih izračunov (PPU ali Physic Processing Unit) PhysX podjetja Ageia. In ker to očitno še vedno ni dovolj, ...

Preberi cel članek »

Novosti DirectXa 9 - 1. del

Novosti DirectXa 9 - 1. del

No pa smo vendarle dočakali novo, dolgo pričakovano, različico knjižnice DirectX, ki jo je zlobni Microsoft, bolj ali manj skrbno skrival pred radovednimi očmi javnosti. V približno enaki tajnosti sta nastala tudi ta dva članka, ki vam bosta, vsaj upam tako, uspela ...

Preberi cel članek »

ATI Radeon 9700 - Nov kralj v grafični deželi

ATI Radeon 9700 - Nov kralj v grafični deželi

Če ne živite ravno v kakšnem zakotnem pragozdu ter lahko berete ta članek, potem ste gotovo že slišali, da je ATi izdal nov grafični čip R300, ki so ga poimenovali Radeon 9700. Kot verjetno že veste, novi Radeon s precejšjno lahkoto pomete z vso konkurenco ...

Preberi cel članek »

Register Combiners vs. Pixel Shaders

Register Combiners vs. Pixel Shaders

Tokrat bo članek nekoliko bolj tehnične narave. Namen tega članka pa je prikazati kontrast med register combinerji (na primeru NVidine implementacije) in pixel shaderji v DirectX 8.x oziroma klasičnim SetTextureStageState mehanizmom v DirectXu. Za vse, ki se bodo ustrašili, ...

Preberi cel članek »

DirectX 8: 3D teksture, multi-sampling in point sprites

DirectX 8: 3D teksture, multi-sampling in point sprites

V prejšnjem članku smo si ogledali pixel in vertex shaderje, ki v svet grafike za osebne računalnike prinašajo programabilnost. V tem članku pa si bomo ogledali še dve veliki novosti v DirectX 8.0 - podpora za 3D teksture in multi-sampling. Če bodo vertex ...

Preberi cel članek »