Lekce 32

Lekce 32 - Picking, alfa blending, alfa testing, sorting

V tomto tutorißlu se pokusφm zodpov∞d∞t n∞kolik otßzek, na kterΘ jsem denn∞ dotazovßn. Chcete v∞d∞t, jak p°i kliknutφ tlaΦφtkem myÜi identifikovat OpenGL objekt nachßzejφcφ se pod kurzorem (picking). Dßle byste se cht∞li dozv∞d∞t, jak vykreslit objekt bez zobrazenφ urΦitΘ barvy (alfa blending, alfa testing). T°etφ v∞cφ, se kterou si nevφte rady, je, jak °adit objekty, aby se p°i blendingu sprßvn∞ zobrazily (sorting). Naprogramujeme hru, na kterΘ si vÜe vysv∞tlφme.

Vφtejte do 32. lekce. Je asi nejdelÜφ, jakou jsem kdy napsal - p°es 1000 °ßdk∙ k≤du a vφce ne₧ 1500 °ßdk∙ HTML. TakΘ je prvnφm, kter² pou₧φvß nov² NeHeGL zßkladnφ k≤d. Tutorißl zabral hodn∞ Φasu, ale myslφm si, ₧e stojφ za to. Probφrß se v n∞m p°edevÜφm: alfa blending, alfa testing, Φtenφ zprßv myÜi, souΦasnΘ pou₧φvßnφ perspektivnφ i pravo·hlΘ projekce, zobrazovßnφ kurzoru myÜi pomocφ OpenGL, ruΦnφ °azenφ objekt∙ podle hloubky, snφmky animace z jednΘ textury a to nejd∙le₧it∞jÜφ: nauΦφte se vÜe o pickingu.

V prvnφ verzi program zobrazoval t°i polygony, kterΘ po kliknutφ m∞nily barvu. Jak vzruÜujφcφ! Tak, jako v₧dycky, chci zap∙sobit super cool tutorißlem. Nejen, ₧e jsou v n∞m zahrnuty vÜechny informace k probφranΘmu tΘmatu, ale samoz°ejm∞ musφ b²t takΘ hezk² na pohled. Dokonce i tehdy, pokud neprogramujete, vßs m∙₧e zaujmout - kompletnφ hra. Objekty se sest°elujφ tak dlouho, dokud vßm neochabne ruka dr₧φcφ myÜ, tak₧e u₧ nejste schopni stisknout tlaΦφtko.

Poznßmka ohledn∞ k≤du: Budu vysv∞tlovat pouze lesson33.cpp. V NeHeGL jsou zm∞ny p°edevÜφm v podpo°e myÜi ve funkci WindowProc(). TakΘ u₧ nebudu vysv∞tlovat loading textur, vytvß°enφ display list∙ fontu a v²stup textu. VÜe bylo vysv∞tleno v minul²ch tutorißlech.

Textury, pou₧φvanΘ v tomto programu, byly nakresleny v Adobe Photoshopu. Ka₧d² z .TGA obrßzk∙ mß barevnou hloubku 32 bit∙ na pixel, obsahuje tedy alfa kanßl. Pokud si nejste jistφ, jak ho p°idat, kupte si n∞jakou knihu, prozkoumejte internet nebo zkuste help. Postup je podobn² vytvß°enφ masky v tutorißlu o maskingu, nahrajte sv∙j obrßzek do Adobe Photoshopu nebo jakΘhokoli grafickΘho editoru s podporou alfa kanßlu. Prove∩te v²b∞r barvy, abyste oznaΦili oblast okolo objektu, zkopφrujte v²b∞r a vlo₧te ho do novΘho obrßzku. Negujte obrßzek, tak₧e oblast, kde by m∞l b²t, bude Φernß. Zm∞≥te okolφ na bφlΘ, vyberte cel² obrßzek a zkopφrujte ho. Vra¥te se na originßl a vytvo°te alfa kanßl, do kterΘho vlo₧te masku. Ulo₧te obrßzek jako 32 bitov² .TGA soubor. Ujist∞te se, ₧e je zaÜkrtnuto Uchovat pr∙hlednost a uklßdejte bez komprese.

Zjistφme, jestli je definovanß symbolickß konstanta CDS_FULLSCREEN a pokud ne, nadefinujeme ji na hodnotu 4. Pro ty z vßs, kte°φ se ·pln∞ ztratili... n∞kterΘ kompilßtory nep°i°azujφ CDS_FULLSCREEN hodnotu. Pokud ji pak v programu pou₧ijeme, kompilace skonΦφ s chybovou zprßvou. Abychom tomuto p°edeÜli, tak ji v p°φpad∞ pot°eby nadefinujeme ruΦn∞.

#ifndef CDS_FULLSCREEN// N∞kterΘ kompilßtory nedefinujφ CDS_FULLSCREEN

#define CDS_FULLSCREEN 4// RuΦnφ nadefinovßnφ

#endif

Deklarujeme funkci DrawTargets(), potom prom∞nnou okna a klßves.

void DrawTargets();// Deklarace funkce

GL_Window* g_window;// Okno

Keys* g_keys;// Klßvesy

Ka₧d² program pot°ebuje prom∞nnΘ. Base uklßdß display listy fontu, roll slou₧φ k pohybu zem∞ a rolovßnφ mrak∙. Jako ve vÜech hrßch i my zaΦφnßme prvnφm levelem. Miss vede zßznam, do kolika objekt∙ se v danΘm levelu st°elec nestrefil, kill je jeho prav² opak. Score zahrnuje souΦty zasa₧en²ch objekt∙ z jednotliv²ch level∙. Game signalizuje konec hry.

GLuint base;// Display listy fontu

GLfloat roll;// Rolovßnφ mrak∙

GLint level = 1;// Aktußlnφ level

GLint miss;// PoΦet nesest°elen²ch objekt∙

GLint kills;// PoΦet sest°elen²ch objekt∙ v danΘm levelu

GLint score;// Aktußlnφ sk≤re

bool game;// Konec hry?

Nadefinujeme nov² datov² typ, dφky kterΘmu budeme moci p°edat struktury porovnßvacφ funkci. Qsort() toti₧ oΦekßvß v poslednφm parametru ukazatel na funkci s parametry (const* void, const* void).

typedef int (*compfn)(const void*, const void*);// Ukazatel na porovnßvacφ funkci

Struktura objects bude uklßdat vÜechny informace popisujφcφ sest°elovan² objekt. Rychl² pr∙zkum prom∞nn²ch: rot urΦuje sm∞r rotace na ose z. Pokud jeÜt∞ nebyl objekt sest°elen, hit bude obsahovat false. Frame definuje snφmek animace p°i explozi, dir urΦuje sm∞r pohybu. Texid je indexem do pole textur, nab²vß hodnot nula a₧ Φty°i, z Φeho₧ plyne, ₧e mßme celkem p∞t druh∙ objekt∙. X a y definuje aktußlnφ pozici, spin ·hel rotace na ose z. Distance je hodn∞ d∙le₧itß prom∞nnß, urΦuje hloubku ve scΘn∞. Prßv∞ podle nφ budeme p°i blendingu °adit objekty, aby se nejd°φve vykreslovali vzdßlen∞jÜφ a a₧ po nich bli₧Üφ.

struct objects// Struktura objektu

{

GLuint rot;// Rotace (0 - ₧ßdnß, 1 - po sm∞ru hodinov²ch ruΦiΦek, 2 - proti sm∞ru)

bool hit;// Byl objekt zasa₧en?

GLuint frame;// Aktußlnφ snφmek exploze

GLuint dir;// Sm∞r pohybu (0 - vlevo, 1 - vpravo, 2 - nahoru, 3 - dol∙)

GLuint texid;// Index do pole textur

GLfloat x;// X pozice

GLfloat y;// Y pozice

GLfloat spin;// Sm∞r rotace na ose z

GLfloat distance;// Hloubka ve scΘn∞

};

Nßsledujφcφ pole vedou zßznamy o deseti texturßch a t°iceti objektech.

TextureImage textures[10];// Deset textur

objects object[30];// 30 Objekt∙

