Hlavní navigace

Grafická knihovna OpenGL (32): operace s fragmenty

10. 2. 2004
Doba čtení: 10 minut

Sdílet

V předposlední části seriálu věnovaného grafické knihovně OpenGL si podrobněji popíšeme operace, které se provádějí s fragmenty před jejich uložením do framebufferu.

Operace prováděné s fragmenty

Obsah

Operace s fragmenty při vykreslování

Scissor test
Alpha test
Stencil test
Depth test
Blending
Dithering
Maskování zápisu do bufferů
Vymazání hodnot všech fragmentů z jednotlivých bufferů
Výběr barvového bufferu pro čtení a/nebo zápis
Demonstrační příklady
Pokračování
Zkomprimovaný článek i s příklady pro modemisty

Operace s fragmenty při vykreslování

Při rasterizaci je možné podrobit vzniklé fragmenty před jejich zápisem do framebufferu celé řadě testů a operací, které mohou změnit obsah fragmentu nebo dokonce celý fragment odstraní z dalšího zpracování.

Již v předchozí části jsme si uvedli schéma zobrazující pořadí jednotlivých operací. Pro větší názornost (a taky proto, že jsem ho pro dnešní článek pěkně vymaloval) si toto schéma ukážeme znovu a v dalších odstavcích si jednotlivé operace postupně popíšeme.

Pořadí operací a testů s vykreslovanými fragmenty
Obrázek 1: Pořadí operací a testů s vykreslovanými fragmenty

Scissor test

Scissor test neboli ořezání nůžkami patří mezi nejjednodušší operace, které jsou s fragmenty prováděny. Scissor test provádí ořezání fragmentů do obdélníku, který je specifikován svou pozicí, šířkou a výškou – viz druhý obrázek. Pokud se fragment nachází uvnitř tohoto obdélníku, putuje k dalším testům, pokud je mimo meze obdélníku, je odstraněn.

Scissor test
Obrázek 2: Scissor test

Na specifikaci oblasti pro ořezání se používá funkce:

void glScissor(
    GLint   x,
    GLint   y,
    GLsizei width,
    GLsizei height
);

Zapnutí (povolení) scissor testu:

void glEnable(
    GL_SCISSOR_TEST
);

Vypnutí scissor testu:

void glDisable(
    GL_SCISSOR_TEST
);

Dotaz na povolení či zakázání scissor testu:

GLboolean glIsEnabled(
    GL_SCISSOR_TEST
);

Dotaz na souřadnice nastaveného ořezávacího obdélníku:

void glGetIntegerv(
    GL_SCISSOR_BOX,
    &params
);

Alpha test

Jedná se o testovací funkci, jejíž výsledek je závislý na alfa hodnotě vykreslovaného fragmentu. Alfa hodnota se porovná s předem nastavenou referenční hodnotou a podle výsledku této relační funkce se může fragment odstranit z dalšího zpracovávání. Je tedy zapotřebí nastavit jak refenční hodnotu (číslo v pohyblivé řádové čárce v rozsahu od 0.0 do 1.0), tak i relační funkci:

void glAlphaFunc(
    GLenum func,
    GLclampf ref
);

Parametrem func se určuje relační funkce (viz níže), parametremref pak referenční hodnota. Typ relační funkce:

  1. GL_NEVER  – fragment bude vždy odstraněn
  2. GL_ALWAYS  – fragment vždy projde do dalšího zpracování
  3. GL_LESS  – přijmout, když alfa<ref
  4. GL_LEQUAL  – přijmout, když alfa<=ref
  5. GL_EQUAL  – přijmout, když alfa=ref
  6. GL_GREATER  – přijmout, když alfa>ref
  7. GL_GEQUAL  – přijmout, když alfa>=ref
  8. GL_NOTEQUAL – přijmout, když alfa<>ref

Povolení alfa testu:

void glEnable(
    GL_ALPHA_TEST
);

Zakázání alfa testu:

void glDisable(
    GL_ALPHA_TEST
);

Dotaz na povolení či zakázání alfa testu:

GLboolean glIsEnabled(
    GL_ALPHA_TEST
);

Získání parametrů alfa testu:

void glGetIntegerv(
    GL_ALPHA_TEST_FUNC,
    &params;
);

void glGetIntegerv(
    GL_ALPHA_TEST_REF,
    &params;
);

Alfa test lze použít například pro billboarding (viz též 22. část), protože když fragment neprojde alfa testem, neovlivní hodnoty v paměti hloubky (tento test se provádí až po alfa testu).

Stencil test

Stencil test (test na šablonu) se provádí pro každý vykreslovaný fragment tak, že se porovnává hodnota ve stencil bufferu se zadanou referenční hodnotou. V závislosti na výsledku testu může být hodnota ve stencil bufferu změněna, což představuje široké možnosti použití, například při tvorbě těles pomocí CSG nebo při zobrazování stínů.

