Lekce 7 - Texturové filtry, osvětlení, ovládání pomocí klávesnice

V tomto dílu se pokusím vysvětlit použití tří odlišných texturových filtrů. Dále pak pohybu objektů pomocí klávesnice a nakonec aplikaci jednoduchých světel v OpenGL. Nebude se jako obvykle navazovat na kód z předchozího dílu, ale začne se pěkně od začátku.

#include <windows.h>// Hlavičkový soubor pro Windows

#include <stdio.h>// Hlavičkový soubor pro standartdní 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

Přidáme tři booleovské proměnné. Proměnná light sleduje zda je světlo zapnuté. Proměnné lp a fp nám indikují stisk klávesy 'L' nebo 'F'. Proč je potřebujeme se dozvíme dále. Teď stačí vědět, že zabraňují opakování obslužného kódu při delším držení.

bool light;// Světlo ON/OFF

bool lp;// Stisknuto L?

bool fp;// Stisknuto F?

GLfloat xrot;// X Rotace

GLfloat yrot;// Y Rotace

GLfloat xspeed;// Rychlost x rotace

GLfloat yspeed;// Rychlost y rotace

GLfloat z=-5.0f;// Hloubka v obrazovce

Následují pole pro specifikaci světla. Použijeme dva odlišné typy. První bude okolní (ambient). Okolní světlo nevychází z jednoho bodu, ale jsou jím nasvíceny všechny objekty ve scéně. Druhým typem bude přímé (diffuse). Přímé světlo vychází z nějakého zdroje a odráží se o povrch. Povrchy objektu, na které světlo dopadá přímo, budou velmi jasné a oblasti málo osvětlené budou temné. To vytváří pěkné stínové efekty po stranách krabice. Světlo se vytváří stejným způsobem jako barvy. Je-li první číslo 1.0f a další dvě 0.0f, dostáváme jasnou červenou. Poslední hodnotou je alfa kanál. Ten tentokrát necháme 1.0f. Červená, zelená a modrá nastavené na stejnou hodnotu vždy vytvoří stín z černé (0.0f) do bílé (1.0f). Bez okolního světla by místa bez přímého světla byla příliš tmavá.

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };// Okolní světlo

V dalším řádku jsou hodnoty pro přímé světlo. Protože, jsou všechny hodnoty 1.0f, bude to nejjasnější světlo jaké můžeme získat. Pěkně osvítí krabici.

GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };// Přímé světlo

Nakonec nastavíme pozici světla. Protože chceme aby světlo svítilo na bednu zpředu, nesmíme pohnout světlem na ose x a y. Třetí parametr nám zaručí, že bedna bude osvětlena zepředu. Světlo bude zářit směrem k divákovi. Zdroj světla neuvidíme, protože je před monitorem, ale uvidíme jeho odraz od bedny. Poslední číslo definujeme na 1.0f. Určuje koordináty pozice světelného zdroje. Více v další lekci.

GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };// Pozice světla

Proměnná filter bude použita při zobrazení textury. První textura je vytvářena použitím GL_NEAREST. Druhá textura bude GL_LINEAR - filtrování pro úplně hladký obrázek. Třetí textura používá mipmapingu, který tvoří hodně dobrý povrch. Proměnná filter tedy bude nabývat hodnot 0, 1 a 2. GLuint texture[3] ukazuje na tři textury.

GLuint filter;// Specifikuje používaný texturový filtr

GLuint texture[3];// Ukládá tři textury

Nahrajeme bitmapu a vytvoříme z ní tři různé textury. Tato lekce používá glaux knihovny k nahrávání bitmap. Vím že Delphi a VC++ mají tuto knihovnu. Co ostatní jazyky, nevím. K tomu už moc říkat nebudu, řádky jsou okomentované a kompletní vysvětlení je v 6 lekci. Nahraje a vytvoří textury z bitmap.

int LoadGLTextures()// Loading bitmapy a konverze na texturu

{

int Status=FALSE;// Indikuje chyby

AUX_RGBImageRec *TextureImage[1];// Ukládá bitmapu

memset(TextureImage,0,sizeof(void *)*1);// Vynuluje paměť

Nyní nahrajeme bitmapu. Když vše proběhne, data obrázku budou uložena v TextureImage[0], status se nastaví na true a začneme sestavovat texturu.

if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))// Nahraje bitmapu a kontroluje vzniklé chyby