Nebudeme limitovat velikost objekt∙. Vßza by m∞la b²t vyÜÜφ ne₧ plechovka coly a k²bl naopak ÜirÜφ ne₧ vßza. Abychom si ulehΦili ₧ivot, vytvo°φme strukturu obsahujφcφ v²Üku a Üφ°ku. Definujeme a ihned inicializujeme pole t∞chto struktur o p∞ti prvcφch. Na ka₧dΘm indexu se nachßzφ jeden z p∞ti typ∙ objekt∙.

struct dimensions// Rozm∞r objektu

{

GLfloat w;// èφ°ka

GLfloat h;// V²Üka

};

// Velikost ka₧dΘho objektu: Modrß tvß°, k²bl, terΦ, Coca-cola, Vßza

dimensions size[5] = {{1.0f,1.0f}, {1.0f,1.0f}, {1.0f,1.0f}, {0.5f,1.0f}, {0.75f,1.5f}};

Tento k≤d bude volßn funkcφ qsort(). Porovnßvß hloubku dvou objekt∙ ve scΘn∞ a vracφ -1, pokud je prvnφ objekt dßle, bude-li ale vzdßlen∞jÜφ druh² objekt vrßtφ funkce 1. Zφskßme-li 0, znamenß to, ₧e jsou oba ve stejnΘ vzdßlenosti od pozorovatele.

// *** Modifikovan² MSDN k≤d pro tento tutorißl ***

int Compare(struct objects *elem1, struct objects *elem2)// Porovnßvacφ funkce

{

if (elem1->distance < elem2->distance)// Prvnφ je vzdßlen∞jÜφ

{

return -1;

}

else if (elem1->distance > elem2->distance)// Prvnφ je bli₧Üφ

{

return 1;

}

else// Vzdßlenosti jsou stejnΘ

{

return 0;

}

}

Ve funkci InitObject() nastavujeme objekt na v²chozφ hodnoty. P°i°adφme mu rotaci po sm∞ru hodinov²ch ruΦiΦek. Animace exploze samoz°ejm∞ zaΦφnß na prvnφm (nultΘm) snφmku. Objekt jeÜt∞ nebyl zasa₧en, tak₧e nastavφme hit na false. Randomem zvolφme jednu z p∞ti dostupn²ch textur.

GLvoid InitObject(int num)// Inicializace objektu

{

object[num].rot = 1;// Rotace po sm∞ru hodinov²ch ruΦiΦek

object[num].frame = 0;// Prvnφ snφmek exploze

object[num].hit = FALSE;// JeÜt∞ nebyl zasa₧en

object[num].texid = rand() % 5;// Nßhodn² index textury

Vzdßlenost od pozorovatele nastavφme op∞t nßhodn∞ na hodnotu 0.0f a₧ -40.0f (4000/100 = 40). P°ed renderingem objektu vÜak scΘnu jeÜt∞ posouvßme do hloubky o dalÜφch deset jednotek, tak₧e se objekt defakto zobrazφ v rozmezφ od -10.0f do -50.0f. Ani p°φliÜ blφzko ani p°φliÜ daleko.

object[num].distance = -(float(rand() % 4001) / 100.0f);// Nßhodnß hloubka

Po definovßnφ hloubky urΦφme v²Üku nad zemφ. Nechceme, aby se objekt nachßzel nφ₧e ne₧ -1.5f, proto₧e by byl pod zemφ. TakΘ by nem∞l b²t v²Üe ne₧ 3.0f. Abychom z∙stali v tomto rozmezφ, v²sledek randomu nesmφ b²t vyÜÜφ ne₧ 4.5f (-1.5f + 4.5f = 3.0f).

object[num].y = -1.5f + (float(rand() % 451) / 100.0f);// Nßhodnß y pozice

V²poΦet poΦßteΦnφ x pozice je maliΦko slo₧it∞jÜφ. Vezmeme pozici objektu v hloubce a odeΦteme od nφ 15.0f. V²sledek operace vyd∞lφme dv∞ma a odeΦteme od n∞j 5*level. Nßsleduje dalÜφ odΦφtßnφ. Tentokrßt odeΦteme nßhodnΘ Φφslo od 0 do 5 nßsobenΘ aktußlnφm levelem. P°edpoklßdßm, ₧e nechßpete :-). Objekty se nynφ ve vyÜÜφch levelech zobrazujφ dßle od viditelnΘ Φßsti scΘny (vlevo nebo vpravo). Kdybychom toto neud∞lali, zobrazovaly by se rychle jeden za druh²m, tak₧e by bylo velmi obtφ₧nΘ vÜechny zasßhnout a dostat se tak do dalÜφho levelu.

Abyste lΘpe pochopili urΦovßnφ x pozice, uvedu p°φklad. ╪ekn∞me, ₧e se objekt nachßzφ -30.0f jednotek hluboko ve scΘn∞ a aktußlnφ level je 1.

object[num].x = ((-30.0f - 15.0f) / 2.0f) - (5*1) - float(rand() % (5*1));

object[num].x = (-45.0f / 2.0f) - 5 - float(rand() % 5);

object[num].x = (-22.5f) - 5 - { °ekn∞me 3.0f };

object[num].x = (-22.5f) - 5 - { 3.0f };

object[num].x = -27.5f - { 3.0f };

object[num].x = -30.5f;

P°ed renderingem objektu provßdφme translaci o deset jednotek do scΘny na ose z a hloubka v naÜem p°φkladu je -30.0f. Celkovß hloubka ve scΘn∞ je tedy -40.0f. Pou₧φvßnφm perspektivnφho k≤du z NeHeGL m∙₧eme p°edpoklßdat, ₧e lev² okraj viditelnΘ scΘny je -20.0f a prav² okraj se nachßzφ na +20.0f. P°ed odeΦφtßnφm random∙ se rovnß x-ovß pozice -22.5f, co₧ je PR┴V╠ okraj viditelnΘ scΘny. Po t∞chto operacφch to u₧ je ale -30.0f a to znamenß, ₧e ne₧ se poprvΘ objevφ, musφ nejd°φve urazit cel²ch 8 jednotek doprava. U₧ je to jasn∞jÜφ?

// Nßhodnß x pozice zalo₧enß na hloubce v obrazovce a s nßhodn²m zpo₧d∞nφm p°ed vstupem na scΘnu

object[num].x = ((object[num].distance - 15.0f) / 2.0f) - (5*level) - float(rand() % (5*level));

Nakonec zvolφme nßhodn² sm∞r pohybu: 0 vlevo nebo 1 vpravo.

object[num].dir = (rand() % 2);// Nßhodn² sm∞r pohybu

Nynφ se podφvßme, kter²m sm∞rem se bude objekt posunovat. Pokud p∙jde doleva (dir == 0), zm∞nφme rotaci na proti sm∞ru hodinov²ch ruΦiΦek (rot = 2). Pozice na ose x je defaultn∞ zßpornß. NicmΘn∞, pokud se mßme pohybovat vlevo, musφme se na zaΦßtku nachßzet vpravo. Negujeme tedy hodnotu x.

if (object[num].dir == 0)// Pohybuje se doleva?

{

object[num].rot = 2;// Rotace proti sm∞ru hodinov²ch ruΦiΦek

object[num].x = -object[num].x;// V²chozφ pozice vpravo

}

Zjistφme, kter² druh objektu poΦφtaΦ vybral. Pokud se index textury rovnß nule, zvolil texturu modrΘ tvß°e a ty se v₧dy pohybujφ t∞sn∞ nad zemφ. RuΦn∞ nastavφme y pozici na -2.0f.

if (object[num].texid == 0)// Modrß tvß°

{

object[num].y = -2.0f;// V₧dy t∞sn∞ nad zemφ

}

Prßce s objektem k²blu bude slo₧it∞jÜφ. Padajφ toti₧ z nebe (dir = 3). Z toho takΘ plyne, ₧e bychom m∞li nastavit novou x-ovou pozici, proto₧e by nikdy nebyl vid∞t (objekty jsou na zaΦßtku v₧dy vlevo nebo vpravo od scΘny). Namφsto odeΦφtßnφ 15 z minulΘho p°φkladu odeΦteme pouze 10. Tφmto dosßhneme menÜφho rozmezφ hodnot, kterΘ udr₧φ objekt viditeln∞ na scΘn∞. P°edpoklßdßme-li, ₧e se hloubka rovnß -30.0f, skonΦφme s nßhodnou hodnotou od 0.0f do +40.0f. Hornφ hodnota je kladnß a ne zßpornß, jak by se mohlo zdßt, proto₧e rand() v₧dy vracφ kladnΘ Φφslo. Zφskali jsme tedy Φφslo od 0.0f do 40.0f, k n∞mu p°iΦteme hloubku (zßpornΘ Φφslo) mφnus 10.0f a to celΘ d∞lenΘ dv∞ma. Op∞t p°φklad: p°epoklßdßme, ₧e vrßcenß nßhodnß hodnota je 15 a objekt se nachßzφ ve vzdßlenosti -30.0f jednotek.

