Lekce 24 - Výpis OpenGL rozšíření, ořezávací testy a textury z TGA obrázků

V této lekci se naučíte, jak zjistit, která OpenGL rozšíření (extensions) podporuje vaše grafická karta. Vypíšeme je do středu okna, se kterým budeme moci po stisku šipek rolovat. Použijeme klasický 2D texturový font s tím rozdílem, že texturu vytvoříme z TGA obrázku. Jeho největšími přednostmi jsou jednoduchá práce a podpora alfa kanálu. Odbouráním bitmap už nebudeme muset inkludovat knihovnu glaux.

Tento tutoriál je daleko od prezentace grafické nádhery, ale naučíte se několik nových věcí. Pár lidí se mě ptalo na OpenGL rozšíření a na to, jak zjistit, které jsou podporovány konkrétním typem grafické karty. Mohu směle říci, že s tímto po dočtení nebudete mít nejmenší problémy. Také se dozvíte, jak rolovat částí scény bez toho, aby se ovlivnilo její okolí. Použijeme ořezávací testy (scissor testing). Dále si ukážeme, jak vykreslovat linky pomocí line strips a co je důležitější, kompletně odbouráme knihovnu glaux, kterou jsme používali kvůli texturám z bitmapových obrázků. Budeme používat Targa (TGA) obrázky, se kterými se snadno pracuje a které podporují alfa kanál.

Začneme programovat. První věcí, které si všimneme u vkládání hlavičkových souborů je, že neinkludujeme knihovnu glaux (glaux.h). Také nepřilikujeme soubor glaux.lib. Už nebudeme pracovat s bitmapami, takže tyto soubory v projektu nepotřebujeme.

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

#include <stdio.h>// Hlavičkový soubor pro standardní vstup/výstup

#include <stdarg.h>// Hlavičkový soubor pro funkce s proměnným počtem parametrů

#include <string.h>// Hlavičkový soubor pro práci s řetězci

#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu

#include <gl\glu.h>// Hlavičkový soubor pro Glu32 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 proměnné. Scroll bude použito pro rolování částí scény nahoru a dolů. Druhá proměnná, maxtokens, bude ukládat záznam kolik rozšíření je podporováno grafickou kartou. Base už tradičně ukazuje na display listy fontu. Do swidth a sheight nagrabujeme aktuální velikost okna, pomohou nám vypočítat koordináty pro ořezání oblasti okna, které umožní rolování.

int scroll;// Pro rolování okna

int maxtokens;// Počet podporovaných rozšíření

GLuint base;// Základní display list fontu

int swidth;// Šířka ořezané oblasti

int sheight;// Výška ořezané oblasti

Napíšeme strukturu, která bude ukládat informace o nahrávaném TGA obrázku. Pointer imageData bude ukazovat na data, ze kterých vytvoříme obrázek. Bpp označuje barevnou hloubku (bits per pixel), která může být 24 nebo 32, podle přítomnosti alfa kanálu. Width a height definuje rozměry. Do texID vytvoříme texturu. Celou strukturu nazveme TextureImage.

typedef struct// Struktura textury

{

GLubyte *imageData;// Data obrázku

GLuint bpp;// Barevná hloubka obrázku

GLuint width;// Šířka obrázku

GLuint height;// Výška obrázku

GLuint texID;// Vytvořená textura

} TextureImage;// Jméno struktury

V tomto programu budeme používat pouze jednu texturu, takže vytvoříme pole textur o velikosti jedna.

TextureImage textures[1];// Jedna textura

Na řadu přichází asi nejobtížnější část - nahrávání TGA obrázku a jeho konvertování na texturu. Musím ještě poznamenat, že kód následující funkce umožňuje loadovat buď 24 nebo 32 bitové nekomprimované TGA soubory. Zabralo dost času zprovoznit kód, který by pracoval s oběma typy. Nikdy jsem neřekl, že jsem génius. Rád bych poukázal, že úplně všechno není z mé hlavy. Spoustu opravdu dobrých nápadů jsem získal pročítáním internetu. Pokusil jsem se je zkombinovat do funkčního kódu, který pracuje s OpenGL. Nic snadného, nic extrémně složitého!

