PDA

Pogledaj cijelu verziju : [Tutorial] DirectX through C++



RayDX
12-10-2007, 22:16
S obzirom da imam bolesnu količinu slobodnog vremena, evo odlučio sam napisati ovaj mali tutorial ako ga mogu takvim nazvati hehe... Bavit ćemo se DirectXom, što je on, zašto je stvoren i kako je podijeljen za početak a kasnije kako bude išlo.

Što je to DirectX?

Veterani gaming industrije, bili oni developeri ili krajnji gameri sigurno se sjećaju dana kada su sve igre vrtile na low-level tekst baziranom operativnom sustavu zvanom DOS. Ja osobno se ne sjećam, jer u to vrijeme nisam bio ni u planu ali sam uspio zaigrati sa zakašnjenjem. U to vrijeme, Microsoft bijaše poštovan kao izbor Allmighty Poslovnjaka, a što se tiče nas gamera i gDevelopera, bila je jako siromašna u smislu da bi mogla podržavati u ondašnje vrijeme "napredni" razvoj igara i samo izvršavanje na istom. Gdje je bio problem, vjerojatno se pitate? Pa, problem se nalazio u činjenici da je programer/developer bio u potpunosti izoliran od podležnog grafičkog hardvera koristeći tadašnji tzv. Graphics Device Interface (GDI u nastavku teksta). Taj interface (sučelje, kako hoćete, meni je interface koolišniji) sadržavao je robusnu kolekciju 2D texta i nekoliko primitivnih funkcija za iscrtavanje koje je developer, naravno, koristio da iscrta 2D output na zaslon korisnika. Zapravo, ovo je imalo i svojih dobrih strana, developer se npr. više nije morao zabrinjavati oko hardverskih kompatibilnosti. Developeru je sve izgledalo isto. Ako ste željeli pravokutnik npr. mogli ste jednostavno dati instrukciju GDIju i on bi, kao međusloj/posrednik između hardvera i developera, napravio ostalo.

Dakle, GDI je izrađen da bude robustan i stabilan. Nažalost, "savršenstvo" je došlo po svoj danak jer developer nije izravno mogao pristupiti video memoriji. Situacija je jednostavno neprihvatljiva za GameDev projekte jer sve naredbe je morao GDI kao međusloj konvertirati tamo 'vamo i tako smanjujući programersku mogućnost da utječe na neke segmente developmenta. I kao šlag na govancu, da se izrazim, bio je izrazito spor.
Igre koje su se vrtile direktno kroz DOS nisu patile od sličnih problema. Bile su prilično brze, ali njihov razvoj i samo igranje bio je pravi izazov, tako da je i to brzo palo u vodu.

Gledajući situaciju iz perspektive developera, PC platforma je postala iznimno popularna da su mnogi proizvođači počeli razvijati grafičke kartice, svaka sa drugačijim čipsetom. To je značilo ogromne muke za developere s obzirom da su morali osigurati kompatibilnost sa različitim hardverom koji se svakim danom gomilao. Nije bilo nikakvog standardnog API-ja (Application Programming Interface) za grafičko renderiranje, a kamoli potpunog multimedia development frameworka. S obzirom da se svaka grafička kartica morala posebno programirati, developeri su morali napraviti gomilu različitih verzija funkcija za iscrtavanje da bi radila igra sa različitim grafičkim hardverom dostupnim na tržištu. Ovo je definitivno uništavalo hobby-developere poput nas ovdje jer nisu imali para da bi kupovali sav mogući grafički hardver da bi osigurali kompatibilnost svoje igre. Kao igrač, mnogi su uvidjeli da su vražja posla upletena sa instalacijom neke jednostavne igre jer većina nije bila dovoljno "tehnološki prosvjetljena". Stalno bi gnjavili, koja grafička, video memorija, blablabla, tako da je tehnička podrška u softverskim kompanijama definitivno bila golem trošak. Konzola poput Super Nintenda koja je bila tehnički nadmoćna nad PC-em imala je puno lakši način "instalacije"... Poznati plug&play heheh...

Microsoft je uvidio da će morati reagirati vrlo brzo ako žele da Windows postane dominantan u gaming industriji, te su ubrzo nakon izlaska Windowsa 95 izbacili van biblioteku za razvoj multimedijalnih sadržaja nazvanu 'The Game SDK'. Ovo je bio predak današnjeg DirectXa, kojeg smo svi naučili voljeti i tolerirati, pogotovo njegovu desetu inačicu *sarkazam*. DirectX kao ime je usvojeno tek u inačici 2.0, vrlo vjerojatno zbog uspjeha u razvoju potpune multimedijske biblioteke za razvoj bogatih aplikacija. Iako su početne inačice DirectXa bile malo grube oko rubova, možemo reći da je odrastao u velikog ekonomista tijekom proteklih par godina. DirectX 5 je vjerojatno prvi krenuo sa ozbiljnim developmentom, vrijednim spomena. Danas smo na već na verziji 10, i iako nam se ne sviđaju taktike Microsofta moramo priznati da je u ovo malo vremena koje je proteklo, gaming industrija stvarno evoluirala i danas je na vrlo visokoj stepenici uvelike zahvaljujući DirectXu i njegovim developerima, pripadnicima mrskog Microsofta.

DirectX je ponudio rješenje mnogim problemima koji su mučili razvoj igara od samih početaka. Kao prvo, razvijen je za Windows platformu, koju drugu, enivejz? Ovo je značilo da developeri mogu napraviti svoje igre u okružju gdje su Win32 API fičrsi poput multithreadinga i UIa već dostupni. No shit, Einstein? heheh. Kao drugo, ustupilo nam je jedinstven API, bolje rečeno framework danas baš kao što je GDI to napravio prije, ali ovaj puta je brže, mnogo brže. Developer se više nije morao brinuti o grafičkom hardveru krajnjeg korisnika, nego je prepustio DirectXu da se za to pobrine. Ovo je omogućeno korištenjem "driver programa". Danas grafičke kartice koje podržavaju DirectX (danas nema niti jedne na tržištu koja ne podržava mislim) dolaze sa driverom koji se instalira na sustav korisnika. Driver piše naravno proizvođač i to je naravno vrlo brz komad softvera i služi kao jako tanak sloj odmah iznad grafičke koji uzima različite zahtjeve od mnogih directx funkcija koje programer upotrijebi, i pretvori ih u instrukcije razumljive hardveru. Ovo znači da DirectX može "pričati" sa svim grafičkim karticama kao da su iste iako su veoma različite jedne od drugih. Dobra strana koja se uspjela zadržati pokraj svih ovih mogućnosti jest činjenica da nam api ostavi prilično kvalitetne slobode i pristupa hardveru krajnjih korisnika. Kad već hvalim toliko DirectX moram spomenuti da iskorištava 3d hardversku akceleraciju bez dodatnog kôdiranja od strane developera što štedi vrijeme i novac... Vrijeme je novac. A vrijeme ne postoji... Kako onda novac postoji... WTF... Heheh, ajmo dalje. Najnoviji grafički hardver također ubrzava 3d matematiku (što je prije bilo u domeni CPUa prije). To znači da mnoge grafičke kartice mogu preuzeti teret od nekoliko tisuća matematičkih kalkulacija potrebnih da bi se scena renderirala ostavljajući presvete resurse CPUa da se bave kreativnijim stvarima... AI anyone? :)

Podjela DirectX Frameworka

Kao i svaki kvalitetan API, DirectX isto ima podjelu u više zasebnih modula. Svaki pokriva određen segment u razvoju multimedijalnih aplikacija. Pođimo opisati ukratko svaki od tih modula da dobijete neku bolju sliku kako je sastavljen i što čini taj magični krug.

DirectX Graphics

U starijim verzijama DirectXa, 2D i 3D operacije bile su podijeljenje u dva zasebna API-ja zvana DirectDraw i Direct3D. Dolaskom DirectXa 8.0 i nadalje to se spaja u jedan API nazvan DirectX Graphics. Mnogi ljudi još uvijek imaju naviku nazivati DirectX Graphics kao Direct3D. Kao što ćete vidjeti u kasnijim tutorijalima, većina DirectX Graphics funkcija i sučelja počinju sa D3D (skraćeno od Direct3D ofkorz), pa naziv stari je još uvijek opravdan.

DirectX Audio

DirectX Audio modul sadržava funkcionalnost organiziranja i puštanja audio uzoraka i glazbe unutar vaših aplikacija. Uključuje podršku za 3D audio te hardversko procesiranje zvuka i okolišnih efekata. DirectX Audio je prijašnje također bio podijeljen na dva manja API-ja zvani DirectSound i DirectMusic, no spajanje se događa sa DirectXom 8.

DirectX Input

DirectInput modul sadržava funkcionalnosti koje kontroliraju ulaznu periferiju koju korisnik koristi. Nudi funkcije za očitavanje inputa sa "uređaja" poput gamepadova, joystickova, volana ali naravno i miša i tipkovnice.

DirectX Play

Ovaj modul nudi funkcionalnost korištenu uglavnom u implementaciji MP igara i sličnih aplikacija. Uključuje podršku za primanje i odašiljanje podataka kroz recimo LAN, Internet, craplikethat. Kao i sa većinom drugih aspekata DirectXa, ovaj modul dizajniran je kao aplikacijiski sloj koji koristi sustav za primanje/odašiljanje podataka bez obzira na podložeću mrežnu infrastrukturu na kojoj korisnikov PC leži.

DirectShow

Zadnji, ali ne i najnebitniji jest DirectShow. Nudi fičrse koje sjedinjavaju mogućnosti snimanja i plejbeka visokokvalitetnih streamova. Ovo uključuje podršku za mnoge popularne formate poput MPEG, AVI, ASF i MP3 fileove.


Nadam ste da ste uživali u ovom nadasve kratkom tutorialu, očekujte me negdje kad se ponovno vratim iz faze lijenosti da napišem novi tutorial za mase. Pozz iz Ivankova zasad...

Rex Regis.cro
13-10-2007, 00:06
Svaka čast! :bravo: :D :friends:

Norther
18-10-2007, 20:03
svaka cast, dao si si truda, nice ! 8)

Legija001
22-10-2007, 12:16
ovo je imala neka knjiga posla ili copy/paste :)


ali svaka cast :pray:

RayDX
22-10-2007, 21:04
ovo je imala neka knjiga posla ili copy/paste :)


ali svaka cast :pray:

Pa mnogi kažu da sam hodajuća enciklopedija pa i jest kopi pejst... Ali još nisam dostojan tog titla :D Burek :pray:

Burek_fr0m_SPACE
22-10-2007, 23:09
Burek :pray:
Ah ti dani kada sam ja pisao članke o koječemu... Možda bih mogao nekad opet...

RayDX
22-10-2007, 23:11
Burek :pray:
Ah ti dani kada sam ja pisao članke o koječemu... Možda bih mogao nekad opet...

Join us! Adventures through the valley of key strokes await!

RayDX
09-12-2007, 22:46
Evo nakon nekog vremena vraćamo se u ovaj zapušteni topic... Jeste li spremni da krenemo u osnove DirectX programiranja?

