Grafická knihovna OpenGL (23): korektní otexturování

9. 12. 2003
Doba čtení: 9 minut

Sdílet

Dnes navážeme na předchozí díl, ve kterém jsme si popsali základní vlastnosti texturování. Ukážeme si postup, který je při použití OpenGL zapotřebí dodržet, aby byly plochy těles korektně otexturované. Vše si samozřejmě procvičíme na přiložených příkladech.

Texturování 2

Obsah

Postup při texturování
1. Vytvoření popř. načtení rastrové předlohy textury
2. Vytvoření texturovacího objektu, přiřazení textury tomuto objektu, nastavení formátu textury
3. Nastavení způsobu nanášení textur na vykreslované povrchy
4. Zapnutí (resp. povolení) nanášení textur
5. Vykreslení scény se zadanými texturovacími souřadnicemi pro každý vrchol
Pokračování
Ukázkové příklady
 

Postup při texturování

Vykreslování objektů s nanesenou texturou je poměrně složité na počet a vzájemnou provázanost kroků, které je potřeba v programu provést:

  1. Vytvoření rastrové předlohy textury nebo její načtení ze souboru.
  2. Vytvoření nového texturovacího objektu, přiřazení textury tomuto objektu a nastavení formátu textury.
  3. Nastavení způsobu nanášení textury na vykreslované povrchy.
  4. Zapnutí (povolení) nanášení textur.
  5. Vykreslení scény se zadanými texturovacími souřadnicemi pro každý vrchol.

Jednotlivé kroky si podrobněji popíšeme v následujících odstavcích.

1. Vytvoření popř. načtení rastrové předlohy textury

Tento krok musí implementovat sám autor programu, protože grafická knihovna OpenGL nenabízí téměř žádnou podporu ani pro vytvoření (výpočet) rastrových textur ani pro jejich načtení ze souboru.

Poměrně jednoduché řešení spočívá ve využití různých knihoven pro práci s grafickými formáty. Mezi tyto knihovny patří například libtiff, libjpg nebo libpng. Pomocí těchto knihoven je mimo jiné možné načíst soubory s rastrovou grafikou a ty dále použít pro texturu. Musí však být splněna podmínka, že rozlišení obrázku (tj. šířka a výška) je mocninou dvou v obou osách!

Další možností je provedení výpočtu procedurální textury přímo v aplikaci. Tento způsob sice poněkud zdrží start aplikace (pokud se pro výpočet nepoužije jiné vlákno), ale na druhou stranu se textury nemusí ukládat na disk, takže se může zjednodušit distribuce programu. Na internetu lze nalézt velké množství algoritmů pro vytváření procedurálních textur, jejich výběr si v případě zájmu můzeme popsat v některém z dalších pokračování tohoto seriálu.

Texturu lze také vytvořit přečtením vykresleného obrázku přímo z framebufferu, což je umožněno příkazem glCopyTexImage2D().

V ukázkových příkladech v dalších pokračováních si uvedeme první dvě možnosti, tj. jak načtení textury ze souboru, tak vytvoření velmi jednoduché procedurální textury.

Složitejší problém nastane při použití mipmappingu, kdy je zapotřebí specifikovat textury v několika rozlišeních. Tento problém lze opět řešit buď programově, nebo lze výpočet ponechat specializovaným funkcím OpenGL popř. nadstavbové knihovně GLU.

2. Vytvoření nového texturovacího objektu, přiřazení textury tomuto objektu, nastavení formátu textury

Popišme si ten nejjednodušší případ, kdy pro všechna vykreslovaná tělesa používáme jen jednu texturu. V tomto případě musíme pouze nastavit základní parametry textury. Pokud by bylo nutné používat více textur (což je v reálných scénách pravděpodobné) musela by se aktivní textura vybírat podle čísla svého texturovacího objektu (ten se musí pro každou texturu nejprve vytvořit a teprve poté nastavovat formát textury).

OpenGL podporuje celou řadu formátů textur, které se podle dimenze textury dají specifikovat příkazy:

void glTexImage1D(
    GLenum  target,
    GLint   level,
    GLint   components,
    GLsizei width,
    GLint   border,
    GLenum  format,
    GLenum  type,
    const GLvoid *pixels
);

void glTexImage2D(
    GLenum  target,
    GLint   level,
    GLint   components,
    GLsizei width,
    GLsizei height,
    GLint   border,
    GLenum  format,
    GLenum  type,
    const GLvoid *pixels
);

