@Passwd
Nije ovo Win32 API tutorial nego DirectX tutorial. Win32 API cemo koristiti samo za kreiranje prozora i to je to. Kasnije cemo koristiti DirectX za kreiranje GUI (dugmici, labeli, liste...)... to je nesto najblize onoga sto zelis... ali lepse izgleda
--------------------
Kada znamo kako da kreiramo prozor pocecemo sa osnovnim stvarima sa DirectX-om. Prvo cemo se okrenuti Direct3D delu. Ovaj deo DirectX-a je zaduzen za prezentaciju 3D i 2D scena. Sadrzi funkcije za kreiranje objekata, shadera, za formiranje matrica, itd...
Krenucemo sa tutorialima koji idu sa DirectX SDK. Prvi tutorial pokazuje kako se Direct3D inicijalizuje.
Klasa prozora kada je u pitanju prozor u kojm ce se prikazivati sadrazj koji crta Direct3D treba da izgleda ovako:
FillChar(WndClass, SizeOf(WndClass), 0);
WndClass.cbSize := SizeOf(WndClass);
WndClass.style := CS_CLASSDC;
WndClass.lpfnWndProc := @WndProc;
WndClass.hInstance := HInstance;
WndClass.hCursor := LoadCursor(0, IDC_ARROW);
WndClass.lpszClassName := 'MojD3DProzor';
style smo postavili na CS_CLASSDC sto omogucava pravilno crtanje kada vise thread-ova pokusa nesto da iscrta. Posto je DirectX multithread-ed onda je ova opcija ono sto mu treba.
Modifikovacemo kod iz prvog tutoriala tako da klasa bude definisana kao sto sam objasnio.
Da bi smo mogli da koristimo D3D funkcije moramo u uses deo dodati i
Direct3D9 unit.
Bice nam potrebne jos 2 globalne promenljive:
pD3D: IDirect3D9;
pd3dDevice: IDirect3DDevice9;
Ove dve promenljive ce drzati podatke o Direct3D objektu i on objektu koji ce biti zaduzen za iscrtavanje svega.
Sada cemo dodati funkciju koja ce pokrenuti Direct3D. Nazvacemo je
SetupD3D i kao rezultat ce vracati podatak tipa
HRESULT.
function SetupD3D: HRESULT;
Direct3D objekat dobijamo vrlo lako... pozivom samo jedne funkcije:
pD3D := Direct3DCreate9(D3D_SDK_VERSION);
Sledece sto treba da uradimo je da kreiramo i objekat koji ce sve crtati (taj objekat se zove Direct3D Device ili skraceno D3DDevice). Da bi smo mogli da kreiramo D3DDevice potrebno je da prosledimo Direct3D objektu sta tacno zelimo. To prosledjujemo jednom promenljivom tipa
TD3DPresentParameters.
TD3DPresentParameters = packed record
BackBufferWidth: LongWord;
BackBufferHeight: LongWord;
BackBufferFormat: TD3DFormat;
BackBufferCount: LongWord;
MultiSampleType: TD3DMultiSampleType;
MultiSampleQuality: DWORD;
SwapEffect: TD3DSwapEffect;
hDeviceWindow: HWND;
Windowed: Bool;
EnableAutoDepthStencil: Bool;
AutoDepthStencilFormat: TD3DFormat;
Flags: LongInt;
FullScreen_RefreshRateInHz: LongWord;
PresentationInterval: LongWord;
end;
BackBufferWidth i BackBufferHeight predstavljaju zeljenu sirinu i visinu backbuffer-a (backbuffer je deo memorije koji se koristi za pripremanje slike za prikaz).
BackBufferFormat oznacava format backbuffera.
BackBufferCount je broj backbuffera koje zelimo.
MultiSampleType oznacava nacin na koji ce slika da bude "ulepsana" (da se ne vide ostro ivice objekata).
MultiSampleQuality je kvalitet ulepsavanja slike.
SwapEffect odredjuje nacin na koji ce se backbufferi menjati.
hDeviceWindow je prozor koji ce prikazivati Direct3D sadrzaj.
Windowed oznacava da li ce prikaz biti fullscrene ili u prozoru.
EnableAutoDepthStencil govori Direct3D-u da li da automatski brine kako se iscrtavaju objekti u zavisnosti od udaljenosti od kamere.
AutoDepthStencilFormat odredjuje preciznost odredjivanja kada je EnableAutoDepthStencil = True.
Flags su razni flag-ovi koji uticu na unutrasnju organizaciju memorije u Direct3D.
FullScreen_RefreshRateInHz mora biti 0 ako se sve prikazuje u prozoru, a ako se prikazuje u fullscreen-u onda predstavlja zenjeni refresh.
PresentationInterval oznacava koliko brzo se iscrtava backbufer (da li se ceka refresh ili ne).
Za vise informacija pogledati
ovde.
Mi cemo podesiti podatke tako da se skoro sve uzima od prozora u kojem ce se crtati:
FillChar(d3dpp, SizeOf(d3dpp), 0);
d3dpp.Windowed := True;
d3dpp.SwapEffect := D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat := D3DFMT_UNKNOWN;
Sada kreiramo D3DDevice funkcijom
pD3D.CreateDevice. Ona uzima 6 parametara.
Prvi parametar predstavlja graficku karticu koju zelimo da koristimo. Za ovaj tutorial cemo koristiti vrednost D3DADAPTER_DEFAULT sto znaci da koristimo primarnu graficku karticu.
Drugi parametar govori da li zelimo da koristimo hardversko ubranje ili ne ili da koristimo software-ski emulator koji podrzava sve mogucnosti Direct3D-a (jaaaaako sporo). Postavicemo ovu vrednost na D3DDEVTYPE_HAL sto znaci da cemo koristiti hardversko ubrzanje ako je moguce.
Sledeci parametar je prozor u kojem ce se crtati.
Cetvrti parametar predstavlja nacin na koji ce podaci biti obradjeni. Postavicemo ga na D3DCREATE_SOFTWARE_VERTEXPROCESSING za slucaj da neko ima malo stariju karticu tako da ce se obradjivanje vrsiti software-ski.
Poslednja dva parametra predstavljaju promenljivu u kojoj smo rekli kakav D3DDevice zelimo i promenljivu u kojoj ce D3DDevice biti smesten.
Za vise informacija pogledajte
ovde.
pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Wnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
@D3DPP, pd3dDevice);
Uvek je dobra praksa proveravati rezultat funkcija DirectX-a jer je moguce da ne uspeju da izvrse ono sto se zahteva tako da ce nasa konacna funkcija izgledati ovako:
function SetupD3D: HRESULT;
var
D3DPP: TD3DPresentParameters;
begin
Result := E_FAIL;
pD3D := Direct3DCreate9(D3D_SDK_VERSION);
if pD3D = nil then
Exit;
FillChar(D3DPP, SizeOf(D3DPP), 0);
D3DPP.hDeviceWindow := Wnd;
D3DPP.Windowed := True;
D3DPP.SwapEffect := D3DSWAPEFFECT_DISCARD;
D3DPP.BackBufferFormat := D3DFMT_UNKNOWN;
Result :=
pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Wnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
@D3DPP, pd3dDevice);
if FAILED(Result) then
begin
Result := E_FAIL;
Exit;
end;
Result := S_OK;
end;
Ovu funkciju pozivamo odmah posle kreiranja prozora i ako se sve zavrsi kako treba obradjujemo poruke prozora:
if SUCCEEDED(SetupD3D) then
begin
ShowWindow(Wnd, SW_SHOWDEFAULT);
UpdateWindow(Wnd);
while GetMessage(Msg, 0, 0, 0) do
DispatchMessage(Msg);
end;
Kada imamo D3DDevice mozemo da pocnemo i da crtamo. Za sada cemo samo da obojimo ceo prozor jednom bojom... recimo crvenom. Napisacemo funkciju
Oboji koja ce to raditi i zatim je objasniti.
procedure Oboji;
begin
pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,0,0), 1.0, 0);
pd3dDevice.Present(nil, nil, 0, nil);
end;
Funkcija pd3dDevice.Clear sluzi za popunjavanje buffer-a odredjenom vrednoscu.
Drugi parametar predstavlja niz TRect promenljivih koje oznacavaju koji deo buffer-a treba da se obrise. Ako je ovaj parametar nil tada se brise ceo buffer.
Prvi parametar oznacava koliko TRect objekata ima u drugom parametru.
Treci parametar oznacava sta treba da se obrise (pozadina, z-buffer ili stencil-buffer).
Cetvrti parametar je bitan samo ako se birse pozadina i odredjuje kojom bojom da se pozadina obrise.
Peti je bitan samo kada se brise z-buffer i odredjuje verdnost koja treba da se upise u z-buffer.
Sesti je bitan samo kada se brise stencil-buffer.
Za vise informacija pogledajte
ovde.
pd3dDevice.Present funkcija prikazuje buffer na ekran ili u prozor.
Prvi, drugi i cetvrti parametar moraju da budu nil osim ako je D3DDevice kreiran tako da koristi D3DSWAPEFFECT_COPY nacin menjanja backbuffer-a i tada oznacavaju delove koji trebaju da budu iscrtani.
Treci parametar moze predstavljati novi prozor u koji zelimo iscrtati ono sto je Direct3D iscrtao.
Za vise informacija pogledajte
ovde.
Da bi se iscrtavalo ono sto DirectX crta moramo dodati i obradu poruke
WM_PAINT:
WM_PAINT:
begin
Oboji;
ValidateRect(Wnd, nil);
Result := 0;
end;
Pozivamo Oboji i time se sve iscrtava, ali Windows ne zna koji smo deo iscrtali pa mu funkcijom ValidateRect to saopstavamo.
Ostalo je jos samo da oslobodimo memoriju kada zavrsimo sa radom.
procedure CloseD3D;
begin
pd3dDevice:= nil;
pD3D:= nil;
end;
Ovo je sasvim dovoljno da se Direct3D i D3DDevice objekti izbrisu iz memorije. Ovu funkciju bi trebalo dodati pre gasenja prozora:
WM_DESTROY:
begin
CloseD3D;
PostQuitMessage(0);
Result:= 0;
end;
To je to