No, kao prvo moramo postaviti neki starting point da ne krenemo skroz ispočetka. Na kraju krajeva, trebali biste imati neko znanje C++a (pokazivači, polja, OOP, strukture). Tijekom vašeg "obrazovanja" ne očekuje se da sve prototipe funkcija učite napamet niti išta drugo po tom pitanju, to bi bilo suludo. Bilo bi fino ako uhvatite čemu što služi, a za ostalo imate ovaj tutorial, dokumentaciju DirectXa te Google naravno ( i nas ovdje). Kao što znate, bez alata, nema ni zanata. A alate koje ćemo koristiti su: Visual C++ (Express Edition) sa DirectX SDK... I to je to. Spremni ste za lansiranje!

---------------------------------------------------------------------------

Osnove Win32 API-ja

Svi znamo što je DirectX iz prošle lekcije, također logično je zaključiti da se vrti na Windowsu (i kako Microsoft implicira, samo na njemu). Windows je zasnovan na konceptu prozora(windowsa) koji se grade uz pomoć Win32 API-ja. Vjerovali ili ne, DirectX pogonjene aplikacije su također prozori (no shit, Einstein!) pa nam nema druge, nego da krenemo od osnovama Win32 API-ja. Evo jedan primjer console aplikacije sa kojima smo se tako dugo družili:


#include <iostream> // uobicajeni input/output header file

int main() // program pocinje main funkcijom
{
cout << "Pozz" << endl; // ispisi "Pozz" na zaslon
return 0; // program/funkcija izvrsena uspješno
}

Vjerojatno znate već da je main() funkcija ulazna točka u svaki program. Ovaj program jednostavno ispisuje dosadnom bijelom bojom "Pozz" na crnu pozadinu Command Prompta. Ništa posebno, zabavno i teško da će ikoga sa mozgom impresionirati (možda neku pijanu plavušu). Ovo gore što vidite je primjer najednostavnijeg programa u C++u. Windows aplikacija, na drugu stranu, je različita po tome što ima dvije funkcije koje joj omogućuju rad sa Windowsom.

Jedna od tih je podjednaka već navedenoj main() funkciji. Druga omogućuje aplikaciji da bude pokretana događajima (iliti event-driven). Ti eventovi, ako ne znate, su interakcija korisnika i aplikacije poput klikanja štakorom, pritisak na tipku i tako dalje. Kada se takav neki event dogodi, Windows ga naravno dokumentira u poruku koju odašilje vašem programu da je procesira i interpretira. Dakle, drugo-spomenuta funkcija rukuje tim porukama i sadrži kôd koji treba pokrenuti pri zaprimanju određene poruke. Mi ćemo sada baciti se na pisanje na našeg prvog prozora i kao prvu funkciju definirati ćemo... WinMain()!

WinMain() funkcija je ekvivalent main funkciji u konzolnim programima. To je mjesto gdje aplikacija započinje i gdje se radi početna inicijalizacija. Inače bih definirao posebnu funkciju gdje bih napravio prozor no za potrebe ovog tutoriala, to ću napraviti izravno u WinMain() funkciji da dodatno ne kompliciramo stvari. Prvo ćemo proći kroz prototip WinMain() funkcije:


int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow);

Ovako ga sveta dokumentacija deklarira... Idemo malo secirati ga na manje dijelove da skužite ovaj naizgled strašni komad kôda. U duši je to obrijani pekinezer ; )

WINAPI

Koji li je sad ovo vrag, pitate se? Da, zalegao je baš između povratnog tipa funkcije i imena WinMain. Radi se o metodi dodjeljivanja parametara, kraće rečeno, redoslijedu čitanja parametara. Normalno to ide od desno prema lijevo, ali WINAPI ga obrne pa ide lijevo-nadesno. Zašto je to tako, pa to baš i nije najbitnija stvar na svijetu ali Windows želi tako da bude. So it shall be then!

HINSTANCE hInstance

Ovo je prvi parametar i stoji za "handle to an instance". Handle je 32-bitni integer koji indentificira nešto, poput objekta. Instanca je kopija aplikacije. Zbog multitaskinga i mogućnosti da se program vrti u više kopija istodobno, Windows treba način da ih indentificira i "žiki" ko' je ko'. To radi tako da dodijeli svakoj instanci "ručicu iliti handle", tj. integer koji omogućuje razlikovanje kopije aplikacija.

HINSTANCE hPrevInstance

Drugi parametar je sličan prvom, stoji za "handle to the previous instance". Što ovo znači? Npr. ako ima više kopija otvoreno, hPrevInstance će sadržavati ručicu ili handle zadnje instance aplikacije koja je napravljena. U biti, ovo je sve što trebate znati o njoj...

LPSTR lpCmdLine

Možda ste zamijetili po nazivu, ovo je dugački pokazivač iliti Long Pointer. On pokazuje na string koji sadržava zapovjednu liniju koja poziva aplikaciju. Ne kužite? Ni ja isto (joke). Evo primjer: ako ste imali aplikaciju nazvanu "MačkeiKerovi.exe" i pokrenuli je sa Run "programom" iz Start Menua, mogli biste dodati na bazu imena "MačkeiKerovi.app devMode" ili "MačkeiKerovi.app cheatMode". U svakom slučaju, sada bi program tražio za dodatne parametre. Shvaćate sada?

int nCmdShow

Ovaj parametar pokazuje kako će se prozor prikazati kada se stvori. Npr, ovo bi moglo pozvati prozor da bude minimiziran, maximiziran i drugi -miziran... Ovo vam neće biti potrebno, ali tu su ako vam zatrebaju.

-------------------------------------------------------------------

V-v-v-v-vrijeme je za d-d-d-d-dvoboj... Dosta teorije...
Dosta smo zjakali o prototipima funkcije WinMain(). 'Ajmo nešto napraviti. Nešto novo, nešto divlje... Predlažem da napravimo napokon taj naš prozor. Može? Naravno da može :)

Ovo dolje je naša aplikacija, kao što vidite, više nismo na običnim console aplikacijama.



// osnovni header fileove koji su nam potrebni

#include <windows.h>
#include <windowsx.h>

//prototip WindowProc funkcije (sjećate se, ona koja rukuje sa porukama)

LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

// ulazna točka u svaku windows aplikaciju

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// "ručica za prozor", ispuni se funkcijom
HWND hWnd;
// struktura prozora
WNDCLASSEX wc;

// očisti strukturu prozora
ZeroMemory(&wc, sizeof(WNDCLASSEX));

// ajmo popuniti članove ove strukture
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";

// registriraj klasu koristeći informacije date wc-u
RegisterClassEx(&wc);

// napravi prozor i koristi rezultat kao ručicu (hWnd)
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // Ime windows klase
L"PCPlay Ucionica", // Naziv prozora
WS_OVERLAPPEDWINDOW, // stil prozora
300, // x-pozicija prozora
300, // y-pozicija prozora
500, // širina prozora
400, // visina prozora
NULL, // nema roditeljskog prozora
NULL, // ne koristimo izbornik
hInstance, // ručica aplikacije (remember)
NULL); // ne koristi se sa više prozora

// napokon ju prikaži:
ShowWindow(hWnd, nCmdShow);

// ulaz u glavni loop

// ova struktura sadrži predivne poruke koje nam Windows šalje
MSG msg;

// dakle, čekamo dok dobijemo poruku od Windowsa
while(GetMessage(&msg, NULL, 0, 0))
{
// prevedi u programu razumljiv format
TranslateMessage(&msg);

// pošalji WindowProc() funkciji da ju procesira
DispatchMessage(&msg);
}

// vrati ovaj dio WM_QUITa Windowsu
return msg.wParam;
}

// ova funkcija rukuje sa svim porukama koje naša aplikacija prima
// dakle, poštar :)

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// zatvori aplikaciju
PostQuitMessage(0);
return 0;
} break;
}

// rukuj sa svakom porukom koju WindowProc nije procesirao osobno
return DefWindowProc (hWnd, message, wParam, lParam);
}

Sada kada bi ovo pokrenuli, dobili bi fini prozorček. Iscrtan vama pod nosom:
http://img47.imageshack.us/img47/7527/ucionicabi7.jpg


Uf, koliko toga ima... Komentari su na mjesto ali definitivno niste sve pohvatali... Idemo, top to bottom:

Osnovno što kod svake windows aplikacije je potrebno napraviti jest registrirati njenu klasu, napraviti prozor i prikazati ga naravno na zaslonu korisnika. Najjednostavnije postavljeno, windows klasa ili klasa prozora je osnovna struktura koju Windows koristi da bi rukovao osobinama i akcijama različitih prozora. Nećemo i ne da mi se ulaziti u detalje svega ovoga.. Za detalje uvijek imate MSDN. Uglavnom, napravite WNDCLASSEX strukture, date podatke njoj i onda koristeći to registrirate klasu prozora.

WNDCLASSEX wc;

Ovo je struktura koja sadrži informaciju o windows klasi. Nećemo ići skroz kroz njen sadržaj, pošto neki neće ni biti potrebni u game programiranju ali vidi se uglavnom kroz kontekst isto. Kao što sam rekao, MSDN je biblija za svakog Windows programera. A biblija kao biblija ima odgovor na sva vaša pitanja...

ZeroMemory(&wc, sizeof(WNDCLASSEX));

ZeroMemory je funkcija koja pokreće cijelu strukturu ili bolje da kažem briše sve iz strukture i postavlja na NULL. Cijeli blok memorije dakle. Adresa je dodijeljena kroz prvi parametar (&wc). Drugi parametar potreban je da kaže funkciji koliko je struktura velika. Dakle, smisao je da se pokrene struktura wc na NULL.

wc.cbSize = sizeof(WNDCLASSEX);

Ovo služi da postavi strukturu na potrebnu veličinu. A to radimo sa sizeof() operatorom.

wc.style = CS_HREDRAW |CS_VREDRAW;

U ovom članu definiramo kakav je stil našeg prozora. Ima mnoštvo vrijednosti koje ovdje možemo ubaciti, ali većinu nećemo koristiti za naše potrebe. Moguće vrijednosti pronađite u MSDNu pod WNDCLASSEX. Za sada koristit ćemo CS_HREDRAW i logički ili (|) sa CS_VREDRAW. Što ovo dvoje radi jest da kažu Windowsu da iscrta ponovno prozor u slučaju da se pomakne vertikalno ili horizontalno. Uglavnom, korisno samo kod aplikacija nevezanih uz igre.

wc.lpfnWndProc = (WNDPROC)WindowProc;

Što ova vrijednost radi jest da govori windows klasi koju funkciju koristiti kada dospije neka poruka od Windowsa. U našem programu, ova funkcija odgovara WindowProc(), ali također može imati drugačiji naziv poput WinProc(), get the point? Nije bitno kako se zove ako je pravilno damo kao vrijednost u član lpfnWndProc strukturi wc u ovom slučaju.

wc.hInstance = hInstance;

S ovim smo upoznati. Ovo je ručica kopiji naše aplikacije. Jednostavno treba postaviti vrijednost koju smo dali WinMain().

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

