Lekce 6 - Textury

Namapujeme bitmapový obrázek na krychli. Použijeme zdrojové kódy z první lekce, protože je jednoduší (a přehlednější) začít s prázdným oknem než složitě upravovat předchozí lekci.

Porozumění texturám má mnoho výhod. Řekněme, že chcete nechat přeletět přes obrazovku střelu. Až do tohoto tutoriálu byste ji pravděpodobně vytvořili z vybarvených n-úhelníků. S použitím textur můžete vzít obrázek skutečné střely a nechat jej letět přes obrazovku. Co myslíte, že bude vypadat lépe? Fotografie, nebo obrázek poskládaný z trojúhelníků a čtverců? S použitím textur to bude nejen vypadat lépe, ale i váš program bude rychlejší. Střela vytvořená pomocí textury bude jen jeden čtverec pohybující se po obrazovce. Střela tvořená n-úhelníky by mohla být tvořena stovkami, nebo tisíci n-úhelníky. Jeden čtverec pokrytý texturou bude mít mnohem menší nároky.

#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

Přidáme tři nové desetinné proměnné... xrot, yrot a zrot. Tyto proměnné budou použity k rotaci krychle okolo os. Poslední řádek GLuint texture[1] deklaruje prostor pro jednu texturu. Pokud chcete nahrát více než jednu texturu, změňte číslo jedna na číslo odpovídající počet textur, které chcete nahrát.

GLfloat xrot;// X Rotace

GLfloat yrot;// Y Rotace

GLfloat zrot;// Z Rotace

GLuint texture[1];// Ukládá texturu

Bezprostředně za předcházející kód a před funkci ReSizeGLScene() přidáme následující funkci. Jejím účelem je nahrávání souboru s bitmapou. Pokud soubor neexistuje, vrátí NULL, což vyjadřuje, že textura nemůže být nahrána. Před vysvětlováním kódu je třeba vědět několik VELMI důležitých věcí o obrázcích použitých pro textury. Výška a šířka obrázku musí být mocnina dvou, ale nejméně 64 pixelů. Z důvodů kompatibility by neměly být větší než 256 pixelů. Pokud by bitmapa, kterou chcete použít neměla velikost 64, 128 nebo 256, změňte její velikost pomocí editoru obrázků. Existují způsoby jak obejít tyto limity, ale my zůstaneme u standardních velikostí textury. První věc kterou uděláme je deklarace ukazatele na soubor. Na začátku jej nastavíme na NULL.

AUX_RGBImageRec *LoadBMP(char *Filename)// Nahraje bitmapu

{

FILE *File=NULL;// Ukazatel na soubor

Dále se ujistíme, že bylo předáno jméno souboru. Je možné zavolat funkci LoadBMP() bez zadání jména souboru, takže to musíme zkontrolovat. Nechceme se snažit nahrát nic. Dále se pokusíme otevřít tento soubor pro čtení, abychom zkontrolovali, zda soubor existuje.

if (!Filename)// Byla předána cesta k souboru?

{

return NULL;// Pokud ne, konec

}

File=fopen(Filename,"r");// Otevření pro čtení

Pokud se nám podařilo soubor otevřít, zjevně existuje. Zavřeme soubor a pomocí funkce auxDIBImageLoad(Filename) vrátíme data obrázku.

if (File)// Existuje soubor?

{

fclose(File);// Zavře ho

return auxDIBImageLoad(Filename);// Načte bitmapu a vrátí na ni ukazatel

}

Pokud se nám soubor nepodařilo otevřít soubor vrátíme NULL, což indikuje, že soubor nemohl být nahrán. Později v programu budeme kontrolovat, zda se vše povedlo v pořádku.

return NULL;// Při chybě vrátíme NULL

}

Nahraje bitmapu (voláním předchozího kódu) a konvertujeme jej na texturu.

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