object[num].x = float(rand() % int(-30.0f - 10.0f)) + ((-30.0f - 10.0f) / 2.0f);

object[num].x = float(rand() % int(-40.0f) + (-40.0f) / 2.0f);

object[num].x = { p°edpoklßdejme 15 } + (-20.0f);

object[num].x = 15.0f - 20.0f;

object[num].x = -5.0f;

Nakonec urΦφme umφst∞nφ na ose y. Chceme, aby padal z oblohy, ale nevystupoval z mrak∙. ╚φslo 4.5f odpovφdß pozici maliΦko nφ₧e pod mraky.

if (object[num].texid == 1)// K²bl

{

object[num].dir = 3;// Padß dol∙

object[num].x = float(rand() % int(object[num].distance - 10.0f)) + ((object[num].distance - 10.0f) / 2.0f);

object[num].y = 4.5f;// T∞sn∞ pod mraky

}

Objekt terΦe by m∞l vystoupit nahoru ze zem∞ (dir = 2). Pro umφst∞nφ na ose x pou₧ijeme stejn² postup jako p°ed chvφlφ. Nechceme, aby jeho poΦßteΦnφ poloha zaΦφnala nad zemφ, tak₧e nastavφme y na -3.0f (pod zemφ). Od n∞j odeΦteme nßhodnΘ Φφslo od nuly do 5*level, aby se neobjevil hned, ale se zpo₧d∞nφm a₧ po chvφli. ╚φm vyÜÜφ level, tφm dΘle trvß, ne₧ se objevφ. To dßvß hrßΦi trochu Φasu na vzpamatovßnφ se - bez tΘto operace by terΦe vyskakovaly rychle jeden za druh²m.

if (object[num].texid == 2)// TerΦ

{

object[num].dir = 2;// Vyletφ vzh∙ru

object[num].x = float(rand() % int(object[num].distance - 10.0f)) + ((object[num].distance - 10.0f) / 2.0f);

object[num].y = -3.0f - float(rand() % (5*level));// Pod zemφ

}

VÜechny ostatnφ objekty se pohybujφ zleva doprava, a proto nenφ nutnΘ, abychom jejich nastavenφ n∞jak²m zp∙sobem m∞nili.

Mohli bychom u₧ skonΦit, ale zb²vß jeÜt∞ ud∞lat jednu velice d∙le₧itou v∞c. Aby alfa blending pracoval sprßvn∞, musφ b²t pr∙hlednΘ polygony vykreslovßny od nejvzdßlen∞jÜφch po nejbli₧Üφ a nesmφ se protφnat. Z buffer toti₧ vy°azuje vzdßlen∞jÜφ polygony, jsou-li ji₧ n∞jakΘ p°ed nimi. Kdyby ty p°ednφ nebyly pr∙hlednΘ, niΦemu by to nevadilo a navφc by se rendering urychlil, nicmΘn∞, kdy₧ jsou objekty vep°edu pr∙hlednΘ, tak by objekty za nimi m∞ly b²t vid∞t. Nynφ se bu∩ nezobrazφ nebo je kolem p°ednφch vykreslen Φtvercov² tvar, reprezentujφcφ p∙vodnφ polygon bez pr∙hlednosti... nic hezkΘho.

Znßme hloubku vÜech objekt∙, tak₧e nenφ ₧ßdn² problΘm, abychom je po inicializaci novΘho se°adili, jak pot°ebujeme. Pou₧ijeme standardnφ funkci qsort() (quick sort - rychlΘ °azenφ). P°i nßslednΘm renderingu vezmeme prvnφ prvek pole a vykreslφme ho. Nebudeme se muset o nic starat, proto₧e vφme, ₧e je ve scΘn∞ nejhloub∞ji.

Tento k≤d jsme nalezl v MSDN, ale ·sp∞chu p°edchßzelo dlouhΘ hledßnφ na internetu. Funkce qsort() pracuje dob°e a dovoluje °adit celΘ struktury. P°edßvßme jφ Φty°i parametry. Prvnφ ukazuje na pole objekt∙, kterΘ majφ b²t se°azeny, druh² urΦuje jejich poΦet (odpovφdß aktußlnφmu levelu). T°etφ parametr definuje velikost jednΘ struktury a Φtvrt² je ukazatelem na porovnßvacφ funkci Compare(). S nejv∞tÜφ pravd∞podobnostφ existuje n∞jakß lepÜφ metoda pro °azenφ struktur, ale qsort() vyhovuje. Je rychlß a snadno se pou₧φvß.

D∙le₧itß poznßmka: Pokud pou₧φvßte glAlphaFunc() a glEnable(GL_ALPHA_TEST) namφsto "klasickΘho" blendingu, nenφ °azenφ nutnΘ. Pou₧φvßnφm alpha funkcφ jste ale omezeni na ·plnou pr∙hlednost nebo ·plnou nepr∙hlednost, nic mezi tφm. Pou₧φvßnφ BlendFunc() a °azenφ objekt∙ stojφ sice trochu prßce navφc, ale dovoluje mφt objekty polopr∙hlednΘ.

// *** Modifikovan² MSDN k≤d pro tento tutorißl ***

qsort((void *) &object, level, sizeof(struct objects), (compfn)Compare);// ╪azenφ objekt∙ podle hloubky

}

Prvnφ dva p°φkazy v inicializaΦnφm k≤du nagrabujφ informace o okn∞ a indikßtoru stisknut²ch klßves. Funkcφ srand() inicializujeme generßtor nßhodn²ch Φφsel, potom loadujeme textury a vytvo°φme display listy fontu.

BOOL Initialize (GL_Window* window, Keys* keys)// Inicializace OpenGL

{

g_window = window;

g_keys = keys;

srand((unsigned)time(NULL));// Inicializace generßtoru nßhodn²ch Φφsel

if ((!LoadTGA(&textures[0],"Data/BlueFace.tga")) ||// Modrß tvß°

(!LoadTGA(&textures[1],"Data/Bucket.tga")) ||// Kbelφk

(!LoadTGA(&textures[2],"Data/Target.tga")) ||// TerΦ

(!LoadTGA(&textures[3],"Data/Coke.tga")) ||// Coca-Cola

(!LoadTGA(&textures[4],"Data/Vase.tga")) ||// Vßza

(!LoadTGA(&textures[5],"Data/Explode.tga")) ||// Exploze

(!LoadTGA(&textures[6],"Data/Ground.tga")) ||// Zem∞

(!LoadTGA(&textures[7],"Data/Sky.tga")) ||// Obloha

(!LoadTGA(&textures[8],"Data/Crosshair.tga")) ||// Kurzor

(!LoadTGA(&textures[9],"Data/Font.tga")))// Font

{

return FALSE;// Inicializace se nezda°ila

}

BuildFont();// Vytvo°φ display listy fontu

Nastavφme ΦernΘ pozadφ. Depth bufferem testujeme na mΘn∞ nebo rovno (GL_LEQUAL).

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// ╚ernΘ pozadφ

glClearDepth(1.0f);// Nastavenφ depth bufferu

glDepthFunc(GL_LEQUAL);// Typ testovßnφ hloubky

glEnable(GL_DEPTH_TEST);// Zapne testovßnφ hloubky

P°φkaz glBlendFunc() je VELMI d∙le₧it². Parametry GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA oznamujφ OpenGL, aby p°i renderingu pou₧φvalo alfa hodnoty ulo₧enΘ v textu°e. Aby se blending mohl projevit, musφme ho zapnout. Dßle zapφnßme i mapovßnφ 2D textur a o°ezßvßnφ zadnφch stran polygon∙. P°i kreslenφ zadßvßme sou°adnice polygon∙ proti sm∞ru hodinov²ch ruΦiΦek, tak₧e odstran∞nφ zadnφch stran polygon∙ niΦemu nevadφ. Navφc se program urychlφ, proto₧e mß s kreslenφm pouze polovinu prßce.

V²Üe v tutorißlu jsem psal o pou₧itφ glAlphaFunc() namφsto blendingu. Pokud chcete pou₧φvat rad∞ji alfa funkci, zakomentß°ujte dva °ßdky d∙le₧itΘ pro blending a odkomentß°ujte dva °ßdky alfy. Zakomentß°ovat m∙₧ete takΘ °azenφ objekt∙ pomocφ qsort() a vÜe s nφm spojenΘ. P°i alfa testingu nenφ po°adφ renderingu d∙le₧itΘ.

Program p∙jde v po°ßdku, ale obloha se nezobrazφ. P°φΦinou je jejφ textura, kterß mß alfa hodnotu 0.5f. Alfa, narozdφl od blendingu, vÜak m∙₧e b²t bu∩ nula nebo jedna, nic mezi. ProblΘm lze vy°eÜit modifikacφ alfa kanßlu textury. Ob∞ metody p°inßÜejφ velmi dobrΘ v²sledky.

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);// Nastavenφ alfa blendingu