Ovaj član pohranjuje uobičajeni kursor miša za windows klasu. Ovo je napravljeno kroz return vrijednost funkcije LoadCursor(), koja sadrži dva parametra. Prvi je hInstance aplikacije koja pohranjuje kursor. Nećemo ulaziti u ovo, nema smisla, samo puknite NULL. Drugi parametar odnosi se na vrijednost koja sadrži uobičajeni kursor. Ako želite laprdekati i gledati druge mogućnosti... MSDN, mensch!

wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

Ovaj član sadrži "brush" u nazivu koji se koristi da oboji pozadinu. Ovo nam neće biti potrebno u DirectXu, nego ćemo ga ostrakizmom protjerati iz naše aplikacije. Čitaj - iskomentirat ćemo ga :)

wc.lpszClassName = L"WindowsClass1";

Ovo je ime klas koju gradimo. Nazivamo ju "WindowClass1" jer nemamo mašte. Nije bitno kako se zove. Može biti "SečenovaBaka" ako se osjećate posebno kreativno. ^^ Što se tiče onog čudnovatog L ispred stringa. To govori compileru da string bi trebao biti napravljen od 16-bitni Unicode charactera, a ako ne znate što je unicode... GOOGLE!

RegisterClassEx(&wc);

Ova funkcija se praktički sama objašnjava. Ona konačno registrira sva ova čudesa iznad. Ide jedan parametar, a to je adresa wc strukture koju smo maloprije popunili. Komplicirano, zar ne?

-----------------------------------------------------------

Vjerovali ili ne, sve ovo dosad je bila registracija klase samo... Baf XD
Hajdemo sada napraviti taj prozorček. Kada je naša klasica fino napravljena možemo napraviti taj prokleti prozor. Samo ćemo opet morati proći kroz mnoštvo patnje i boli... Dakle prototip CreateWindowEx():


HWND CreateWindowEx(DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);

Divno, zar ne? Ah, ne prigovarajte, niste vi taj koji piše :P
Gladan sam, žedan sam, a u pozadini slušam Nightwish... Jesam li spomenuo da sam gladan? Hehe, dosta cviljenja... NA POSAO!


DWORD dwExStyle,

Ovo je nadodatak na četvrti parametar, dwStyle pa se nećemo previše bakćati sa detaljima, jednostavno puknite NULL i haj'mo dalje.

LPCTSTR lpClassName,

Naizgled dugačko, no ipak tako jednostavno. Ovo je ime klasu koju naš prozor koristi. Pošto imamo samo jednu klasu, koristit ćemo L"WindowsClass1". Ovaj string također koristi 16-bitne Unicode charactere i to je razlog L-a ispred njega.

LPCTSTR lpWindowName,

Ovo je ime prozora, ono što piše u gornjem lijevom kutu. Također koristi Unicode.

DWORD dwStyle,

Ovo je mjesto gdje deklariramo različite opcije za naš prozor. Možemo stilizirati ga u biti, izbaciti stvari koje smatramo da nam ne trebaju ili ne smiju biti omogućene. Više detalja o mogućim opcijama, naravno, po 3.14159265 put, MSDN pod CreateWindowEx(). Mi ćemo koristiti WS_OVERLAPPEDWINDOW, što je najjednostavniji način da dođemo do osnovnih fičra prozora.

int x, int y, int nWidth, int nHeight,

Ovo vjerojatno svi već kužite... Dakle pozicija prozora na zaslonu i širina te visina istog.

HWND hWndParent,

Ovo je parametar koji govori windowsu da li ima neki roditeljski /parent\ prozor koji sadrži druge prozorčiće. Nema ga, dakle null. PaintShop Pro npr sastoji se od jednog parent prozora i malih prozorčića. Svi povezani u jedan app.

HMENU hMenu,

Ovo je ručica za izbornik. Nemamo ga, dakle NULL.

LPVOID lpParam

Ovo je parametar koji bi koristili da tvorimo više prozora. Mi ih ne radimo, opet NULL postavljamo kao vrijednost.

Povratna vrijednost

Sve ovo što unutar ove funkcije uradimo (CreateWindowsEx()) spremamo direktno u našu hWnd varijablu. I to je sve što se tiče kreiranja prozora.

----------------------------------------------------------

Sljedeći zadatak jest da prikažemo taj prozor koji cijeli dan radimo. To je prilično jednostavan zadatak. Koristimo funkciju ShowWindow() sa povratnom vrijednosti BOOL.

Kako smo se navikli, sve započinjemo prototipom:


BOOL ShowWindow(HWND hWnd,
int nCmdShow);


HWND hWnd,

Ovaj parametar je ručica prozoru kojeg smo toliko mukotrpno stvarali. Ovdje vraćamo vrijednost koju nam je Windows vratio nakon funkcije CreateWindowEx().

int nCmdShow

Ako zavirite u sjećanje, zadnji parametar u WinMain() funkciji... Ovo je mjesto gdje ga koristimo. Naravno, ne moramo, ali u ovom slučaju ići ćemo prema pravilima i dati Windowsu što želi. Ovo u fullscreenu neće biti bitno. Dakle, 'nough chit-chat.

---------------------------------------------------------


Iako smo napravili prve tri faze, ovo još nije gotovo ( OMG!!)... Hehe, ima još jedna mala fazica i funkcijica koja je prijeko potrebna. Prvo ćemo se pozabaviti glavnim loopom. On glasi u našem programu ovako:


// ova struktura sadrži predivne poruke koje nam Windows šalje
MSG msg;

// dakle, čekamo dok dobijemo poruku od Windowsa
while(GetMessage(&msg, NULL, 0, 0))
{
// prevedi u programu razumljiv format
TranslateMessage(&msg);

// pošalji WindowProc() funkciji da ju procesira
DispatchMessage(&msg);
}

Ajmo od vrha prema dolje:

MSG msg;

MSG je struktura koja sadrži sve podatke potrebne za zasebnu poruku. Mislim da nećemo direktno pristupati sadržaju njenom. Tako da napravite to i jednostavno nastavite :)

while(GetMessage(&msg, NULL, 0, 0))

GetMessage() je funkcija koja preuzima svaku poruku iz reda čekanja i ubacuje ju u našu msg strukturu. Uvijek vraća true, osim kada program se gasi, tada vraća naravno, false. Na taj način, naš while() loop je razbijen samo kada je program izvršen uspješno.

GetMessage() funkcija neće biti nama od velike koristi jer ću vas već u sljedećoj lekciji upoznati sa boljim načinom.

WindowProc() funkcija

Sada kad imamo većinu toga dovršeno, ostaje nam još poštar da se sredi. Gore spomenuta GetMessage() funkcija dobiva poruku, prevodi ju u format razumljiv programu i šalje ju ovdje. Kada je WindowProc() pozvan četiri podatka moraju stići da bi bilo uspješno.


LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);


U ovom slučaju ćemo se usredotočiti na sadržaj ove funkcije, izostavljajući parametre koje ću objasniti prilikom sljedećeg tutoriala kada vam predstavim zamjenu za GetMessage() funkciju. Kada poruka uđe u WindowProc, možemo koristiti uMsg argument da ustanovimo koja je to poruka. 99% programera koristi switch() izjavu da ustanove o čemu se radi.


// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// kôd ide ovdje
} break;
}

Ovako izgleda switch izjava za kôd koji treba vrtiti. Naravno, imamo ovdje samo WM_DESTROY, što znači da će se druge poruke ignorirati. WM_DESTROY poziva se samo na kraju kada se prozor zatvara. Ovo činimo kada želimo zatvoriti potpuno program:


// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// zatvori aplikaciju
PostQuitMessage(0);
return 0;
} break;
}

PostQuitMessage() funkcija šalje vrijednost '0' WM_QUITu. Ako se prisjetite opet, u vašem glavnom loopu. GetMessage() vraća samo false ako se program gasi. 0 = false. Dakle, omogućuje da se WinMain() završi uspješno.


Evo ga... Ovo je tek mali uvod u Win32, ali moram reći ako ste naučili sve što sam vam ovdje prepričao da niste loši... Polako napredujete i uskoro ćete imati priliku baciti se u Direct3D.

Ja ode jest', pit' i promijenit' pjesmu. :)

RayDX
10-12-2007, 11:45
Ops, greška... XD :rotfl:

SkunK
10-12-2007, 16:29
Jako dobro.

SkunK
10-12-2007, 18:48
Zato jer ima malo ovakvih tekstova ( ovakve tematike ) na Hrvatskom.
Jedino neka knjiga.

LukA33
10-12-2007, 20:31
Svaka čast care jedini si na svijetu takav nema boljih samo nastavi tako

RayDX
10-12-2007, 22:54
hvala na komentarima, pošto su me ispiti ovaj tjedan pritisnuli da jedva malo zavirim na net svakih 8h, jer konstantno moram učiti tek u petak i subotu ću moći napisati novi tutorijal, kada ćemo napraviti prvi naš 2D realtime render... A triangle :) Odmah dan poslije stiže 3D pravi kada ćemo 2D trokut gurnuti kroz 3D pipeline (i zavrtiti ga oko osi) kada ću objasniti matrice, projekcije, kameru itd. Ali naravno, krećemo od osnova d3d-a na svima razumljivom jeziku.

Plexihack
11-12-2007, 15:10
Odličan tutorial...napokon nešto šta nije na ingleškom jer mi se više neda čitati engleski...

RayDX
29-12-2007, 17:12
Evo me, jeste spremni da vam izlistam novu lekciju? Naravno da ste spremni! Kao prvo, isprike na podebljem kašnjenju. Ovo će biti prilično dugačka lekcija (u dva poglavlja jer ćemo postupno ići do našeg prvog trokuta u fullscreen prostoru. Bez previše zjakanja i okolišanja, here goes nothin'! Ovo će biti lekcija koja će zatvoriti Win32 poglavlje. Odmah pišem sljedeći dio, pa se strpite.

Voajerska poruka

Kao što naslov kaže, danas ćemo govoriti o nestašnom bratu GetMessage poruke koji će nam zapravo omogućiti razvoj 3D/2D aplikacija kako spada. Voajerska (nisam siguran da sam dobro napisao hehe) poruka iliti PeekMessage() funkcija, koji je to vrag? Pa naša zamjena na klupi koja upravo izlazi na teren. Kao prvo, osjećam čudni nagon da vam pojasnim zašto baš PeekMessage kad je GetMessage funkcija radila super. Istina, u Windows aplikacijama je prilično solidna, no kod 3D aplikacija moramo vrtiti minimalno 30 sličica u sekundi da bi mogli uživati u ugodnoj fluidnoj atmosferi igre. Eh, tu GetMessage zakaže jer kada mu windows da poruku on ju procesira i čeka dok se vrati informacija njemu da ide dalje. PeekMessage kako je maštovito nazvan, on ju primi, pošalje i ide dalje, ništa drugo ga ne zanima što nam je kritično kod 3D aplikacija kako ćete vidjeti kasnije kada budem objašnjavao swap chainove i back buffere. Zasada nadam se da ste shvatili ovu definiciju i držite se nje. Pređimo sada na prototip same PeekMessage funkcije:


BOOL PeekMessage(LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg);

Ah... Prva četiri argumenta su nam dobro poznata, ako nisu to znači da ste lijeno čitali prvi dio pa vam preporučam da se vratite na prijašnju lekciju te da naučite to. Hehe, ako ste malo zaboravili, samo idite tamo i podsjetite se.

UINT wRemoveMsg

Čemu ovo služi? Prema onome što mi je dokumentacija rekla, indicira da li će zaprimljena poruka ostati ili ne u event queueu ( eng. queue (kjui) == red ). Tu stavljamo ili PM_REMOVE ili PM_NOREMOVE. Uglavnom, mi ćemo se koristiti prvom zasad jer ona vadi poruke iz queuea nakon što su pročitane. Kako će izgledati sada naš kôd:


// primijetite kako ovo zapravo stvara infinite loop
while(TRUE)
{
// Provjeri za porukice u event queueu... Kako glupa riječ ^^
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Ako je poruka jednaka WM_QUIT, break out!
if (msg.message == WM_QUIT)
break;

// Standarno procesiranje kroz WindowProc funkciju
TranslateMessage(&msg);
DispatchMessage(&msg);
}

}

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

