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>