Funkci předáváme dva parametry. První ukazuje do paměti, kam uložíme texturu. Druhý určuje diskovou cestu k souboru, který chceme nahrát.

bool LoadTGA(TextureImage *texture, char *filename)// Do paměti nahraje TGA soubor

{

Pole TGAheader[] definuje 12 bytů. Porovnáme je s prvními 12 bity, které načteme z TGA souboru - TGAcompare[], abychom se ujistili, že je to opravdu Targa obrázek a ne nějaký jiný.

GLubyte TGAheader[12] = { 0,0,2,0,0,0,0,0,0,0,0,0 };// Nekomprimovaná TGA hlavička

GLubyte TGAcompare[12];// Pro porovnání TGA hlavičky

Header[] ukládá prvních šest DŮLEŽITÝCH bytů z hlavičky souboru (šířka, výška, barevná hloubka).

GLubyte header[6];// Prvních 6 užitečných bytů z hlavičky

Do bytesPerPixel přiřadíme výsledek operace, kdy vydělíme barevnou hloubku v bitech osmi, abychom získali barevnou hloubku v bytech na pixel. ImageSize definuje počet bytů, které jsou zapotřebí k vytvoření obrázku (šířka*výška*barevná hloubka).

GLuint bytesPerPixel;// Počet bytů na pixel použitý v TGA souboru

GLuint imageSize;// Ukládá velikost obrázku při alokování RAM

Temp umožní prohodit byty dále v programu. A konečně poslední proměnnou použijeme ke zvolení správného parametru při vytváření textury. Bude záviset na tom, zda je TGA 24 nebo 32 bitová. V případě 24 bitů předáme GL_RGB a máme-li 32 bitový obrázek použijeme GL_RGBA. Implicitně předpokládáme, že je obrázek 32 bitový, tudíž do type přiřadíme GL_RGBA.

GLuint temp;// Pomocná proměnná

GLuint type = GL_RGBA;// Implicitním GL módem je RGBA (32 BPP)

Pomocí funkce fopen() otevřeme TGA soubor filename pro čtení v binárním módu (rb). Následuje větvení if, ve kterém děláme hned několik věcí najednou. Nejprve testujeme jestli soubor obsahuje data. Pokud tam žádná nejsou, vrátíme false. Obsahuje-li informace, přečteme prvních dvanáct bytů do TGAcompare. Použijeme funkci fread(), která po jednom bytu načte ze souboru file dvanáct bytů (sizeof(TGAcompare)) a výsledek uloží do TGAcompare. Vrací počet přečtených bytů, které porovnáme se sizeof(TGAcompare). Mělo by jich být, jak tušíte :-), dvanáct. Pokud jsme bez potíží došli až tak daleko, porovnáme funkcí memcmp() pole TGAheader a TGAcompare. Nebudou-li stejné zavřeme soubor a vrátíme false, protože se nejedná o TGA obrázek. Do header nakonec načteme dalších šest bytů. Při chybě opět zavřeme soubor a funkci ukončíme.

FILE *file = fopen(filename, "rb");// Otevře TGA soubor

if(file == NULL || // Existuje soubor?

fread(TGAcompare,1,sizeof(TGAcompare),file) != sizeof(TGAcompare) ||// Podařilo se načíst 12 bytů?

memcmp(TGAheader,TGAcompare,sizeof(TGAheader)) != 0 ||// Mají potřebné hodnoty?

fread(header,1,sizeof(header),file) != sizeof(header))// Pokud ano, načte dalších šest bytů

{

if (file == NULL)// Existuje soubor?

return false;// Konec funkce

else

{

fclose(file);// Zavře soubor

return false;// Konec funkce

}

}

Pokud program prošel kódem bez chyby máme dost informací pro definování některých proměnných. První bude šířka obrázku. Problém spočívá v tom, že toto číslo je rozděleno do dvou bytů. Nižší byte může nabývat 256 hodnot (8 bitů), takže vynásobíme vyšší byte 256 a k němu přičteme nižší. Získali jsme šířku obrázku. Stejným postupem dostaneme i výšku, akorát použijeme jiné indexy v poli.