Vjerojatno ste zamijetili ovo kao veću novost u promijenjenom kôdu. Vrlo je jednostavno zapravo, pošto više ne čekamo poruke dok se vrate nazad nego jednostavno jurimo dalje potrebno je samo da funkcija preleti preko sadržaja poruke i ide dalje. Koristimo if da nam vrati ili 1 (true) ukoliko ima poruke, a 0 (false) ako nema. Malo izgleda čudno ali sve je logično, rekao bi Tuvok.

if (msg.message == WM_QUIT)

Otkud sad ovo ovdje gore, pitate se? Ako poruka kaže WM_QUIT, znači da je vrijeme da izađemo iz našeg infinite loopa. Ako ste kao ja, dva sata u budućnosti stalno, vjerojatno se pitate, a kako ćemo samo izaći iz fullscreena poslije? Hehe, vidjet ćete...

Ako ste se malo zagubili u kôdu, evo vam moj:


// osnovni header fileove koji su nam potrebni

#include <windows.h>
#include <windowsx.h>

//prototip WindowProc funkcije (sjećate se, ona koja rukuje sa porukama)

LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

// ulazna točka u svaku windows aplikaciju

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// "ručica za prozor", ispuni se funkcijom
HWND hWnd;
// struktura prozora
WNDCLASSEX wc;

// očisti strukturu prozora
ZeroMemory(&wc, sizeof(WNDCLASSEX));

// ajmo popuniti članove ove strukture
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";

// registriraj klasu koristeći informacije date wc-u
RegisterClassEx(&wc);

// napravi prozor i koristi rezultat kao ručicu (hWnd)
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // Ime windows klase
L"PCPlay Ucionica", // Naziv prozora
WS_OVERLAPPEDWINDOW, // stil prozora
300, // x-pozicija prozora
300, // y-pozicija prozora
500, // širina prozora
400, // visina prozora
NULL, // nema roditeljskog prozora
NULL, // ne koristimo izbornik
hInstance, // ručica aplikacije (remember)
NULL); // ne koristi se sa više prozora

// napokon ju prikaži:
ShowWindow(hWnd, nCmdShow);

// ulaz u glavni loop

// ova struktura sadrži predivne poruke koje nam Windows šalje
MSG msg;

// primijetite kako ovo zapravo stvara infinite loop
while(TRUE)
{
// Provjeri za porukice u event queueu... Kako glupa riječ ^^
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Ako je poruka jednaka WM_QUIT, break out!
if (msg.message == WM_QUIT)
break;

// Standarno procesiranje kroz WindowProc funkciju
TranslateMessage(&msg);
DispatchMessage(&msg);
}

}

// vrati ovaj dio WM_QUITa Windowsu
return msg.wParam;
}

// ova funkcija rukuje sa svim porukama koje naša aplikacija prima
// dakle, poštar :)

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// zatvori aplikaciju
PostQuitMessage(0);
return 0;
} break;
}

// rukuj sa svakom porukom koju WindowProc nije procesirao osobno
return DefWindowProc (hWnd, message, wParam, lParam);
}

Razlike u izgledu programa nema, ali uskoro ćete shvatiti zašto smo prolazili kroz ovo.

STICKY DOK SE NISAM NALJUTIOS

:rotfl:

Luka
29-12-2007, 19:16
evo sticky kad već toliko želiš :)

dobar tut inače ;)

RayDX
29-12-2007, 20:18
Welcome to the World of DirectX (Additional fees may apply)

http://www.toondra.ru/3d/001.jpg

Ovo gore nećete još dugo proizvoditi... Ako i nakon ovog nastavite čitati, onda ste vrijedni DirectXa. Sada ću što kraće nastojati udarati sa teorijom, pretpostavljam da imate osnovno znanje srednjoškolske algebre, znate što su koordinate, osnovno poznavanje trigonometrije (valjda svi znaju, ja imam 16 godina pa sam upoznat s njom... oke, 15 :(). Nastojat ću održati ovo na što kraćim definicijama no malo teorije je potrebno. Za sve ostalo, tu je izvrsna dokumentacija od strane velikog M$a. Moram još napomenuti da ćemo za vrijeme igranja sa DirectXom nabasati na više pointera nego što u Bosni ima ćevapa (hehe, teško) ali probat ćemo, tako da definitivno ponovite pointere/pokazivače, kako god ih zovete.

Swap chain i Page Swapping

Dakle, sada ćemo raspraviti nešto od iznimne važnosti za bilo koju 2D/3D aplikaciju. Nadam se da će vam dati bolju sliku o načinu funkcioniranja runtime grafike. Grafički adapter u svojoj memoriji sadržava pointer prema spremniku pixela koji sadržavaju sliku koja se prikazuje na zaslonu. Svaki put kada želite prikazati neki 3D model, scenu, 2D lik... U biti, štogod vam padne napamet, treba se nadopuniti array koji sadrže potrebne podatke i dodati nove te ponovno iscrtati na monitor. Ovo npr. ne bi mogli napraviti sa GetMessage() funkcijom u glavnom loopu. Uskoro ćete saznati zašto. Nadovezujući se na prijašnju temu, dolazimo do prvog problema. Refresh rate... Zapravo, naši monitori ne refreshiraju se dovoljno brzo sa istinski realtime rendering, većina se kreće od 60 do 100 Hz (moj je 60Hz). Kada bi usred refreshiranja ubacili novi model u grafički adapter, slika bi prikazala prerezane dijelove novog rendera i starog rendera (refresha se odozgo prema dolje). Taj efekt se maštovito naziva deranjem (tearing in eng). DirectX je "prokužio" ovaj problem i uveo swapping.

Umjesto da izravno renderira novu sliku na monitor, Direct3D iscrtava sljedeću sliku u pomoćni spremnik pixela nazvan back buffer. Dakle, ono što je trenutno iscrtano je front buffer. Sve slike se iscrtavaju u back buffer i onda tek ih Direct3D ažurira na zaslon vašeg monitora, a nakon što je gotovo, jednostavno ju odbaci oslobađajući mjesto za sljedeće slike. Naravno problem još nije otišao, DirectX koristi pointer za svaki spremnik (buffer) i jednostavno mijenja njihove vrijednosti. Trik je mali, ali pali. Aplikacija se dodatno poboljšava dodavanjem dodatnih back buffera koji koriste vrijeme između iscrtavanja da bi se što bolje iskoristili resursi, a samim time i performanse ojačaju.

IDEMO!!!

Ubi' ja vas sa teorijom hehe, evo napokon, ali stvarno napokon krećemo sa DirectXom. Nadam se da ste instalirali DirectX i dodali putove u compiler, a ako niste ima hrpa odličnih tutorijala na netu da to napravite. U biti, nastavljamo na starom projektu, ne morate se ništa brinuti, samo moramo dodati par stvari na njega.


// osnovni header fileove koji su nam potrebni

#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

// uključi Direct3D biblioteku u projekt
#pragma comment (lib, "d3d9.lib")

// globalne deklaracije
LPDIRECT3D9 d3d; // pointer prema našem D3D sučelju
LPDIRECT3DDEVICE9 d3ddev; // pointer prema klasi uređaja

//prototip funkcija vezanih uz Direct3D
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); //renderira jedan frejm/sliku/render
void cleanD3D(void); // Čisti nered koji smo napravili

//prototip WindowProc funkcije (sjećate se, ona koja rukuje sa porukama)
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);


#include <d3d9.h>

Razmišljao sam da li da ovo uopće boldam i opisujem. Ovo je header file koji uključujemo u projekt da bi imali pristup Direct3D funkcijama, različitim klasama i structovima.

#pragma comment(lib, "d3d9.lib")

Ovo uključuje biblioteku D3Da u naš projekt. #pragma comment postavlja određeni komad informacije u naš object file (od projekta). Prvi parametar (lib) govori compileru da želimo dodati biblioteku u projekt, a poslije, logično, navodimo njeno ime i eventualno lokaciju ako to nismo napravili jednostavnijim putem.

LPDIRECT3D9 d3d;

Ovo je dugački pokazivač ilitiga Long Pointer (naglašavam LP) prema Direct3Du. Ovo znači da ćemo napraviti klasu zvanu iDirect3D9. Kada COM (Component Object Model) napravi ovu klasu, mi ćemo ju zapravo ignorirati i koristit ju samo indirektno preko našeg dugačkog pointera.

LPDIRECT3DDEVICE9 d3ddev;

Uf, pogle ovoga... Direct3D uređaj sadrži sve informacije koje se tiču grafičkih drivera, grafičke kartice i svega ostalog što ima svoje prste u hardverskoj strani grafike. Ovo je također dugački pointer prema klasi koja sprema sve ove podatke.

(Nadam se da shvaćate da ova imena nisu statična i da ih možete nazvati kako god vam padne napamet. D3D device možete umjesto d3ddev nazvati pade_ovca_sa_grane ako se osjećate vrlo kreativno.)

-----------------------------------------------------------------------------

U ovom segmentu ćemo obraditi funkcije čije smo prototipe definirali maloprije. Da bismo dobili naš današnji cilj što je zapravo čistka ekrana u totalno crnu boju. Joj, hvala meni, ja sam tako koristan... Oke, dosta egotripa. Na posao. Evo i prve funkcije, inicijalizacija D3Da.

initD3D()


// ova funkcija priprema Direct3D za korištenje
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface

D3DPRESENT_PARAMETERS d3dpp; // stvaramo strukturu koja će sadržavate neke parametre, kul, zar ne?

ZeroMemory(&d3dpp, sizeof(d3dpp)); // počisti istu za korištenje
d3dpp.Windowed = TRUE; // program je zasad prozor
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // odbaci stare slike
d3dpp.hDeviceWindow = hWnd; // odredi koji će prozor D3D koristiti

// sada stvori uređaj na bazi svih podataka gore.
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);

return;
}

Zamislite da mi sada nestane struje u kući... brrrr :oS


d3d = Direct3DCreate9(D3D_SDK_VERSION);