Kada pokrenemo program imamo crvenu pozadinu koju je iscrtao Direct3D
Source (Delphi) |
Source (Lazarus) |
Bin
Dopuna: 16 Jan 2006 19:33
Ovog puta cemo koristiti Vertex Buffer za iscrtavanje poligona. Pixel je tacka u 2D prostoru (X i Y koordinata), a Vertex je tacka u 3D prostoru (X, Y i Z koordinata). U sustini vertex buffer je niz vertex-a koje ce Direct3D da iscrta na neki od mogucih nacina (kao tacke, kao linije (spaja 2 tacke) ili kao trougao(spaja 3 tacke)).
Menjacemo kod koji smo imali u proslom tutorialu. Promenljiva koja ce sadrzati vertex buffer ce biti tipa
IDirect3DVertexBuffer9 i postavicemo je kao globalnu promenljivu.
var
pVB: IDirect3DVertexBuffer9;
Direct3D koristi Flexible Vertex Format (FVF). To omogucava da u vertex-u prosledimo samo podatke koje zelimo. Za vise informacija o FVF pogledajte
ovde. U ovom primeru cemo koristiti transformisane vertex-e sto znaci da se u obzir uzimaju X i Y koordinate i predstavljaju prave koordinate (Direct3D ih nece menjati). Vertex ce takodje sadrzati i podatak o boji. Napravicemo tip podatka koji ce cuvati te podatke:
type
TCustomVertex = packed record
X, Y, Z, RHW: Single;
Color: DWORD;
end;
Svi podaci su jasni... X, Y su koordinate na kojima ce vertex biti na ekranu, Z i RHW parametri ne uticu na poziciju vertex-a i Color je boja. (Ne znam sta je tacno RHW parametar, ali je obavezan i skoro uvek se postavlja na 1).
Imamo spreman tip podatka koji cemo ubaciti u vertex buffer... treba nam samo jos i nacin da objasnimo buffer-u koje mu podatke dajemo. To se radi tako sto u jednoj promenljivoj stavljamo sta ce sve vertex sadrzati:
const
D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZRHW or D3DFVF_DIFFUSE);
D3DFVF_XYZRHW konstantom oznacavamo koje cemo koordinate koristiti, a
D3DFVF_DIFFUSE govori da uz koordinatu ide i boja. To je dovoljno da vertex buffer zna sta mu dajemo.
Sada imamo sve sto nam je potrebno da popunimo vertex buffer podacima koje zelimo. Podatke cemo spremiti u jednu promenljivu:
const
Vertices: array[0..2] of TCustomVertex = (
(X : 200.0; Y : 20.0; Z : 1; RHW : 1; Color: $ffffff00),
(X : 350.0; Y : 250.0; Z : 1; RHW : 1; Color: $ff00ff00),
(X : 50.0; Y : 250.0; Z : 1; RHW : 1; Color: $ff0000ff)
);Napisacemo funkciju
PopuniBuffer koja ce to uraditi:
function PopuniBuffer: HRESULT;
var
pVertices: Pointer;
begin
Result:= E_FAIL;
if FAILED(
pd3dDevice.CreateVertexBuffer(
3 * SizeOf(TCustomVertex),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, pVB, nil)) then
Exit;
if FAILED(
pVB.Lock(0, SizeOf(Vertices), pVertices, 0)) then
Exit;
CopyMemory(pVertices, @Vertices, SizeOf(Vertices));
pVB.Unlock;
Result:= S_OK;
end;
Vertex buffer kreiramo funkcijom
pd3dDevice.CreateVertexBuffer.
Prvi parametar je velicina buffer-a. Zelimo da u nasem buffer-u stane bar 3 vertexa i zato rezervisemo memoriju velicine 3 puta vecu od jednog vertex-a.
Drugi parametar predstavlja nacin na koji ce Direct3D postupati sa buffer-om.
Treci parametar je promenljiva koja objasnjava nas vertex format.
Cetvrti parametar oznacava gde ce buffer biti kreiran.
Sledeci parametar je promenljiva gde ce biti smesten buffer kada se kreira.
Poslednji mora biti nil.
Za vise informacija o parametrima pogledati
ovde.
Kada se buffer kreira uzimamo pointer koji pokazuje na memoriju koji buffer koristi da bi mogli da je popunimo nasim podacima. To se radi funkcijom
pVB.Lock.
Prvi parametar odredjuje na koji element u nizu ce pointer da pokazuje. Tako mozemo da popunimo samo elemente od recimo 3 do 6 vertex-a (pod uslovom da vertex buffer ima mesta za bar 6 vertex-a).
Drugi parametar govori koliko elemenata zelimo da uzmemo.
Treci je pointer u kojem ce biti adresa gde se nalaze podaci.
Poslednji odredjuje kako ce podaci biti dostupni korisniku (read-only, automatski se brisu...).
Za vise informacija pogledati
ovde.
Kada imamo pointer tada smo kopiramo podatke na tu memorijsku lokaciju. Kada zavrsimo sa tim pozivamo funkciju
pVB.Unlock i tako javljamo da smo zavrsili.
Ovu funkciju cemo pozvati odmah posle kreiranja D3DDevice.
Na red konacno dolazi crtanje. Promenucemo funkciju Oboji da izgleda ovako:
procedure Oboji;
begin
pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,0,0), 1.0, 0);
if (SUCCEEDED(pd3dDevice.BeginScene)) then
begin
pd3dDevice.SetStreamSource(0, pVB, 0, SizeOf(TCustomVertex));
pd3dDevice.SetFVF(D3DFVF_CUSTOMVERTEX);
pd3dDevice.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
pd3dDevice.EndScene;
end;
pd3dDevice.Present(nil, nil, 0, nil);
end;
Pre nego sto pocnemo sa crtanjem potrebno je da pozovemo
pd3dDevice.BeginScene, a kada zavrsimo
pd3dDevice.EndScene.
Da bi iscrtali buffer prvo moramo da ga selektijemo. To se radi
SetStreamSource funkcijom.
Posto Direct3D podrzava rad sa vise buffer-a prvi parametar je mesto gde ce buffer biti.
Drugi parametar je buffer.
Treci govori od kog elementa u nizu da se pripremi buffer tako da ne moramo ako necemo da uzimamo ceo buffer.
Cetvrti je velicina jednog vertexa.
Vise informacija mozete naci
ovde.
Sledece je izabir tipa vertexa. To radi funkcija
pd3dDevice.SetFVF koju ne treba posebno objasnjavati.
Poslednje je, konacno, crtanje
pd3dDevice.DrawPrimitive uzima 3 parametra:
Prvi je nacin crtanja vertex-a.
Drugi predstavlja od kog elementa se pocinje sa crtanjem.
Poslednji odredjuje koliko se elemenata (ne vertex-a) iscrtava (ovaj broj zavisi od prvog parametra).
Za vise informacija pogledati
ovde.
Ostaje nam jos da na kraju obrisemo buffer.
procedure CloseD3D;
begin
pVB := nil;
pd3dDevice := nil;
pD3D := nil;
end;
Evo prvog trougla u Direct3D
Source (Delphi) |
Source (Lazarus) |
Bin
Dopuna: 17 Jan 2006 21:10
Radili smo sa 2D koordinatama, a sad cemo raditi sa 3D koordinatama i matricama. Kod 3D koordinata je potrebno izvrsiti transformacije kako bi se one prevele u 2D koordinate. Za to nam sluze matrice. Postoji nekoliko vrsta matrica u Direct3D-u. Matrica koja postavlja objekat koji zelimo da crtamo na odredjenu poziciju se zove World matrica. Na osnovu nje se zna gde, u kom polozaju i koje velicine objekat treba da se nalazi u 3D svetu. Sledeca matrica je Camera ili View matrica. Ona odredjuje gde se nalazi posmatrac i tako odredjuje gde se objekat nalazi u odnosu na posmatraca. Poslednja matrica koju cemo koristiti je Projection matrica i njen zadatak je da do sada obradjenu 3D koordinatu prevede u 2D koordinatu za prikaz na ekranu. Mozda sada deluje komplikovano, ali na Direct3D olaksava posao jer sve sam racuna, a za popunjavanje matrica nam na raspolaganju stoji Direct3DX sa svojim pomocnim funkcijama i tipovima.
Pocnimo sa radom. Prvo sto cemo uraditi je izmena FVF definicije vertex-a. Koristicemo 3D koordinatu i boju:
type
TCustomVertex = packed record
X, Y, Z: Single;
Color: DWORD;
end;
const
D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ or D3DFVF_DIFFUSE);
Vertices: array[0..2] of TCustomVertex = (
(X : -1; Y : -1; Z : 0; Color: $ffffff00),
(X : 1; Y : -1; Z : 0; Color: $ff00ff00),
(X : 0; Y : 1; Z : 0; Color: $ff0000ff)
);
Ok... to smo resili. Sledeca stvar koju treba znati je da Direct3D koristi pravilo desne ruke da odredi koja strana poligona je gornja i po default-u iscrtava samo nju. Posto cemo mi rotirati nas trougao doci ce i trenutak kada ce on biti okrenut tako da vidimo njegovu donju stranu. Da bi i nju Direct3D iscrtao moramo podesiti jedan parametar:
pd3dDevice.SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
Ovo radimo kada kreiramo D3DDevice. D3DDevice ima dosta parametara koji odredjuju kako se radi sa svetlima, da li se koristi transparentnost i slicno... medju njima je i kontrola da li se prikazuje poligon kada je gornja strana prema kameri (moze da se bira da li se koristi pravilo leve ili desne ruke) ili da se crtaju i jedna i druga strana. Pozivom funkcije smo podesili da se prikazuju obe strane.
Jos jedna od default vrednosti je da boja 3D vertex-a zavisi od svetla. Posto jos necemo da koristimo svetla, iskljucicemo i tu opciju tako da se koristi boja koju smo mi postavili bez modifikovanja:
pd3dDevice.SetRenderState(D3DRS_LIGHTING, iFalse);
Za spisak svih parametara pogledati
ovde.
Sada cemo napisati proceduru koja ce podesavati matrice, ali pre nego sto pocnemo ubacicemo u uses listu jos 2 unit-a.
uses
Windows, Messages, MMSystem, Direct3D9, D3DX9;
MMSystem cemo koristiti za merenje vremena, a D3DX9 nam daje funkcije i tipove uz pomoc kojih ce nam biti lakse da radimo sa matricama.
Funkcija koja ce ih kreirati ide ovako:
procedure Rotiraj;
var
matWorld: TD3DMatrix;
iTime: LongWord;
fAngle: Single;
vEyePt, vLookatPt, vUpVec: TD3DVector;
matView: TD3DMatrix;
matProj: TD3DMatrix;
begin
iTime := timeGetTime mod 10000;
fAngle := iTime * (2 * D3DX_PI) / 10000;
D3DXMatrixRotationY(matWorld, fAngle);
pd3dDevice.SetTransform(D3DTS_WORLD, matWorld);
vEyePt:= D3DXVector3(0, 3, -5);
vLookatPt:= D3DXVector3(0, 0, 0);
vUpVec:= D3DXVector3(0, 1, 0);
D3DXMatrixLookAtLH(matView, vEyePt, vLookatPt, vUpVec);
pd3dDevice.SetTransform(D3DTS_VIEW, matView);
D3DXMatrixPerspectiveFovLH(matProj, D3DX_PI / 4,
400 / 300, 1, 100);
pd3dDevice.SetTransform(D3DTS_PROJECTION, matProj);
end;
matWorld, matView i matProj ce sadrzati matrice o kojima smo pricali na pocetku.
matWorld u ovom slucaju postavljamo tako da ona rotira nas objekat (trougao) oko Y ose. Posto smo vertex-e trougla postavili tako da su donja leva i donja desna tacka jednako udaljene od centra, a gornja tacka se nalazi tacno na sredini, izgledace kao da se trougao okrece oko sopstvene ose (ako zelite, mozete pomeriti koordinate vertex-a za, recimo, 0.2 i videcete kako ce se onda trougao rotirati).
timeGetTime vraca vreme u milisekundama od kada je Windows pokrenut. Na osnovu tog vremena izracunavamo ugao rotacije.
D3DXMatrixRotationY funkcija puni matricu podacima za rotaciju oko Y ose za zeljeni ugao. To je to za matWorld.
Sada postavljamo kameru. Trebaju nam tacka u kojoj se nalazi kamera, tacka u koju kamera gleda i vektor koji nam oznacava gde je "gore" (kamera moze da je okrenuta naopako pa je "gore" ustvari dole, ili da je kamera oborena na neku stranu pa je "gore" ustvari ta strana i slicno).
vEyePt ce sadrzati podatak o poziciji kamere i postavicemo je horiznotalno na sredini (3), vertikalno malo gore (3) i malo nazad (-5) jer nam se trougao nalazi na udaljenosti 0 po Z osi, a World matrica ga nece pomerati odatle nego cemo ga samo rotirati.
vLookatPt pokazuje na centar (0, 0, 0) trougla i tu ce kamera gledati.
vUpVec je postavljen na 0, 1, 0 sto znaci da je gore u pozitivnom smeru Y ose.
Kada imamo sve te podatke mozemo popuniti matricu funkcijom D3DXMatrixLookAtLH.
Za matricu projekcije cemo koristiti perspektivu. D3DXMatrixPerspectiveFovLH funkcija vraca rezultat u prvom parametru. Drugi parametar oznacava koliko siroko kamera vidi u Y pravcu u radijanima. Standardna vrednost je D3DX_PI / 4. Treci parametar je odnos sirine i visine dela ekrana u kojem se prikazuje ono sto kamera vidi. Poslednja dva parametra oznacavaju od koliko blizu ispred sebe do koliko daleko ispred sebe kamera moze da vidi. Sve sto je blize od minimalne daljine ili je iza kamere, ili je dalje od maximalne daljine se nece videti. Bitno je da (minimalna daljina) / (maximalna daljina) ne bude previse blizu nule jer ce se mozda pojaviti greska u racunanju pozicije vertex-a zbog preciznosti realnih brojeva u racunaru.
Matrice koje smo kreirali se postavljaju funkcijom pd3dDevice.SetTransform. Na osnovu prvog parametra Direct3D zna koju mu matricu prosledjujemo u drugom parametru. Ova funkcija ne mora posebno da se objasnjava.
Funkciju Rotiraj cemo pozivati odmah pre crtanja buffer-a tako da ce matrice biti spremne i uticace na izgled 3D scene.
Ovaj primer, za razliku od prethodnih, konstantno treba da se iscrtava. Posto GetMessage funkcija koja uzima poruke zaustavlja program dok ceka da joj Windows nesto posalje, crtanje nece moci da se odvija neometano. Zbog toga cemo malo modifikovati deo koda za citanje poruka:
FillChar(Msg, SizeOf(Msg), 0);
while (Msg.message <> WM_QUIT) do
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
DispatchMessage(Msg)
else
Oboji;
end;
Koristicemo PeekMessage funkciju koja ce da procita poruku i smesti je u Msg promenljivu i vratiti True. Tako da ce poruka da se obradi ako je ima, a ako je nema iscrtace se nasa 3D scena. Posto ce iscrtavanje da ovde da se vrsi, nema vise potrebe da imamo obradu WM_PAINT poruke pa mozemo da je izbrisemo iz koda.
To je sve za ovaj put. Pokrenite program... iii... ipak se okrece
Source (Delphi) |
Source (Lazarus) |
Bin
Dopuna: 22 Jan 2006 16:06
U ovom tutorialu cemo raditi sa svetlima i materijalima. Postoje nekoliko vrsta svtela.
Ambijentalno (ambient) svetlo osvetljava svaki vertex bez obzira gde se nalazi.
Svetlo koje dolazi iz jednog pravca (directional). Ono je kao Sunceva svetlost... dolazi negde iz daleka pod nekim uglom i osvetljava samo vertexe koji su okrenuti prema svetlu.
Svetlo koje obasjava sve oko sebe (point).
Svetlo koje se ponasa kao baterijska lampa (spot).
Za koriscenje svetla potrebno je da svaki verex pored informacije o koordinatama ima jos jedan podatak. To je podatak koji govori na koju stranu je vertex okrenut. Na osnovu tog podatka moze da se izracuna koliko svetlosti pada na njega i koja ce biti njegova konacna boja.
Materijal sadrzi informacije o boji kojom ce objekat biti iscrtan. Moze sadrzati boji koja zavisi od ambijentalnog svetla, koja zavisi od difuznog svetla (to su sva svetla koja nisu ambijentalna), odsjaj na objektu, svetlost koja izgleda kao da izlazi iz objekta.
Koristicemo materijale i zbog toga nece biti potrebno da definisemo boju za svaki vertex posebno kako smo do sada radili.
Posto smo do sada presli osnovne stvari, necu se vise na njima toliko detaljno zadrzavati i pisacu samo onaj deo koda koji je bitan za objasnjavanje. Konacan kod ce biti prikacen uz tutorial.
Da krenemo sa kodom. U ovom primeru cemo crtati malu cev u kojoj ce se nalaziti izvor svetlosti. Posto ce u ovom slucaju doci do slucajeva kada ce jedan verex biti iza drugog i zbog toga ne bi trebalo da se vidi ukljucicemo upotrebu ZBuffer-a koji ce vrsiti proveru vidljivosti tacaka koje se iscrtavaju.
D3DPP.EnableAutoDepthStencil := True;
D3DPP.AutoDepthStencilFormat := D3DFMT_D16;
pd3dDevice.SetRenderState(D3DRS_ZENABLE, iTrue);
Format vertex-a ce sada biti ovakav:
type
PCustomVertex = ^TCustomVertex;
TCustomVertex = packed record
Position: TD3DVector;
Normal: TD3DVector;
end;
PCustomVertexArray = ^TCustomVertexArray;
TCustomVertexArray = array [0..MaxInt div SizeOf(TCustomVertex)-1] of TCustomVertex;
const
D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ or D3DFVF_NORMAL);
Sada, kao sto vidite, pored pozicije, imamo i normalu za svaki vertex (TD3DVector tip sadrzi 3 podatka, X, Y i Z koordinatu).
Dodali smo i jos par tipova da nam omoguce lakse popunjavanje podataka. Sluzice nam da buffer mozemo da gledamo kao niz podataka tipa TCustomVertex.
Buffer cemo popuniti ovako:
function PopuniBuffer: HRESULT;
var
I: Integer;
Ugao: Single;
pVertices: PCustomVertexArray;
begin
Result:= E_FAIL;
if FAILED(
pd3dDevice.CreateVertexBuffer(
50 * 2 * SizeOf(TCustomVertex),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, pVB, nil)) then
Exit;
if FAILED(
pVB.Lock(0, SizeOf(Vertices), pVertices, 0)) then
Exit;
for I := 0 to 49 do
begin
Ugao := (2 * D3DX_PI * I) / (50 - 1);
pVertices[2 * I].Position := D3DXVector3(Sin(Ugao), -1, Cos(Ugao));
pVertices[2 * I].Normal := D3DXVector3(Sin(Ugao), 0, Cos(Ugao));
pVertices[2 * I + 1].Position := D3DXVector3(Sin(Ugao), 1, Cos(Ugao));
pVertices[2 * I + 1].Normal := D3DXVector3(Sin(Ugao), 0, Cos(Ugao));
end;
pVB.Unlock;
Result:= S_OK;
end;
Popunjavamo buffer tako sto postavljamo jedan vertex na dnu cevi i zatim jedan na vrhu. Tako idemo u krug sve dok ne stignemo na pocetak. Normalu postavljamo tako da je svaki vertex okrenut vertikalno prema centru objekta tako da ce svaki gledati u svetlo i bice obasjan.
Imamo objekat koji cemo da crtamo... sada da dodamo materijal i svetlo.
procedure Svetlo;
var
Mtrl: TD3DMaterial9;
vecDir: TD3DXVector3;
Light: TD3DLight9;
begin
ZeroMemory(@Mtrl, SizeOf(TD3DMaterial9));
Mtrl.Diffuse.r := 1; Mtrl.Ambient.r := 1;
Mtrl.Diffuse.g := 1; Mtrl.Ambient.g := 1;
Mtrl.Diffuse.b := 0; Mtrl.Ambient.b := 0;
Mtrl.Diffuse.a := 1; Mtrl.Ambient.a := 1;
pd3dDevice.SetMaterial(Mtrl);
ZeroMemory(@Light, SizeOf(TD3DLight9));
Light._Type := D3DLIGHT_DIRECTIONAL;
Light.Diffuse.r := 1;
Light.Diffuse.g := 1;
Light.Diffuse.b := 1;
vecDir :=
D3DXVector3(Cos(timeGetTime / 350),
1,
Sin(timeGetTime / 350)
);
D3DXVec3Normalize(Light.Direction, vecDir);
Light.Range := 1000;
pd3dDevice.SetLight(0, Light);
pd3dDevice.LightEnable(0, True);
end;
TD3DMaterial9 tip sadrzi podatke o materijalu. Ovog puta cemo koristiti samo boju koja se vidi kada se svetli na objekat (nema odsjaja i slicnih stvari). Skoro uvek se ambijentala i difuzna boja postavljaju na istu i ona predstavlja boju zeljenog objekta. Nas ce biti zut.
Kada kreiramo materijal postavljamo ga funkcijom pd3dDevice.SetMaterial i svi objekti koji se budu crtali ce imati taj materijal sve dok se on ne promeni.
TD3DLight9 sadrzi informacije o svetlu. Mi cemo kreirati DIRECTIONAL svetlo (dolazi iz daljeine i ima neki odredjeni ugao pod kojim pada na objekte). Posto podesimo tip i boju svetla (postavili smo je na belu) postavljamo i ugao pod kojim pada na scenu. Iskoristicemo Sin i Cos funkciju da simuliramo okretanje svetla. D3DXVec3Normalize funkcijom vektor koji smo izracunali svodimo na vektor intenziteta 1 i postavljamo ga. Ovaj tip svetla ima i svojstvo Range koje oznacava koliko daleko svetlost osvetljava predmete. Postavili mo ga na veliku vrednost tako da se svi vertex-i osvetle.
Posto Direct3D podrzava vise izvora svetlosti kada postavljamo svetlo moramo navesti i koje je to svetlo po redu. Funkcijom pd3dDevice.SetLight bas to radimo. Kada postavimo svetlo ostaje nam jos samo da ga ukljucimo funkcijom pd3dDevice.LightEnable.
Da bi se svetlos uzela u obzir kada se iscrtava scena mora se ukljuciti jos par opcija u Direct3D-u
pd3dDevice.SetRenderState(D3DRS_LIGHTING, iTrue);
pd3dDevice.SetRenderState(D3DRS_AMBIENT, $00202020);
Prva opcija kaze da se svetlo uzima u obzir, a druga postavlja boju ambijentalnog svetla.
Sada imamo sve sto je potrebno da bi iscrtali scenu.
procedure Oboji;
begin
pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0, 0);
if (SUCCEEDED(pd3dDevice.BeginScene)) then
begin
Svetlo;
Rotiraj;
pd3dDevice.SetStreamSource(0, pVB, 0, SizeOf(TCustomVertex));
pd3dDevice.SetFVF(D3DFVF_CUSTOMVERTEX);
pd3dDevice.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2 * 50 - 2);
pd3dDevice.EndScene;
end;
pd3dDevice.Present(nil, nil, 0, nil);
end;
Prvo brisemo pozadinu i ZBuffer... postavljamo svetlo, rotiramo scenu i iscratavamo nas objekat.
To je to
Source (Delphi) |
Source (Lazarus) |
Bin
Dopuna: 22 Jan 2006 18:05
Nas sledeci cilj je da postavimo sliku na objekat koji crtamo. Iskoristicemo kod iz primera gde smo rotirali trougao. Izmenucemo ga tako da se umesto trougla iscrta pravougaonik (dodacemo jos jedan verex) i na njega cemo nalepiti sliku (MyCity... valjda to nije protiv pravila

).
Hajde prvo da sredimo laksi deo... da podesimo da se umesto trougla iscrtava pravougaonik.
const
Vertices: array[0..3] of TCustomVertex = (
(X : -2; Y : -1; Z : 0; Color: $ffffffff),
(X : 2; Y : -1; Z : 0; Color: $ffffffff),
(X : -2; Y : 1; Z : 0; Color: $ffffffff),
(X : 2; Y : 1; Z : 0; Color: $ffffffff)
);
Ovo su podaci za vertex-e. Popunjavanje ostaje isto osim sto se ne popunjava 3 nego 4 vertex-a.
Za iscrtavanje cemo napisati kod ovako:
pd3dDevice.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
sto znaci da ce se u buffer-u nalaziti lista trouglova koji se nadovezuju jedan na drugi i iscrtacemo 2 trougla.
To je to... sad da se bacimo na dodavanje texture (malo cemo izmenuti i Vertices konstantu).
Tip vertexa ce biti ovakav:
type
TCustomVertex = packed record
X, Y, Z: Single;
Color: DWORD;
tU, tV: Single;
end;
const
D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ or D3DFVF_DIFFUSE or D3DFVF_TEX1);
tU i tV ce sadrzati koordinate textura. tU je koordinata po X osi, a tV po Y osi na texturi. Kada je vrednost tU = 0 to je leva ivica texture, kada je tU = 1 to je desna ivica texture. Isto vazi i za tV. tV = 0 je gornja ivica, tV = 1 je donja.
Kada ubacimo i koordinate textura u Vertices dobijamo konacnu verziju podataka za buffer
Vertices: array[0..3] of TCustomVertex = (
(X : -2; Y : -1; Z : 0; Color: $ffffffff; tU : 0; tV : 1),
(X : -2; Y : 1; Z : 0; Color: $ffffffff; tU : 0; tV : 0),
(X : 2; Y : -1; Z : 0; Color: $ffffffff; tU : 1; tV : 1),
(X : 2; Y : 1; Z : 0; Color: $ffffffff; tU : 1; tV : 0)
);
Potrebna nam je promenljiva u kojoj ce biti sacuvana textura... dodacemo je i odmah napisati i kod koji ce je na kraju osloboditi.
var
pTexture: IDirect3DTexture9;
procedure CloseD3D;
begin
pTexture := nil;
pVB := nil;
pd3dDevice := nil;
pD3D := nil;
end;
Kod za ucitavanje texture cemo postaviti u PopuniBuffer funkciju. Kod se svodi na pozivanje jedne funkcije zahvaljujuci Direct3DX funkcijama:
function PopuniBuffer: HRESULT;
var
pVertices: Pointer;
begin
Result:= E_FAIL;
if FAILED(D3DXCreateTextureFromFile(
pd3dDevice,
'MyCity.jpg',
pTexture)
) then
begin
MessageBox(0, 'Ne mogu da nadjem MyCity.jpg', 'D3DTut5', MB_OK);
Exit;
end;
if FAILED(
pd3dDevice.CreateVertexBuffer(
4 * SizeOf(TCustomVertex),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, pVB, nil)) then
Exit;
if FAILED(
pVB.Lock(0, SizeOf(Vertices), pVertices, 0)) then
Exit;
CopyMemory(pVertices, @Vertices, SizeOf(Vertices));
pVB.Unlock;
Result:= S_OK;
end;
D3DXCreateTextureFromFile funkcija uzima D3DDevice za koji se kreira textura, putanju do texture i promenljivu u kojoj ce textura biti kreirana. To je sve. Imamo texturu, imamo objekat... ostaje nam da sve iscrtamo.
procedure Oboji;
begin
pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,0,0), 1.0, 0);
if (SUCCEEDED(pd3dDevice.BeginScene)) then
begin
Rotiraj;
pd3dDevice.SetTexture(0, pTexture);
pd3dDevice.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
pd3dDevice.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
pd3dDevice.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
pd3dDevice.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
pd3dDevice.SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
pd3dDevice.SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
pd3dDevice.SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR );
pd3dDevice.SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
pd3dDevice.SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );
pd3dDevice.SetStreamSource(0, pVB, 0, SizeOf(TCustomVertex));
pd3dDevice.SetFVF(D3DFVF_CUSTOMVERTEX);
pd3dDevice.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
pd3dDevice.EndScene;
end;
pd3dDevice.Present(nil, nil, 0, nil);
end;
pd3dDevice.SetTexture funkcija postavlja texturu. Moguce je postaviti vise textura od jednom i prvi parametar govori koju texturu postavljamo, a drugi parametar je buffer u kojoj je textura. Kada postavimo texturu potrebno je objasniti Dirrect3D-u kako zelimo da je postavi na objekat. To se vrsi funkcijom pd3dDevice.SetTextureStageState. Prvi parametar oznacava za koju texturu podesavamo opcije. Drugi je opcija koju zelimo da menjamo i treci predstavlja vrednost koju zelimo da postavimo.
U ovom slucaju kazemo da se boja texture stapa sa bojom vertex-a. To znaci da ako je boja vertex-a bela videce se prava boja texture. ako je vertex crven i textura ce biti crvenkasta.
Za vise informacija o ovoj funkciji i opcijama koje mogu da se postave pogledajte
ovde.
Sledece sto radimo je postavljanje nacina na koji ce se kreirati dodatne manje texture texture. Kada se objekat dovoljno udalji od kamere Direct3D ce automatski iskoristiti texturu koju je kreirao.
Za vise informacija o ovoj funkciji i opcijama koje mogu da se postave pogledajte
ovde.
To bi bilo to... kada se program pokrene, videce se pravougaonik na kojem ce se nalaziti textura
Source (Delphi) |
Source (Lazarus) |
Bin |
Media
Dopuna: 22 Jan 2006 20:20
Poslednji od tutoriala koji idu sa SDK je ucitavanje i prikazivanje objekta iz *.x fajla. U tom fajlu se nalaze podaci o vertex-im, materijalima, texturama, animacijama... Opet cemo koristiti funkcije iz Direct3DX da bismo lakse ucitali sve te podatke.
Pocecemo definisanjem tipova i postavljanjem globalnih promenljivih.
type
PD3DMaterial9Array = ^TD3DMaterial9Array;
TD3DMaterial9Array = array [0..MaxInt div SizeOf(TD3DMaterial9) - 1] of TD3DMaterial9;
PIDirect3DTexture9Array = ^IDirect3DTexture9Array;
IDirect3DTexture9Array = array [0..MaxInt div SizeOf(IDirect3DTexture9) - 1] of IDirect3DTexture9;
var
WndClass: TWNDClassEx;
Wnd: HWnd;
Msg: TMsg;
pD3D: IDirect3D9;
pd3dDevice: IDirect3DDevice9;
pMesh: ID3DXMesh;
pMeshMaterials: PD3DMaterial9Array;
pMeshTextures: PIDirect3DTexture9Array;
NumMaterials: Integer;
Tipove koje smo definisali ce nam omoguciti da lako mozemo da radimo sa nizovima materijala i nizovima textura jer jedan 3D objekad (mesh) moze u svom sastavu imati vise materijala i textura.
pMesh promenljiva ce sadrzati podatke o mesh-u, a u pMeshMaterials i pMeshTextures podaci o materijalima i texturama koje mesh koristi.
NumMaterials ce sadrzati broj materijala koje koristi mesh.
Hajde da ucitamo mesh iz fajla.
function PopuniBuffer: HRESULT;
type
PD3DXMaterialArray = ^TD3DXMaterialArray;
TD3DXMaterialArray = array [0..MaxInt div SizeOf(TD3DXMaterial) - 1] of TD3DXMaterial;
var
pD3DXMtrlBuffer: ID3DXBuffer;
D3DXMaterials: PD3DXMaterialArray;
I: Integer;
begin
Result:= E_FAIL;
if FAILED(D3DXLoadMeshFromX('Tiger.x', D3DXMESH_SYSTEMMEM,
pd3dDevice, nil,
@pD3DXMtrlBuffer, nil, @NumMaterials,
pMesh)) then
begin
MessageBox(0, 'Ne mogu da nadjem Tiger.x', 'D3DTut6', MB_OK);
Exit;
end;
D3DXMaterials := pD3DXMtrlBuffer.GetBufferPointer;
try
GetMem(pMeshMaterials, SizeOf(TD3DMaterial9) * NumMaterials);
GetMem(pMeshTextures, SizeOf(IDirect3DTexture9) * NumMaterials);
except
Result:= E_OUTOFMEMORY;
Exit;
end;
ZeroMemory(pMeshTextures, SizeOf(IDirect3DTexture9) * NumMaterials);
I := 0;
while (I < NumMaterials) do
begin
pMeshMaterials[I] := D3DXMaterials[I].MatD3D;
pMeshMaterials[I].Ambient := pMeshMaterials[I].Diffuse;
pMeshTextures[I] := nil;
if (D3DXMaterials[I].pTextureFilename <> nil) and
(StrLen(D3DXMaterials[I].pTextureFilename) > 0) then
begin
if FAILED(D3DXCreateTextureFromFile(pd3dDevice,
D3DXMaterials[I].pTextureFilename,
pMeshTextures[I])) then
begin
MessageBox(0, 'Ne mogu da nadjem texturu', 'D3DTut6', MB_OK);
end;
end;
Inc(I);
end;
pD3DXMtrlBuffer := nil;
Result := S_OK;
end;
Funkcija D3DXLoadMeshFromX ucitava mesh iz fajla koji je naveden u prvom parametru. Od drugog zavisi kako ce se u memoriji mesh kreirati. Treci je D3DDevice za koji se mesh kreira. Sledeci parametar predstavlja informaciju o poligonima koji su jedan pored drugog. Ovaj podatak se koristi u nekim drugim funkcijama pa nam sad nije potreban. Sledeca dva parametra predstavljaju niz materijala i efekata koje mesh koristi. Noviji programi uopste ne koriste materijale nego se oslanjaju samo na efekte, ali cemo se mi zasada zadrzati samo na materijalima. Naredni parametar je promenljiva u kojojo ce biti smesten broj materijala, a poslednji je promenljiva u kojoj ce biti 3D objekat.
Kada smo ucitali mesh treba da uzmemo materijale i texture kako bi kasnije mogli da ih koristimo. U sustini ovde nema vise niceg novog. Kopiramo podatke o materijalima (u *.X fajlu se ne nalazi podatak o ambijentalnoj boji pa je sami postavljamo na verdnost difuzne boje) i ucitavamo texture za svaki materijal koji je sadrzi.
Imamo sve potrebne podatke i zato cemo sad da iscrtamo mesh:
procedure Oboji;
var
I: Integer;
begin
pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,255), 1.0, 0);
if SUCCEEDED(pd3dDevice.BeginScene) then
begin
Rotiraj;
I := 0;
while (I < NumMaterials) do
begin
pd3dDevice.SetMaterial(pMeshMaterials[I]);
pd3dDevice.SetTexture(0, pMeshTextures[I]);
pMesh.DrawSubset(I);
Inc(I);
end;
pd3dDevice.EndScene;
end;
pd3dDevice.Present(nil, nil, 0, nil);
end;
Prvo brisemo sadrzaj ekrana i postavljamo matrice kao i uvek. Zatim idemo redom i iscrtavamo deo po deo mesh-a. Postavljamo materijal i texturu za odredjeni deo i pozivom funkcije pMesh.DrawSubset iscrtavamo zeljeni deo. Kada sve iscrtamo zavrsavamo scenu i prikazujemo gotovu sliku. Nista komplikovano.
Sve sto nam ostaje je da napisemo kod za osobadanje memorije.
procedure CloseD3D;
var
I: Integer;
begin
if (pMeshMaterials <> nil) then
FreeMem(pMeshMaterials);
if (pMeshTextures <> nil) then
begin
I := 0;
while (I < NumMaterials) do
begin
pMeshTextures[I] := nil;
Inc(I);
end;
FreeMem(pMeshTextures);
end;
pMesh := nil;
pd3dDevice := nil;
pD3D := nil;
end;
To je kraj tutoriala koji idu sa SDK... sledece cemo sami da smisljamo
Source (Delphi) |
Source (Lazarus) |
Bin |
Media
Dopuna: 23 Jan 2006 22:00
Ovog puta cemo pokazati kako je moguce saznati koje sve graficke adaptere ima korisnik na racunaru i koje modove prikaza podrzavaju. Koristicemo VCL komponente u ovom primeru da bismo lakse prikazali podatke.
Pocecemo stavljanjem jednog combo box-a (cmbKartice) u kojem ce se videti sve graficke, i jednog list box-a (lstMod) gde ce se prikazati svi modovi koje graficka podrzava.
Bice nam potrebno da kreiramo Direct3D objekat i da ga kasnije obrisemo pa cemo napisati procedure i dodati promenljive:
pD3D: IDirect3D9;
procedure TfrmGlavna.Init3D;
begin
pD3D := Direct3DCreate9(D3D_SDK_VERSION);
if pD3D = nil then
Application.Terminate;
end;
procedure TfrmGlavna.Done3D;
begin
pD3D := nil;
end;
Prelazimo na glavni deo... da bi smo saznali koje graficke korisnik ima iskoristicemo sledeci kod:
procedure TfrmGlavna.EnumAdapters;
var
D3DAI: TD3DAdapterIdentifier9;
Num, I: Integer;
begin
cmbKartice.Clear;
Num := pD3D.GetAdapterCount;
for I := 0 to Num - 1 do
begin
pD3D.GetAdapterIdentifier(I, 0, D3DAI);
cmbKartice.AddItem(D3DAI.Description, nil);
end
end;
TD3DAdapterIdentifier9 tip sluzi za cuvanje svih podataka koji su potrebni za oznacavanje gravicke kartice i njen opis.
Uz pomoc pD3D.GetAdapterCount funkcije saznajemo koliko postoji grafickih kartica na racunaru.
Da bismo dosli do podataka pozivamo pD3D.GetAdapterIdentifier funkciju. Prvi parametar je redni broj graficke (prva kartica je po rednim brojem 0). Drugi oznacava sta zelimo da uzmemo od podataka (0 znaci da uzimamo sve) i poslednji parametar je promenljiva u kojoj ce biti smesten rezultat... prilicno jednostavno.
Taj rezultat smestamo u combo box i time zavrsavamo ovu proceduru.
Procedura za pronalazenje modova u kojima graficka moze da radi je malo komplikovanija, ali ne previse:
procedure TfrmGlavna.EnumModes(Adapter: Integer);
var
D3DDM: TD3DDisplayMode;
ModeText: String;
Num, I, M: Integer;
const
FormatArray: array [0..5] of TD3DFormat = (
D3DFMT_A1R5G5B5,
D3DFMT_A2R10G10B10,
D3DFMT_A8R8G8B8,
D3DFMT_R5G6B5,
D3DFMT_X1R5G5B5,
D3DFMT_X8R8G8B8
);
begin
lstMod.Clear;
for M := 0 to 5 do
begin
Num := pD3D.GetAdapterModeCount(Adapter, FormatArray[M]);
for I := 0 to Num - 1 do
begin
pD3D.EnumAdapterModes(Adapter, FormatArray[M], I, D3DDM);
ModeText := IntToStr(D3DDM.Width) + 'x' +
IntToStr(D3DDM.Height) + 'x';
case D3DDM.Format of
D3DFMT_R3G3B2,
D3DFMT_A8,
D3DFMT_P8,
D3DFMT_L8,
D3DFMT_A4L4: ModeText := ModeText + '8';
D3DFMT_R5G6B5,
D3DFMT_X1R5G5B5,
D3DFMT_A1R5G5B5,
D3DFMT_A4R4G4B4,
D3DFMT_A8R3G3B2,
D3DFMT_X4R4G4B4,
D3DFMT_A8P8,
D3DFMT_L16,
D3DFMT_A8L8: ModeText := ModeText + '16';
D3DFMT_R8G8B8: ModeText := ModeText + '24';
D3DFMT_A8R8G8B8,
D3DFMT_X8R8G8B8,
D3DFMT_A2B10G10R10,
D3DFMT_A8B8G8R8,
D3DFMT_X8B8G8R8,
D3DFMT_G16R16,
D3DFMT_A2R10G10B10: ModeText := ModeText + '32';
D3DFMT_A16B16G16R16: ModeText := ModeText + '64';
end;
ModeText := ModeText + '@' + IntToStr(D3DDM.RefreshRate);
lstMod.AddItem(ModeText, nil);
end;
end;
end;
Krenimo od pocetka... tip TD3DDisplayMode sluzi za cuvanje svih potrebnih podataka o modu za prikaz kao sto su sirina, visina, osvezavanje ekrana...
pD3D.GetAdapterModeCount funkcija vraca broj razlicitih modova prikaza za zeljenu karticu. Drugi parametar odredjuje koji se modovi prebrojavaju tako da ne moramo da prebrojavamo i one koji nas ne interesuju. Posto u ovom primeru zelimo da prikazemo sve moguce modove koristicemo i sve moguce formate koje ova funkcija prihvata. Oni se nalaze u konstanti FormatArray.
Kada dobijemo broj modova za odredjeni format koristimo pD3D.EnumAdapterModes da bismo dobili podatke o odredjenom modu. Prva dva parametra su ista kao i pri pozivu pD3D.GetAdapterModeCount funkcije. Druga dva predstavljaju redni broj moda koji zelimo da uzmemo i promenljivu u kojoj ce biti smesten rezultat.
Ostatak koda je formatiranje texta kako bi bio laksi za citanje. Koristimo sirinu i visinu moda za dobijanje texta kao sto je npr
800x600, zatim na osnovu formata odredjujemo koliko se bitova koristi za definisanje jedne boje i na kraju dodajemo i frekvenciju osvezavanja ekrana. Na kraju dobijamo text kao sto je
800x600x16@75 gde je prvi broj sirina, drugi visina, treci broj bitova za jednu boju i poslednji frekvencija osvezavanja ekrana.
Glavni deo programa je gotov. Sada jos samo treba dodati kod koji ce sve ovo da poziva kada je potrebno:
procedure TfrmGlavna.FormCreate(Sender: TObject);
begin
Init3D;
EnumAdapters;
end;
procedure TfrmGlavna.FormDestroy(Sender: TObject);
begin
Done3D;
end;
procedure TfrmGlavna.cmbKarticeChange(Sender: TObject);
begin
if cmbKartice.ItemIndex <> -1 then
EnumModes(cmbKartice.ItemIndex);
end;
Na osnovu ovog koda se moze napraviti program koji dozvoljava korisniku izabir moda za full screen aplikaciju.
Source (Delphi) |
Source (Lazarus) |
Bin