texture->width = header[1] * 256 + header[0];// Získá šířku obrázku

texture->height = header[3] * 256 + header[2];// Získá výšku obrázku

Zkontrolujeme jestli je šířka i výška větší než nula. Pokud ne zavřeme soubor a vrátíme false. Zároveň zkontrolujeme i barevnou hloubku, kterou hledáme v header[4]. Musí být buď 24 nebo 32 bitová.

if(texture->width <= 0 ||// Platná šířka?

texture->height <= 0 ||// Platná výška?

(header[4] != 24 && header[4] != 32))// Platná barevná hloubka?

{

fclose(file);// Zavře soubor

return false;// Konec funkce

}

Spočítali a zkontrolovali jsme šířku a výšku, můžeme přejít k barevné hloubce v bitech a bytech a velikosti paměti potřebné k uložení dat obrázku. Už víme, že v header[4] je barevná hloubka v bitech na pixel. Přiřadíme ji do bpp. Jeden byte se skládá z 8 bitů. Z toho plyne, že barevnou hloubku v bytech získáme dělením bpp osmi. Velikost dat obrázku získáme vynásobením šířky, výšky a bytů na pixel.

texture->bpp = header[4];// Bity na pixel (24 nebo 32)

bytesPerPixel = texture->bpp / 8;// Byty na pixel

imageSize = texture->width * texture->height * bytesPerPixel;// Velikost paměti pro data obrázku

Potřebujeme alokovat paměť pro data obrázku. Funkci malloc() předáme požadovanou velikost. Měla by vrátit ukazatel na zabrané místo v RAM. Následující if má opět několik úloh. V prvé řadě testuje správnost alokace. Pokud při ní něco nevyšlo, ukazatel má hodnotu NULL. V takovém případě zavřeme soubor a vrátíme false. Nicméně pokud se alokace podařila, tak pomocí fread() načteme data obrázku a uložíme je do právě alokované paměti. Pokud se data nepodaří zkopírovat, uvolníme paměť, zavřeme soubor a ukončíme funkci.

texture->imageData = (GLubyte *)malloc(imageSize);// Alokace paměti pro data obrázku

if(texture->imageData == NULL ||// Podařilo se paměť alokovat?

fread(texture->imageData, 1, imageSize, file) != imageSize)// Podařilo se kopírování dat?

{

if(texture->imageData != NULL)// Byla data nahrána?

free(texture->imageData);// Uvolní paměť

fclose(file);// Zavře soubor

return false;// Konec funkce

}

Pokud se až doteď nestalo nic, čím bychom ukončovali funkci, máme vyhráno. Stojí před námi, ale ještě jeden úkol. Formát TGA specifikuje pořadí barevných složek BGR (modrá, zelená, červená) narozdíl od OpenGL, které používá RGB. Pokud bychom neprohodili červenou a modrou složku, tak všechno, co má být v obrázku modré by bylo červené a naopak. Deklarujeme cyklus, jehož řídící proměnná i nabývá hodnot od nuly do velikosti obrázky. Každým průchodem se zvětšuje o 3 nebo o 4 v závislosti na barevné hloubce. (24/8=3, 32/8=4). Uvnitř cyklu prohodíme R a B složky. Modrá je na indexu i a červená i+2. Modrá by byla na i+1, ale s tou nic neděláme, protože je umístěná správně.

for(GLuint i=0; i < int(imageSize); i += bytesPerPixel)// Prochází data obrázku

{

temp = texture->imageData[i];// B uložíme do pomocné proměnné

texture->imageData[i] = texture->imageData[i + 2];// R je na správném místě

texture->imageData[i + 2] = temp;// B je na správném místě

}

Po této operaci máme v paměti uložen obrázek TGA ve formátu, který podporuje OpenGL. Nic nám nebrání, abychom zavřeli soubor. Už ho k ničemu nepotřebujeme.