Nastavení stencil testu se provádí pomocí funkce:

void glStencilFunc(
    GLenum func,
    GLint ref,
    GLuint mask
);

kde parametr func představuje relační funkci, která se bude provádět, parametr ref referenční hodnotu a parametr mask bitovou masku, která se aplikuje jak na referenční hodnotu, tak i na hodnotu uloženou ve stencil bufferu. Možnosti relační funkce jsou stejné jako u alfa testu:

  1. GL_NEVER  – fragment bude vždy odstraněn
  2. GL_ALWAYS  – fragment vždy projde do dalšího zpracování
  3. GL_LESS  – přijmout, když (ref & mask) < (stencil & mask)
  4. GL_LEQUAL  – přijmout, když (ref & mask) <= (stencil & mask)
  5. GL_EQUAL  – přijmout, když (ref & mask) = (stencil & mask)
  6. GL_GREATER  – přijmout, když (ref & mask) > (stencil & mask)
  7. GL_GEQUAL  – přijmout, když (ref & mask) >= (stencil & mask)
  8. GL_NOTEQUAL – přijmout, když (ref & mask) <> (stencil & mask)

Pomocí funkce glStencilOp(fail, zfail, zpass); se specifikuje, co se má stát s hodnotou ve stencil bufferu v případě, že:

  1. test na šablonu fragment odmítne z dalšího zpracování
  2. test na hloubku fragmentu bude neúspěšný
  3. test na hloubku fragmentu bude úspěšný

Možné hodnoty všech tří parametrů této funkce jsou:

  1. GL_KEEP – ponechat původní hodnotu
  2. GL_ZERO – vynulovat hodnotu ve stencil bufferu
  3. GL_INCR – inkrementace uložené hodnoty
  4. GL_DECR – dekrementace uložené hodnoty
  5. GL_INVERT – vynásobení konstantou –1 (inverze bitů)

Povolení stencil testu:

void glEnable(
    GL_STENCIL_TEST
);

Zakázání stencil testu:

void glDisable(
    GL_STENCIL_TEST
);

Dotaz na povolení či zakázání stencil testu:

GLboolean glIsEnabled(
    GL_STENCIL_TEST
);

Získání parametrů stencil testu:

glGetIntegerv(        // relacni funkce
    GL_STENCIL_FUNC,
    &params;
);

glGetIntegerv(        // referencni hodnota
    GL_STENCIL_REF,
    &params;
);

glGetIntegerv(
    GL_STENCIL_VALUE_MASK, // bitova maska
    &params;
);

glGetIntegerv(
    GL_STENCIL_FAIL,  // funkce zadana glStencilOp
    &params;
);

glGetIntegerv(
    GL_STENCIL_PASS_DEPTH_FAIL, // dtto
    &params;
);

glGetIntegerv(
    GL_STENCIL_PASS_DEPTH_PASS, // dtto
    &params;
);

Depth test

Test na hodnotu uloženou v paměti hloubky jsme si již několikrát vysvětlili – viz též 14. část. Zde si pouze popíšeme nastavení relační funkce, která se provádí mezi hloubkou právě vykreslovaného fragmentu a hodnotou uloženou v paměti hloubky. Relační funkci nastavujeme pomocí příkazu:

void glDepthFunc(
    GLenum func
);

kde parametr func může nabývat následujících hodnot:

  • GL_NEVER  – fragment bude vždy odstraněn
  • GL_ALWAYS  – fragment vždy projde do dalšího zpracování
  • GL_LESS  – přijmout, když Zfragmentu<Zbuf­feru – standardní nastavení
  • GL_LEQUAL  – přijmout, když Zfragmentu<=Zbuf­feru
  • GL_EQUAL  – přijmout, když Zfragmentu=Zbuf­feru
  • GL_GREATER  – přijmout, když Zfragmentu>Zbuf­feru
  • GL_GEQUAL  – přijmout, když Zfragmentu>=Zbuf­feru
  • GL_NOTEQUAL – přijmout, když Zfragmentu<>Zbuf­feru

Povolení testu na hloubku:

void glEnable(
    GL_DEPTH_TEST
);

Zakázání testu na hloubku:

void glDisable(
    GL_DEPTH_TEST
);

Dotaz na povolení či zakázání testu na hloubku:

GLboolean glIsEnabled(
    GL_DEPTH_TEST
);

Blending

Operaci blendingu už dobře známe, vysvětlovali jsme si ji v 28. dílu. Zde si pouze stručně řekneme, že se jedná a výpočet barevné kombinace právě zpracovávaného fragmentu s fragmentem uloženým ve framebufferu. Výpočet výsledné barvy závisí na nastavené blendovací (míchací) funkci:

void glBlendFunc(
    GLenum sfactor,
    GLenum dfactor
);

Povolení blendingu:

void glEnable(
    GL_BLEND
);

Zakázání blendingu:

void glDisable(
    GL_BLEND
);

Dotaz na povolení či zakázání blendingu:

GLboolean glIsEnabled(
    GL_BLEND
);

Dithering

Dithering může být prováděn za účelem zdánlivého přidání počtu barev do zobrazované scény. Jedná se o dobře známý postup, který se běžně používá při tisku na všech tiskárnách – z pixelů několika základních barev se jejich vzájemným promícháním získají další barevné odstíny.

V OpenGL může být dithering použit jak v true-color, tak i paletových režimech.

Povolení ditheringu:

glEnable(
    GL_DITHER
);

Zákaz ditheringu:

glDisable(
    GL_DITHER
);

Dotaz na stav ditherignu:

GLboolean glIsEnabled(
    GL_DITHER
);

Maskování zápisu do bufferů

Při zápisu dat do jednotlivých bufferů se mohou provádět bitové nebo logické operace logického součinu mezi zapisovanými daty a předem zadanou hodnotou. Pro každý typ bufferů z framebufferu je určena jedna z následujících funkcí.

Při zápisu fragmentů do barvových bufferů, které jsou nastaveny do paletového režimu, je každá hodnota fragmentu podrobena bitové operaci logického součinu (and) s hodnotou mask specifikovanou pomocí funkce:

void glIndexMask(
    GLuint mask
);

Pokud jsou barvové buffery nastaveny do režimu true-color, je každá barevná složka zapisovaného fragmentu podrobena logické operaci and, tj. barevná složka je podle nastaveného příznaku buď zapsána, nebo nezapsána. Nastavování příznaků se provádí pomocí funkce:

void glColorMask(
    GLboolean red,
    GLboolean green,
    GLboolean blue,
    GLboolean alpha
);

Pro paměť hloubky lze také nastavit příznak, zda se má, či nemá fragment zapsat. Použitá funkce má hlavičku:

void glDepthMask(
    GLboolean flag
);

Posledním bufferem je paměť šablony, kde lze při zápisu bitovou operací and nulovat jednotlivé bity, podobně jako u barvového bufferu nastaveného do paletového režimu. Funkce pro nastavení masky má hlavičku:

void glStencilMask(
    GLuint mask
);

Inicializační hodnoty při spuštění aplikace jsou nastaveny tak, jako by se volala sekvence funkcí:

glIndexMask((GLuint)~0);
glColorMask(GLtrue, GLtrue, GLtrue);
glDepthMask(GLtrue);
glStencilMask((GLuint)~0);

Vymazání hodnot všech fragmentů z jednotlivých bufferů

Pro vymazání hodnot všech fragmentů (nebo fragmentů ve vybraném obdélníkovém regionu), tj. pro nastavení fragmentů na předem zadanou hodnotu, se používá nám již známá funkce:

void glClear(
    GLbitfield mask
);

Jak je z hlavičky výše uvedené funkce patrné, předává se této funkci pouze jeden parametr typu GLbitfield, v němž jsou pomocí hodnot jednotlivých bitů specifikovány buffery, které se mají vymazat. Pro vymazání více bufferů současně (například zadního barvového bufferu a paměti hloubky) je vhodné použít pouze jednu funkci glClear() s vhodně nastaveným parametrem, neboť vymazání může vlivem optimalizace přístupu do framebufferu proběhnout rychleji než v případě sekvenčního volání více funkcíglClear().

Pokud není zapotřebí mazat celou oblast bufferu, lze vybrat pouze obdélníkový region, neboť mazání je závislé na scissor boxu – viz výše uvedený scissor test.