glEnable(GL_BLEND);// Zapne alfa blending

// glAlphaFunc(GL_GREATER, 0.1f);// Nastavenφ alfa testingu

// glEnable(GL_ALPHA_TEST);// Zapne alfa testing

glEnable(GL_TEXTURE_2D);// Zapne mapovßnφ textur

glEnable(GL_CULL_FACE);// O°ezßvßnφ zadnφch stran polygon∙

Na tomto mφst∞ inicializujeme vÜechny objekty, kterΘ program pou₧φvß a potom ukonΦφme funkci.

for (int loop = 0; loop < 30; loop++)// Prochßzφ vÜechny objekty

{

InitObject(loop);// Inicializace ka₧dΘho z nich

}

return TRUE;// Inicializace ·sp∞Ünß

}

Naprogramujeme detekci zßsah∙ do objekt∙. Ze vÜeho nejd°φve deklarujeme buffer, kter² pou₧ijeme k ulo₧enφ informacφ o vybran²ch objektech. Prom∞nnß hits slou₧φ k poΦφtßnφ zßsah∙.

void Selection(void)// Detekce zasa₧enφ objekt∙

{

GLuint buffer[512];// Deklarace selection bufferu

GLint hits;// PoΦet zasa₧en²ch objekt∙

SkonΦila-li hra, nenφ ₧ßdn² d∙vod, abychom hledali, kter² objekt byl zasa₧en, a proto ukonΦφme funkci. Pokud je hrßΦ stßle ve h°e, p°ehrajeme zvuk v²st°elu. Tato funkce je volßna pouze tehdy, kdy₧ hrßΦ stiskl tlaΦφtko myÜi. A pokud stiskl tlaΦφtko myÜi, znamenß to, ₧e cht∞l vyst°elit. Nezßle₧φ, jestli zasßhl nebo ne, zvuk v²st°elu je slyÜet v₧dy. P°ehrajeme ho v asynchronφm m≤du (SND_ASYNC), aby b∞₧el na pozadφ a program nemusel Φekat a₧ skonΦφ.

if (game)// Konec hry?

{

return;// Nenφ d∙vod testovat na zßsah

}

PlaySound("data/shot.wav", NULL, SND_ASYNC);// P°ehraje zvuk v²st°elu

Nastavφme pole viewport tak, aby obsahovalo pozici x, y se Üφ°kou a v²Ükou aktußlnφho viewportu (OpenGL okna). Volßnφm funkce glSelectBuffer() na°φdφme OpenGL, aby pou₧ilo naÜe pole buffer pro sv∙j selection buffer.

GLint viewport[4];// Velikost viewportu. [0] = x, [1] = y, [2] = v²Üka, [3] = Üφ°ka

glGetIntegerv(GL_VIEWPORT, viewport);// Nastavφ pole podle velikosti a lokace scΘny relativn∞ k oknu

glSelectBuffer(512, buffer);// P°ikß₧e OpenGL, aby pro selekci objekt∙ pou₧ilo pole buffer

VÜechen k≤d nφ₧e je velmi d∙le₧it². Nejd°φve p°evedeme OpenGL do selection m≤du. Nic, co se vykresluje, se nezobrazφ, ale namφsto toho se informace o renderovan²ch objektech ulo₧φ do selection bufferu. Potom volßnφm glInitNames() a glPushName(0) inicializujeme name stack (stack jmen). Kdyby OpenGL nebylo v selection m≤du, glPushName() by bylo ignorovßno.

(void) glRenderMode(GL_SELECT);// P°evedenφ OpenGL do selection m≤du

glInitNames();// Inicializace name stacku

glPushName(0);// Vlo₧φ 0 (nejmΘn∞ jedna polo₧ka) na stack

Po p°φprav∞ name stacku musφme omezit kreslenφ na oblast pod kurzorem. Zvolφme projekΦnφ matici, pushneme ji na stack a resetujeme ji volßnφm glLoadIdentity().

glMatrixMode(GL_PROJECTION);// Zvolφ projekΦnφ matici

glPushMatrix();// Ulo₧enφ projekΦnφ matice

glLoadIdentity();// Reset matice

Oblast kreslenφ omezφme p°φkazem gluPickMatrix(). Prvnφ parametr urΦuje pozice myÜi na ose x, druh² je na ose y. JedniΦky p°edstavujφ Üφ°ku a v²Üku picking regionu. Poslednφm parametrem je pole viewport, kterΘ urΦuje aktußlnφ okraje viewportu. Mouse_x a mouse_y budou st°edem picking regionu.

// Vytvo°enφ matice, kterß zv∞tÜφ malou Φßst obrazovky okolo kurzoru myÜi

gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3] - mouse_y), 1.0f, 1.0f, viewport);

Volßnφm gluPerspective vynßsobφme perspektivnφ matici pick maticφ, kterß omezuje vykreslovßnφ na oblast vy₧ßdanou od gluPickMatrix(). Potom p°epneme na matici modelview a vykreslφme sest°elovanΘ objekty. Kreslφme je funkcφ DrawTargets() a ne Draw(), proto₧e chceme urΦit zßsahy do objekt∙ a ne do oblohy, zem∞ nebo kurzoru. Po vykreslenφ objekt∙ p°epneme zp∞t na projekΦnφ matici a popneme ji ze stacku. Nakonec se znovu vrßtφme k matici modelview. Poslednφm p°φkazem p°epneme OpenGL zp∞t do renderovacφho m≤du, tak₧e se op∞t budou vykreslovanΘ objekty zobrazovat na scΘnu. Prom∞nnß hits bude po p°i°azenφ obsahovat poΦet objekt∙, kterΘ byly vykresleny na oblast specifikovanou gluPickMatrix(). Tedy tam, kde se nachßzel kurzor myÜi p°i v²st°elu.

// Aplikovßnφ perspektivnφ matice

gluPerspective(45.0f, (GLfloat) (viewport[2] - viewport[0]) / (GLfloat) (viewport[3] - viewport[1]), 0.1f, 100.0f);

glMatrixMode(GL_MODELVIEW);// Modelview matice

DrawTargets();// Renderuje objekty do selection bufferu

glMatrixMode(GL_PROJECTION);// ProjekΦnφ matice

glPopMatrix();// Obnovenφ projekΦnφ matice

glMatrixMode(GL_MODELVIEW);// Modelview matice

hits = glRenderMode(GL_RENDER);// P°epnutφ do renderovacφho m≤du, ulo₧enφ poΦtu objekt∙ pod kurzorem

Zjistφme, jestli bylo zaznamenßno vφce ne₧ nula zßsah∙. Pokud ano, p°i°adφme prom∞nnΘ choose jmΘno prvnφho objektu, kter² byl vykreslen do picking oblasti. Depth uklßdß, jak hluboko ve scΘn∞ se tento objekt nachßzφ. Ka₧d² zßsah zabφrß v bufferu Φty°i polo₧ky. Prvnφ je poΦtem jmen v name stacku, kdy₧ se zßsah udßl. Druhß polo₧ka p°edstavuje minimßlnφ z hodnotu (hloubku) ze vÜech vertex∙, kterΘ protφnaly zobrazenou oblast v Φase zßsahu. T°etφ naopak obsahuje maximßlnφ z hodnotu a poslednφ polo₧ka je obsahem name stacku v Φase zßsahu, nebo-li jmΘno objektu. V tomto programu nßs zajφmß minimßlnφ z hodnota a jmΘno objektu.