Význam jednotlivých parametrů je následující:

  • Parametrem target určujeme dimenzi textury, pro jednodimenzionální texturu bude v tomto parametru předána hodnota GL_TEXTURE_1D, pro dvoudimenzionální GL_TEXTURE_2D atd. Pokud současně nastavíme parametry 1D a 2D textury, mají přednost parametry pro 2D texturu.
  • Parametr level má svůj význam zejména při používánímipmap­pingu, tj. textur s více úrovněmi detailu (viz další pokračování). Pro běžnou texturu s jedním rozlišením tento parametr nastavujeme na nulovou hodnotu.
  • Pomocí parametru components se specifikuje počet barevných složek použitých pro reprezentaci jednoho pixelu. Hodnota tohoto parametru může být 1, 2, 3 nebo 4. Hodnota 1 značí, že se používá pouze barevná složka R (red). Hodnotou dvě specifikujeme barevnou složku R a alfa složku A. Hodnota 3 představuje klasickou barevnou reprezentaci pixelu pomocí barevných složek R, G, B. Hodnotou 4 specifikujeme použití tří barevných složek R, G, B společně s alfa složkou A.
  • Velikost (resp. rozlišení) textury se zadává v parametrech width a height. Pro 1D texturu se parametr height nezadává, protože je vždy jednotkový. Oba parametry musí mít hodnotu rovnu2n+2(bor­der), kde n je celé kladné číslo z rozsahu daného konkrétní implementací OpenGL a border je šířka okraje textury. Minimální rozlišení textury, kterou musí podporovat všechny implementace, je 64×64 pixelů bez okraje, popř. 66×66 pixelů s okrajem. Pokud je jeden z parametrů width nebo height nastaven na nulu, je texturování zakázáno.
  • Parametrem border se zadává šířka okraje textury v pixelech. Okraj může být použit například pro hladké navazování textur. Jeho použití si ukážeme v některém z následujících dílů. Pro běžně mapované textury se okraj nastavuje na nulovou hodnotu.
  • V parametru format se předává formát dat použitý pro jednotlivé pixely textury. Tento parametr má stejný význam jako u již dříve probraných funkcí glDrawPixels() a glReadPixels(). Každá implementace by měla podporovat minimálně tyto formáty: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE a GL_LUMINANCE_ALPHA, ale můžeme se setkat i s grafickými akcelerátory, které poslední dva formáty špatně interpretují (nenastaví například korektně všechny barevné složky).
  • Parametr type, stejně jako parametr předchozí, má stejný význam jako u funkcí glDrawPixels() a glReadPixels(), tj. udává typ dat každé barevné složky pixelu. Mezi běžně podporované typy patří GL_BITMAP, GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT a GL_FLOAT. Jak je vidět, máme díky parametrům format a type značnou volnost při tvorbě a/nebo načítání textur ze souboru. V dalším dílu si popíšeme rozšířené (extended) formáty textur, které lze u dnešních implementací OpenGL použít.
  • V posledním parametru pixels je předáván ukazatel na rastrová data textury, tj. ve většině případů na statické nebo dynamické pole.

Po zadání příkazu glTexImage*() jsou data textury odeslána do texturovací paměti – viz první obrázek:

Obrázek 1: zápis textury do texturovací paměti
Obrázek 1: zápis textury do texturovací paměti

3. Nastavení způsobu nanášení textury na vykreslované povrchy

Texturu lze na objekt nanášet více způsoby. Nejjednodušší a nejpoužívanější způsob spočívá v nanášení barev jednotlivých texelů přímo na zobrazovanou plochu – přímo se tím mění barvy pixelů zobrazované plochy. Druhý způsob moduluje barvu texelu barvou fragmentu, čehož lze využít například při kombinaci světelných efektů s texturou. Třetím způsobem je využití blendingu, tj. kombinace původní barvy pixelu s barvou texelu.

V některých implementacích OpenGL je také zapotřebí nastavit korektní mapování textur na zobrazovaná tělesa. Toho lze dosáhnout zavoláním funkceglHint(GL_PER­SPECTIVE_CORREC­TION_HINT, GL_NICEST). Pokud by tato funkce nebyla zavolána, mohlo by se stát, že texturovací souřadnice budou počítány pouze ve vrcholech tělesa, což by mohlo způsobit viditelné „posouvání“ textury po povrchu.

4. Zapnutí (resp. povolení) nanášení textur

Texturování musíme nejprve zapnout, jinak se nebude provádět (v tomto případě by se při vykreslování plošek použily buď barvy jednotlivých vertexů, nebo by se spočítalo osvětlení). Současně se zapnutím texturování specifikujeme, kolik dimenzí budou mít vykreslované textury.

Texturování lze zapnout příkazem glEnable(), kterému předáme jako jediný parametr konstantu určující dimenzi textury, tedy GL_TEXTURE_1D, GL_TEXTURE_2D nebo GL_TEXTURE_3D. Třídimenzionální textury však pravděpodobně na běžných grafických akcelerátorech nebudou podporovány.

Opětovné vypnutí texturování lze provést příkazem glDisable() se stejnými parametry jako u předchozího příkazu.

5. Vykreslení scény se zadanými texturovacími souřadnicemi pro každý vrchol

