Lekce 12 - Display list

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 obrazovky. 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 <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>

Zdrojové kódy

Lekce 12

<<< Lekce 11 | Lekce 13 >>>