if (hits > 0)// Bylo vφce ne₧ nula zßsah∙?

{

int choose = buffer[3];// Ulo₧φ jmΘno prvnφho objektu

int depth = buffer[1];// Ulo₧φ jeho hloubku

Zalo₧φme cyklus skrz vÜechny zßsahy, abychom se ujistili, ₧e ₧ßdn² z objekt∙ nenφ blφ₧e ne₧ ten prvnφ. Jin²mi slovy pot°ebujeme najφt nejbli₧Üφ objekt ke st°elci. Kdybychom ho nehledali a st°elec zasßhl dva p°ekr²vajφcφ se objekty najednou, mohl by ten v po°adφ pole prvnφ b²t vzdßlen∞jÜφ od pozorovatele. kliknutφ myÜφ by sest°elilo Üpatn² objekt. Je jasnΘ, ₧e pokud je n∞kolik terΦ∙ za sebou, tak se p°i v²st°elu zasßhne v₧dy ten nejbli₧Üφ.

Ka₧d² objekt mß v poli buffer Φty°i polo₧ky, tak₧e nßsobφme aktußlnφ pr∙b∞h Φty°mi. Abychom zφskali hloubku objektu (druhß polo₧ka), p°iΦφtßme jedniΦku. pokud je prßv∞ testovanß hloubka menÜφ ne₧ aktußln∞ nejni₧Üφ, p°ipφÜeme informace o jmΘnu objektu a jeho hloubce. Po vÜech pr∙chodech cyklem, bude choose obsahovat jmΘno ke st°elci nejbli₧Üφho zasa₧enΘho objektu a depth jeho hloubku.

for (int loop = 1; loop < hits; loop++)// Prochßzφ vÜechny detekovanΘ zßsahy

{

if (buffer[loop*4 + 1] < GLuint(depth))// Je tento objekt blφ₧e ne₧ n∞kter² z p°edchozφch?

{

choose = buffer[loop*4 + 3];// Ulo₧φ jmΘno bli₧Üφho objektu

depth = buffer[loop*4 + 1];// Ulo₧φ jeho hloubku

}

}

NaÜli jsme zasa₧en² objekt. P°i°azenφm TRUE do hit ho oznaΦφme, aby nemohl b²t zasa₧en po druhΘ nebo zniΦen automaticky po opuÜt∞nφ scΘny. P°iΦteme k hrßΦovu score jedniΦku a takΘ inkrementujeme poΦet zßsah∙ v danΘm levelu.

if (!object[choose].hit)// Nebyl jeÜt∞ objekt zasa₧en?

{

object[choose].hit = TRUE;// OznaΦφ ho jako zasa₧en²

score += 1;// Zv²Üφ celkovΘ sk≤re

kills += 1;// Zv²Üφ poΦet zßsah∙ v levelu

Chceme, aby v ka₧dΘm nßsledujφcφm levelu musel hrßΦ sest°elit v∞tÜφ poΦet objekt∙. Tφm se znesnad≥uje postup mezi levely. Zkontrolujeme, jestli je kills v∞tÜφ ne₧ aktußlnφ level nßsoben² p∞ti. V levelu jedna staΦφ pro postup sest°elit pouze p∞t objekt∙ (1*5). V druhΘm levelu u₧ je to deset (2*5), atd. Hra zaΦφnß b²t t∞₧Üφ a t∞₧Üφ.

Nastal-li Φas pro p°esun do nßsledujφho levelu, nastavφme poΦet nezasa₧en²ch objekt∙ na nulu, aby jφm m∞l hrßΦ v∞tÜφ Üanci ·sp∞Ün∞ projφt. Ale aby vÜe nebylo zase tak jednoduchΘ, vynulujeme i poΦet zasa₧en²ch objekt∙. Nakonec nesmφme zapomenout inkrementovat level a otestovat, jestli u₧ nebyl poslednφ. D∙vod proΦ mßme zrovna t°icet level∙ je velice jednoduch². T°icßt² level je u₧ Üφlen∞ obtφ₧n², myslφm, ₧e nikdo nemß Üanci ho dosßhnout. Druh²m d∙vodem je maximßlnφ poΦet objekt∙ - je jich prßv∞ t°icet. Chcete-li jich vφce poupravujte program.

Na scΘn∞ m∙₧ete mφt ale MAXIM┴LN╠ 64 objekt∙ (0 a₧ 63). Pokud jich zkusφte renderovat 65 a vφce, PICKING P╪ESTANE PRACOVAT SPR┴VN╠ a zaΦnou se dφt podivnΘ v∞ci. VÜechno od nßhodn∞ vybuchujφcφch objekt∙ a₧ k celΘmu vaÜemu poΦφtaΦi se kompletn∞ zhroutφ. 64 objekt∙ je fyzikßlnφ limit OpenGL, stejn∞ jako nap°φklad 8 sv∞tel ve scΘn∞.

Pokud jste n∞jakou Ü¥astnou nßhodou bohem :-) a dostanete se a₧ k t°icßtΘmu levelu, v²Üe u₧ bohu₧el nepostoupφte. NicmΘn∞ celkovΘ sk≤re se bude stßle zvyÜovat a poΦet zasa₧en²ch i nezasa₧en²ch objekt∙ se v₧dy na tomto mφst∞ resetuje.

if (kills > level*5)// ╚as pro dalÜφ level?

{

miss = 0;// Nulovßnφ nezasa₧en²ch objekt∙

kills = 0;// Nulovßnφ zasa₧en²ch objekt∙ v tomto levelu

level += 1;// Posun na dalÜφ level

if (level > 30)// Poslednφ level?

{

level = 30;// Nastavenφ levelu na poslednφ

}

}

}

}

}

Ve funkci Update() testujeme stisk klßves a aktualizujeme umφst∞nφ objekt∙ ve scΘn∞. Jednou z p°φjemn²ch v∞cφ je p°edßvan² parametr miliseconds, kter² definuje uplynul² Φas od p°edchozφho volßnφ. Na jeho bßzi posuneme objekt o danou vzdßlenost. A v²sledek? Hra p∙jde stejn∞ rychle na libovolnΘm procesoru. ALE je zde jeden nedostatek. ╪ekn∞me, ₧e mßme objekt pohybujφcφ se p∞t jednotek za deset sekund. Rychl² poΦφtaΦ posune objektem o p∙l jednotky za sekundu. Na pomalΘm systΘmu m∙₧e trvat 2 sekundy, ne₧ se funkce znovu zavolß. Tφm vznikajφ r∙znß zpo₧d∞nφ a trhßnφ, zkrßtka animace u₧ nenφ plynulß. LepÜφ °eÜenφ vÜak neexistuje. Pomal² poΦφtaΦ nezrychlφte, leda koupit nov²...

Ale zpßtky ke k≤du. Prvnφ podmφnka zjiÜ¥uje stisk klßvesy ESC, kter² ukonΦuje aplikaci.

void Update(DWORD milliseconds)// Aktualizace pohyb∙ ve scΘn∞ a stisk klßves