fclose(file);// Zavře soubor

Můžeme začít vytvářet texturu. Tento postup je v principu úplně stejný, jako ten, který jsme používali v minulých tutoriálech. Požádáme OpenGL o vygenerování jedné textury na adrese texture[0].textID, kterou jsme získali předáním parametru ve funkci InitGL(). Pokud bychom chtěli vytvořit druhou texturu z jiného obrázku TGA, tak se tato funkci vůbec nezmění. V InitGL() bychom provedli volání dvakrát, ale s jinými parametry. Programujeme obecněji...

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

Zvolíme právě vytvářenou texturu za aktuální a nastavíme jí lineární filtrování pro zmenšení i zvětšení.

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

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// Lineární filtrování

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Lineární filtrování

Zkontrolujeme, jestli je textura 24 nebo 32 bitová. V prvním případě nastavíme type na GL_RGB (bez alfa kanálu), jinak ponecháme implicitní hodnotu GL_RGBA (s alfa kanálem). Pokud bychom test neprovedli, program by se s největší pravděpodobností zhroutil.

if (texture[0].bpp == 24)// Je obrázek 24 bitový?

{

type = GL_RGB;// Nastaví typ na GL_RGB

}

Teď konečně sestavíme texturu. Jako obvykle, tak i tentokrát, použijeme funkci glTexImage2D(). Místo ručního zadání typu textury (GL_RGB, GL_RGBA) předáme hodnotu pomocí proměnné. Jednoduše řečeno: Program sám detekuje, co má předat.

glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);// Vytvoří texturu

return true;// Všechno je v pořádku

}

ReSizeGLScene() nastavuje pravoúhlou projekci. Souřadnice [0; 1] jsou levým horním rohem okna a [640; 480] pravým dolním. Dostáváme rozlišení 640x480. Na začátku nastavíme globální proměnné swidth a sheight na aktuální rozměry okna. Při každém přesunutí nebo změně velikosti okna se aktualizují. Ostatní kód znáte.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)// Změna velikosti a inicializace OpenGL okna

{

swidth = width;// Šířka okna

sheight = height;// Výška okna

if (height == 0)// Zabezpečení proti dělení nulou

{

height = 1;// Nastaví výšku na jedna

}

glViewport(0,0,width,height);// Resetuje aktuální nastavení

glMatrixMode(GL_PROJECTION);// Zvolí projekční matici

glLoadIdentity();// Reset matice

glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f);// Pravoúhlá projekce 640x480, [0; 0] vlevo nahoře

glMatrixMode(GL_MODELVIEW);// Zvolí matici Modelview

glLoadIdentity();// Reset matice

}

Inicializace OpenGL se minimalizovala. Zůstala z ní jenom kostra. Nahrajeme TGA obrázek a vytvoříme z něj texturu. V prvním parametru je určeno, kam ji uložíme a v druhém disková cesta k obrázku. Vrátí-li funkce z jakéhokoli důvodu false, inicializace se přeruší, program zobrazí chybovou zprávu a ukončí se. Pokud byste chtěli nahrát druhou nebo i další textury použijte volání několik. Podmínka se logicky ORuje.

int InitGL(GLvoid)// Nastavení OpenGL

{

if (!LoadTGA(&textures[0], "Data/Font.TGA"))// Nahraje texturu fontu z TGA obrázku

{

return false;// Při chybě ukončí program

}

Po úspěšném nahrání textury vytvoříme font. Je důležité upozornit, že se BuildFont() musí volat až po funkci LoadTGA(), protože používá jí vytvořenou texturu. Dále nastavíme vyhlazené stínování, černé pozadí, povolíme mazání depth bufferu a zvolíme texturu fontu.

BuildFont();// Sestaví font

glShadeModel(GL_SMOOTH);// Vyhlazené stínování

glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Černé pozadí

glClearDepth(1.0f);// Nastavení hloubkového bufferu

glBindTexture(GL_TEXTURE_2D, textures[0].texID);// Zvolí texturu

return TRUE;// Inicializace v pořádku

}

