Vykreslování bitmap
Pojem bitmapa v OpenGL
V OpenGL značí pojem bitmapa obdélníkové pole, kde každý prvek tohoto pole má hodnotu 0 nebo 1 – jedná se tedy o pole bitů. Toto pole slouží jako vykreslovací maska, která je použita při vyplnění obdélníkové, osově orientované části okna OpenGL. V místě, kde je prvek pole nastavený na jedničku, se provede vykreslení pixelu a v místě s nulovým prvkem se žádný pixel nevykreslí, tj. již nakreslený obrázek na pozadí v tomto místě zůstane. Vykreslovaná bitmapa tedy může být pouze jednobarevná, protože pro každý pixel rozlišujeme jen dva stavy (vykresleno, nebo nevykresleno) a informaci o barvě pixelu již tedy nelze do jednoho bitu zakódovat.
Je důležité zdůraznit, že pokud při programování v OpenGL mluvíme o bitmapách, máme na mysli vždy pouze jednobarevné rastrové obrázky s výše popsanými vlastnostmi. Všechny ostatní typy rastrových obrázků s větší bitovou hloubkou (a tím pádem i s větším počtem barev nebo i průhledností) nazýváme v OpenGL termínem pixmapa. Pixmapami, které mají při programování OpenGL aplikací širší oblasti použití, se budeme podrobněji zabývat v dalších částech tohoto seriálu.
Cesta rastrových dat ve vykreslovacím řetězci
Při práci s bitmapami se v OpenGL pracuje s tou částí vykreslovacího řetězce, ve kterém se provádí zpracování rastrových dat. Na dalším obrázku je tato část řetězce, která se nazývá imaging pipeline, zobrazena. Kromě jednotlivých bloků jsou naznačeny i cesty, po kterých se mohou rastrová data přenášet.
Jak je z obrázku patrné, není vykreslování rastrových dat úplně přímočaré, protože mezi zdrojem rastrových dat (což je v případě bitmap buď paměť procesoru, nebo display list) a cílem (což je paměť zobrazeného snímku, takzvaný framebuffer) leží dalších pět bloků, ve kterých se mohou rastrová data dále zpracovávat. Při práci s bitmapami však nejsou použity ani všechny bloky, ani všechny cesty dat. Cestu dat si v případě práce s bitmapami můžeme zvýraznit na následujícím obrázku.
Rastrová data, která tvoří pixely bitmapy, musí být nejprve vytvořena v hlavní paměti počítače. Potom jsou tato data přenesena do grafického akcelerátoru a je provedeno jejich případné rozbalení (unpacking). To, zda bude rozbalení provedeno, závisí na módu uložení bitmapy v hlavní paměti počítače. Poté jsou data bitmapy přenesena do dalších bloků, kterými data pouze projdou, protože s bitmapou (narozdíl od pixmap) nelze při vykreslování provádět téměř žádné další operace. Jednotlivé pixely bitmapy jsou poté převedeny na fragmenty a ty jsou přeneseny do framebufferu, který je následně zobrazen.
Abychom mohli bitmapu vykreslit, potřebujeme pomocí několika příkazů OpenGL specifikovat všechny vlastnosti této bitmapy. Tyto vlastnosti a příkazy pro jejich nastavení si popíšeme v následujících podkapitolách:
Mód uložení pixelů v bitmapě
Pomocí funkce glPixelStorei(properties, value) je možné grafickému subsystému sdělit, jakým způsobem má transformovat rastrová data, která jsou uložena v hlavní paměti počítače. Každá platforma má totiž vlastní způsob uložení dat, zejména se to týká pořadí bytů při čtení větších celků (little endian, big endian, smíšené architektury) a zarovnání dat v paměti. Pro dosažení co nejlepší přenositelnosti je tedy nutné sdělit způsob uložení dat a tím i nastavit potřebné konverze.
Funkce glPixelStorei() má dva parametry. Do prvního parametru (properties) se zadává vlastnost, která se bude měnit, do druhého parametru (value) potom hodnota tohoto parametru.
Pro práci s bitmapami lze měnit hodnoty těchto vlastností:
- GL_UNPACK_SWAP_BYTES může být nastaveno na hodnotu GL_TRUE nebo GL_FALSE. Touto vlastností se řídí prohození bytů (swap) v operačním slově počítače. Vlastnost je implicitně nastavena na hodnotu FALSE, prohození se tedy neprovádí.
- GL_UNPACK_LSB_FIRST může být taktéž nastaveno na hodnotu GL_TRUE nebo GL_FALSE. Tato vlastnost řídí otočení pořadí bitů v každém bytu. Implicitně je nastavena hodnota GL_FALSE, otáčení se tedy neprovádí.
- GL_UNPACK_ALIGNMENT specifikuje zarovnání každého řádku pixelů v paměti. Možné hodnoty tohoto parametru jsou 1 (zarovnání na byte), 2 (zarovnání na sudé byty), 4 (zarovnání na 32bitové slovo) a 8 (zarovnání na 64bitové). Je potřeba dát pozor na to, že implicitně je nastavena hodnota 4, tedy zarovnání na 32bitové slovo, což může zejména u bitmap způsobit nekorektní vykreslení.
Barva bitmapy
Barva bitmapy se specifikuje už známým příkazem void glColor*(). Pro vykreslení bitmapy sytě zelenou barvou můžeme použít například funkci glColor3f(0.0f, 1.0f, 0.0f). Je potřeba dát pozor na to, aby barva byla zadána před zavoláním příkazu pro specifikaci aktuální polohy bitmapy ve scéně, protože jinak by se změna barvy na právě vykreslované bitmapě neprojevila. Aktuální barva bitmapy je totiž jeden ze stavů, který je po zadání polohy již neměnný.
Aktuálně používanou barvu bitmapy lze získat pomocí funkce glGetFloatv() s prvním parametrem nastaveným na hodnotu GL_CURRENT_RASTER_COLOR. Další parametr je ukazatel na pole čtyř čísel typu GLfloat, do kterého je aktuálně používaná barva uložena, přičemž se postupně ukládají barevné složky Red, Green, Blue a Alpha.
Aktuální pozice bitmapy
Aktuální pozice bitmapy udává počáteční bod, od kterého se bitmapa bude vykreslovat. Pro specifikaci počátečního bodu se používá příkaz void glRasterPos(), pomocí kterého lze zadat, podobně jako u příkazu void glVertex(), dvě, tři nebo čtyři souřadnice. Pokud jsou zadány pouze dvě souřadnice x a y, je třetí souřadnice z nastavena na nulu a čtvrtá souřadnice w (váha) nastavena na jedničku. Pokud jsou zadány tři souřadnice x, y a z, je čtvrtá souřadnice w
nastavena na jedničku. Nejčastěji se počáteční bod bitmapy zadává ve 2D pomocí dvou celočíselných souřadnic a používá se příkaz void glRasterPos2i(GLint x, GLint y). Jsou však samozřejmě možné i další kombinace počtu souřadnic (2, 3, 4) a datových typů těchto souřadnic (GLshort, GLint, GLfloat,
GLdouble)Počáteční bod vykreslování bitmapy je podroben stejným transformacím jako vrcholy geometrických primitiv – na bod jsou tedy postupně aplikovány transformace specifikované v ModelView matici, Transformation matici a výsledné abstraktní souřadnice jsou následně transformovány dle nastaveného Viewportu. Podrobnější popis transformací byl uveden v předchozích dílech tohoto seriálu, kde je také popsán způsob nastavení jednotlivých transformačních matic.
Při vykreslování bitmap i pixmap je zapotřebí dávat pozor na skutečnost, že počáteční bod bitmapy či pixmapy může být v průběhu transformací odstraněn (například proto, že je umístěn mimo zobrazované okno). V případě odstranění počátečního bodu se nevykreslí ani ta část bitmapy či pixmapy, která by byla viditelná. Proto je při zobrazování částečně viditelných rastrových obrázků zapotřebí buď vhodně zvětšit oblast zadanou ve Viewportu, nebo rastrový obrázek nanést jako texturu na obdélníky (podrobnější popis texturování bude uveden v dalších dílech věnovaných 3D scénám). Pokud je počáteční bod v průběhu průchodu vykreslovacím řetězcem odstraněn, je aktuální pozice bitmapy označena za neplatnou (invalid).
Naposledy použitou aktuální pozici bitmapy lze zjistit pomocí funkce glGetFloatv() s prvním argumentem nastaveným na hodnotu GL_CURRENT_RASTER_POSITION. Druhým parametrem je ukazatel na pole čtyř čísel typu GLfloat, do kterého se tato pozice uloží. Dále je možné zjistit, zda je aktuální pozice bitmapy platná – použije se funkce glGetBooleanv() s prvním parametrem nastaveným na hodnotu GL_CURRENT_RASTER_POSITION_VALID.
Rozměry bitmapy, relativní pozice počátku a vlastní bitmapová data
Posledními údaji, které se při vykreslení bitmap musí zadat, jsou její rozměry, relativní pozice bitmapy vůči zadanému počátečnímu bodu a vlastní bitmapová data (hodnoty bitů rastrového obrázku). Všechny tyto údaje se zadávají současně v jednom příkazu glBitmap(), jehož hlavička vypadá následovně:
void glBitmap( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * bitmap );
Význam jednotlivých parametrů funkce glBitmap() je vysvětlen v dalších odstavcích:
- width
- V tomto parametru je uvedena šířka vykreslované bitmapy. Tato hodnota je zadána v pixelech. Narozdíl od dalších příkazů pro vykreslování rastrových dat (pixmapy, textury) můžeme zadat šířku libovolnou. Může jít tedy například i o číslo nedělitelné osmi, liché číslo apod. Pixely bitmapy jsou sice uloženy po bytech (8 bitů), ale případné nepotřebné bity na konci řádku jsou při vykreslování ignorovány.
- height
- V tomto parametru je uvedena výška vykreslované bitmapy. Podobně jako u předchozího parametru, i zde je hodnota zadána v pixelech a nejsou kladeny žádné zvláštní požadavky na velikost či zarovnání zadávané hodnoty.
- xorig
- Relativní horizontální pozice počátku vykreslované bitmapy od aktuální pozice bitmapy. Pokud je xorig=yorig=0, aktuální pozice bitmapy určuje levý dolní roh bitmapy. Pokud jsou tyto hodnoty nenulové, lze bitmapou vůči této pozici posouvat. Kladné hodnoty posouvají počátek doprava, záporné doleva.
- yorig
- Relativní vertikální pozice počátku vykreslované bitmapy. Význam je podobný jako u předchozího parametru. Kladné hodnoty posouvají počátek nahoru, záporné dolů. Tento parametr se často používá při vykreslování písma, protože některé znaky (například g, j a y) mají svůj obraz vykreslený až k místu dolní dotažnice, ale pozice znaku se většinou udává relativně k hlavní dotažnici.
- xmove
- Horizontální posun nové aktuální pozice bitmapy od pozice současné. Po vykreslení je k bodu aktuální pozice bitmapy připočten vektor (xmove, ymove) a nová bitmapa se tedy vykreslí relativně posunutá (pokud se ovšem mezitím nepoužije příkaz glRasterPos*(), který nastaví novou aktuální pozici bitmapy). Tento parametr se často používá v případě, že pomocí bitmap vykreslujeme písmo. Kladné hodnoty posouvají počátek nové bitmapy doprava, záporné doleva.
- ymove
- Vertikální posun nové aktuální pozice bitmapy od pozice současné. Význam je podobný jako u předchozího parametru. Kladné hodnoty posouvají počátek nové bitmapy směrem nahoru, záporné dolů.
- bitmap
- V tomto parametru je funkci glBitmap() předán ukazatel na datovou oblast, ve které jsou uložena rastrová data bitmapy. Pro každý pixel v bitmapě je vyhrazen jeden bit, ovšem data jsou při čtení z paměti přístupná po vyšších celcích – bytech, wordech a dwordech. Vzhledem k tomu, že organizace paměti může být na jednotlivých platformách odlišná (nelze například číst paměť po bytech, ale pouze po dwordech apod.), je potřeba při psaní přenositelných programů v OpenGL zadat interní formát bitmapových dat. Pro tento účel se používá funkce void glPixelStore*(), která již byla popsána v podkapitole Mód uložení pixelů v bitmapě.
Vlastní vykreslení bitmapy
Pro úspěšné vykreslení první bitmapy je většinou zapotřebí zavolat všechny výše popsané příkazy: glPixelStore(), glColor(), glRasterPos*() a glBitmap() (první příkaz nemusí být uveden v případě, že nám vyhovují standardně nastavené parametry uložení pixelů). Pokud je vyžadováno vykreslení více bitmap vedle sebe nebo pod sebe (například při vykreslování písma), je možné další bitmapy vykreslit pouze příkazem glBitmap() s vhodně nastaveným posunem.
Ukázkové příklady
Příklad vykreslení jednoduché bitmapy, kde je zobrazeno písmeno F je demonstrován na příkladu číslo 10. K dispozici je i zdrojový kód s obarvenou syntaxí. V tomto příkladu je také ukázáno, že změna barvy bitmapy musí být provedena před příkazem, který mění pozici bitmapy, jinak se barva další vykreslované bitmapy nezmění.
V demonstračním příkladu číslo 11 je ukázáno řetězové zobrazení více bitmap s využitím posunu počátku vykreslování další bitmapy. Také k tomuto příkladu je k dispozici zdrojový kód s obarvenou syntaxí.
V posledním dnešním příkladu číslo 12 je ukázáno, jak se změní vykreslení bitmapy, pokud se zadá jiný režim uložení pixelů. Samozřejmě dodávám i zdrojový kód s obarvenou syntaxí.
V další části tohoto seriálu si popíšeme využití bitmap a display-listů při vykreslování písma.