Čestitam, ovo je vaša prva Direct3D funkcija koju ste potegnuli. Njena primarna svha jest da stvori Direct3D sučelje. Povratna vrijednost vraća adresu stvorenog uređaja pa ćemo ovo spremiti u pointer koji smo prije napravili, baš za ovo. Slatko kako se sve poklapa zar ne? A ovaj parametar, kako se on uklapa u pričicu? Pa on je uvijek isti i prilično nam pojednostavljuje cijelu stvar, u biti prepustite D3Du da odredi koja mu je verzija.

D3DPRESENT_PARAMETERS d3dpp;

Ovo je prilično bitno pri stvaranju d3d uređaja. Ima ih hrpa, a mi smo početne fino popunili. Zasad, D3DPRESENT_PARAMETERS je struktura čiji članovi sadržavaju vitalne informacije kojima ćemo nahraniti grafički d3d uređaj. Prvo što smo uradili, a to i od prije znate, koristili smo ZeroMemory funkciju da inicijaliziramo strukturu na početne vrijednosti. Kroz komentare sam pojasnio čemu služi određeni član, a vjerujem da se vidi iz aviona. Ako netko ne kuži, neka napiše dolje pa ću dodatno pojasniti.

d3d->CreateDevice()

Malo poveća funkcija, hehe. No, kao što sam i milju puta do sada rekao, sve je logično i jednostavno, samo ne pokazujte strah. Možda ste primijetili da je ova funkcija član d3d klase do koje dolazimo indirektno preko pointera (->). Pošto je ovo prilično bitna stvar za današnju lekciju, vrijeme je da ju disociramo. Evo ga prototip, behold:


HRESULT CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 **ppReturnedDeviceInterface
);

Boo! Pa i nije toliko strašno, zar ne? Krenimo od početka.

UINT Adapter,

Ovo je nepotpisani (unsigned) integer koji pohranjuje uvijek pozitivnu vrijednost koja indicira koji grafički adapter, ili grafičku karticu, Direct3D treba koristiti. Pošto se trudim da održim sve što kraćim i jednostavnijim definicijama objašnjeno, neću ulaziti u detalje. Pošto je u 95% slučajeva (99% statistika je izmišljeno na mjestu btw.) samo jedan takav, pustit ćemo D3D da odluči. Dakle, stavljamo vrijednost D3DADAPTER_DEFAULT.

D3DDEVTYPE DeviceType,

Ovdje postoje 4 moguće vrijednosti koje možemo ubaciti, mi ćemo se koncentirati na jednu, nama najbitniju, a to je D3DEVTYPE_HAL.

D3DDEVTYPE_HAL govori D3Du da koristi ono što nazivamo hardverskim apstraktnim slojeva ( Hardware Abstraction Layer iliti HAL), njega koristimo da bi indicirali da D3D treba koristiti hardver da bi procesirao grafiku. Ako bi u kojem slučaj hardverski dio zakazao, softver bi uskočio... Što neće, ne brinite... Ako se dogodi, onda se zabrinite :rotfl:

HWND hFocusWindow,

Ovo je ručica našega prozora, ubacite samo hWnd i to je to.

DWORD BehaviorFlags,

Ovdje ima hrpa vrijednosti koje možemo ubaciti u ovaj parametar, ali samo ćemo ih par pokriti sad. D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DCREATE_MIXED_VERTEXPROCESSING i D3DCREATE_HARDWARE_VERTEXPROCESSING. Ove tri vrijednosti se skoro same objašnjavaju. Softverska indicira da sve 3D kalkulacije će se odvijati sa softwarem, hardverska na hardwareu, a kombinacija sa kombinacijom. Hehe, jednostavno. Mi ćemo naravno koristiti prvu navedenu jer softverska osigurava kompatibilnost s vašim kantama. I mojom naravno.

IDirect3DDevice9 **ppReturnedDeviceInterface

Ovo predstavlja pointer prema sučelju grafičkog uređaj. Otprije je definiran u našem "malom" programu kao d3ddev, pa ćemo staviti &d3ddev u ovaj parametar. Ampersand(&) operator znate čemu služi, nadam se. Uf, fala bogu, sector alpha je gotov ^^

render_frame()

Sada ćemo se baviti render_frame() funkcijom. Ona je više nego jednostavna i prilično kratka. Želimo renderirati jedan jedini frame, čudno? Ne, jer se ova funkcija stalno iterira kroz glavni loop programa pa tako 1 frame, pa 2 framea, 30 frameova. Get it? Pa naravno da kužite, hehe.

Ajmo sada malo pogledati izbliza tu famoznu funkciju.


// ovu funkciju koristimo da renderiramo jedan frame
void render_frame(void)
{
// pozadinu oboji u crno.
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene(); // započinje 3D scenu

// ovdje dodajete stvari da se renderiraju u back bufferu.

d3ddev->EndScene(); // završava 3D scenu

d3ddev->Present(NULL, NULL, NULL, NULL); // prikazuje renderiranu "scenu"

return;
}

d3ddev->Clear()

Ova funkcija "čisti" spremnik na određenu boju. U našem slučaju, to je 0, 0, 0 (dakle, crna). Zbog činjenice da je većina parametara u ovom trenutku nebitna, jednostavno ćemo ih ostaviti na miru. Prva dva su postavljena na 0 i NULL kako se vidi u kôdu, tako indiciramo da se cijeli back buffer očisti. Treći parametar postavljen na D3DCLEAR_TARGET, indicira da se back buffer treba očistiti. Ima i drugih tipova buffera pa moramo biti specifični. Sljedeće na red dolazi boja gdje koristimo funkciju D3DCOLOR_XRGB() da bi ju dobili. Sljedeća dva parametra su zasad nebitna, stavite ih na 1.0f i 0.

d3ddev->BeginScene()

Ovdje pozivamo funkciju BeginScene() koja kaže Direct3Du (D3Du) da smo spremni renderirati. Ova funkcija mora biti pozvana zbog dva razloga. Prvo, treba reći D3Du da kontroliramo memoriju. Kao drugi razlog, ova funkcija radi nešto na hardverskom nivou što nazivamo zaključavanje (locking). Spremnik se na VRAMu doslovce zaključava te eksluzivno vama dopušta korištenje memorije.

d3ddev->EndScene()

Vjerojatno predpostavljate da ova funkcija djeluje suprotno od BeginScene() funkcije. Otključava pristup memoriji, čime dozvoljava drugim procesima da ju koriste.

d3ddev->Present()

Zadnji, ali i najbitniji bez čega ovo gore ne bi imalo nikakvog smisla. Ime ga samo objašnjava, a parametri su zasad totalni nebitni, dakle samo pobacajte NULL svuda (x4 ^^).

cleanD3D()

Uf, evo je... Zadnja funkcija ovog napornog ali vrijednog tutorijala. cleanD3D() čisti sav nered i oslobađa Direct3D i memoriju koju je gutao dosad.


// ova funkcija čisti nered za nama
void cleanD3D(void)
{
d3ddev->Release(); // oslobodi D3D grafički uređaj
d3d->Release(); // oslobodi Direct3D
return;
}

Mislim da ovdje ne treba ništa posebno pojašnjavati, koristimo Release() funkciju. Bez ove funkcije, Direct3D bi nastavio raditi na vašoj pozadini i jednostavno bi gušio vaše dragocjene resurse. Gadno, a? Tako da ni slučajno ne promašite ovu funkciju. A sada, evo sav naš posao sa svim komentarima zasad, pa proučavajte i testirajte:


// osnovni header fileove koji su nam potrebni

#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

// uključi Direct3D biblioteku u projekt
#pragma comment (lib, "d3d9.lib")

// globalne deklaracije
LPDIRECT3D9 d3d; // pointer prema našem D3D sučelju
LPDIRECT3DDEVICE9 d3ddev; // pointer prema klasi uređaja

//prototip funkcija vezanih uz Direct3D
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); //renderira jedan frejm/sliku/render
void cleanD3D(void); // Čisti nered koji smo napravili

//prototip WindowProc funkcije (sjećate se, ona koja rukuje sa porukama)

LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

// ulazna točka u svaku windows aplikaciju

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// "ručica za prozor", ispuni se funkcijom
HWND hWnd;
// struktura prozora
WNDCLASSEX wc;

// očisti strukturu prozora
ZeroMemory(&wc, sizeof(WNDCLASSEX));

// ajmo popuniti članove ove strukture
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";

// registriraj klasu koristeći informacije date wc-u
RegisterClassEx(&wc);

// napravi prozor i koristi rezultat kao ručicu (hWnd)
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // Ime windows klase
L"PCPlay Ucionica", // Naziv prozora
WS_OVERLAPPEDWINDOW, // stil prozora
300, // x-pozicija prozora
300, // y-pozicija prozora
500, // širina prozora
400, // visina prozora
NULL, // nema roditeljskog prozora
NULL, // ne koristimo izbornik
hInstance, // ručica aplikacije (remember)
NULL); // ne koristi se sa više prozora

// napokon ju prikaži:
ShowWindow(hWnd, nCmdShow);
// inicijalizacija D3Da
initD3D(hWnd);

// ulaz u glavni loop

// ova struktura sadrži predivne poruke koje nam Windows šalje
MSG msg;

// primijetite kako ovo zapravo stvara infinite loop
while(TRUE)
{
// Provjeri za porukice u event queueu... Kako glupa riječ ^^
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Ako je poruka jednaka WM_QUIT, break out!
if (msg.message == WM_QUIT)
break;

// Standarno procesiranje kroz WindowProc funkciju
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//pozivamo funkciju da renderira unutar loopa
render_frame();

}
//kada izađe iz loopa, zatvori Direct3D uređaj i direct3D
cleanD3D();
// vrati ovaj dio WM_QUITa Windowsu
return msg.wParam;
}

// ova funkcija rukuje sa svim porukama koje naša aplikacija prima
// dakle, poštar :)

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// zatvori aplikaciju
PostQuitMessage(0);
return 0;
} break;
}

// rukuj sa svakom porukom koju WindowProc nije procesirao osobno
return DefWindowProc (hWnd, message, wParam, lParam);
}

// ova funkcija priprema Direct3D za korištenje
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface

D3DPRESENT_PARAMETERS d3dpp; // stvaramo strukturu koja će sadržavate neke parametre, kul, zar ne?

ZeroMemory(&d3dpp, sizeof(d3dpp)); // počisti istu za korištenje
d3dpp.Windowed = TRUE; // program je zasad prozor
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // odbaci stare slike
d3dpp.hDeviceWindow = hWnd; // odredi koji će prozor D3D koristiti

// sada stvori uređaj na bazi svih podataka gore.
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);

return;
}

// ovu funkciju koristimo da renderiramo jedan frame
void render_frame(void)
{
// pozadinu oboji u crno.
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene(); // započinje 3D scenu

// ovdje dodajete stvari da se renderiraju u back bufferu.

d3ddev->EndScene(); // završava 3D scenu

d3ddev->Present(NULL, NULL, NULL, NULL); // prikazuje renderiranu "scenu"

return;
}

// ova funkcija čisti nered za nama
void cleanD3D(void)
{
d3ddev->Release(); // oslobodi D3D grafički uređaj
d3d->Release(); // oslobodi Direct3D
return;
}
Ovo bi trebali dobiti kada kompajlirate program:

http://i5.tinypic.com/6u9s10p.jpg