{

Status=TRUE;// Vše je bez problémů

Data bitmapy jsou nahrána do TextureImage[0]. Použijeme je k vytvoření tří textur. Následující řádek oznámí, že chceme sestavit 3 textury a chceme je mít v uloženy v texture[0], texture[1] a texture[2].

glGenTextures(3, &texture[0]);// Generuje tři textury

V šesté lekci jsme použili lineární filtrování, které vyžaduje hodně výkonu, ale vypadá velice pěkně. Pro první texturu použijeme GL_NEAREST. Spotřebuje málo výkonu, ale výsledek je relativně špatný. Když ve hře vidíte čtverečkovanou texturu, používá toto filtrování, nicméně dobře funguje i na slabších počítačích. Všimněte si že jsme použili GL_NEAREST pro MIN i MAG. Můžeme smíchat GL_NEAREST s GL_LINEAR a textury budou vypadat slušně, ale zároveň nevyžadují vysoký výkon. MIN_FILTER se užívá při zmenšování, MAG_FILTER při zvětšování.

// Vytvoří nelineárně filtrovanou texturu

glBindTexture(GL_TEXTURE_2D, texture[0]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Další texturu vytvoříme stejně jako v lekci 6. Lineárně filtrovaná. Jediný rozdíl spočívá v použití texture[1] místo texture[0], protože se jedná o druhou texturu.

// Vytvoří lineárně filtrovanou texturu

glBindTexture(GL_TEXTURE_2D, texture[1]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Mipmaping ještě neznáte. Používá se při malém obrázku, kdy mnoho detailů mizí z obrazovky. Takto vytvořený povrch vypadá z blízka dost špatně. Když chcete sestavit mipmapovanou texturu, sestaví se více textur odlišné velikosti a vysoké kvality. Když kreslíte takovou texturu na obrazovku vybere se nejlépe vypadající textura. Nakreslí na obrazovku místo toho, aby změnilo rozlišení původního obrázku, které je příčinou ztráty detailů. V šesté lekci jsem se zmínil o stanovených limitech šířky a výšky - 64, 128, 256 atd. Pro mipmapovanou texturu můžeme použít jakoukoli šířku a výšku bitmapy. Automaticky se změní velikost. Protože toto je textura číslo 3, použijeme texture[2]. Nyní máme v texture[0] texturu bez filtru, texture[1] používá lineární filtrování a texture[2] používá mipmaping.

// Vytvoří mipmapovanou texturu

glBindTexture(GL_TEXTURE_2D, texture[2]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

}

Můžeme uvolnit všechnu paměť zaplněnou daty bitmapy. Otestujeme zda se data nachází v TextureImage[0]. Když tam budou, tak je smažeme. Nakonec uvolníme strukturu obrázku.

if (TextureImage[0])// Pokud obrázek existuje

{

if (TextureImage[0]->data)// Pokud existují data obrázku

{

free(TextureImage[0]->data);// Uvolní paměť obrázku

}

free(TextureImage[0]);// Uvolní strukturu obrázku

}

return Status;// Oznámí případné chyby

}

Nejdůležitější část inicializace spočívá v použití světel.

int InitGL(GLvoid)// Všechno nastavení OpenGL

{

if (!LoadGLTextures())// Nahraje texturu

{

return FALSE;

}

glEnable(GL_TEXTURE_2D);// Zapne mapování textur

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);// Zapne hloubkové testování

glDepthFunc(GL_LEQUAL);// Typ hloubkového testování

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Nejlepší perspektivní korekce

Nastavíme světla - konkrétně light1. Na začátku této lekce jsme definovali okolní světlo do LightAmbient. Použijeme hodnoty nastavené v poli.

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);// Nastavení okolního světla

Hodnoty přímého světla jsou v LightDiffuse.

glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);// Nastavení přímého světla

Nyní nastavíme pozici světla. Ta je uložena v LightPosition.

glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);// Nastavení pozice světla

Nakonec zapneme světlo jedna. Světlo je nastavené, umístěné a zapnuté, jakmile zavoláme glEnable(GL_LIGHTING) rozsvítí se.

glEnable(GL_LIGHT1);// Zapne světlo

return TRUE;// Inicializace proběhla v pořádku

}

Vykreslíme krychli s texturami. Když nepochopíte co některé řádky dělají, podívejte se do lekce 6.

int DrawGLScene(GLvoid)// Vykreslování

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vymaže obrazovku a hloubkový buffer

glLoadIdentity();// Reset matice

glTranslatef(0.0f,0.0f,z);

glRotatef(xrot,1.0f,0.0f,0.0f);

glRotatef(yrot,0.0f,1.0f,0.0f);

Další řádek je podobný řádku v lekci 6, ale namísto texture[0] tu máme texture[filter]. Když stiskneme klávesu F, hodnota ve filter se zvýší. Bude-li větší než 2, nastavíme zase 0. Při startu programu bude filter nastaven na 0. Proměnnou filter tedy určujeme, kterou ze tří textur máme použít.

glBindTexture(GL_TEXTURE_2D, texture[filter]);// Zvolí texturu

Při použití světel musíme definovat normálu povrchu. Je to čára vycházející ze středu polygonu v 90 stupňovém úhlu. Řekne jakým směrem je čelo polygonu. Když ji neurčíte, stane se hodně divných věcí. Povrchy které by měly svítit se nerozsvítí, špatná strana polygonu svítit bude, atd. Normála požaduje bod vycházející z polygonu. Pohled na přední povrch ukazuje že normála je kladná na ose z. To znamená že normála ukazuje k divákovi. Na zadní straně normála jde od diváka, do obrazovky. Když bude kostka otočená o 180 stupňů v na ose x nebo y, přední povrch bude ukazovat do obrazovky a zadní uvidí divák. Bez ohledu na to který povrch je vidět divákem, normála tohoto povrchu jde směrem k němu. Když se tak stane, povrch bude osvětlen. U dalších bodů normály směrem k světlu bude povrch také světlý. Když se posunete do středu kostky, bude tmavý. Normála je bod ven, nikoli dovnitř, proto není světlo uvnitř a tak to má být.