{

if (g_keys->keyDown[VK_ESCAPE])// Klßvesa ESC?

{

TerminateApplication(g_window);// UkonΦenφ programu

}

Klßvesa F1 p°epφnß m≤d okna mezi systΘmem a fullscreenem.

if (g_keys->keyDown[VK_F1])// Klßvesa F1?

{

ToggleFullscreen(g_window);// P°epnutφ fullscreen/okno

}

Stisk mezernφku po skonΦenφ hry zalo₧φ novou. Inicializujeme vÜech t°icet objekt∙, nastavφme konec hry na false, sk≤re na nulu, prvnφ level a zasa₧enΘ i nezasa₧enΘ objekty v tomto levelu takΘ na nulu. Nic nepochopitelnΘho.

if (g_keys->keyDown[' '] && game)// Mezernφk na konci hry?

{

for (int loop = 0; loop < 30; loop++)// Prochßzφ vÜechny objekty

{

InitObject(loop);// Jejich inicializace

}

game = FALSE;// JeÜt∞ nenφ konec hry

score = 0;// NulovΘ sk≤re

level = 1;// Prvnφ level

kills = 0;// Nula zasa₧en²ch objekt∙

miss = 0;// Nula nezasa₧en²ch objekt∙

}

K vytvo°enφ iluze plujφcφch mrak∙ a pohybujφcφ se zem∞, odeΦteme od roll Φφslo 0.00005f nßsobenΘ poΦtem milisekund od minulΘho renderingu. Princip Φasovßnφ jsme si vysv∞tlili v²Üe.

roll -= milliseconds * 0.00005f;// Mraky plujφ a zem∞ se pohybuje

Zalo₧φme cyklus, kter² prochßzφ vÜechny objekty ve scΘn∞ a aktualizuje je. Jejich poΦet je roven aktußlnφmu levelu.

for (int loop = 0; loop < level; loop++)// Aktualizace vÜech viditeln²ch objekt∙

{

Pot°ebujeme zjistit, kter²m sm∞rem se kter² objekt otßΦφ. Podle sm∞ru rotace upravφme aktußlnφ ·hel natoΦenφ o 0.2 stup≥∙ vynßsoben²ch °φdφcφ prom∞nnou cyklu seΦtenou s milisekundami. P°iΦφtßnφm loop zφskßme rozdφlnou rotaci pro ka₧d² objekt. Druh² objekt se nynφ otßΦφ rychleji ne₧ prvnφ a t°etφ objekt jeÜt∞ rychleji ne₧ druh².

if (object[loop].rot == 1)// Rotace po sm∞ru hodinov²ch ruΦiΦek?

object[loop].spin -= 0.2f * (float(loop + milliseconds));

if (object[loop].rot == 2)// Rotace proti sm∞ru hodinov²ch ruΦiΦek?

object[loop].spin += 0.2f * (float(loop + milliseconds));

P°esuneme se ke k≤du zajiÜ¥ujφcφmu pohyby. Pokud se objekt pohybuje doprava (dir == 1), p°iΦteme k x pozici 0.0012f. Podobn²m zp∙sobem oÜet°φme posun doleva (dir == 0). P°i sm∞ru nahoru (dir == 2) zv∞tÜφme y hodnotu, proto₧e kladnß Φßst osy y le₧φ naho°e. Sm∞r dol∙ (dir == 3) je ·pln∞ stejn² jako p°edchozφ. OdeΦφtßme vÜak menÜφ Φφslo, aby byl pßd pomalejÜφ.

if (object[loop].dir == 1)// Pohyb doprava?

object[loop].x += 0.012f * float(milliseconds);

if (object[loop].dir == 0)// Pohyb doleva?

object[loop].x -= 0.012f * float(milliseconds);

if (object[loop].dir == 2)// Pohyb nahoru?

object[loop].y += 0.012f * float(milliseconds);

if (object[loop].dir == 3)// Pohyb dol∙?

object[loop].y -= 0.0025f * float(milliseconds);

Posunuli jsme objektem a nynφ pot°ebujeme otestovat, jestli je na scΘn∞ jeÜt∞ vid∞t. M∙₧eme to zjistit podle hloubky ve scΘn∞ mφnus 15.0f (malß tolerance navφc) a d∞lenφm dv∞ma. Pro ty z vßs, kte°φ od inicializace objekt∙ u₧ zapomn∞li... Pokud jste dvacet jednotek ve scΘn∞, mßte z ka₧dΘ strany zhruba deset jednotek viditelnΘ scΘny (zßle₧φ na nastavenφ perspektivy). Tak₧e -20.0f (hloubka) -15.0f (extra okraj) = -35.0f. Vyd∞lφme 2.0f a zφskßme -17.5f, co₧ je p°ibli₧n∞ 7.5 jednotek vlevo od viditelnΘ scΘny. Objekt tedy u₧ urΦit∞ nenφ vid∞t.

Musφ takΘ platit podmφnka, ₧e se objekt pohybuje doleva (dir == 0). Pokud ne, nestarßme se o n∞j. Poslednφ Φßst logickΘho v²razu p°edstavuje test zßsahu. Shrneme to: pokud objekt vylet∞l vlevo ze scΘny, pohybuje se doleva a nebyl zasa₧en, u₧ivatel ho u₧ nemß Üanci zasßhnout. Zv²Üφme poΦet nezasa₧en²ch objekt∙ a oznaΦφme objekt jako zasa₧en², aby se o n∞j program u₧ p°φÜt∞ nestaral. Touto cestou (hit = true) takΘ zajistφme autodestrukci, co₧ nßm po n∞jakΘ dob∞ umo₧nφ jeho automatickou reinicializaci - novß textura, sm∞r pohybu, rotace ap.

// Objekt vylet∞l vlevo ze scΘny, pohybuje se vlevo a jeÜt∞ nebyl zasa₧en

if ((object[loop].x < (object[loop].distance - 15.0f) / 2.0f) && (object[loop].dir == 0) && !object[loop].hit)

{

miss += 1;// Zv²Üenφ poΦtu nezasa₧en²ch objekt∙

object[loop].hit = TRUE;// Odstran∞nφ objektu (zajiÜ¥uje animaci exploze a reinicializaci)

}

Analogicky oÜet°φme opuÜt∞nφ scΘny vpravo a nßraz do zem∞.

// Objekt vylet∞l vpravo ze scΘny, pohybuje se vpravo a jeÜt∞ nebyl zasa₧en

if ((object[loop].x > -(object[loop].distance - 15.0f) / 2.0f) && (object[loop].dir == 1) && !object[loop].hit)

{

miss += 1;// Zv²Üenφ poΦtu nezasa₧en²ch objekt∙

object[loop].hit = TRUE;// Odstran∞nφ objektu (zajiÜ¥uje animaci exploze a reinicializaci)

}

// Objekt narazil do zem∞, pohybuje se dol∙ a jeÜt∞ nebyl zasa₧en

if ((object[loop].y < -2.0f) && (object[loop].dir == 3) && !object[loop].hit)

{

miss += 1;// Zv²Üenφ poΦtu nezasa₧en²ch objekt∙

object[loop].hit = TRUE;// Odstran∞nφ objektu (zajiÜ¥uje animaci exploze a reinicializaci)

}

Narozdφl od p°edchozφch test∙ p°i letu vzh∙ru ud∞lßme menÜφ zm∞nu. Pokud se objekt dostane na ose y v²Üe ne₧ 4.5f jednotek (t∞sn∞ pod mraky), nezniΦφme ho, ale pouze zm∞nφme jeho sm∞r, aby se pohyboval dol∙. Destrukci zajistφ p°edchozφ k≤d pro nara₧enφ do zem∞.

if ((object[loop].y > 4.5f) && (object[loop].dir == 2))// Objekt je pod mraky a sm∞°uje vzh∙ru

object[loop].dir = 3;// Zm∞na sm∞ru na pßd

}

}

Do mapy zprßv ve funkci WindowProc() p°idßme dv∞ v∞tve, kterΘ obsluhujφ udßlosti myÜi. P°i stisknutφ levΘho tlaΦφtka ulo₧φme pozici kliknutφ v okn∞ a ve funkci Selection() zjistφme, jestli se hrßΦ strefil do n∞kterΘho z objekt∙ nebo ne. Proto₧e vykreslujeme vlastnφ OpenGL kurzor, pot°ebujeme p°i renderingu znßt jeho pozici. O to se starß WM_MOUSEMOVE.

// Funkce WindowProc

case WM_LBUTTONDOWN:// Stisknutφ levΘho tlaΦφtka myÜi

mouse_x = LOWORD(lParam);

mouse_y = HIWORD(lParam);

Selection();

break;

case WM_MOUSEMOVE:// Pohyb myÜi

mouse_x = LOWORD(lParam);

mouse_y = HIWORD(lParam);

break;

P°istoupφme k vykreslenφ objektu. Funkci se p°edßvajφ celkem t°i parametry, kterΘ ho dostateΦn∞ popisujφ - Üφ°ka, v²Üka a textura. ObdΘlnφk renderujeme zadßvßnφm bod∙ proti sm∞ru hodinov²ch ruΦiΦek, abychom mohli pou₧φt culling.

void Object(float width, float height, GLuint texid)// Vykreslφ objekt