Přejdeme k vykreslování. Začneme deklarováním proměnných. O ukazateli token zatím jen tolik, že bude ukládat řetězec jednoho podporovaného rozšíření a cnt je pro zjištění jeho pořadí.

int DrawGLScene(GLvoid)// Vykreslování

{

char* token;// Ukládá jedno rozšíření

int cnt = 0;// Čítač rozšíření

Smažeme obrazovku a hloubkový buffer. Potom nastavíme barvu na středně tmavě červenou a do horní části okna vypíšeme slova Renderer (jméno grafické karty), Vendor (její výrobce) a Version (verze). Důvod, proč nejsou všechny umístěny 50 pixelů od okraje na ose x, je ten, že je nezarovnáváme doleva, ale doprava.

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

glColor3f(1.0f,0.5f,0.5f);// Červená barva

glPrint(50,16,1,"Renderer");// Výpis nadpisu pro grafickou kartu

glPrint(80,48,1,"Vendor");// Výpis nadpisu pro výrobce

glPrint(66,80,1,"Version");// Výpis nadpisu pro verzi

Změníme červenou barvu na oranžovou a nagrabujeme informace z grafické karty. Použijeme funkci glGetString(), která vrátí požadované řetězce. Kvůli glPrint() přetypujeme výstup funkce na char*. Výsledek vypíšeme doprava od nadpisů.

glColor3f(1.0f,0.7f,0.4f);// Oranžová barva

glPrint(200,16,1,(char *)glGetString(GL_RENDERER));// Výpis typu grafické karty

glPrint(200,48,1,(char *)glGetString(GL_VENDOR));// Výpis výrobce

glPrint(200,80,1,(char *)glGetString(GL_VERSION));// Výpis verze

Definujeme modrou barvu a dolů na scénu vypíšeme NeHe Productions.

glColor3f(0.5f,0.5f,1.0f);// Modrá barva

glPrint(192,432,1,"NeHe Productions");// Výpis NeHe Productions

Kolem právě vypsaného textu vykreslíme bílý rámeček. Resetujeme matici, protože v glPrint() se volají funkce, které ji mění. Potom definujeme bílou barvu.

glLoadIdentity();// Reset matice

glColor3f(1.0f,1.0f,1.0f);// Bílá barva

Vykreslování linek pomocí GL_LINE_STRIP je velmi jednoduché. První bod definujeme úplně vpravo, 63 pixelů (480-417=63) nad spodním okrajem okna. Druhý vertex umístíme ve stejné výšce, ale vlevo. OpenGL je spojí přímkou. Třetí bod posuneme dolů do levého dolního rohu. OpenGL opět zobrazí linku, tentokrát mezi druhým a třetím bodem. Čtvrtý bod patří do pravého dolního rohu a k pátému projedeme výchozím vertexem nahoru. Ukončíme triangle strip, abychom mohli začít vykreslovat z nové pozice a stejným způsobem vykreslíme druhou část rámečku, ale tentokrát nahoře.

Asi jste pochopili, že pokud vykreslujeme více na sebe navazujících přímek, tak LINE_STRIP ušetří spoustu zbytečného kódu, který vzniká opakovaným definováním vertexů při obyčejném GL_LINES.

Pořadí zadávání bodů

glBegin(GL_LINE_STRIP);// Začátek kreslení linek

glVertex2d(639,417);// 1

glVertex2d(0,417);// 2

glVertex2d(0,480);// 3

glVertex2d(639,480);// 4

glVertex2d(639,128);// 5

glEnd();// Konec kreslení

glBegin(GL_LINE_STRIP);// Začátek kreslení linek

glVertex2d(0,128);// 6

glVertex2d(639,128);// 7

glVertex2d(639,1);// 8

glVertex2d(0,1);// 9

glVertex2d(0,417);// 10

glEnd();// Konec kreslení

Nám neznámá funkce glScissor(x, y, v, š) vytváří něco, co by se dalo popsat jako okno. Pokud zapneme GL_SCISSOR_TEST, bude se ořezávat okolí této části obrazovky, tudíž se objekty budou moci vykreslovat pouze uvnitř definovaného obdélníku. Určíme ho parametry předanými funkci. V našem případě je to první pixel na ose x ve výšce 13,5% (0,135...f) od spodního okraje. dále bude 638 pixelů široký (swidth-2) a 59,7% (0,597...f) výšky okna vysoký. Druhým řádkem povolíme ořezávací testy. Můžete se pokusit vykreslit obrovský obdélník přes celé okno, ale uvidíte pouze část v neořezané oblasti. zbytek dosud nakreslené scény zůstane nezměněn. Perfektní příkaz!

glScissor(1, int(0.135416f*sheight), swidth-2, int(0.597916f*sheight));// Definování ořezávací oblasti

glEnable(GL_SCISSOR_TEST);// Povolí ořezávací testy

Na řadu přichází asi nejtěžší část této lekce - vypsání podporovaných OpenGL rozšíření. V první fázi je musíme získat. Pomocí funkce malloc() alokujeme buffer pro řetězec znaků text. Předává se jí velikost požadované paměti. Strlen() spočítá počet znaků řetězce vráceného glGetString(GL_EXTENSIONS). Přičteme k němu ještě jeden znak pro '\0', který uzavírá každý c-éčkovský řetězec. Strcpy() zkopíruje řetězec podporovaných rozšíření do proměnné text.

char* text = (char *)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);// Alokace paměti pro řetězec

