Tento tutoriál byl napsán pro všechny z vás, kteří se chtěli dozvědět, jak do jednoho okna zobrazit více pohledů na jednu scénu, kdy v každém probíhá jiný efekt. Jako bonus přidám získávání velikosti OpenGL okna a velice rychlý způsob aktualizace textury bez jejího znovuvytváření.
Vítejte do dalšího perfektního tutoriálu. Tentokrát se pokusíme v jednom okně zobrazit čtyři viewporty, které se budou při změně velikosti okna bez problémů zmenšovat i zvětšovat. Ve dvou z nich zapneme světla, jeden bude používat pravoúhlou projekci a tři perspektivní. Abychom demu zajistili kvalitní efekty, budeme do textury postupně generovat půdorys bludiště a mapovat ji na objekty v jednotlivých viewportech.
Jakmile jednou porozumíte tomuto tutoriálu, nebudete mít nejmenší problémy při vytváření her pro více hráčů s rozdělenými scénami nebo 3D aplikací, ve kterých potřebujete několik pohledů na modelovaný objekt (půdorys, nárys, bokorys, drátěný model ap.).
Jako základní kód můžete použít buď nejnovější NeHeGL nebo IPicture. Je to, dá se říct, jedno, ale provedeme v něm několik úprav. Nejdůležitější změnu najdete ve funkci ReshapeGL(), ve které se definují dimenze scény (hlavní viewport). Všechna nastavení přesuneme do vykreslovací smyčky, zůstane zde pouze definování rozměrů hlavního okna.
void ReshapeGL(int width, int height)// Volá se při změně velikosti okna
{
glViewport(0, 0, (GLsizei)(width), (GLsizei)(height));// Reset aktuálního viewportu
}
Druhá změna spočívá v ošetření systémové události WM_ERASEBKGND. Ukončením funkce zamezíme různému mihotání a blikání scény při roztahování okna, kdy systém automaticky maže pozadí. Pokud nerozumíte, odstraňte oba řádky a porovnejte chování okna při změně jeho velikosti.
// Funkce WindowProc()
switch (uMsg)// Větvení podle došlé zprávy
{
case WM_ERASEBKGND://Okno zkouší smazat pozadí
return 0;// Zákaz mazání (prevence blikání)
Nyní přejdeme k opravdovému kódu tohoto tutoriálu. Začneme deklarací globálních proměnných. Mx a my specifikují místnost v bludišti, ve které se právě nacházíme. Width a height definují rozměry textury, každému pixelu bludiště odpovídá jeden pixel na textuře. Pokud vaše grafická karta podporuje větší textury, zkuste zvětšit toto číslo na následující násobky dvou např. 256, 512, 1024. Ujistěte se ale, že ho nezvětšíte příliš mnoho. Má-li například okno šířku 1024 pixelů, viewporty budou poloviční, takže nemá cenu, aby textura byla větší než 512, protože by se stejně zmenšovala. To samé samozřejmě platí i pro výšku.
int mx, my;// Řídící proměnné cyklů
const width = 128;// Šířka textury (musí být mocninou čísla 2)
const height = 128;// Výška textury (musí být mocninou čísla 2)
Done povede záznam o tom, jestli už bylo generování bludiště dokončeno. Více podrobností se dozvíte později. Sp používáme k ošetření toho, aby program nebral dlouhý stisk mezerníku za několik spolu nesouvisejících stisků. Po jeho zmáčknutí resetujeme texturu a začneme kreslit bludiště od znova.
BOOL done;// Bludiště vygenerováno?
BOOL sp;// Flag stisku mezerníku
Čtyřprvková pole r, g, b ukládají složky barev pro jednotlivé viewporty. Používáme datový typ BYTE, protože se lépe získávají náhodná čísla od 0 do 255 než od 0.0f do 1.0f. Tex_data ukazuje na paměť dat textury.
BYTE r[4], g[4], b[4];// Čtyři náhodné barvy
BYTE* tex_data;// Data textury
Xrot, yrot a zrot specifikují úhel rotace 3D objektu na jednotlivých souřadnicových osách. Quadratic použijeme pro kreslení koule a válce.
GLfloat xrot, yrot, zrot;// Úhly rotací objektů
GLUquadricObj *quadric;// Objekt quadraticu
Pomocí následující funkce budeme moci snadno zabílit pixel textury na souřadnicích dmx, dmy. Tex_data představuje ukazatel na data textury. Lokaci pixelu získáme vynásobením y pozice (dmy) šířkou řádku (width) a přičtením pozice na řádku (dmx). Protože se každý pixel skládá ze tří bytů násobíme výsledek třemi. Aby konečná barva byla bílá, musíme přiřadit číslo 255 všem třem barevným složkám.
void UpdateTex(int dmx, int dmy)// Zabílí určený pixel na textuře
{
tex_data[0 + ((dmx + (width * dmy)) * 3)] = 255;// Červená složka
tex_data[1 + ((dmx + (width * dmy)) * 3)] = 255;// Zelená složka
tex_data[2 + ((dmx + (width * dmy)) * 3)] = 255;// Modrá složka
}
Reset má na starosti několik relativně důležitých úkonů. V první řadě kompletně začerní texturu a tím odstraní dosavadní bludiště, dále přiřazuje nové barvy viewportům a reinicializuje pozici v bludišti. První řádkou kódu nulujeme data textury, což ve výsledku znamená, že všechny pixely budou černé.
void Reset(void)// Reset textury, barev, aktuální pozice v bludišti
{
ZeroMemory(tex_data, width * height * 3);// Nuluje paměť textury
Potřebujeme nastavit náhodnou barvu viewportů. Pro ty z vás, kteří to ještě neví, random není zase tak náhodný, jak by se mohl na první pohled zdát. Pokud vytvoříte jednoduchý program, který má vypsat deset náhodných čísel, tak samozřejmě vypíše deset náhodných čísel, která nemáte šanci předem odhadnout. Ale při příštím spuštění se bude všech deset "náhodných" čísel opakovat. Abychom tento problém odstranili, inicializujeme generátor. Pokud bychom ho ale nastavili na konstantní hodnotu (1, 2, 3...), výsledkem by opět byla při více spuštěních stejná čísla. Proto předáváme funkci srand() hodnotu aktuálního času (Překl.: počet milisekund od spuštění OS), který se samozřejmě vždy mění.
Překl.: Bývá zvykem inicializovat generátor náhodných čísel pouze jednou a to někde na začátku funkce main() a ne, jak děláme zde, při každém volání Reset() - není to špatně, ale je to zbytečné.
srand(GetTickCount());// Inicializace generátoru náhodných čísel
V cyklu, který projde všechny čtyři viewporty nastavujeme pro každý náhodnou barvu. Mohli bychom generovat číslo v plném rozsahu (0 až 255), ale neměli bychom tak zaručeno, že nezískáme nějakou nízkou hodnotu (aby na černé byla vidět). Přičtením 128 získáme světlejší barvy.
for (int loop = 0; loop < 4; loop++)// Generuje čtyři náhodné barvy
{
r[loop] = rand() % 128 + 128;// Červená složka
g[loop] = rand() % 128 + 128;// Zelená složka
b[loop] = rand() % 128 + 128;// Modrá složka
}
Nakonec nastavíme počáteční bod v bludišti - opět náhodný. Výsledkem musí být sudé číslo (zajistí násobení dvěma), protože liché pozice označují stěny mezi místnostmi.
mx = int(rand() % (width / 2)) * 2;// Náhodná x pozice
my = int(rand() % (height / 2)) * 2;// Náhodná y pozice
}
Prvním řádkem v inicializaci alokujeme dynamickou paměť pro uložení textury.
BOOL Initialize (GL_Window* window, Keys* keys)// Inicializace OpenGL
{
tex_data = new BYTE[width * height * 3];// Alokace paměti pro texturu
g_window = window;// Okno
g_keys = keys;// Klávesy
Voláme Reset(), abychom ji začernili a nastavili barvy viewportů.
Reset();// Reset textury, barev, pozice
Inicializaci textury začneme nastavením clamp parametrů do rozmezí [0; 1]. Tímto odstraníme možné artefakty v podobě tenkých linek, které vznikají na okrajích textury. Příčina jejich zobrazování spočívá v lineárním filtrování, které se pokouší vyhladit texturu, ale zahrnuje do ní i její okraje. Zkuste odstranit první dva řádky a uvidíte, co myslím. Jak už jsem zmínil, nastavíme lineární filtrování a vytvoříme texturu.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);// Clamp parametry textury
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Lineární filtrování
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data);// Vytvoří texturu
Pozadí budeme mazat černou barvou a hloubku jedničkou. Dále nastavíme testování hloubky na menší nebo rovno a zapneme ho.
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// Černé pozadí
glClearDepth(1.0f);// Nastavení depth bufferu
glDepthFunc(GL_LEQUAL);// Typ hloubkového testování
glEnable(GL_DEPTH_TEST);// Zapne testování hloubky
Povolení GL_COLOR_MATERIAL umožní měnit barvu textury použitím funkce glColor3f(). Také zapínáme mapování textur.
glEnable(GL_COLOR_MATERIAL);// Zapne vybarvování materiálů
glEnable(GL_TEXTURE_2D);// Zapne mapování textur
Vytvoříme a inicializujeme objekt quadraticu tak, aby obsahoval normálové vektory pro světlo a texturové koordináty.
quadric = gluNewQuadric();// Vytvoří objekt quadraticu
gluQuadricNormals(quadric, GLU_SMOOTH);// Normály pro světlo
gluQuadricTexture(quadric, GL_TRUE);// Texturové koordináty
I když ještě nemáme povoleny světla globálně, zapneme světlo 0.
glEnable(GL_LIGHT0);// Zapne světlo 0
return TRUE;// Vše v pořádku
}
Po jakékoli alokaci dynamické paměti musí přijít její uvolnění. Tuto akci vložíme do funkce Deinitialize(), která se volá před ukončením programu.
void Deinitialize(void)// Deinicializace
{
delete [] tex_data;// Smaže data textury
}
Update má na starosti aktualizaci zobrazované scény, stisky kláves, pohyby, rotace a podobně. Celočíselnou proměnnou dir využijeme k pohybu náhodným směrem.
void Update(float milliseconds)// Aktualizace scény
{
int dir;// Ukládá aktuální směr pohybu
V první fázi ošetříme klávesnici. Při stisku Esc ukončíme program, F1 přepíná mód fullscreen/okno a mezerník resetuje bludiště.
if (g_keys->keyDown[VK_ESCAPE])// Klávesa Esc
{
TerminateApplication (g_window);// Konec programu
}
if (g_keys->keyDown[VK_F1])// Klávesa F1
{
ToggleFullscreen (g_window);// Přepne fullscreen/okno
}
if (g_keys->keyDown[' '] && !sp)// Mezerník
{
sp = TRUE;
Reset();// Resetuje scénu
}
if (!g_keys->keyDown[' '])// Uvolnění mezerníku
{
sp = FALSE;
}
Rot proměnné zvětšíme v závislosti na počtu uplynulých milisekund od minulého volání této funkce. Tím zajistíme rotaci objektů.
xrot += (float)(milliseconds) * 0.02f;// Aktualizace úhlů natočení
yrot += (float)(milliseconds) * 0.03f;
zrot += (float)(milliseconds) * 0.015f;
Kód níže zjišťuje, jestli bylo kreslení bludiště ukončeno (textura kompletně zaplněna). Nejdříve nastavíme flag done na true. Předpokládáme tedy, že už vykresleno bylo. Ve dvou vnořených cyklech procházíme jednotlivé řádky i sloupce a kontrolujeme, zda byl náš odhad správný. Pokud ne, nastavíme done na false.
Jak pracuje kód? Řídící proměnné cyklů zvyšujeme o dva, protože nám jde jen o sudé indexy v poli. Každé bludiště se skládá ze stěn (liché) a místností (sudé). Když otevřeme dveře, dostaneme se do místnosti a právě ty tedy musíme testovat. Kontroly stěn jsou samozřejmě zbytečné. Pokud se hodnota v poli rovná nule, znamená to, že jsme do něj ještě nekreslili a místnost nebyla navštívena.
done = TRUE;// Předpokládá se, že je už bludiště kompletní
for (int x = 0; x < width; x += 2)// Prochází všechny místnosti na ose x
{
for (int y = 0; y < height; y += 2)// Prochází všechny místnosti na ose y
{
if (tex_data[((x + (width * y)) * 3)] == 0)// Pokud má pixel černou barvu
{
done = FALSE;// Bludiště ještě není hotové
}
}
}
Pokud byly všechny místnosti objeveny, změníme titulek okna na ...Maze Complete!, potom počkáme pět sekund, aby si ho stihl uživatel přečíst, vrátíme titulek zpět a resetujeme bludiště.
if (done)// Je bludiště hotové?
{
// Změna titulku okna
SetWindowText(g_window->hWnd, "Lesson 42: Multiple Viewports... 2003 NeHe Productions... Maze Complete!");
Sleep(5000);// Zastavení na pět sekund
SetWindowText(g_window->hWnd, "Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");
Reset();// Reset bludiště a scény
}
Předpokládám, že pro vás následující podmínka vypadá totálně šíleně, ale vůbec není těžká. skládá se ze čtyř AND-ovaných podpodmínek a každá z nich ze dvou dalších. Všechny čtyři hlavní části jsou skoro stejné a dohromady zjišťují, jestli existuje místnost okolo aktuální pozice, která ještě nebyla navštívena. Vše si vysvětlíme na první z podpodmínek: Nejprve se ptáme, jestli jsme v místnosti vpravo už byli a potom, jestli jsou vpravo ještě nějaké místnosti (kvůli okraji textury). Pokud se červená složka pixelu rovná 255, podmínka platí. Okraj textury v daném směru nalezneme také snadno.
To samé vykonáme pro všechny směry a pokud nemáme kam jít, musíme vygenerovat novou pozici. Vše si ztížíme tím, že chceme, abychom se objevili na pozici, která už byla navštívena. Pokud ne, vygenerujeme v cyklu další souřadnici. Možná se ptáte, proč hledáme navštívenou místnost? Protože nechceme spoustu malých oddělených částí bludiště, ale jedno obrovské. Dokážete si to představit?
Zdá se vám to moc složité? Abychom udrželi velikost kódu na minimu, nekontrolujeme, jestli je mx-2 menší než nula a podobně pro všechny směry. Pokud si přejete 100% ošetření chyb, modifikujte podmínku tak, aby netestovala paměť, která už nepatří textuře.
// Máme kam jít?
if (((tex_data[(((mx+2)+(width*my))*3)] == 255) || mx>(width-4)) && ((tex_data[(((mx-2)+(width*my))*3)] == 255) || mx<2) && ((tex_data[((mx+(width*(my+2)))*3)] == 255) || my>(height-4)) && ((tex_data[((mx+(width*(my-2)))*3)] == 255) || my<2))
{
do
{
mx = int(rand() % (width / 2)) * 2;// Nová pozice
my = int(rand() % (height / 2)) * 2;
}
while (tex_data[((mx + (width * my)) * 3)] == 0);// Hledá se navštívená místnost
}
Do proměnné dir vygenerujeme náhodné číslo od nuly do tří, které vyjadřuje směr, kterým se pokusíme jít.
dir = int(rand() % 4);// Náhodný směr pohybu
Pokud se rovná nule (směr doprava) a pokud nejsme na okraji bludiště (textury), zkontrolujeme, jestli už byla místnost vpravo navštívena. Pokud ne, označíme dveře (pixel stěny, ne místnosti) jako navštívené a projdeme do další místnosti.
if ((dir == 0) && (mx <= (width-4)))// Směr doprava; vpravo je místo
{
if (tex_data[(((mx+2) + (width*my)) * 3)] == 0)// Místnost vpravo ještě nebyla navštívena
{
UpdateTex(mx+1, my);// Označí průchod mezi místnostmi
mx += 2;// Posunutí doprava
}
}
Analogicky ošetříme všechny další směry.
if ((dir == 1) && (my <= (height-4)))// Směr dolů; dole je místo
{
if (tex_data[((mx + (width * (my+2))) * 3)] == 0)// Místnost dole ještě nebyla navštívena
{
UpdateTex(mx, my+1);// Označí průchod mezi místnostmi
my += 2;// Posunutí dolů
}
}
if ((dir == 2) && (mx >= 2))// Směr doleva; vlevo je místo
{
if (tex_data[(((mx-2) + (width*my)) * 3)] == 0)// Místnost vlevo ještě nebyla navštívena
{
UpdateTex(mx-1, my);// Označí průchod mezi místnostmi
mx -= 2;// Posunutí doleva
}
}
if ((dir == 3) && (my >= 2))// Směr nahoru; nahoře je místo
{
if (tex_data[((mx + (width * (my-2))) * 3)] == 0)// Místnost nahoře ještě nebyla navštívena
{
UpdateTex(mx, my-1);// Označí průchod mezi místnostmi
my -= 2;// Posunutí nahoru
}
}
Po přesunutí se do nové místnosti ji musíme označit.
UpdateTex(mx, my);// Označení nové místnosti
}
Vykreslování začneme netradičně. Potřebujeme zjistit velikost klientské oblasti okna, abychom mohli jednotlivé viewporty roztahovat korektně. Deklarujeme objekt struktury obdélníku a nagrabujeme do něj souřadnice okna. Šířku a výšku spočítáme jednoduchým odečtením.
void Draw(void)// Vykreslování
{
RECT rect;// Struktura obdélníku
GetClientRect(g_window->hWnd, &rect);// Grabování rozměrů okna
int window_width = rect.right - rect.left;// Šířka okna
int window_height = rect.bottom - rect.top;// Výška okna
Texturu musíme aktualizovat při každém překreslení scény. Nejrychlejší metodou je příkaz glTexSubImage2D(), který namapuje jakoukoli část obrázku na objekt ve scéně jako texturu. První parametr oznamuje, že chceme použít 2D texturu. Číslo úrovní detailů nastavíme na nulu a také nechceme žádný x ani y offset. Šířka a výška je určena rozměry obrázku. Každý pixel se skládá z RGB složek a data jsou ve formátu bezznaménkových bytů. Poslední parametr představuje ukazatel na začátek dat.
Jak jsem již napsal, funkcí glTexSubImage2D() velmi rychle aktualizujeme texturu bez nutnosti jejího opakovaného smazání a sestavení. Tento příkaz ji ale NEVYTVÁŘÍ!!! Musíte ji tedy sestavit před první aktualizací, v našem případě se jedná o glTexImage2D() ve funkci Initialize().
// Zvolí aktualizovanou texturu
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, tex_data);
Všimněte si následujícího řádku, je opravdu důležitý. Smažeme jím kompletně celou scénu. Z toho plyne, že nemažeme podscény jednotlivých viewportů postupně, ale VŠECHNY NAJEDNOU před tím, než cokoli vykreslíme. Také si všimněte, že v tuto chvíli k volání nepřidáváme mazání depth bufferu. Ten naopak ošetříme u každého viewportu zvlášť.
glClear(GL_COLOR_BUFFER_BIT);// Smaže obrazovku
Chceme vykreslit čtyři rozdílné viewporty, takže založíme cyklus od nuly do tří, pomocí řídící proměnné nastavíme barvu.
for (int loop = 0; loop < 4; loop++)// Prochází viewporty
{
glColor3ub(r[loop], g[loop], b[loop]);// Barva
Předtím než cokoli vykreslíme, potřebujeme nastavit viewporty. První bude umístěn vlevo nahoře. Na ose x tedy začíná na nule a na ose y v polovině okna. Šířku i výšku nastavíme na polovinu rozměrů okna. Pokud se nacházíme ve fullscreenu s rozlišením obrazovky 1024x768, bude tento viewport začínat na souřadnicích [0; 384]. Šířka se bude rovna 512 a výška 384.
if (loop == 0)// První scéna
{
// Levý horní viewport, velikost poloviny okna
glViewport(0, window_height / 2, window_width / 2, window_height / 2);
Po definování viewportu zvolíme projekční matici, resetujeme ji a nastavíme kolmou 2D projekci, která kompletně zaplňuje celý viewport. Levý roh spočívá na nule a pravý na polovině velikosti okna (šířka viewportu). Spodní bod je také polovinou okna a hornímu předáme nulu. Souřadnice [0; 0] tedy odpovídá levému hornímu rohu.
glMatrixMode(GL_PROJECTION);// Projekční matice
glLoadIdentity();// Reset projekční matice
gluOrtho2D(0, window_width / 2, window_height / 2, 0);// Pravoúhlá projekce
}
Druhý viewport leží v pravém horním rohu. Opět zvolíme projekční matici a resetujeme ji. Tentokrát nenastavujeme pravoúhlou, ale perspektivní scénu.
if (loop == 1)// Druhá scéna
{
// Pravý horní viewport, velikost poloviny okna
glViewport(window_width / 2, window_height / 2, window_width / 2, window_height / 2);
glMatrixMode(GL_PROJECTION);// Projekční matice
glLoadIdentity();// Reset projekční matice
// Perspektivní projekce
gluPerspective(45.0, (GLfloat)(width) / (GLfloat)(height), 0.1f, 500.0);
}
Třetí viewport umístíme vpravo a čtvrtý vlevo dolů.
if (loop == 2)// Třetí scéna
{
// Pravý dolní viewport, velikost poloviny okna
glViewport(window_width / 2, 0, window_width / 2, window_height / 2);
glMatrixMode(GL_PROJECTION);// Projekční matice
glLoadIdentity();// Reset projekční matice
// Perspektivní projekce
gluPerspective(45.0, (GLfloat)(width) / (GLfloat)(height), 0.1f, 500.0);
}
if (loop == 3)// Čtvrtá scéna
{
// Levý dolní viewport, velikost poloviny okna
glViewport(0, 0, window_width / 2, window_height / 2);
glMatrixMode(GL_PROJECTION);// Projekční matice
glLoadIdentity();// Reset projekční matice
// Perspektivní projekce
gluPerspective(45.0, (GLfloat)(width) / (GLfloat)(height), 0.1f, 500.0);
}
Zvolíme matici modelview, resetujeme ji a smažeme hloubkový buffer.
glMatrixMode(GL_MODELVIEW);// Matice modelview
glLoadIdentity();// Reset matice
glClear(GL_DEPTH_BUFFER_BIT);// Smaže hloubkový buffer
Scéna prvního viewportu bude obsahovat plochý otexturovaný obdélník. Protože se nacházíme v pravoúhlé projekci, nepotřebujeme zadávat souřadnice na ose z. Objekty by se stejně nezmenšily. Vertexům předáme rozměry viewportu, který tudíž bude kompletně vyplněn.
if (loop == 0)// První scéna, bludiště přes celý viewport
{
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex2i(window_width / 2, 0);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
glTexCoord2f(0.0f, 1.0f); glVertex2i(0, window_height / 2);
glTexCoord2f(1.0f, 1.0f); glVertex2i(window_width / 2, window_height / 2);
glEnd();
}
Jako druhý objekt nakreslíme kouli. Máme zapnutou perspektivu, takže se nejdříve přesuneme o 14 jednotek do obrazovky. Potom objekt natočíme o daný úhel na všech třech souřadnicových osách, zapneme světla, vykreslíme kouli o poloměru 4.0f jednotky a vypneme světla.
if (loop == 1)// Druhá scéna, koule
{
glTranslatef(0.0f, 0.0f, -14.0f);// Přesun do hloubky
glRotatef(xrot, 1.0f, 0.0f, 0.0f);// Rotace
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
glRotatef(zrot, 0.0f, 0.0f, 1.0f);
glEnable(GL_LIGHTING);// Zapne světlo
gluSphere(quadric, 4.0f, 32, 32);// Koule
glDisable(GL_LIGHTING);// Vypne světlo
}
Třetí viewport se velmi podobá prvnímu, ale na rozdíl od něj používá perspektivu. Přesuneme obdélník o dvě jednotky do hloubky a natočíme matici o 45 stupňů. Horní hrana se tím pádem vzdálí a spodní přiblíží. Abychom ještě přidali nějaký ten efekt, rotujeme jím také na ose z.
if (loop == 2)// Třetí scéna, bludiště na rovině
{
glTranslatef(0.0f, 0.0f, -2.0f);// Přesun do hloubky
glRotatef(-45.0f, 1.0f, 0.0f, 0.0f);// Rotace o 45 stupňů
glRotatef(zrot / 1.5f, 0.0f, 0.0f, 1.0f);// Rotace na ose z
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
glEnd();
}
Čtvrtým a posledním objektem je válec, který se nachází sedm jednotek hluboko ve scéně a rotuje na všech třech osách. Zapneme světla a potom se ještě posuneme o dvě jednotky (o polovinu jeho délky) na ose z. Chceme, aby se otáčel okolo svého středu a ne konce. Vykreslíme ho a vypneme světla.
if (loop == 3)// Třetí scéna, válec
{
glTranslatef(0.0f,0.0f,-7.0f);// Přesun do hloubky
glRotatef(-xrot/2,1.0f,0.0f,0.0f);// Rotace
glRotatef(-yrot/2,0.0f,1.0f,0.0f);
glRotatef(-zrot/2,0.0f,0.0f,1.0f);
glEnable(GL_LIGHTING);// Zapne světlo
glTranslatef(0.0f,0.0f,-2.0f);// Vycentrování
gluCylinder(quadric,1.5f,1.5f,4.0f,32,16);// Válec
glDisable(GL_LIGHTING);// Vypne světlo
}
}
Na konci vykreslování flushneme renderovací pipeline.
glFlush();// Vyprázdnění pipeline
}
Doufám, že tento tutoriál zodpověděl všechny vaše otázky ohledně více viewportů v jednom okně. Nyní také znáte jeden z mnoha způsobů generování bludiště a umíte upravit texturu bez jejího komplikovaného mazání a znovuvytváření. Co víc si přát?
napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>