Chcete v∞d∞t, jak urychlit vaÜe programy v OpenGL? Jste unaveni z nesmyslnΘho opisovßnφ ji₧ napsanΘho k≤du? Nejde to n∞jak jednoduÜeji? NeÜlo by nap°φklad jednφm p°φkazem vykreslit otexturovanou krychli? Samoz°ejm∞, ₧e jde. Tento tutorißl je urΦen² specißln∞ pro vßs. P°edvytvo°enΘ objekty a jejich vykreslovßnφ jednφm °ßdkem k≤du. Jak snadnΘ...
╪ekn∞me, ₧e programujete hru "Asteroidy". Ka₧d² level zaΦφnß alespo≥ se dv∞ma. No, tak₧e se v klidu posadφte a p°ijdete na to, jak vytvo°it 3d asteroid. Jist∞ bude z polygon∙, jak jinak. T°eba osmist∞nn². Pokud byste cht∞li pracovat elegantn∞, vytvo°φte cyklus a v n∞m m∙₧ete vÜe vykreslovat. SkonΦφte s osmnßcti nebo vφce °ßdky. V klidu. Ale pozor! Pokud tento cyklus prob∞hne vφcekrßt, znateln∞ zpomalφ vykreslovßnφ. Jednou, a₧ budete vytvß°et mnohem komplexn∞jÜφ objekty a scΘny, pochopφte, co mßm na mysli.
Tak₧e, jakΘ je °eÜenφ? Display list, neboli p°edvytvo°enΘ objekty! Tφmto zp∙sobem vytvß°φte vÜe pouze jednou. Namapovat textury, barvy, cokoli, co chcete. A samoz°ejm∞ musφte tento display list pojmenovat. Jeliko₧ vytvß°φme asteroidy nazveme display list "asteroid". Ve chvφli, kdy budete chtφt vykreslit texturovan²/obarven² asteroid na monitor, vÜechno, co ud∞lßte je zavolßnφ funkce glCallList(asteroid). P°edvytvo°en² asteroid se okam₧it∞ zobrazφ. Proto₧e je jednou vytvo°en² v pam∞ti (display listu), OpenGL nemusφ vÜe znovu p°epoΦφtßvat. Odstranili jsme velkΘ zatφ₧enφ procesoru a umo₧nili programu b∞₧et o mnoho rychleji.
P°ipraveni? Vytvo°φme scΘnu sklßdajφcφ se z patnßcti krychlφ. Tyto krychle jsou vytvo°eny z krabice a vφka - celkem dva display listy. Vφko bude vybarveno na tmavÜφ odstφn. K≤d vychßzφ z ÜestΘ lekce. P°epφÜeme v∞tÜinu programu, aby bylo snazÜφ najφt zm∞ny.
#include <windows.h>// HlaviΦkov² soubor pro Windows
#include <stdio.h>// HlaviΦkov² soubor pro standardnφ vstup/v²stup
#include <gl\gl.h>// HlaviΦkov² soubor pro OpenGL32 knihovnu
#include <gl\glu.h>// HlaviΦkov² soubor pro Glu32 knihovnu
#include <gl\glaux.h>// HlaviΦkov² soubor pro Glaux knihovnu
HDC hDC = NULL;// Privßtnφ GDI Device Context
HGLRC hRC = NULL;// Trval² Rendering Context
HWND hWnd = NULL;// Obsahuje Handle naÜeho okna
HINSTANCE hInstance;// Obsahuje instanci aplikace
bool keys[256];// Pole pro uklßdßnφ vstupu z klßvesnice
bool active = TRUE;// Ponese informaci o tom, zda je okno aktivnφ
bool fullscreen = TRUE;// Ponese informaci o tom, zda je program ve fullscreenu
Deklarujeme prom∞nnΘ. Nap°ed mφsto pro texturu. DalÜφ dv∞ prom∞nnΘ budou vystupovat jako pointery na mφsto do pam∞ti RAM, kde jsou ulo₧eny display listy.
GLuint texture[1];// Uklßdß texturu
GLuint box;// Uklßdß display list krabice
GLuint top;// Uklßdß display list vφka
GLuint xloop;// Pozice na ose x
GLuint yloop;// Pozice na ose y
GLfloat xrot;// Rotace na ose x
GLfloat yrot;// Rotace na ose y
Vytvo°φme dv∞ pole barev. Prvnφ uklßdß sv∞tlΘ barvy. Hodnoty ve slo₧en²ch zßvorkßch reprezentujφ ΦervenΘ, zelenΘ a modrΘ slo₧ky. DruhΘ pole urΦuje tmavÜφ barvy, kterΘ pou₧ijeme ke kreslenφ vφka krychlφ. Chceme, aby bylo tmavÜφ ne₧ ostatnφ st∞ny.
static GLfloat boxcol[5][3]=// Pole pro barvy st∞n krychle
{
// Sv∞tlΘ: Φervenß, oran₧ovß, ₧lutß, zelenß, modrß
{1.0f,0.0f,0.0f},{1.0f,0.5f,0.0f},{1.0f,1.0f,0.0f},{0.0f,1.0f,0.0f},{0.0f,1.0f,1.0f}
};
static GLfloat topcol[5][3]=// Pole pro barvy vφka krychle
{
// TmavΘ: Φervenß, oran₧ovß, ₧lutß, zelenß, modrß
{0.5f,0.0f,0.0f},{0.5f,0.25f,0.0f},{0.5f,0.5f,0.0f},{0.0f,0.5f,0.0f},{0.0f,0.5f,0.5f}
};
Nßsledujφcφ funkce generuje display listy.
GLvoid BuildLists(// Generuje display listy)
{
ZaΦneme oznßmenφm OpenGL, ₧e chceme vytvo°it dva listy. glGenList(2) pro n∞ alokuje mφsto v pam∞ti a vrßtφ pointer na prvnφ z nich.
box=glGenLists(2);// 2 listy
Vytvo°φme prvnφ list. U₧ jsme zabrali mφsto pro dva listy a vφme, ₧e box ukazuje na zaΦßtek p°ipravenΘ pam∞ti. Pou₧ijeme p°φkaz glNewList(). Prvnφ parametr box °ekne, ₧e chceme ulo₧it list do pam∞ti, kam ukazuje. Druh² parametr GL_COMPILE °φkß, ₧e chceme p°edvytvo°it list v pam∞ti tak, aby se nemuselo p°i ka₧dΘm vykreslovßnφ znovu vÜechno generovat a p°epoΦφtßvat. GL_COMPILE je stejnΘ jako programovßnφ. Pokud napφÜete program a nahrajete ho do vaÜeho p°ekladaΦe (kompileru), musφte ho zkompilovat v₧dy, kdy₧ ho chcete spustit. Ale pokud bude zkompilovßn do .exe souboru, vÜechno, co se musφ pro spuÜt∞nφ vykonat je kliknout myÜφ na tento .exe soubor a spustit ho. Samoz°ejm∞ bez kompilace. Cokoli OpenGL zkompiluje v display listu je mo₧no pou₧φt bez jakΘkoli dalÜφ pot°eby p°epoΦφtßvßnφ. Urychlφ se vykreslovßnφ.
glNewList(box,GL_COMPILE);// Nov² kompilovan² display list - krabice
glBegin(GL_QUADS);
// Spodnφ st∞na
glNormal3f( 0.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// P°ednφ st∞na
glNormal3f( 0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// Zadnφ st∞na
glNormal3f( 0.0f, 0.0f,-1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// Pravß st∞na
glNormal3f( 1.0f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// Levß st∞na
glNormal3f(-1.0f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
glEndList();
P°φkazem glEndList() oznßmφme, ₧e konΦφme vytvß°enφ listu. Cokoli je mezi glNewList() a glEndList() je souΦßstφ display listu a naopak, pokud je n∞co p°ed nebo za u₧ k n∞mu nepat°φ. Abychom zjistili, kam ho ulo₧φme druh² display list, vezmeme hodnotu ji₧ vytvo°enΘho a p°iΦteme k n∞mu jedniΦku (na zaΦßtku funkce jsme °ekli, ₧e d∞lßme 2 display listy, tak₧e je to v po°ßdku).
top=box+1;// Do top vlo₧φme adresu druhΘho display listu
glNewList(top,GL_COMPILE);// Kompilovan² display list - vφko
glBegin(GL_QUADS);
// Hornφ st∞na
glNormal3f( 0.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glEnd();
glEndList();
}
Vytvo°ili jsme oba display listy. Nahrßvßnφ textur je stejnΘ, jako v minul²ch lekcφch. Rozhodl jsem se pou₧φt mimapping, proto₧e nemßm rßd, kdy₧ vidφm pixely. Pou₧ijeme obrßzek cube.bmp ulo₧en² v adresß°i data. Najd∞te funkci LoadBMP() a upravte °ßdek se jmΘnem bitmapy.
if (TextureImage[0]=LoadBMP("Data/Cube.bmp"))// Loading textury
V inicializaΦnφ funkci je jen n∞kolik zm∞n. P°idßme °ßdek BuildList(). VÜimn∞te si, ₧e jsme ho umφstili a₧ za LoadGLTextures(). Display list by se zkompiloval bez textur.
int InitGL(GLvoid)// VÜechna nastavenφ OpenGL
{
if (!LoadGLTextures())// Nahraje texturu
{
return FALSE;
}
BuildLists();// Vytvo°φ display listy
glEnable(GL_TEXTURE_2D);// Zapne texturovΘ mapovßnφ
glShadeModel(GL_SMOOTH);// JemnΘ stφnovßnφ
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// ╚ernΘ pozadφ
glClearDepth(1.0f);// Nastavenφ hloubkovΘho bufferu
glEnable(GL_DEPTH_TEST);// Povolφ hloubkovΘ testovßnφ
glDepthFunc(GL_LEQUAL);// Typ hloubkovΘho testovßnφ
Nßsledujφcφ t°i °ßdky zapφnajφ rychlΘ a ÜpinavΘ osv∞tlenφ (quick and dirty lighting). Light0 je p°eddefinovßno na v∞tÜin∞ video karet, tak₧e zamezφ nep°φjemnostem p°i nastavenφ sv∞tel. Po light0 nastavφme osv∞tlenφ. Pokud vaÜe karta nepodporuje light0, uvidφte Φern² monitor - musφte vypnout sv∞tla. Poslednφ °ßdka p°idßvß barvu do mapovßnφ textur. Nezapneme-li vybarvovßnφ materißlu, textura bude mφt v₧dy originßlnφ barvu. glColor3f(r,g,b) nebude mφt ₧ßdn² efekt (ve vykreslovacφ funkci.
glEnable(GL_LIGHT0);// Zapne implicitnφ sv∞tlo
glEnable(GL_LIGHTING);// Zapne sv∞tla
glEnable(GL_COLOR_MATERIAL);// Zapne vybarvovßnφ materißl∙
Nakonec nastavφme perspektivnφ korekce, aby obraz vypadal lΘpe. Vrßcenφm true oznßmφme programu, ₧e inicializace prob∞hla v po°ßdku.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// NejlepÜφ perspektivnφ korekce
return TRUE;
}
P°ichßzφ na °adu vykreslovacφ funkce. Jako obyΦejn∞, p°idßme pßr Üφlenostφ s matematikou. Tentokrßt, ale nebudou ₧ßdnΘ siny a kosiny. ZaΦneme vymazßnφm obrazovky a depth bufferu. Potom namapujeme texturu na krychli. Mohl bych tento p°φkaz p°idat do k≤du display listu, Ale te∩ kdykoli mohu vym∞nit aktußlnφ texturu za jinou. Doufßm, ₧e u₧ rozumφte, ₧e cokoli je v display listu, tak se nem∙₧e zm∞nit.
int DrawGLScene(GLvoid)// Vykreslovßnφ
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Sma₧e obrazovku a hloubkov² buffer
glBindTexture(GL_TEXTURE_2D, texture[0]);// V²b∞r textury
Mßme cyklus s °φdφcφ prom∞nnou yloop. Tato smyΦka je pou₧ita k urΦenφ pozice krychlφ na ose y. Vykreslujeme p∞t °ßdk∙, proto k≤d prob∞hne p∞tkrßt.
for (yloop=1;yloop<6;yloop++)// Prochßzφ °ßdky
{
Dßle mßme vno°en² cyklus s prom∞nnou xloop. Je pou₧it² pro pozici krychlφ na ose x. Jejich poΦet zßvisφ na tom, ve kterΘm °ßdku se nachßzejφ. Pokud se nachßzφme v hornφm °ßdku vykreslφme jednu, ve druhΘm dv∞, atd.
for (xloop=0;xloop<yloop;xloop++)// Prochßzφ sloupce
{
glLoadIdentity();// Reset matice
Nßsledujφcφ °ßdek p°esune poΦßtek sou°adnic na dan² bod na obrazovce. Na prvnφ pohled je to trochu matoucφ.
Na ose X: Posuneme se doprava o 1,4 jednotky, tak₧e pyramida je na st°edu obrazovky. Potom nßsobφme prom∞nnou xloop hodnotou 2,8 a p°iΦteme 1,4. (Nßsobφme hodnotou 2,8, tak₧e krychle nejsou jedna nad druhou. 2,8 je p°ibli₧n∞ jejich Üφ°ka, kdy₧ jsou pootoΦeny o 45 stup≥∙.) Nakonec odeΦteme yloop*1,4. To je posune doleva v zßvislosti na tom, ve kterΘ °ad∞ jsme. Pokud bychom je nep°esunuli, se°adφ se na levΘ stran∞. (A nevypadajφ jako pyramida.)
Na ose Y: OdeΦteme prom∞nnou yloop od Üesti jinak by pyramida byla vytvo°ena vzh∙ru nohama. PotΘ nßsobφme v²sledek hodnotou 2,4. Jinak krychle budou jedna na vrcholu druhΘ na ose Y. (2,4 se p°ibli₧n∞ rovnß v²Üce krychle). PotΘ odeΦteme 7, tak₧e pyramida zaΦφnß na spodku obrazovky a je sestavovßna ze zdola nahoru.
Na ose Z: Posuneme 20 jednotek dovnit°. Tak₧e se pyramida vejde akorßt na obrazovku.
// Pozice krychle na obrazovce
glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);
Naklonφme krychle o 45 stup≥∙ k pohledu a odeΦteme 2*yloop. Perspektivnφ m≤d nach²lφ krychle automaticky, tak₧e odeΦφtßme, abychom vykompenzovali naklon∞nφ. Nenφ to nejlepÜφ cesta, ale pracuje to. Potom p°iΦteme xrot. To nßm dßvß mo₧nost ovlßdat ·hel klßvesnicφ. TakΘ pou₧ijeme rotaci o 45 stup≥∙ na ose y. P°iΦteme yroot kv∙li ovlßdßnφ klßvesnicφ.
// Rotace
glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f);
glRotatef(45.0f+yrot,0.0f,1.0f,0.0f);
Vybereme barvu krabice (sv∞tlou). VÜimn∞te si, ₧e pou₧φvßme glColor3fv(). Tato funkce vybφrß najednou vÜechny t°i hodnoty (Φervenß, zelenß, modrß) najednou a tφm nastavφ barvu. V tomto p°φpad∞ ji najdeme v poli boxcol s indexem yloop-1. Tφm zajistφme rozliÜnou barvu, pro ka₧d² °ßdek pyramidy. Kdybychom pou₧ili xloop-1, dostali bychom stejnΘ barvy pro ka₧d² sloupec.
glColor3fv(boxcol[yloop-1]);// Barva
Po nastavenφ barvy zb²vß jedinΘ - vykreslit krabici. Pro vykreslenφ zavolßme pouze funkci glCallList(box). Parametr °ekne OpenGL, kter² display list mßme na mysli. Krabice bude vybarvenß d°φve vybranou barvou, bude posunutß a taky natoΦenß.
glCallList(box);// Vykreslenφ
Barvu vφka vybφrßme ·pln∞ stejn∞, jako p°ed chvφlφ, akorßt z pole tmavÜφch barev. Potom ho vykreslφme.
glColor3fv(topcol[yloop-1])// Barva;
glCallList(top);// Vykreslenφ
}
}
return TRUE;
}
Poslednφ zbytek zm∞n ud∞lßme ve funkci WinMain(). K≤d p°idßme za p°φkaz SwapBuffers(hDC). Ov∞°φme, zda jsou stisknuty Üipky a podle v²sledku pohybujeme krychlemi.
// Funkce WinMain()
SwapBuffers(hDC);// V²m∞na buffer∙
if (keys[VK_LEFT])// èipka vlevo
{
yrot-=0.2f;
}
if (keys[VK_RIGHT])// èipka vpravo
{
yrot+=0.2f;
}
if (keys[VK_UP])// èipka nahoru
{
xrot-=0.2f;
}
if (keys[VK_DOWN])// èipka dolu
{
xrot+=0.2f;
}
Po doΦtenφ tΘto lekce, byste m∞li rozum∞t, jak display list pracuje, jak ho vytvo°it a jak ho vykreslit. Jsou velk²m p°φnosem. Nejen, ₧e zjednoduÜφ psanφ slo₧it∞jÜφch projekt∙, ale takΘ p°idajφ trochu na rychlosti celΘho programu.
napsal: Jeff Molofee - NeHe
p°elo₧il: Michal Turek - Woq