strcpy(text,(char *)glGetString(GL_EXTENSIONS));// Zkopíruje seznam rozšíření do text

Nyní jsme do text nagrabovali z grafické karty řetězec, který vypadá nějak takto: "GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra". Pomocí strtok() z něj vyjmeme v pořadí první rozšíření. Funkce pracuje tak, že prochází řetězec a v případě, že najde mezeru zkopíruje příslušnou část z text do token. První hodnota token tedy bude "GL_ARB_multitexture". Zároveň se však změní i text. První mezera se nahradí oddělovačem. Více dále.

token = strtok(text, " ");// Získá první podřetězec

Vytvoříme cyklus, který se zastaví tehdy, když v token nezbudou už žádné další informace - bude se rovnat NULL. Každým průchodem inkrementujeme čítač a zkontrolujeme, jestli je jeho hodnota větší než maxtokens. Touto cestou velice snadno získáme maximální hodnotu v čítači, kterou využijeme při rolování po stisku kláves.

while(token != NULL)// Prochází podporovaná rozšíření

{

cnt++;// Inkrementuje čítač

if (cnt > maxtokens)// Je maximum menší než hodnota čítače?

{

maxtokens = cnt;// Aktualizace maxima

}

V této chvíli máme v token uložené první rozšíření. Jeho pořadové číslo napíšeme zeleně do levé části okna. Všimněte si, že ho na ose x napíšeme na souřadnici 0. Tím bychom mohli zlikvidovat levý (bílý) rámeček, který jsme už vykreslili, ale protože máme zapnuté ořezávání, pixely na nule nebudou modifikovány. Na ose y začínáme kreslit na 96. Abychom nevykreslovali všechno na sebe, přičítáme pořadí násobené výškou textu (cnt*32). Při vypisování prvního rozšíření se cnt==1 a text se nakreslí na 96+(32*1)=128. U druhého je výsledkem 160. Také odečítáme scroll. Implicitně se rovná nule, ale po stisku šipek se jeho hodnota mění. Umožníme tím rolování ořezaného okna, do kterého se vejde celkem devět řádek (výška okna/výška textu = 288/32 = 9). Změnou scrollu můžeme změnit offset textu a tím ho posunout nahoru nebo dolů. Efekt je podobný filmovému projektoru. Film roluje tak, aby v jednom okamžiku byl vidět vždy jen jeden frame. Nemůžete vidět oblast nad nebo pod ním i když máte větší plátno. Objektiv sehrává stejnou roli jako ořezávací testy.

glColor3f(0.5f,1.0f,0.5f);// Zelená barva

glPrint(0, 96+(cnt*32)-scroll, 0, "%i", cnt);// Pořadí aktuálního rozšíření

Po vykreslení pořadového čísla zaměníme zelenou barvu za žlutou a konečně vypíšeme text uložený v proměnné token. Vlevo se začne na padesátém pixelu.

glColor3f(1.0f,1.0f,0.5f);// Žlutá barva

glPrint(50,96+(cnt*32)-scroll,0,token);// Vypíše jedno rozšíření

Po zobrazení prvního rozšíření potřebujeme připravit půdu pro další průchod cyklem. Nejprve zjistíme, jestli je v text ještě nějaké další rozšíření. Namísto opětovného volání token = strtok(text, " "), napíšeme token = strtok(NULL, " "); NULL určuje, že se má hledat DALŠÍ podřetězec a ne všechno provádět od znova. V našem příkladě jsem výše napsal, že se mezera nahradí oddělovačem - "GL_ARB_multitextureoddělovačGL_EXT_abgr GL_EXT_bgra". Najdeme tedy oddělovač a až od něj se bude hledat další mezera. Poté se do token zkopíruje podřetězec mezi oddělovačem a mezerou (GL_EXT_abgr) a text bude modifikován na "GL_ARB_multitextureoddělovačGL_EXT_abgroddělovačGL_EXT_bgra". Po dosažení konce textu se token nastaví na NULL a cyklus se ukončí.

token = strtok(NULL, " ");// Najde další rozšíření

}