glBegin(GL_QUADS);

// Přední stěna

glNormal3f( 0.0f, 0.0f, 1.0f);// Normála

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);// Normála

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);

// Horní stěna

glNormal3f( 0.0f, 1.0f, 0.0f);// Normála

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);

// Spodní stěna

glNormal3f( 0.0f,-1.0f, 0.0f);// Normála

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);

// Pravá stěna

glNormal3f( 1.0f, 0.0f, 0.0f);// Normála

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);// Normála

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();

xrot+=xspeed;

yrot+=yspeed;

return TRUE;

}

Posuneme se dolů k WinMain(). Přidáme kód k zapnutí/vypnutí světla, otáčení, výběr filtru a posun kostky do/z obrazovky. Těsně u konce WinMain() uvidíte příkaz SwapBuffers(hDC). Ihned za tento řádek přidáme kód.

Následující kód zjišťuje, zda je stisknuta klávesa L. Je-li stisknuta ale lp není false, klávesa ještě nebyla uvolněna.

// Funkce WinMain()

SwapBuffers(hDC);// Prohození bufferů

if (keys['L'] && !lp)// Klávesa L - světlo

{

Když bude lp false, L nebylo stisknuto, nebo bylo uvolněno. Tento trik je použit pro případ, kdy je klávesa držena déle a my chceme, aby se kód vykonal pouze jednou. Při prvním průchodu se lp nastaví na true a proměnná light se invertuje. Při dalším průchodu je už lp true a kód se neprovede až do uvolnění klávesy, které nastaví lp zase na false. Kdyby zde toto nebylo, světlo by při stisku akorát blikalo.

lp=TRUE;

light=!light;

Nyní se podíváme na proměnnou light. Když bude false, vypneme světlo, když ne zapneme ho.

if (!light)

{

glDisable(GL_LIGHTING);// Vypne světlo

}

else

{

glEnable(GL_LIGHTING);// Zapne světlo

}

}

Následuje nastavení proměnné lp na false při uvolnění klávesy L.

if (!keys['L'])

{

lp=FALSE;

}

Nyní ošetříme stisk F. Když se stiskne, dojde ke zvýšení filter. Pokud bude větší než 2, nastavíme ho zpět na 0. K ošetření delšího stisku klávesy použijeme stejný způsob jako u světla.

if (keys['F'] && !fp)// Klávesa F - změna texturového filtru

{

fp=TRUE;

filter+=1;

if (filter>2)

{

filter=0;

}

}

if (!keys['F'])// Uvolnění F

{

fp=FALSE;

}

Otestují stisk klávesy Page Up. Když bude stisknuto, snížíme proměnnou z. To způsobí vzdalování kostky v příkazu glTranslatef(0.0f,0.0f,z).

if (keys[VK_PRIOR])// Klávesa Page Up - zvýší zanoření do obrazovky

{

z-=0.02f;

}

Otestují stisk klávesy Page Down. Když bude stisknuta, zvýšíme proměnnou z. To způsobí přibližování kostky v příkazu glTranslatef(0.0f,0.0f,z).

if (keys[VK_NEXT])// Klávesa Page Down - sníží zanoření do obrazovky

{

z+=0.02f;

}

Dále zkontrolujeme kurzorové klávesy. Bude-li stisknuto vlevo/vpravo, proměnná xspeed se bude zvyšovat/snižovat. Bude-li stisknuto nahoru/dolů, proměnná yspeed se bude zvyšovat/snižovat. Jestli si vzpomínáte, výše jsem psal, že vysoké hodnoty způsobí rychlou rotaci. Dlouhý stisk nějaké klávesy způsobí právě rychlou rotaci kostky.

if (keys[VK_UP])// Šipka nahoru

{

xspeed-=0.01f;

}

if (keys[VK_DOWN])// Šipka dolu

{

xspeed+=0.01f;

}

if (keys[VK_RIGHT])// Šipka vpravo

{

yspeed+=0.01f;

}

if (keys[VK_LEFT])// Šipka vlevo

{

yspeed-=0.01f;

}

Nyní byste měli vědět jak vytvořit vysoce kvalitní, realisticky vypadající, texturovaný objekt. Také jsme se něco dozvěděli o třech různých filtrech. Stiskem určitých kláves můžete pohybovat objektem na obrazovce, a nakonec víme jak aplikovat jednoduché světlo. Zkuste experimentovat s jeho pozicí a barvou.

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Jiří Rajský - RAJSOFT junior <predator.jr (zavináč) seznam.cz>

Zdrojové kódy

Lekce 7

<<< Lekce 6 | Lekce 8 >>>