Ode pojest kolač, sredit laptop i napokon zaigrati CoHa u miru... A vi učite... Za zadaću, hoću ovdje da vidim vaš prozor u drugoj boji... Sutra
ćemo napokon krenuti na famozni 2D trokut. I da, čestitam, upravo ste postali istinski grafički programeri. John Carmack nije vam ni do bokserica.

PEACE!

RIPtheREAPER
30-12-2007, 01:19
ja ti ovo nist ne kuzim...












ali naravno pohvale za rad su tu!

:bravo:

RayDX
30-12-2007, 01:45
ja ti ovo nist ne kuzim...












ali naravno pohvale za rad su tu!

:bravo:

Treba truda... Namjerno nisam išao u prevelike detalje jer onoga koga ovo istinski zanima će onda se zainteresirati i potražiti dalje odgovore koje traži. Razvijanje 3D aplikacija jedna je od težih grana u softverskoj industriji i treba vremena i strpljenja te najviše volje da bi se razumjelo.

Plexihack
30-12-2007, 11:58
Dobar tutorial,ali shvatio sam da nisam ja za D3D,OpenGL je za mene...

RayDX
01-01-2008, 03:58
Evo čim prođe ovaj nered od nove godine, pišem vam sljedeći tutorial (famozni RGB trokutić). Evo još uvijek sam na novogodišnjem partiju. Heheh

djelim s nulom
09-01-2008, 20:15
Super ti je tutorial, samo nastavi molim te. :pray:
Samo moram dovršiti ovu knjigu "Demistificirani C++" (tek završavam pokazivače) da bi naučio klase i ostalo što trenutno ne poznajem, a nalazi se u ovim tutorialima.

A ovaj prvi primjer što si dao, izrada prozora. Nalazi mi dva errora:

Compiling...
dix.cpp
D:\c++\dx\dix.cpp(35) : error C2440: '=' : cannot convert from 'unsigned short [13]' to 'const char *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
D:\c++\dx\dix.cpp(52) : error C2664: 'CreateWindowExA' : cannot convert parameter 2 from 'unsigned short [13]' to 'const char *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
Error executing cl.exe.

dix.obj - 2 error(s), 0 warning(s)

Znaš li zbog čega ne radi?

RayDX
10-01-2008, 14:12
Pretpostavljam da radiš u Visual Studiu... Moraš otići New Project -> Windows Application, klikneš Next i onda staviš potvrdno da bude Empty Project i klikneš finish... Ako ti ni tada tvoj napisani kôd ne radi, onda kopipejstaj moj source. On definitivno radi.

djelim s nulom
10-01-2008, 22:19
Otvaram Visual Studio --> New... --> win32 application --> odaberem "an empty project" i kliknem finish. I nema mi ništa, baš ništa, ni mista gdje se piše kod, baš ništa. U što ću copypaste ako nema ničega?

Dosad sam probavao u c++ source file zalipit tvoj kod i pojavi se ono iz prijašnjeg posta!

SkunK
11-01-2008, 09:51
Desni klik ljevo na direktorij source code, new file - source file

Halo2
11-01-2008, 10:41
E ovo je fakat ludnica al ni ja nis nekuzim :?

RayDX
11-01-2008, 13:04
Desni klik ljevo na direktorij source code, new file - source file

Da, to sam zaboravio... trebaš dodati source file (.cpp) :rotfl: Thx reiko

SkunK
11-01-2008, 13:15
np ;)

djelim s nulom
11-01-2008, 18:53
Napravio sam empty project.
U njega sam dodao prazan c++ source file. Nisam nasa to sa desnim klikom nego sam isao file/new i odabrao sam source file i označio checkbox add to project, u prazan source file zalipio sam tvoj kod i opet iste greške!!!!!!!

SkunK
11-01-2008, 18:56
Odi na new - project

Napravi novi projekt
zatim odi ljevo i desni klik na direktoriji source files i napravi new - source file (.cpp) i onda probaj.

djelim s nulom
11-01-2008, 19:00
Otisa sam file/new project/win 32 application i označio "an empty project".
A gdje je taj direktorij source files, ja neznam šta je to točno.
Jeli to ovo?
http://img413.imageshack.us/img413/8387/94554838gv8.png (http://imageshack.us)

SkunK
11-01-2008, 19:48
Ti koristis VC++ 6?
Onda ti je prvi cilj nabavit VS 2005 jer je dosta stvari drukcije.

djelim s nulom
11-01-2008, 19:58
znaš li možda kako se to radi na šestici?

RayDX
11-01-2008, 21:06
znaš li možda kako se to radi na šestici?

VC++ 6.0 je mračno doba Visual Studija... Moj prijedlog je da skočiš na Visual Studio 2005 ili čak Visual Studio 2008.

djelim s nulom
11-01-2008, 21:40
Ali opet bi tribali znat kako se to radi i u VC++ 6.0, znaš onda kako se radi u dev++ ili kako se već zove?

SkunK
11-01-2008, 22:15
Nabavi VS 2005 ili Orcas tj VS 2008, ne zamaraj se DevC++om itd.

RayDX
11-01-2008, 22:46
Ali opet bi tribali znat kako se to radi i u VC++ 6.0, znaš onda kako se radi u dev++ ili kako se već zove?

Znam kako se radi u svim navedenima samo mi je lijeno objasniti, a i nema koristi jer moj program garantirano neće raditi u Visual C++ 6.0 jer njegova interpretacija standarda C++a je poprilično loša i mnoge totalno normalne stvari su tamo potpuno "izvrnute". Najjednostavnije rečeno, nabavi Visual Studio ako se misliš baviti windows/directx aplikacijama, moguće je i u Dev-C++, ali je mnogo teže.

djelim s nulom
12-01-2008, 16:46
I tako nabavio ja visual studio 2005 kad error u ovom zadnjem primjeru.

cannot open include file 'd3d9.h'. no such file or directory


rješeno

Vukas15
21-01-2008, 16:18
si razmišlo da napraviš knjigu, pa da prodaješ?

RayDX
22-01-2008, 21:57
@djelim s nulom - directx sdk nisi instalirao, ha? :D
@vuka - ne, hehe. Cisto smatram da bi znanje trebalo biti besplatno i dostupno svima. Razlog je jednostavan; ne mozes staviti cijenu na intelektualni rast covjeka koji je voljan uciti. Planiram uskoro dignuti digitalni casopis u pdf formatu na domacem jeziku o novostima iz gd industrije, tutorijalima i pregledima novih tehnologije iz doticne industrije.

Odiee
24-01-2008, 10:24
Super ti je tutorial, samo nastavi molim te. :pray:
Samo moram dovršiti ovu knjigu "Demistificirani C++" (tek završavam pokazivače) da bi naučio klase i ostalo što trenutno ne poznajem, a nalazi se u ovim tutorialima.

A ovaj prvi primjer što si dao, izrada prozora. Nalazi mi dva errora:

Compiling...
dix.cpp
D:\c++\dx\dix.cpp(35) : error C2440: '=' : cannot convert from 'unsigned short [13]' to 'const char *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
D:\c++\dx\dix.cpp(52) : error C2664: 'CreateWindowExA' : cannot convert parameter 2 from 'unsigned short [13]' to 'const char *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
Error executing cl.exe.

dix.obj - 2 error(s), 0 warning(s)

Znaš li zbog čega ne radi?
Greska se nalazi u deklaraciji imena prozora i imena klase, ispravices ju tako da umjesto


wc.lpszClassName = L"WindowClass1";

napises


const char *wClass = "WindowClass1";
const char *wName = "PCPlay Ucionica";
wc.lpszClassName = wClass;

I pri stvaranju klase umjesto druga dva parametra napises


CreateWindowEx(NULL,
wClass, // Ime windows klase
wName, // Naziv prozora
...

pozdrav.

Blacky_123
10-02-2008, 13:34
Odlican tutorial
:pray: :pray:

RayDX
10-02-2008, 14:04
Hehe hvala, sorry što sam malo zanemario pisanje novih, imam sad engine i školu pa me malo sve to sputava da se upustim u daljno pisanje tekstova...

RayDX
27-03-2008, 23:22
DirectX coding - TRIANGLE

http://www.fegelein.com/images/ColoredTriangle.png

Evo nas nazad... Nema čekanja, svi koji ste gladni znanja, 'ajmo u red! Danas ćemo prvo imati malo teorije... Već vidim kako ste se svi snuždili. Znam da ste svi željni odmah skočiti na 3D grafiku, no brzo biste se vratili na mjesto gdje se nalazite, zato se fino opustite, nađite Coca-Colu da ostanete budni i to je sve što vam treba zasad.

KOORDINATNI SUSTAVI

Svi smo svjesni da je matematika osnova računala, a tako i 3D grafike i programiranja iste. Kao što sam na početku ove serije istaknuo, nije ništa komplicirano, ako možete koristiti zdravi razum, shvatit ćete ove koncepte. Ono što moramo ponoviti sada su koordinatni sustavi, vjerujem da smo svi upoznati sa Kartezijevim koordinatnim tj. možda bolje rečeno 2D koordinatnim sustavom ( x i y). Njegova upotreba je vrlo jednostavna i služi za lociranje neke točke na velikoj 2D ploči. Dvije dimenzije su godinama vladale gaming industrijom, no danas bez jednog elementa ne možemo zamisliti igranje igara. Vjerojatno pogađate, radi se o osjećaju dubine, prostora ekvivaletnom ovom našem u kojem živimo. Z os, divan nadodatak na 2D koord. sustav koji nam je omogućio da vidimo Larine... erm... oči iz milijun kuteva u 3D svemiru.

http://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/3D_coordinate_system.svg/487px-3D_coordinate_system.svg.pngS
(Slika sa Wikipedije)

Oke, imamo taj 3D koordinatni sustav ali što s njim? Pa, pogledajmo kako se on koristi u 3D grafici. Ako neka točka(vertex, REMEMBER THIS) na 3D koordinatnom sustavu predstavlja neku poziciju u prostoru, mogla bi se napraviti mreža takvih pozicija koje bi eventualno stvorile 3D model. Da ne spajamo bezveze točke kao u zabavnoj rubrici u nekim novinama, puno efikasniji način je "izumljen"... A korijen uzima u trigonometriji, dakle, koristit ćemo osnovu geometrije za formiranje kompleksnih 3D modela. Radi se o trokutima. I mi ćemo upravo napraviti jedan, u čast matematičkog elementa koji je stvorio modernu gaming industriju =).

