Lekce 05 - Textury
Předmluva
Těm z vás kdo se setkávají s pojmem textura poprvé se pokusím vysvětlit o co vlastně jde. Nyní už umíme vytvářet objekty ve 3D a vyplnit je barvou. To samo o sobě je pokrok nicméně v praxi v podstatě k ničemu. Uživatel jistě nebude zvědav na barevné kostky příp. jiné objekty. Od 3D aplikace se očekává především pastva pro oči. Toho lze jednoduše dosáhnout použitím obrázků (textur) nanesených (namapovaných) na naše objekty. Ano textura je v podstatě jen obrázek převedený do formátu jemuž OGL rozumí. Každý obrázek se skládá z pixelů přičemž každý pixel prezentuje 3 barevné kanály. Nejčastěji používané formáty jsou BMP a TGA. TGA může ještě obsahovat kanál Alpha (průhlednost), tomu se budeme věnovat v některé z dalších lekcí.
Ještě upozornění. Z důvodu kompatibility by obrázky použité jako textury měly mít velikost nejméně 64 a nejvíce 256 pixelů. Rovněž výška a šířka obrázku by měla být mocnina dvou.
Formát BMP
Obrázek ve formátu BMP je nejjednoduší možnost jak vytvářet textury. Obsahuje pouze hlavičku kde jsou uloženy všechny potřebné informace a dále uz následují jen bajty představující barevnou složku jednotlivých pixelů. Definici potřebných typů naleznete ve WinApi. Dnes se jimi nebudeme zabývat ( to nás čeká až u TGA ), ale vrhneme se rovnou na načítání souboru.
Pro snažší použití jsem vše vložil do samostatné funkce. Předáme jí pouze cestu k platnému souboru BMP a odkaz na dynamické pole jenž nám bude uchovávat indexy všech vytvořený textur. Můžeme tak opakovaným voláním načíst a vytvořit libovolné množství textur a ty potom v programu prohazovat, nebo mapovat na více objektů apod. .
'################################################################
'## Soubor = cesta k souboru s platnym obrazkem BMP
'## TexturesBMP() = dynamicke pole Long, provede ReDim Preserve + 1 a na predposledni index vlozi texturu
'## pokud se texturu nepodari vytvorit vraci False
'#################################################################
Public Function LoadTexturesBMP(ByVal Soubor As String, ByRef TexturesBMP() As Long) As Boolean
Nejdříve si definujeme potřebné proměnné
Dim FileInfo As BITMAPFILEHEADER 'obsahuje informace o hlavičce viz. WinApi
Dim Header As BITMAPINFOHEADER 'samotná hlavička viz. WinApi
Dim FileRGB() As RGBQUAD 'použitá barevná paleta viz. WinApi
Dim Data() As Byte 'pole potřebné pro uložení samotného obrázku
Dim ImageSize As Long 'velikost obrázku v bytech
Dim PixelSize As Integer ' počet bitů na pixel
Dim Nr As Integer, tmpTex As Glint ' pomocné
Načteme hlavičku souboru
Nr = FreeFile
Open Soubor For Binary As Nr
Get #Nr, , FileInfo
Get #Nr, , Header
Pokud má obrázek méně než 24 bitů musíme načíst barevnou paletu abychom se dostaly na správnou pozici v souboru.
If (Header.biBitCount < 24) Then
ReDim FileRGB(Header.biClrUsed)
Get #Nr, , FileRGB
End If
Počet bitů na pixel zjistíme tak že biBitCount dělíme 8
PixelSize = Header.biBitCount / 8
Velikost obrázku (bez hlavičky) v bajtech potom vzájemným násobením výšky, šířky a počtem bitů.
ImageSize = Header.biWidth * Header.biHeight * PixelSize
Upravíme velikost našeho pole aby se do něj obrázek vešel.
ReDim Data(ImageSize)
A načteme data.
Get #Nr, , Data
Zavřeme soubor, už ho nebudeme potřebovat.
Close #Nr
Teď změníme velikost pole které bude obsahovat indexy našich textur. Pokud použijeme funkci opakovaně budou se další textury ukládat na následující indexy. Pole bude vždy obsahovat o jeden rozměr více než bude celkový počet textur. To je daň za dynamické pole při opakovaném použití.
Nr = UBound(TexturesBMP)
ReDim Preserve TexturesBMP(Nr + 1)
Tady říkáme OGL že chceme generovat jednu texturu a její index uložit do pole TexturesBMP()
glGenTextures 1, TexturesBMP(Nr)
Nastavíme právě vytvářenou texturu jako aktuální.
glBindTexture glTexture2D, TexturesBMP(Nr)
Následující řádky říkají OGL jaké filtrování má použít pokud je obrázek větší nebo menší než plocha na kterou ho budeme mapovat. Filtrování GL_LINEAR je hladší, ale vyžaduje spoustu práce procesoru. Pokud vám jde o výkon použijte GL_NEAREST to je méně náročné, ale při zvětšení vypadá textura kostičkovaně. Můžete tedy použít kompromis GL_LINEAR pro zvětšení a GL_NEAREST pro zmenšení.
glTexParameteri glTexture2D, tpnTextureMagFilter, GL_LINEAR
glTexParameteri glTexture2D, tpnTextureMinFilter, GL_LINEAR
Vytvoříme 2D texturu (GL_TEXTURE_2D), nula reprezentuje hladinu podrobností obrázku . Tři je počet barevných kanálů. V našem případě jsou 3 (R, G, B). Header.biWidth je šířka a Header.biHeight výška textury. Nula je rámeček (obvykle nechán nulový). tiBGRExt říká OpenGL, že obrazová data jsou tvořena červenou, zelenou a modrou v tomto pořadí (u TGA je pořadí BGR a ne RGB) . GL_UNSIGNED_BYTE znamená, že data (jednotlivé hodnoty R, G a B) jsou tvořeny z bezznaménkových bytů. Pole Data(0) říká že použitelná data pro vytvoření textury jsou uložena v tomto poli. Předáváme adresu prvního prvku.
glTexImage2D glTexture2D, 0, 3, Header.biWidth, Header.biHeight, _
0, tiBGRExt, GL_UNSIGNED_BYTE, Data(0)
Uvolnění paměti a pokud vše proběhlo v pořádku, opustíme funkci
Erase Data
LoadTexturesBMP = True
Exit Function
Nyní máme vytvořenou 1 texturu a uloženou na indexu 0 v poli IDTexture() (předávaly jsme odkazem).
Teď se přesuneme do standartního modulu, vytvoříme zde proceduru pro kreslení krychle a namapujeme na ni naši texturu. Tuto proceduru potom budeme volat z DrawScene (pro mne je to takhle přehlednější).
První věcí kterou musíme udělat pokud chceme používat textury je povolit jejich mapování. To provedeme přidáním řádku glEnable glcTexture2D do funkce DrawCube v modulu mdlMain.
Sub DrawCube()
glEnable glcTexture2D ' Zapne mapování textur
Jako aktuální texuru zvolíme tu jenž je uložena na indexu 0. Pokud jich vytvoříte více, můžete je potom za běhu programu prohazovat voláním jejich indexů.
glBindTexture glTexture2D, IDTexture(0)
Začátek kreslení čtverců z nichž složíme krychli, ale to už znáte.
glBegin (GL_QUADS)
Voláním glTexCoord2f určujeme směr kterým bude textura mapována na objekt. V podstatě jde o to abychom levý dolní roh textury přiřadily levému dolnímu rohu čtverce, horní hornímu atd. Hodnota 0 je levá strana, 0,5 střed a 1 pravá strana. Pokud koordináty textury nezadáme správně, bude textura přetočená, ořezaná nebo jinak deformovaná.
Přední stěna
glTexCoord2f 0, 0 'levý dolní roh textury (0,0)
glVertex3f -5, -5, 5 'levý dolní roh čtverce
glTexCoord2f 1, 0 'pravý dolní roh textury (1,0)
glVertex3f 5, -5, 5 'pravý dolní roh čtverce
atd........
Celý kód vypadá takto
Private Sub DrawCube()
glEnable glcTexture2D
glBindTexture glTexture2D, IDTexture(0)
glBegin (GL_QUADS)
'Přední stěna
glTexCoord2f 0, 0: glVertex3f -5, -5, 5
glTexCoord2f 1, 0: glVertex3f 5, -5, 5
glTexCoord2f 1, 1: glVertex3f 5, 5, 5
glTexCoord2f 0, 1: glVertex3f -5, 5, 5
'Zadní stěna
glTexCoord2f 1, 0: glVertex3f -5, -5, -5
glTexCoord2f 1, 1: glVertex3f -5, 5, -5
glTexCoord2f 0, 1: glVertex3f 5, 5, -5
glTexCoord2f 0, 0: glVertex3f 5, -5, -5
'Horní stěna
glTexCoord2f 0, 1: glVertex3f -5, 5, -5
glTexCoord2f 0, 0: glVertex3f -5, 5, 5
glTexCoord2f 1, 0: glVertex3f 5, 5, 5
glTexCoord2f 1, 1: glVertex3f 5, 5, -5
'Spodní stěna
glTexCoord2f 1, 1: glVertex3f -5, -5, -5
glTexCoord2f 0, 1: glVertex3f 5, -5, -5
glTexCoord2f 0, 0: glVertex3f 5, -5, 5
glTexCoord2f 1, 0: glVertex3f -5, -5, 5
'Pravá stěna
glTexCoord2f 1, 0: glVertex3f 5, -5, -5
glTexCoord2f 1, 1: glVertex3f 5, 5, -5
glTexCoord2f 0, 1: glVertex3f 5, 5, 5
glTexCoord2f 0, 0: glVertex3f 5, -5, 5
'Levá stěna
glTexCoord2f 0, 0: glVertex3f -5, -5, -5
glTexCoord2f 1, 0: glVertex3f -5, -5, 5
glTexCoord2f 1, 1: glVertex3f -5, 5, 5
glTexCoord2f 0, 1: glVertex3f -5, 5, -5
glEnd
End Sub
Nyní již stačí jen zavolat naši proceduru z DrawScene. Přidal jsem rotaci pro lepší zážitek :). Velikost kroku si upravte dle potřeby.
Sub DrawScene()
glClear GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT 'Vymaže obrazovku a hloubkový buffer
glLoadIdentity 'Reset matice
rot = rot + 0.5
glTranslatef 0, 0, -40
glRotatef 20, 1, 0, 0
glRotatef rot, 0, 1, 0
'vykresleni kostky
Call DrawCube
glFlush
Call SwapBuffers(frmMain.hDC)
End Sub
Zdrojové kódy ke stažení zde..
To je pro dnešek vše. Příště vás nechám trochu vydechnout a naučíme se jak počítat FPS (počet vykreslených snímků za sekundu), což je srandovně jednoduché.
|