Lekce 20 - Maskování

Černé okraje obrázků jsme dosud ořezávali blendingem. Ačkoli je tato metoda efektivní, ne vždy transparentní objekty vypadají dobře. Modelová situace: vytváříme hru a potřebujeme celistvý text nebo zakřivený ovládací panel, ale při blendingu scéna prosvítá. Nejlepším řešením je maskování obrázků.

Bitmapový formát obrázku je podporován každým počítačem a každým operačním systémem. Nejen, že se s nimi snadno pracuje, ale velmi snadno se nahrávají a konvertují na textury. K ořezání černých okrajů textu a obrázků jsme s výhodou používali blending, ale ne vždy výsledek vypadal dobře. Při spritové animaci ve hře nechcete, aby postavou prosvítalo pozadí. Podobně i text by měl být pevný a snadno čitelný. V takových situacích se s výhodou využívá maskování. Má dvě fáze. V první do scény umístíme černobílou texturu, ve druhé na stejné místo vykreslíme hlavní texturu. Použitý typ blendingu zajistí, že tam, kde se v masce (první obrázek) vyskytovala bílá barva zůstane původní scéna. Textura se neprůhledně vykreslí na černou barvu.

#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

Masking ukládá příznak zapnutého/vypnutého maskování a podle scene se rozhodujeme, zda vykreslujeme první nebo druhou verzi scény. Loop je řídící proměnná cyklů, roll použijeme pro rolování textur a rotaci objektu při zapnuté druhé scéně.

bool masking=TRUE;// Maskování on/off

bool mp;// Stisknuto M?

bool sp;// Stisknut mezerník?

bool scene;// Která scéna se má kreslit

GLuint texture[5];// Ukládá 5 textur

GLuint loop;// Řídící proměnná cyklů

GLfloat roll;// Rolování textur

Generování textur je ve svém principu úplně stejné jako ve všech minulých lekcích, ale velmi přehledně demonstruje nahrávání více textur najednou. Téměř vždy jsme používali pouze jednu. Deklarujeme pole ukazatelů na pět bitmap, vynulujeme je a nahrajeme do nich obrázky, které vzápětí změníme na textury.

int LoadGLTextures()// Nahraje bitmapu a konvertuje na texturu

{

int Status=FALSE;

AUX_RGBImageRec *TextureImage[5];// Alokuje místo pro bitmapy

memset(TextureImage,0,sizeof(void *)*5);

if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&// Logo

(TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&// První maska

(TextureImage[2]=LoadBMP("Data/image1.bmp")) &&// První obrázek

(TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&// Druhá maska

(TextureImage[4]=LoadBMP("Data/image2.bmp")))// Druhý obrázek

{

Status=TRUE;

glGenTextures(5, &texture[0]);

for (loop=0; loop<5; loop++)// Generuje jednotlivé textury

{

glBindTexture(GL_TEXTURE_2D, texture[loop]);

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[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);

}

}

for (loop=0; loop<5; loop++)

{

if (TextureImage[loop])

{

if (TextureImage[loop]->data)

{

free(TextureImage[loop]->data);

}

free(TextureImage[loop]);

}

}

return Status;

}

Z inicializace zůstala doslova kostra.

int InitGL(GLvoid)// Všechno nastavení OpenGL

{

if (!LoadGLTextures())// Nahraje textury

{

return FALSE;

}

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

glClearDepth(1.0);// Povolí mazání Depth Bufferu

glEnable(GL_DEPTH_TEST);// Zapne hloubkové testování

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

glEnable(GL_TEXTURE_2D);// Zapne mapování textur

return TRUE;

}

Při vykreslování začneme jako obyčejně mazáním bufferů, resetem matice a translací do obrazovky.

int DrawGLScene(GLvoid)// Vykreslování

{

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

glLoadIdentity();// Reset matice

glTranslatef(0.0f,0.0f,-2.0f);// Přesun do obrazovky

Zvolíme texturu loga a namapujeme ji na obdélník. Koordináty vypadají nějak divně. Namísto obvyklých hodnot 0 až 1 tentokrát zadáme čísla 0 a 3. Předáním trojky oznámíme, že chceme namapovat texturu na polygon třikrát. Pro vysvětlení mě napadá vlastnost vedle sebe při umístění malého obrázku na plochu OS. Trojku zadáváme do šířky i do výšky, tudíž se na polygon rovnoměrně namapuje celkem devět stejných obrázků. Ke koordinátům také přičítáme (defakto odečítáme) proměnnou roll, kterou na konci funkce inkrementujeme. Vzniká dojem, že vykreslovaná hladina scény roluje, ale v programu se vlastně mění pouze texturové koordináty. Rolování může být použito pro různé efekty. Například pohybující se mraky nebo text létající po objektu.

Po rolování Před rolováním

glBindTexture(GL_TEXTURE_2D, texture[0]);// Výběr textury loga

Logo

glBegin(GL_QUADS);// Kreslení obdélníků

glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f,-1.1f, 0.0f);

glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f,-1.1f, 0.0f);

glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f, 1.1f, 0.0f);

glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f, 1.1f, 0.0f);

glEnd();// Konec kreslení

Zapneme blending. Aby efekt pracoval musíme vypnout testování hloubky. Kdyby se nevypnulo největší pravděpodobností by nic nebylo vidět.

glEnable(GL_BLEND);// Zapne blending

glDisable(GL_DEPTH_TEST);// Vypne testování hloubky

Podle hodnoty proměnné se rozhodneme, zda budeme obrázek maskovat nebo použijeme mnohokrát vyzkoušený blending. Maska je černobílá kopie textury, kterou chceme vykreslit. Bílé oblasti masky budou průhledné, černé nebudou. Pod bílými sekcemi zůstane scéna nezměněna.

if (masking)// Je zapnuté maskování?

{

glBlendFunc(GL_DST_COLOR,GL_ZERO);// Blending barvy obrazu pomocí nuly (černá)

}

Pokud bude scene true vykreslíme druhou, jinak první scénu.

if (scene)// Vykreslujeme druhou scénu?

{

Nechceme objekty příliš velké, takže se přesuneme hlouběji do obrazovky. Provedeme rotaci na ose z o 0° až 360° podle proměnné roll.

glTranslatef(0.0f,0.0f,-1.0f);// Přesun o jednotku do obrazovky

glRotatef(roll*360,0.0f,0.0f,1.0f);// Rotace na ose z

Pokud je zapnuté maskování, vykreslíme nejdříve masku a potom objekt. Při vypnutém pouze objekt.

if (masking)// Je zapnuté maskování?

{

Nastavení blendingu pro masku jsme provedli dříve. Zvolíme texturu masky a namapujeme ji na obdélník. Po vykreslení se na scéně objeví černá místa odpovídající masce.

Druhá maska Druhý obrázek

glBindTexture(GL_TEXTURE_2D, texture[3]);// Výběr textury druhé masky

glBegin(GL_QUADS);// Začátek kreslení obdélníků

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f,-1.1f, 0.0f);

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f,-1.1f, 0.0f);

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);

glEnd();// Konec kreslení

}

Znovu změníme mód blendingu. Oznámíme tím, že chceme vykreslit všechny části barevné textury, které NEJSOU černé. Protože je obrázek barevnou kopií masky, tak se vykreslí jen místa nad černými částmi masky. Protože je maska černá, nic ze scény nebude prosvítat skrz textury. Vznikne dojem pevně vypadajícího obrázku. Zvolíme barevnou texturu. Poté ji vykreslíme se stejnými souřadnicemi bodů v prostoru a stejnými texturovými koordináty jako masku. Kdybychom masku nevykreslily, obrázek by se zkopíroval do scény, ale díky blendingu by byl průhledný. Objekty za ním by prosvítaly.

glBlendFunc(GL_ONE, GL_ONE);// Pro druhý barevný obrázek

glBindTexture(GL_TEXTURE_2D, texture[4]);// Zvolí druhý obrázek

glBegin(GL_QUADS);// Začátek kreslení obdélníků

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f,-1.1f, 0.0f);

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f,-1.1f, 0.0f);

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);

glEnd();// Konec kreslení

}

Při hodnotě FALSE uložené ve scene se vykreslí první scéna. Opět větvíme program podle maskování. Při zapnutém vykreslíme masku pro scénu jedna. Textura roluje zprava doleva (roll přičítáme k horizontálním koordinátům). Chceme, aby textura zaplnila celou scénu, takže neprovádíme translaci do obrazovky.

První maska První obrázek

else// Vykreslení první scény