{

Deklarujeme bool proměnnou zvanou Status. Použijeme ji k sledování, zda se nám podařilo nebo nepodařilo nahrát bitmapu a sestavit texturu. Její počáteční hodnotu nastavíme na FALSE.

int Status=FALSE;// Indikuje chyby

Vytvoříme záznam obrázku, do kterého můžeme bitmapu uložit. Záznam bude ukládat výšku, šířku a data bitmapy.

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

Abychom si byli jisti, že je obrázek prázdný, vynulujeme přidělenou paměť.

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

Nahrajeme bitmapu a konvertujeme ji na texturu. TextureImage[0]=LoadBMP("Data/NeHe.bmp") zavolá dříve napsanou funkci LoadBMP(). Pokud se vše podaří, data bitmapy se uloží do TextureImage[0], Status je nastaven na TRUE a začneme sestavovat texturu.

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

{

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

Teď, když máme nahrána data obrázku do TextureImage[0], sestavíme texturu s použitím těchto dat. První řádek glGenTextures(1, &texture[0]) řekne OpenGL, že chceme sestavit jednu texturu a chceme ji uložit na index 0 pole. Vzpomeňte si, že jsme na začátku vytvořili místo pro jednu texturu pomocí GLuint texture[1]. Druhý řádek glBindTexture(GL_TEXTURE_2D, texture[0]) řekne OpenGL, že texture[0] (první textura), bude 2D textura. 2D textury mají výšku (na ose Y) a šířku (na ose X). Hlavní funkcí glBindTexture() je ukázat OpenGL dostupnou paměť. V tomto případě říkáme OpenGL, že volná paměť je na &texture[0]. Když vytvoříme texturu, bude uložena na tomto paměťovém místě. V podstatě glBindTexture() ukáže do paměti RAM, kde je uložena naše textura.

glGenTextures(1, &texture[0]);// Generuje texturu

glBindTexture(GL_TEXTURE_2D, texture[0]);// Typické vytváření textury z bitmapy

Vytvoříme 2D texturu (GL_TEXTURE_2D), nula reprezentuje hladinu podrobností obrázku (obvykle se zadává nula). Tři je počet datových komponent. Protože je obrázek tvořen červenou, zelenou a modrou složkou dat, jsou to tři komponenty. TextureImage[0]->sizeX je šířka textury. Pokud znáte šířku, můžete ji tam přímo napsat, ale je jednodušší a univerzálnější nechat práci na počítači. TextureImage[0]->sizeY je analogicky výška textury. Nula je rámeček (obvykle nechán nulový). GL_RGB říká OpenGL, že obrazová data jsou tvořena červenou, zelenou a modrou v tomto pořadí. GL_UNSIGNED_BYTE znamená, že data (jednotlivé hodnoty R, G a B) jsou tvořeny z bezznaménkových bytů a konečně TextureImage[0]->data říká OpenGL, kde vzít data textury. V tomto případě jsou to data uložená v záznamu TextureImage[0].

glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);// Vlastní vytváření textury

Další dva řádky oznamují OpenGL, jaké použít typy filtrování, když je obrázek větší (GL_TEXTURE_MAG_FILTER) nebo menší (GL_TEXTURE_MIN_FILTER) než originální bitmapa. Já obvykle používám GL_LINEAR pro oba případy. To způsobuje, že textura vypadá hladce ve všech případech. Použití GL_LINEAR požaduje spoustu práce procesoru a video karty, takže když je váš systém pomalý, měli by jste použít GL_NEAREST. Textura filtrovaná pomocí GL_NEAREST bude při zvětšení vypadat kostičkovaně. Lze také kombinovat obojí. GL_LINEAR pro případ zvětšení a GL_NEAREST na zmenšení.

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);// Filtrování při zmenšení

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);// Filtrování při zvětšení

}

Uvolníme paměť RAM, kterou jsme potřebovali pro uložení dat bitmapy. Ujistíme se, že data bitmapy byla uložena v TextureImage[0]. Pokud ano, ujistíme se, že data byla uložena v položce data, pokud ano smažeme je. Potom 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

}

Nakonec vrátíme status. Pokud je všechno v pořádku, obsahuje TRUE. FALSE indikuje chybu.

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

}

Přidáme pár řádků kódu do InitGL. Vypíši celou funkci znovu, takže bude jednoduché najít změny. První řádek if (!LoadGLTextures()) skočí do kódu, který jsme napsali v předchozí části. Nahraje bitmapu a vygeneruje z ní texturu. Pokud z jakéhokoli důvodu selže, tak ukončíme funkci s návratovou hodnotou FALSE. Pokus se texturu podařilo nahrát, povolíme mapování 2D textur - glEnable(GL_TEXTURE_2D). Pokud jej zapomeneme povolit, budou se objekty obvykle zobrazovat jako bílé, což nám asi nebude vyhovovat.

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

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

}

Přejdeme k vykreslování. Pokusíme se o otexturovanou krychli.

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,-5.0f);// Posun do obrazovky

glRotatef(xrot,1.0f,0.0f,0.0f);// Natočení okolo osy x

glRotatef(yrot,0.0f,1.0f,0.0f);// Natočení okolo osy y

glRotatef(zrot,0.0f,0.0f,1.0f);// Natočení okolo osy z

Následující řádek vybere texturu, kterou chceme použít. Pokud máte více než jednu texturu, vyberete ji úplně stejně, ale s jiným indexem pole glBindTexture(GL_TEXTURE_2D, texture[číslo textury kterou chcete použít]). Tuto funkci nesmíte volat mezi glBegin() a glEnd(). Musíte ji volat vždy před nebo za blokem ohraničeným těmito funkcemi.

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

Ke správnému namapování textury na čtyřúhelník se musíte ujistit, že levý horní roh textury je připojen k levému hornímu rohu čtyřúhelníku ap. Pokud rohy textury nejsou připojeny k odpovídajícím rohům čtyřúhelníku, zobrazí se textura natočená, převrácená nebo se vůbec nezobrazí. První parametr funkce glTexCoord2f je souřadnice x textury. 0.0 je levá strana textury, 0.5 střed, 1.0 pravá strana. Druhý parametr je souřadnice y. 0.0 je spodek textury, 0.5 střed, 1.0 vršek. Takže teď víme, že 0.0 na X a 1.0 na Y je levý horní vrchol čtyřúhelníka atd. Vše, co musíme udělat je přiřadit každému rohu čtyřúhelníka odpovídající roh textury. Zkuste experimentovat s hodnotami x a y funkce glTexCoord2f. Změnou 1.0 na 0.5 vykreslíte pouze polovinu textury od 0.0 do 0.5 atd.

glBegin(GL_QUADS);

// Přední stěna

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

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

// Vrchní stěna

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

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

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

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

Nakonec zvětšíme hodnoty proměnných xrot, yrot a zrot, které určují natočení krychle. Změnou hodnot můžeme změnit rychlost i směr natáčení.

xrot+=0.3f;

yrot+=0.2f;

zrot+=0.4f;

return TRUE;

}

Po dočtení této lekce byste měli rozumět texturovému mapování. Měli by jste být schopni namapovat libovolnou texturu na libovolný objekt. Až si budete jistí, že tomu rozumíte, zkuste namapovat na každou stěnu krychle jinou texturu

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Milan Turek <nalim.kerut (zavináč) email.cz>

Zdrojové kódy

Lekce 6

<<< Lekce 5 | Lekce 7 >>>