Vykreslení scény s texturovanými ploškami probíhá podobně jako u barevných plošek nebo osvětlených plošek. Jedinou změnou je, že pro každý vrchol musíme k jeho 2D/3D/4D souřadnicím navíc specifikovat souřadnice v textuře. Každá textura má nezávisle na své velikosti souřadnice v rozsahu od 0.0 do 1.0, tj. 2D textura je chápána jako čtverec o straně délky 1. Souřadnice do textury však můžeme zadávat libovolně, protože díky opakování (pokud je zapnuto) se obrázek textury může na zobrazované ploše šachovnicově skládat, podobně jako například obrázky na pozadí HTML stránek (tam prosím nepoužívat).

Souřadnice v textuře se zadávají příkazy:

void glTexCoord1d(
    GLdouble s
);

void glTexCoord1f(
    GLfloat s
);

void glTexCoord1i(
    GLint s
);

void glTexCoord1s(
    GLshort s
);

void glTexCoord2d(
    GLdouble s,
    GLdouble t
);

void glTexCoord2f(
    GLfloat s,
    GLfloat t
);

void glTexCoord2i(
    GLint s,
    GLint t
);

void glTexCoord2s(
    GLshort s,
    GLshort t
);

void glTexCoord3d(
    GLdouble s,
    GLdouble t,
    GLdouble r
);

void glTexCoord3f(
    GLfloat s,
    GLfloat t,
    GLfloat r
);

void glTexCoord3i(
    GLint s,
    GLint t,
    GLint r
);

void glTexCoord3s(
    GLshort s,
    GLshort t,
    GLshort r
);

void glTexCoord4d(
    GLdouble s,
    GLdouble t,
    GLdouble r,
    GLdouble q
);

void glTexCoord4f(
    GLfloat s,
    GLfloat t,
    GLfloat r,
    GLfloat q
);

void glTexCoord4i(
    GLint s,
    GLint t,
    GLint r,
    GLint q
);

void glTexCoord4s(
    GLshort s,
    GLshort t,
    GLshort r,
    GLshort q
);

které se liší jak datovým typem souřadnic (GLint, GLfloat apod.), tak jejich počtem – lze zadávat jednu až čtyři texturovací souřadnice. Pro 2D textury budeme v příkladech používat funkci glTexCoord2f(s, t), kde parametry s a t specifikují pozici v textuře (souřadnice těles ve scéně jsou podle úmluvy zadány v prostoru x – y -z, souřadnice do textury v ploše s – t).

Zadávané vlastnosti vrcholů vykreslovaných plošek se tedy liší podle toho, jakým způsobem potřebujeme plošku vykreslit:

  • Buď můžeme plošky vykreslovat jednou konstantní barvou, nebo každý vrchol jinou barvou s provedením interpolace barvy. V tomto případě se u vrcholů zadává jejich barva a pozice: glColor() a glVertex().
  • Nebo můžeme zadat vlastnosti materiálu tělesa a parametry světelných zdrojů. Potom se u vrcholů zadává jejich normála a pozice: glNormal() a glVertex().
  • Poslední možností je vykreslování otexturovaného tělesa. V tomto případě se u každého vrcholu zadává souřadnice do textury a samozřejmě jeho pozice: glTexCoord() a glVertex().

Obrázek 2: použití texturovací paměti při vykreslování
Obrázek 2: použití texturovací paměti při vykreslování

Pokračování

V dalším pokračování si popíšeme způsob nastavování parametrů textur a rozšířenou podporu formátů textur v některých verzích knihovny OpenGL.

Ukázkové příklady

V prvním demonstračním příkladu je ukázán způsob vykreslení čtverce s nanesenou texturou. Textura není v tomto případě načtena z externího souboru, ale je vypočtena pomocí jednoduché funkce po spuštění programu. K dispozici je zdrojový kód tohoto příkladuzdrojový kód se zvýrazněnou syntaxí.

Obrázek 3: Screenshot z prvního příkladu
Obrázek 3: Screenshot z prvního příkladu

Druhý demonstrační příklad zobrazuje tentýž čtverec, ale pro každý vrchol je možné interaktivně měnit souřadnice v textuře (tj. v ploše s-t). Opět je k dispozici zdrojový kód a zdrojový kód se zvýrazněnou syntaxí.

Obrázek 4: Screenshot z druhého příkladu
Obrázek 4: Screenshot z druhého příkladu

Po spuštění posledního příkladu se zobrazí otexturované trojrozměrné těleso (jedná se o domeček použitý i v několika předcházejících dílech), kterým je možné natáčet a pohybovat pomocí levého a pravého tlačítka myši. Zde je zdrojový kód a zdrojový kód se zvýrazněnou syntaxí.

bitcoin_skoleni

Obrázek 5: Screenshot ze třetího příkladu
Obrázek 5: Screenshot ze třetího příkladu

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

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.