Tím jsme ukončili vykreslování, ale ještě nám zbývá po sobě uklidit. Vypneme ořezávací testy a uvolníme dynamickou paměť - informace získané pomocí glGetString(GL_EXTENSIONS) uložené v RAM. Příště až budeme volat DrawGLScene() se paměť opět alokuje a provedou se znovu všechny rozbory řetězců.

glDisable(GL_SCISSOR_TEST);// Vypne ořezávací testy

free(text);// Uvolní dynamickou paměť

Příkaz glFlush() není bezpodmínečně nutný, ale myslím, že je dobrý nápad se o něm zmínit. Nejjednodušší vysvětlení je takové, že oznámí OpenGL, aby dokončilo, co právě dělá (některé grafické karty např. používají vyrovnávací paměti, jejichž obsah se tímto pošle na výstup). Pokud si někdy všimnete mihotání nebo blikání polygonů, zkuste přidat na konec všeho vykreslování volání glFlush(). Vyprázdní renderovací pipeline a tím zamezí mihotání, které vzniká tehdy, když program nemá dostatek času, aby dokončil rendering.

glFlush();// Vyprázdní renderovací pipeline

return TRUE;// Všechno v pořádku

}

Na konec KillGLWindow() přidáme volání KillFont, které smaže display listy fontu.

// Konec KillGLWindow()

KillFont();// Smaže font

}

V programu testujeme stisk šipky nahoru a dolů. V obou případech přičteme nebo odečteme od scroll dvojku, ale pouze tehdy, pokud bychom nerolovali mimo okno. U šipky nahoru je situace jednoduchá - nula je vždy nejnižší možné rolování. Maximum u šipky dolů získáme násobením výšky řádku a počtu rozšíření. Devítku odečítáme, protože se v jednom okamžiku vejde na scénu devět řádků.

// Funkce WinMain()

if (keys[VK_UP] && (scroll > 0))// Šipka nahoru?

{

scroll -= 2;// Posune text nahoru

}

if (keys[VK_DOWN] && (scroll < 32*(maxtokens-9)))// Šipka dolů?

{

scroll += 2;// Posune text dolů

}

Doufám, že byl pro vás tento tutoriál zajímavý. Již víte, jak získat informace o výrobci, jménu a verzi grafické karty a také, která OpenGL rozšíření podporuje. Měli byste vědět, jak použít ořezávací testy a neméně důležitou věcí je nahrávání TGA místo bitmapových obrázků a jejich konverze na textury.

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>

Zdrojové kódy

Lekce 24

<<< Lekce 23 | Lekce 25 >>>