Da biste više saznali o 3D modeliranju, što je 3D model... Napisao sam nedavno tekst za Vidipediju. KLIK OVDJE! (http://vidipedija.com/~vidipedi/index.php?title=3D_modeliranje)

TROKUT ( tris, triangle, trougao, kako god želite )

Sada kada smo se upoznali sa osnovom koordinatnih sustava, vrijeme je napokon da se bacimo na posao. Ali... Da, ali... Moramo nešto veoma bitno raspraviti prije toga. Radi se FVFima ili Flexible Vertex Formatima. Kako smo već spominjali točke (vertex jednina, vertices množina) u 3D prostoru obilježavaju se kroz 3 brojčane vrijednosti spremljene u x, y i z. Svojstva samih točaka također se mogu odrediti uz pomoć brojčanih vrijednosti. DirectX (Direct3D točnije) koristi tehnologiju koja se naziva Flexible Vertex Format (FVF) i taj vertex format je zapravo kolekcija podataka koje sadržavaju informacije o lokaciji i svojstvima nekog vertexa (točke). Fleksibilan je jer nije statičan, kakav zaključak hehe... Može se modificirati i prilagoditi vašim potrebama. Valja pojasniti kako ovo funkcionira...

Vertex je napravljen od strukture (struct, C++) koja sadrži podatke potrebne za stvaranje neke 3D slike. Da bi prikazali sliku, moramo kopirati cijelu vojsku podataka u video RAM i narediti Direct3Du da kopira podatke u back buffer. Što bi se dogodilo kada bi nas sustav tjerao da šaljemo sve podatke koji bi mogli biti povezani sa vertex structom? Svi smo upoznati da je računalo, tupo ali moćno oružje te uz malu pomoć ljudskog uma, inženjeri su uspjeli "izolirati" samo one podatke potrebne, a upravo tome služi Flexible Vertex Format!

U Direct3Du, svaki vertex je definiran od već napravljenog vertex formata. Naslov slovi da se radi o fleksibilnom sustavu pa je veoma moguće da mi kao programeri možemo modificirati vertex format da se prilagodi našim potrebama. Uskoro ćemo morati uključiti lokaciju i difuznu boju naših točaka (vertices-a). Taj kôd bi glasio ovako:


#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

S ovim, više nećemo morati stalno pisati FVF kôd svaki put kad nam zatreba u radu sa vertices-ima. Ima mnogo zastavica na koje se FVF može postaviti, a zasad vam baš i nisu potrebne... Oni koji su znatiželjni mogu se konzultirati sa MSDNom.

KREIRANJE VERTICES-a

Došlo je vrijeme da iskoristimo napokon ovaj FVF format te da napravimo par verticesa. Pa napravimo onda CUSTOMVERTEX strukturu.


struct CUSTOMVERTEX
{
FLOAT x, y, z, rhw; // Sjećate se prvog flaga?
DWORD color; // a drugog?
}

Kao što možete vidjeti ovdje, prve četiri vrijednosti FLOAT (radi preciznosti) reprezentirane su zapravo uz pomoć D3DFVF_XYZRHW zastavice, dok je DWORD reprezentiran koristeći drugu zastavicu (D3DFVF_DIFFUSE). Da malo zagrijemo atmosferu, idemo napraviti vertex koristeći našu malu strukturu koja je ispisana gore.


CUSTOMVERTEX nVertex = { 320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255)};

Koristeći novostečeno znanje, napravimo sada mrežu od 3 vertexa koji će formirati naš trokut:


CUSTOMVERTEX nVertices[]S=
{
{320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255),},
{520.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 255, 0),},
{120.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0),},
};

VERTEX SPREMNIK (Vertex Buffer eng.)

Došli smo do jako bitnog segmenta koji će omogućiti vizualizaciju našega trokuta. Što želim reći jest da ga moramo pripremiti za izlazak pred publiku tj. da ga Direct3D može eventualno prikazati na zaslonu naše kante. Vertex buffer je sučelje koje priprema dio u memoriji da se u njega ubace potrebni podaci o vertices-ima. DirectX arsenal nudi nam veoma jednostavnu funkciju da brzo i efikasno sagradimo naš vertex buffer, CreateVertexBuffer(). Evo prototipa kako ga golog majka MSDN pokazuje:


HRESULT CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer,
HANDLE* pSharedHandle);

Kao što nam je to trademark postao, 'ajmo razbiti na manje dijelove.

UINT Length

Ovaj parametar sadržava veličinu buffera (spremnika) kojeg želimo stvoriti. Ovaj broj možemo dobiti množenjem veličine jednog vertexa sa brojem verticesa koji će biti pohranjeni u buffer. Najjednostavnije: 3 * sizeof(CUSTOMVERTEX).

DWORD Usage

Nekad je potrebno koristiti vertices-e na specijalan način pa ovo mijenja način na koji DirectX rukuje s njima. Uglavnom, ovdje ubacite zastavicu koja indicira da se treba specijalno rukovati navedenim. Samo puknite 0 ovdje.

DWORD FVF

Ovdje dolazi FVF kôd kojeg smo definirali par trenutaka prije. pošto smo definirali ga kao CUSTOMFVF, znate što vam je činiti ovdje. Ovo kaže DirectXu u kojem su formatu verticesi koje prima u vertex buffer

D3DPOOL Pool

Ovaj parametar kaže Direct3Du gdje stvoriti vertex buffer i kako. Trenutno, nama najviše odgovara zastavica D3DPOOL_MANAGED. Tako da ju jednostavno umjestite na ovo ljepuškasto parking mjesto. Za više zastavica, MSDN vam stoji na usluzi.

LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer

Je l' tko prebrojao slova na ovom gadu? Doduše, objašnjenje je puno kraće i jednostavnije... Ovo je pokazivač/pointer na vertex buffer sučelje koje sada stvaramo. Samo ćemo ubaciti prazni pokazivač i funkcija će ga popuniti.

HANDLE *pSharedHandle

Ovaj ni meni nije jasan, dokumentacija ga deklarira ovako: "Reserved. Set this parameter to NULL". Ok, DirectX timu, vi ste gazde... Čuli ste što su velike face rekle, tu ide NULL.

Zaslužili smo vidjeti kako izgleda u samom programu kada se pretvori sve ovo trabunjanje u praksu:


LPDIRECT3DVERTEXBUFFER9 t_buffer;

d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
0,
CUSTOMFVF,
D3DPOOL_MANAGED,
&t_buffer,
NULL);

Imamo vertex buffer... Što sad? Trebamo utjerati one podatke iz CUSTOMVERTEX strukture u vertex buffer. Ali vertex buffer ne da unutra lako. Da bi dobili pristup bufferu, moramo ga zaključati. Kinky... Dva su veoma jednostavna razloga zašto je ovo potrebno. Prvi jest da trebamo reći Direct3Du da nam treba potpuna kontrola nad memorijom. Ne želimo da niti jedan drugi aktivni proces prčka po njoj dok se mi bavimo ovim delikatnim stvarima. Još bitnije, moramo reći hardveru da se ne miče naokolo... Metaforički jelte... Želim reći da memorija ne lunja naokolo sa jedne adrese na drugu dok mi pokušavamo ugurati sve ove podatke unutra.

Da bismo zaključali ga, koristimo logično nazvanu funkciju, Lock() koja ima četiri zanimljiva parametra. Pljuc-prototip:


HRESULT Lock(UINT OffsetToLock,
UINT SizeToLock,
VOID** ppbData,
DWORD Flags);

UINT OffsetToLock, UINT SizeToLock

Kada bismo željeli zaključati samo dio našeg vertex buffera, ovdje bismo to indicirali. Prvi pita koliko duboko u buffer, u bajtovima, zaključavanje treba započeti. Drugi služi tome da se pokaže koliko toga treba biti zaključano, također u bajtovima. Želimo potpuni lockdown, tako da puknete 0.

VOID** ppbData

Evo jedan jako zanimljiv parametar. Ovo što vidite ovdje, void* jest pokazivač koji pokazuje prema "praznini" tj. nedefiniranom tipu podataka. Uzmite primjer double*, ovaj pointer pokazuje prema double data typeu, dok int* pokazuje prema intu. Svaki ima svoj data type, te prijelaz iz jednog u drugog bi uzrokovao gubitak vitalne preciznosti... Zato se koristi pointer prema voidu, jer tu se ništa ne gubi, a lako se prijelazi u int, double, bilo što po tom pitanju. Ovdje trenutno imamo pokazivač prema void*-u. Ovakav se pokazivač popuni sa lokacijom memorije koja će sadržavati naše vertices-e. Sučelje vertex buffera će se pobrinuti za sve sitne detalje, ali mi moramo "ponuditi" pokazivač. Nije strašno, zar ne?

DWORD Flags

Uf, ovo je kompliciranije od prethodnog parametra, a iskreno ni ja ga najbolje ne razumijem. Uglavnom, ovo bi trebalo ponuditi specijalne načine da se manipulira zaključanom memorijom. DirectX dokumentacija možda vam da više informacija. Zasad, samo ubacite ovdje 0.

Evo kako izgleda sve ovo gore u praksi:


VOID* pVoid; // praznina o kojoj smo pričali =)

t_buffer->Lock(0, 0, (void**)&pVoid, 0); // zaključava t_buffer koji smo maloprije napravili

Sada moramo utjerati sve ove podatke iz structa u taj zaključani vertex buffer. Koristimo jednostavnu funkciju memcpy():


memcpy(pVoid, nVertices, sizeof(nVertices)); // kopiramo podatke u vertex buffer

Na kraju, kada je sve unutra, otključamo buffer:


t_buffer->Unlock(); // otključaj =)
Baš je simpatična mala funkcija...

Zbog ogromne količine stvari koje smo upravo napravili, koristeći zdravu programsku logiku, strpajmo ovo u funkciju init_graphics():