{

if (masking)// Je zapnuté maskování?

{

glBindTexture(GL_TEXTURE_2D, texture[1]);// Výběr textury první masky

glBegin(GL_QUADS);// Začátek kreslení obdélníků

glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f,-1.1f, 0.0f);

glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f,-1.1f, 0.0f);

glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);

glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);

glEnd();// Konec kreslení

}

Blending nastavíme stejně jako minule. Vybereme texturu scény jedna a vykreslíme ji na stejné místo jako masku.

glBlendFunc(GL_ONE, GL_ONE);// Pro první barevný obrázek

glBindTexture(GL_TEXTURE_2D, texture[2]);// Zvolí první obrázek

glBegin(GL_QUADS);// Začátek kreslení obdélníků

glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f,-1.1f, 0.0f);

glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f,-1.1f, 0.0f);

glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);

glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);

glEnd();// Konec kreslení

}

Zapneme testování hloubky a vypneme blending. V malém programu je to věc celkem zbytečná, ale u rozsáhlejších projektů někdy nevíte, co zrovna máte zapnuté nebo vypnuté. Tyto chyby se obtížně hledají a kradou čas. Po určité době ztrácíte orientaci, kód se stává složitějším - preventivní opatření.

glEnable(GL_DEPTH_TEST);// Zapne testování hloubky

glDisable(GL_BLEND);// Vypne blending

Aby se scéna dynamicky pohybovala musíme inkrementovat roll.

roll+=0.002f;// Inkrementace roll

if (roll>1.0f)// Je větší než jedna?

{

roll-=1.0f;// Odečte jedna

}

return TRUE;

}

Ošetříme vstup z klávesnice. Po stisku mezerníku změníme vykreslovanou scénu.

// Funkce WinMain()

if (keys[' '] && !sp)// Mezerník - změna scény

{

sp=TRUE;

scene=!scene;

}

if (!keys[' '])// Uvolnění mezerníku

{

sp=FALSE;

}

Stiskem klávesy M zapneme, popř. vypneme maskování.

if (keys['M'] && !mp)// Klávesa M - zapne/vypne maskování

{

mp=TRUE;

masking=!masking;

}

if (!keys['M'])// Uvolnění klávesy M

{

mp=FALSE;

}

Vytvoření masky není příliš těžké. Pokud máte originální obrázek již nakreslený, otevřete ho v nějakém grafickém editoru a transformujte ho do šedé palety barev. Po této operaci zvyšte kontrast, takže se šedé pixely ztmaví na černé. Zkuste také snížit jas ap. Je důležité, aby bílá byla opravdu bílá a černá čistě černá. Máte-li pochyby převeďte obrázek do černobílého režimu (2 barvy). Pokud by v masce zůstaly šedé pixely byly by průhledné. Je také důležité, aby barevný obrázek měl černé pozadí a masku bílou. Otestujte si barvy masky kapátkem (většinou bývají chyby na rozhraní). Bílá je v RGB 255 255 255 (FF FF FF), černá 0 0 0.

Lze zjistit barvu pixelů při nahrávání bitmapy. Chcete-li pixel průhledný můžete mu přiřadit alfu rovnou nule. Všem ostatním barvám 255. Tato metoda také pracuje spolehlivě, ale vyžaduje extra kód. Tímto chci poukázat, že k výsledku existuje více cest - všechny mohou být správné.

Naučili jsme se, jak vykreslit část textury bez použití alfa kanálu. Klasický blending, který známe, nevypadal nejlépe a textury s alfa kanálem potřebují obrázky, které alfa kanál podporují. Bitmapy jsou vhodné především díky snadné práci, ale mají již zmíněné omezení. Tento program ukázal, jak obejít nedostatky bitmapových obrázků a vykreslování jedné textury vícekrát na jeden obdélník. Vše jsme rozšířili rolováním textur po scéně.

Děkuji Robu Santovi za ukázkový kód, ve kterém mi poprvé představil trik mapování dvou textur. Nicméně ani tato cesta není úplně dokonalá. Aby efekt pracoval, potřebujete dva průchody - dvakrát vykreslujete jeden objekt. Z toho plyne, že vykreslování tímto způsobem je dvakrát pomalejší. Nicméně... co se dá dělat?

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

Zdrojové kódy

Lekce 20 - scéna 1
Lekce 20 - scéna 2

<<< Lekce 19 | Lekce 21 >>>