Hodnota parametru mask se nastaví nejlépe pomocí bitové operace or (v jazyce C se používá znak roura – pipe), kterou aplikujeme na symbolická jména jednotlivých bufferů. Tato jména jsou v souboru gl.hvytvořena tak, že vyjadřují váhy jednotlivých bitů datového typu GLbitfield:

  • GL_COLOR_BUFFER_BIT – při nastavení bitu s touto váhou se vymaže barvový buffer (či více barvových bufferů), který je v době volání funkce glClear() povolen. Barva, na kterou se všechny fragmenty nastaví, se specifikuje pomocí funkce glClearColor(). V případě, že jsou barvové buffery nastaveny do paletového režimu, se mazací barva specifikuje pomocí funkce glClearIndex(). Nastavení barvových bufferů, které se mají současně vymazat, se provede pomocí funkce glDrawBuffer(). Aktuálně nastavenou barvu pro mazání lze zjistit pomocí funkceglGetDou­blev(GL_COLOR_CLE­AR_VALUE, &value) resp. glGetIntegerv(GL_IN­DEX_CLEAR_VALU­E, &value).
  • GL_DEPTH_BUFFER_BIT – při nastavení tohoto bitu se vymaže paměť hloubky. Hodnota, na kterou se nastaví hloubky všech fragmentů ve framebufferu, se specifikuje pomocí funkce glClearDepth(). Tato hodnota se po zadání ořízne do intervalu [0, 1]. Aktuálně nastavenou hodnotu lze zjistit pomocí funkce glGetDoublev(GL_DEP­TH_CLEAR_VALU­E, &value).
  • GL_ACCUM_BUFFER_BIT – při nastavení tohoto bitu se vymaže akumulační buffer. Barva, na kterou se nastaví všechny fragmenty v akumulačním bufferu, se specifikuje pomocí funkce glClearAccum(). Aktuálně nastavenou hodnotu lze zjistit pomocí funkce glGetDoublev(GL_AC­CUM_CLEAR_VALU­E, &value). Pro paletové režimy není použití akumulačního bufferu povoleno.
  • GL_STENCIL_BUF­FER_BIT – při nastavení tohoto bitu se vymaže paměť šablony. Hodnotu, na kterou se fragmenty v paměti šablony nastaví, je možno zadat pomocí funkce glClearStencil(). Aktuální hodnotu lze získat pomocí funkce glGetIntegerv(GL_STEN­CIL_CLEAR_VALU­E).

Výběr barvového bufferu pro čtení a/nebo zápis

Jak jsme si již napsali v minulém dílu, je možné vytvořit a používat více barvových bufferů. Knihovna OpenGL poskytuje dvě funkce, pomocí nichž lze specifikovat, do kterého bufferu se bude provádět zápis barevných hodnot fragmentů a ze kterých bufferů se budou informace naopak číst.

Barvový buffer pro zápis a kreslení se vybere pomocí funkce:

void glDrawBuffer(
    GLenum mode
);

Buffer pro čtení (zejména ho využívají funkce glReadPixels() a glCopyPixels()) se vybere pomocí funkce:

void glReadBuffer(
    GLenum mode
);

V parametru mode se musí uvést specifikace zvoleného barvového bufferu. Možné jsou tyto hodnoty:

  • GL_FRONT – přední barvový buffer
  • GL_BACK – zadní barvový buffer (může se nastavit pouze u double-bufferingu)
  • GL_RIGHT – pravý buffer (nastavení má smysl u stereo pohledů)
  • GL_LEFT – levý buffer (pouze pro stereo pohledy)
  • GL_FRONT_RIGHT – přední pravý buffer (pouze pro stereo pohledy)
  • GL_FRONT_LEFT – přední levý buffer (pouze pro stereo pohledy
  • GL_BACK_RIGHT – zadní pravý buffer (pouze pro stereo pohledy
  • GL_BACK_LEFT – zadní levý buffer (pouze pro stereo pohledy
  • GL_FRONT_AND_BAC­K – přední i zadní buffer (může se nastavit pouze pro zápis pomocí funkce glDrawBuffer())
  • GL_NONE – žádný buffer pro kreslení nebude vybrán (opět se může nastavit pouze pro zápis pomocí funkce glReadBuffer())
  • GL_AUXi – jeden z přídavných barvových bufferů, pokud jsou podporovány. Hodnota i může nabývat hodnot mezi nulou a GL_AUX_BUFFERS-1.

Demonstrační příklady

První příklad (obarvená verze) ukazuje použití scissor testu při omezení vykreslování do části okna.

Screenshot z prvního  demonstračního příkladu
Obrázek 1: Screenshot z prvního demonstračního příkladu

Druhý příklad (obarvená verze): demonstruje práci se stencil bufferem. Do jedné scény jsou vykreslena dvě tělesa, jejichž překrývání je řízeno pomocí hodnot uložených ve stencil bufferu.

Screenshot ze druhého  demonstračního příkladu
Obrázek 2: Screenshot ze druhého demonstračního příkladu

Třetí příklad (obarvená verze): ukazuje, jakým způsobem lze maskovat zápis do bufferů, konkrétně do barvového bufferu. Maskování zápisu se projeví i při mazání bufferu.

Screenshot ze třetího  demonstračního příkladu
Obrázek 3: Screenshot ze třetího demonstračního příkladu

UX DAy - tip 2

Pokračování

Závěrečný díl bude věnován soupisu všech témat a demonstračních příkladů z předchozích čás­tí.

Zkomprimovaný článek i s příklady pro modemisty

Pro majitele pomalejšího připojení k internetu je zde k dispozici celý článek i s přílohami zabalený do jednoho zip souboru.

Byl pro vás článek přínosný?