void init_graphics(void)
{
// napravi tri vertexa koji će tvoriti vrhove trokuta
CUSTOMVERTEX nVertices[]S=
{
{ 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
{ 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
{ 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
};

// napravi vertex buffer i stavi pokazivač u t_buffer, koji je napravljen na "globalnoj razini"
d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
0,
CUSTOMFVF,
D3DPOOL_MANAGED,
&t_buffer,
NULL);

VOID* pVoid; // pokazivač u prazno

t_buffer->Lock(0, 0, (void**)&pVoid, 0); // zaključaj
memcpy(pVoid, nVertices, sizeof(nVertices)); // kopiraj vertices iz strukture CUSTOMVERTEX u vertex buffer
t_buffer->Unlock(); // otključaj

return;
}

ISCRTAVANJE TROKUTA, NAPOKON!

Vjerujem da ste se umorili, ali držite se još malo, idemo napokon to sve istjerati na monitor. Imamo tri jednostavne funkcije koje koristimo za to... Ovo bi trebalo biti brzo gotovo.

SetFVF()

SetFVF() je funkcija koja kaže Direct3Du koji FVF kôd trenutno koristimo. Možemo imati više različitih zastavica aktivnih na različitim segmentima scene. Prije nego li išta iscrtamo, moramo Direct3Du reći što je naš izbor.


d3ddev->SetFVF(CUSTOMFVF);

SetStreamSource()

Ova funkcija govori Direct3Du koji vertex buffer treba koristiti za iscrtavanje. Ima nekoliko parametara koji su poprillično jednostavni:


HRESULT SetStreamSource(UINT StreamNumber,
LPDIRECT3DVERTEXBUFFER9 pStreamData,
UINT OffsetInBytes,
UINT Stride);

Prvi parametar je broj izvora. Ako budem nastavio ovu seriju, reći ću više o tome, zasad ostavimo to na 0.

Drugi parametar je pointer prema vertex bufferu kojeg smo napravili nedavno.

Treći parametar je veličina svakog vertexa. Ovo se popuni sa: sizeof(CUSTOMVERTEX).

Sve ovo gore postavimo u praksu:


d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

DrawPrimitive()

Sada kada smo Direct3Du rekli kakve vertices-e koristimo i lokaciju odakle ih treba potegnuti, moramo mu reći da iscrta trokut baziranu na podacima iz vertex buffera. Evo prototipa pa ćemo polako naprijed.


HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount);

Prvi parametar je način na koji ćemo povezati vertices-e iz vertex buffera... Koristit ćemo TRIANGLELIST kao parametar ovdje, a za ostale konzultirajte se sa DirectX dokumentacijom kao i uvijek.

Drugi parametar je broj prvog vertexa kojeg želimo istjerati na scenu. Mogli bismo početi u sredini vertex buffera, ali želim cijeli buffer iscrtan, dakle? 0... Nula, null...

Zadnji parametar je broj trokuta koje želimo iscrtati. Logično, tu stavljamo 1.

Pogledajmo sada što smo to zapravo napravili od render_frame() funkcije:


// ovu funkciju koristimo da renderiramo jedan frame
void render_frame(void)
{
// pozadinu oboji u crno.
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene(); // započinje 3D scenu

// kažemo koji vertex format koristimo
d3ddev->SetFVF(CUSTOMFVF);

// koji vertex buffer treba prikazati
d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

// kopiramo vertex buffer u back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

d3ddev->EndScene(); // završava 3D scenu

d3ddev->Present(NULL, NULL, NULL, NULL); // prikazuje renderiranu "scenu"

return;
}


Također je potrebno nakon zatvaranja programa releasati vertex buffer... Što se radi veoma jednostavnom funkcijom... Release():


t_buffer->Release(); // oslobodi vertex buffer

Dodaje se u cleanD3D() funkciju...


EVO GA CJELOKUPNI KOD SA KOMENTARIMA:

// osnovni header fileove koji su nam potrebni

#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

// MALI BONUS ZA SVE KOJI SE TRUDE =)
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// uključi Direct3D biblioteku u projekt
#pragma comment (lib, "d3d9.lib")

// globalne deklaracije
LPDIRECT3D9 d3d; // pointer prema našem D3D sučelju
LPDIRECT3DDEVICE9 d3ddev; // pointer prema klasi uređaja
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL; // pointer prema vertex bufferu

//prototip funkcija vezanih uz Direct3D
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); //renderira jedan frejm/sliku/render
void cleanD3D(void); // Čisti nered koji smo napravili
void init_graphics(void); // inicijalizacija grafike

//prototip WindowProc funkcije (sjećate se, ona koja rukuje sa porukama)

LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

// ulazna točka u svaku windows aplikaciju

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// "ručica za prozor", ispuni se funkcijom
HWND hWnd;
// struktura prozora
WNDCLASSEX wc;

// očisti strukturu prozora
ZeroMemory(&wc, sizeof(WNDCLASSEX));

// ajmo popuniti članove ove strukture
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
//wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";

// registriraj klasu koristeći informacije date wc-u
RegisterClassEx(&wc);

// napravi prozor i koristi rezultat kao ručicu (hWnd)
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // Ime windows klase
L"PCPlay Ucionica", // Naziv prozora
WS_EX_TOPMOST | WS_POPUP, // elementi za full screen prozor
0, 0, // x i y moraju biti na 0
SCREEN_WIDTH, SCREEN_HEIGHT, // postavi prozor na 1024x768
NULL, // nema roditeljskog prozora
NULL, // ne koristimo izbornik
hInstance, // ručica aplikacije (remember)
NULL); // ne koristi se sa više prozora

// napokon ju prikaži:
ShowWindow(hWnd, nCmdShow);
// inicijalizacija D3Da
initD3D(hWnd);

// ulaz u glavni loop

// ova struktura sadrži predivne poruke koje nam Windows šalje
MSG msg;

// primijetite kako ovo zapravo stvara infinite loop
while(TRUE)
{
// Provjeri za porukice u event queueu... Kako glupa riječ ^^
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Ako je poruka jednaka WM_QUIT, break out!
if (msg.message == WM_QUIT)
break;

// Standarno procesiranje kroz WindowProc funkciju
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//pozivamo funkciju da renderira unutar loopa
render_frame();

}
//kada izađe iz loopa, zatvori Direct3D uređaj i direct3D
cleanD3D();
// vrati ovaj dio WM_QUITa Windowsu
return msg.wParam;
}

// ova funkcija rukuje sa svim porukama koje naša aplikacija prima
// dakle, poštar :)

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// traži određenu akciju koja treba biti provedena pri dobivanju poruke
switch(message)
{
// ovo se pročita kada korisnik pritisne crveni X
case WM_DESTROY:
{
// zatvori aplikaciju
PostQuitMessage(0);
return 0;
} break;
}

// rukuj sa svakom porukom koju WindowProc nije procesirao osobno
return DefWindowProc (hWnd, message, wParam, lParam);
}

// ova funkcija priprema Direct3D za korištenje
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface

D3DPRESENT_PARAMETERS d3dpp; // stvaramo strukturu koja će sadržavate neke parametre, kul, zar ne?

ZeroMemory(&d3dpp, sizeof(d3dpp)); // počisti istu za korištenje
d3dpp.Windowed = TRUE; // program je zasad prozor
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // odbaci stare slike
d3dpp.hDeviceWindow = hWnd; // odredi koji će prozor D3D koristiti

// sada stvori uređaj na bazi svih podataka gore.
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);

init_graphics(); // inicijalizacija trokuta

return;
}

// ovu funkciju koristimo da renderiramo jedan frame
void render_frame(void)
{
// pozadinu oboji u crno.
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene(); // započinje 3D scenu

// kažemo koji vertex format koristimo
d3ddev->SetFVF(CUSTOMFVF);

// koji vertex buffer treba prikazati
d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

// kopiramo vertex buffer u back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

d3ddev->EndScene(); // završava 3D scenu

d3ddev->Present(NULL, NULL, NULL, NULL); // prikazuje renderiranu "scenu"

return;
}

void init_graphics(void)
{
// napravi tri vertexa koji će tvoriti vrhove trokuta
CUSTOMVERTEX nVertices[]S=
{
{ 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
{ 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
{ 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
};

// napravi vertex buffer i stavi pokazivač u t_buffer, koji je napravljen na "globalnoj razini"
d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
0,
CUSTOMFVF,
D3DPOOL_MANAGED,
&t_buffer,
NULL);

VOID* pVoid; // pokazivač u prazno

t_buffer->Lock(0, 0, (void**)&pVoid, 0); // zaključaj
memcpy(pVoid, nVertices, sizeof(nVertices)); // kopiraj vertices iz strukture CUSTOMVERTEX u vertex buffer
t_buffer->Unlock(); // otključaj

return;
}

// ova funkcija čisti nered za nama
void cleanD3D(void)
{
t_buffer->Release(); // oslobodi vertex buffer
d3ddev->Release(); // oslobodi D3D grafički uređaj
d3d->Release(); // oslobodi Direct3D
return;
}


Rezultat uočite sami ;)

ŽIVJELI VI MENI ;)

Luka
27-03-2008, 23:28
bravo ... sad bih stavio sticky ali već je stavljen :)

Plexihack
28-03-2008, 10:14
0.o

Gg za tutorial,ali...toliko koda za trokut 0.o ?
That is why OpenGL owns DirectX :P

btw,trebaš pomoć na onom gd časopisu?

fps_gamer
05-05-2008, 14:14
zasto mi u devu nece raditi????? trebam u parametre nesto dodati?

(fuck opengl... we all love Microsoft... :gitara: :gitara: )

Plexihack
06-05-2008, 20:41
Another day,another noob...

OpenGL podržavaju sve grafičke kartice,a jel podržavaju sve DX10?

fps_gamer
06-05-2008, 21:09
taj se post odnosi na mene ili??

Tracer
16-09-2008, 12:57
A kako učitati objekt iz 3d max studia u C++ program?

RayDX
16-09-2008, 17:03
A kako učitati objekt iz 3d max studia u C++ program?

Svjestan dakako jesi da imaš na raspolaganju .x format za directx, no ukoliko želiš bilo koji drugi format modela ubaciti u program, morat ćeš ili naći biblioteku koja će interpretaciju formata napraviti za tebe, ili ćeš isprogramirati za svaki format sam (koji ti treba naravno)... Evo ti jedan malo stariji tut o tome sa GameDeva, importanje iz 3ds maxa modele dakle... Pa prouči malo. Nezgodno, da... Ali neophodno.

http://www.gamasutra.com/features/19980 ... ann_01.htm (http://www.gamasutra.com/features/19980220/baumann_01.htm)
http://www.gamasutra.com/features/19980 ... ann_01.htm (http://www.gamasutra.com/features/19980320/baumann_01.htm)

Također imaš opciju *.fbx formata, ali tome se priklanjaju više XNAovci... Mogao bih ja ovih dana napisati tutorial o loadanju 3D modela u program, petak navečer recimo...

Tracer
16-09-2008, 19:45
A kako učitati objekt iz 3d max studia u C++ program?

Svjestan dakako jesi da imaš na raspolaganju .x format za directx, no ukoliko želiš bilo koji drugi format modela ubaciti u program, morat ćeš ili naći biblioteku koja će interpretaciju formata napraviti za tebe, ili ćeš isprogramirati za svaki format sam (koji ti treba naravno)... Evo ti jedan malo stariji tut o tome sa GameDeva, importanje iz 3ds maxa modele dakle... Pa prouči malo. Nezgodno, da... Ali neophodno.

http://www.gamasutra.com/features/19980 ... ann_01.htm (http://www.gamasutra.com/features/19980220/baumann_01.htm)
http://www.gamasutra.com/features/19980 ... ann_01.htm (http://www.gamasutra.com/features/19980320/baumann_01.htm)

Također imaš opciju *.fbx formata, ali tome se priklanjaju više XNAovci... Mogao bih ja ovih dana napisati tutorial o loadanju 3D modela u program, petak navečer recimo...

Može li 3D Max exportirati u X format? Pretpostavljam da bi trebao... Ili kojim drugim alatom da napravim neki model s X formatom za directx? Ako ćeš već raditi tutorijal onda molim te pogledaj donji topic:

viewtopic.php?f=45&t=26559 (http://pcplay.hr/forum/viewtopic.php?f=45&t=26559)

Znači, molio bi što detaljnije ako može. Počevši i od samog alata kojeg ćeš koristiti za izradu 3D modela pa i do objašnjavanja source. Pogotovo me zanimaju ove rotacije kamere a i objekta (modela). Da to ne kanim raditi ne bi te ni gnjavio ;) Hvala.

RayDX
19-09-2008, 19:35
Oke, nije problem, ali moram danas razmotriti kako ću ga postaviti... Što prije puknem ovdje..

Tracer
22-09-2008, 14:03
Našao sam primjere u DirectX SDK. Poz.

RayDX
25-09-2008, 08:42
Našao sam primjere u DirectX SDK. Poz.

sorry komšija što nisam uhvatio vremena, sad sam u pripizdini kd tetke na modemu -.- vraćam se tek u petak

Tracer
25-09-2008, 13:02
sve 5