{

glBindTexture(GL_TEXTURE_2D, textures[texid].texID);// Zvolφ sprßvnou texturu

glBegin(GL_QUADS);// Kreslenφ obdΘlnφk∙

glTexCoord2f(0.0f, 0.0f); glVertex3f(-width,-height, 0.0f);// Lev² dolnφ

glTexCoord2f(1.0f, 0.0f); glVertex3f( width,-height, 0.0f);// Prav² dolnφ

glTexCoord2f(1.0f, 1.0f); glVertex3f( width, height, 0.0f);// Prav² hornφ

glTexCoord2f(0.0f, 1.0f); glVertex3f(-width, height, 0.0f);// Lev² hornφ

glEnd();// Konec kreslenφ

}

K≤d pro renderovßnφ exploze dostßvß pouze jeden parametr - identifikßtor objektu. Pot°ebujeme nagrabovat sou°adnice oblasti na textu°e exploze. Ud∞lßme to podobnou cestou, jako kdy₧ jsme zφskßvali jednotlivΘ znaky z textury fontu. Ex a ey p°edstavujφ sloupec a °ßdek zßvisl² na po°adφ snφmku animace (framu).

Textura snφmk∙ exploze

Pozici na ose x zφskßme d∞lenφm aktußlnφho snφmku Φty°mi. Proto₧e mßme 64 snφmk∙ a pouze 16 obrßzk∙, pot°ebujeme animaci zpomalit. Zbytek po d∞lenφ upravφ Φφslo na hodnoty 0 a₧ 3 a aby texturovΘ koordinßty byly v rozmezφ 0.0f a 1.0f, d∞lφme Φty°mi. Zφskali jsme sloupec, nynφ jeÜt∞ °ßdek. Prvnφ d∞lenφ op∞t zmenÜuje Φφslo, druhΘ d∞lenφ eliminuje cel² °ßdek a poslednφm d∞lenφm zφskßme vertikßlnφ sou°adnici na textu°e.

Pokud je aktußlnφ snφmek 16, ey = 16/4/4/4 = 4/4/4 = 0,25. Jeden °ßdek dol∙. je-li snφmek 60, ey = 60/4/4/4 = 15/4/4 = 3/4 = 0,75. Matematici nev∞°φ vlastnφm oΦφm... D∙vod proΦ se 15/4 nerovnß 3,75 je to, ₧e do poslednφho d∞lenφ pracujeme s cel²mi Φφsly. PoΦφtßme-li se zaokrouhlovßnφm dojdeme k zßv∞ru, ₧e v²sledkem jsou v₧dy Φφsla 0.0f, 0.25f, 0.50f nebo 0.75f. Doufßm, ₧e to dßvß smysl. Je to jednoduchΘ, ale matematika zastraÜuje.

void Explosion(int num)// Animace exploze objektu

{

float ex = (float)((object[num].frame/4)%4)/4.0f;// V²poΦet x snφmku exploze (0.0f - 0.75f)

float ey = (float)((object[num].frame/4)/4)/4.0f;// V²poΦet y snφmku exploze (0.0f - 0.75f)

Zφskali jsme texturovacφ koordinßty, zb²vß vykreslit obdΘlnφk. Vertexy jsou fixovßny na -1.0f a 1.0f. U textur odeΦφtßme ey od 1.0f. Pokud bychom to neud∞lali animace by probφhala v opaΦnΘm po°adφ. PoΦßtek texturovacφch sou°adnic je vlevo dole.

glBindTexture(GL_TEXTURE_2D, textures[5].texID);// Textura exploze

glBegin(GL_QUADS);// Kreslenφ obdΘlnφk∙

glTexCoord2f(ex, 1.0f - (ey));

glVertex3f(-1.0f, -1.0f, 0.0f);// Lev² dolnφ

glTexCoord2f(ex + 0.25f, 1.0f - (ey));

glVertex3f( 1.0f, -1.0f, 0.0f);// Prav² dolnφ

glTexCoord2f(ex + 0.25f, 1.0f - (ey + 0.25f));

glVertex3f( 1.0f, 1.0f, 0.0f);// Prav² hornφ

glTexCoord2f(ex, 1.0f - (ey + 0.25f));

glVertex3f(-1.0f, 1.0f, 0.0f);// Lev² hornφ

glEnd();// Konec kreslenφ

Jak je vysv∞tleno v²Üe, snφmek nesmφ b²t vyÜÜφ ne₧ 63, jinak by animace zaΦala nanovo. P°i p°esßhnutφ tohoto Φφsla reinicializujeme objekt.

object[num].frame += 1;// Zv²Üφ snφmek exploze

if (object[num].frame > 63)// Poslednφ snφmek?

{

InitObject(num);// Reinicializace objektu

}

}

Nßsledujφcφ sekce k≤du vykresluje objekty. ZaΦneme resetovßnφm matice a p°esunem o deset jednotek do hloubky.

void DrawTargets(void)// Vykreslφ objekty

{

glLoadIdentity();// Reset matice

glTranslatef(0.0f, 0.0f, -10.0f);// Posun do hloubky

Zalo₧φme cyklus prochßzejφcφ vÜechny aktivnφ objekty. Funkcφ glLoadName() skryt∞ oznaΦφme individußlnφ objekty - ka₧dΘmu se urΦφ jmΘno (Φφslo), kterΘ odpovφdß indexu v poli. Prvnφmu se p°i°adφ nula, druhΘmu jedniΦka atd. Podle tohoto jmΘna m∙₧eme zjistit, kter² objekt byl zasa₧en. Pokud program nenφ v selection m≤du glLoadName() je ignorovßno. Po p°i°azenφ jmΘna ulo₧φme matici.

for (int loop = 0; loop < level; loop++)// Prochßzφ aktivnφ objekty

{

glLoadName(loop);// P°i°adφ objektu jmΘno (pro detekci zßsah∙)

glPushMatrix();// Ulo₧enφ matice

P°esuneme se na pozici objektu, kde mß b²t vykreslen.

glTranslatef(object[loop].x, object[loop].y, object[loop].distance);// Umφst∞nφ objektu

P°ed renderingem testujeme, jestli byl zasa₧en nebo ne. Pokud podmφnka platφ, vykreslφme mφsto objektu snφmek animace exploze, jinak otoΦφme objektem na ose z o jeho ·hel spin a a₧ potom ho vykreslφme. Pro urΦenφ rozm∞r∙ pou₧ijeme pole size, kterΘ jsme vytvo°ili na zaΦßtku programu. Texid reprezentuje typ objektu (texturu).

if (object[loop].hit)// Byl objekt zasa₧en?

{

Explosion(loop);// Vykreslφ snφmek exploze

}

else// Objekt nebyl zasa₧en

{

glRotatef(object[loop].spin,0.0f,0.0f,1.0f);// NatoΦenφ na ose z

Object(size[object[loop].texid].w, size[object[loop].texid].h, object[loop].texid);// Vykreslenφ

}

Po renderingu popneme matici, abychom zruÜili posun a natoΦenφ.

glPopMatrix();// Obnovφ matici

}

}

Draw() je hlavnφ vykreslovacφ funkcφ. Jako obvykle sma₧eme buffery a resetujeme matici, kterou nßsledn∞ pushneme.

void Draw(void)// Vykreslenφ scΘny

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Sma₧e buffery

glLoadIdentity();// Reset matice

glPushMatrix();// Ulo₧φ matici

Zvolφme texturu (v po°adφ sedmß) a pokusφme se vykreslit oblohu. Je slo₧ena ze Φty° otexturovan²ch obdΘlnφk∙. Prvnφ p°edstavuje oblohu od zem∞ p°φmo vzh∙ru. Textura na n∞m roluje docela pomalu. Druh² obdΘlnφk je vykreslen na stejnΘm mφst∞, ale jeho textura roluje rychleji. Ob∞ textury se blendingem spojφ dohromady a vytvo°φ tak hezk² vφcevrstv² efekt.

glBegin(GL_QUADS);// Kreslenφ obdΘlnφk∙

glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);// Prav² hornφ

glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);// Lev² hornφ

glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);// Lev² dolnφ

glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);// Prav² dolnφ

glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);// Prav² hornφ

glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);// Lev² hornφ

glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);// Lev² dolnφ

glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);// Prav² dolnφ

Abychom p°idali iluzi, ₧e mraky plujφ sm∞rem k pozorovateli, t°etφ obdΘlnφk sm∞°uje z hloubky dop°edu. DalÜφ obdΘlnφk je op∞t na stejnΘ mφst∞, ale textura roluje rychleji. V²sledkem Φty° obyΦejn²ch obdΘlnφk∙ je obloha, kterß se jevφ, jako by stoupala od zem∞ vzh∙ru a p°ibli₧ovala se k pozorovateli. Mohl jsem pou₧φt otexturovanou polokouli, ale byl jsem p°φliÜ lφn². Efekt s obdΘlnφky vypadß celkem sluÜn∞.

glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);// Prav² hornφ

glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);// Lev² hornφ

glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);// Lev² dolnφ

glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);// Bottom Right

glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);// Prav² hornφ

glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);// Lev² hornφ

glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);// Lev² dolnφ

glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);// Prav² dolnφ

glEnd();// Konec kreslenφ

Nynφ vykreslφme zemi. ZaΦφnß tam, kde se nachßzφ nejni₧Üφ bod oblohy a sm∞°uje sm∞rem k pozorovateli. Roluje stejn∞ rychle jako mraky. Abychom p°idali trochu vφce detail∙ a zamezili tak nep°φjemnΘmu kostiΦkovßnφ p°i velkΘm zv∞tÜenφ, namapujeme texturu sedmkrßt na ose x a Φty°ikrßt na ose y.

glBindTexture(GL_TEXTURE_2D, textures[6].texID);// Textura zem∞

glBegin(GL_QUADS);// Kreslenφ obdΘlnφk∙

glTexCoord2f(7.0f,4.0f-roll); glVertex3f( 27.0f,-3.0f,-50.0f);// Prav² hornφ

glTexCoord2f(0.0f,4.0f-roll); glVertex3f(-27.0f,-3.0f,-50.0f);// Lev² hornφ

glTexCoord2f(0.0f,0.0f-roll); glVertex3f(-27.0f,-3.0f,0.0f);// Lev² dolnφ

glTexCoord2f(7.0f,0.0f-roll); glVertex3f( 27.0f,-3.0f,0.0f);// Prav² dolnφ

glEnd();// Konec kreslenφ

Pozadφ je vykresleno, p°istoupφme k sest°elovan²m objekt∙m. Napsali jsme pro n∞ specißlnφ funkci. Potom obnovφme matici.

DrawTargets();// Sest°elovanΘ objekty

glPopMatrix();// Obnovenφ matice

Vykreslφme kurzor myÜi. NagrabovanΘ rozm∞ry okna ulo₧φme do struktury obdΘlnφku window. Zvolφme projekΦnφ matici a pushneme ji, resetujeme ji a p°evedeme scΘnu z perspektivnφho m≤du do pravo·hlΘ projekce. Sou°adnice 0, 0 se nachßzejφ vlevo dole.

Ve funkci glOrtho() prohodφme t°etφ a Φtvrt² parametr, aby byl kurzor renderovßn proti sm∞ru hodinov²ch ruΦiΦek a culling pracoval tak, jak chceme. Kdyby byl poΦßtek sou°adnic naho°e, zadßvßnφ bod∙ by probφhalo v opaΦnΘm sm∞ru a kurzor s textem by se nezobrazil.

RECT window;// Prom∞nnß obdΘlnφku

GetClientRect (g_window->hWnd,&window);// Grabovßnφ rozm∞r∙ okna

glMatrixMode(GL_PROJECTION);// ProjekΦnφ matice

glPushMatrix();// Ulo₧φ projekΦnφ matici

glLoadIdentity();// Reset projekΦnφ matice

glOrtho(0, window.right, 0, window.bottom, -1, 1);// Nastavenφ pravo·hlΘ scΘny

Po nastavenφ kolmΘ projekce zvolφme modelview matici a umφstφme kurzor. ProblΘm je v tom, ₧e poΦßtek scΘny (0, 0) je vlevo dole, ale okno (systΘm) ho mß vlevo naho°e. Kdybychom pozici kurzoru neinvertovali, tak by se p°i posunutφ dol∙, pohyboval nahoru. Od spodnφho okraje okna odeΦteme mouse_y. Namφsto p°edßvßnφ velikosti v OpenGL jednotkßch, specifikujeme Üφ°ku a v²Üku v pixelech.

Rozhodl jsem se pou₧φt vlastnφ a ne systΘmov² kurzor ze dvou d∙vod∙. Prvnφ a vφce d∙le₧it² je, ₧e vypadß lΘpe a m∙₧e b²t modifikovßn v jakΘmkoli grafickΘm editoru, kter² podporuje alfa kanßl. Druh²m d∙vodem je, ₧e n∞kterΘ grafickΘ karty kurzor ve fullscreenu nezobrazujφ. Hrßt hru podobnΘho typu bez kurzoru nenφ v∙bec snadnΘ :-).

glMatrixMode(GL_MODELVIEW);// Zvolφ matici modelview

glTranslated(mouse_x, window.bottom-mouse_y, 0.0f);// Posun na pozici kurzoru

Object(16, 16, 8);// Vykreslφ kurzor myÜi

VypφÜeme logo NeHe Productions zarovnanΘ na st°ed hornφ Φßsti okna, dßle zobrazφme aktußlnφ level a sk≤re.

glPrint(240, 450, "NeHe Productions");// Logo

glPrint(10, 10, "Level: %i", level);// Level

glPrint(250, 10, "Score: %i", score);// Sk≤re

Otestujeme, jestli hrßΦ nestrefil vφce ne₧ dev∞t objekt∙. Pokud ano, nastavφme game na true, Φφm₧ indikujeme konec hry.

if (miss > 9)// Nestrefil hrßΦ vφce ne₧ dev∞t objekt∙?

{

miss = 9;// Limit je dev∞t

game = TRUE;// Konec hry

}

Po skonΦenφ hry vypisujeme text GAME OVER. Je-li hrßΦ jeÜt∞ ve h°e, vypφÜeme kolik objekt∙ mu m∙₧e jeÜt∞ uniknout. Text je ve formßtu nap°. '6/10' - m∙₧e jeÜt∞ nezasßhnout Üest objekt∙ z deseti.

if (game)// Konec hry?

{

glPrint(490, 10, "GAME OVER");// VypφÜe konec hry

}

else

{

glPrint(490, 10, "Morale: %i/10", 10-miss);// VypφÜe poΦet objekt∙, kterΘ nemusφ sest°elit

}

Zb²vß obnovit p∙vodnφ nastavenφ. Zvolφme projekΦnφ matici, obnovφme ji, zvolφme modelview matici a vyprßzdnφme buffer, abychom se ujistili, ₧e vÜechny objekty byly v po°ßdku zobrazeny.

glMatrixMode(GL_PROJECTION);// ProjekΦnφ matice

glPopMatrix();// Obnovenφ projekΦnφ matice

glMatrixMode(GL_MODELVIEW);// Modelview matice

glFlush();// Vyprßzdnφ OpenGL renderovacφ pipeline

}

Tento tutorißl je v²sledkem mnoha probd∞l²ch nocφ p°i k≤dovßnφ a psanφ HTML. Nynφ byste m∞li rozum∞t pickingu, alfa testingu a °azenφ podle hloubky p°i alfa blendingu. Picking umo₧≥uje vytvo°it interaktivnφ software, kter² se ovlßdß myÜφ. VÜechno od her a₧ po nßdhernΘ GUI. Nejv∞tÜφ v²hodou pickingu je, ₧e si nemusφme vΘst slo₧it² zßznam, kde se objekty nachßzejφ, o translacφch a rotacφch ani nemluv∞. Objektu staΦφ p°i°adit jmΘno a poΦkat na v²sledek. S alfa blendingem a testingem m∙₧ete vykreslit objekt kompletn∞ nepr∙hledn² a/nebo pln² otvor∙. V²sledek je ·₧asn², nemusφte se starat o prosvφtßnφ textur.

Mohl jsem strßvit spoustu Φasu p°idßvßnφm pohyb∙ podle fyzikßlnφch zßkon∙, grafiky, zvuk∙ a podobn∞. NicmΘn∞ jsem vysv∞tlil OpenGL techniky bez dalÜφch zbyteΦnostφ. Doufßm, ₧e se po Φase objevφ n∞jakΘ skv∞lΘ modifikace k≤du, kterΘ u₧ ale nechßm na vßs.

napsal: Jeff Molofee - NeHe
p°elo₧il: Michal Turek - Woq

ZdrojovΘ k≤dy

Lekce 32

<<< Lekce 31 | Lekce 33 >>>