Texturování 4
Obsah
Co je to mipmapping a kde ho použít?Podpora mipmappingu v OpenGL
Příklad použití
Pokračování
Demonstrační příklady
Co je to mipmapping a kde ho použít?
Mipmapping představuje techniku, která je často používaná pro odstranění či alespoň zmenšení některých vizuálních chyb (resp. nežádoucích rozdílů mezi jednotlivými snímky) vzniklých při pohybu těles s nanesenou texturou v trojrozměrné scéně nebo při pohybu celé scény (tj. změně orientace nebo pozice pozorovatele). Obraz těles s nanesenou texturou se na obrazovce při pohybu zmenšuje, zvětšuje či jinak deformuje, čímž také dochází k nutnosti zvětšování a zmenšování textury při nanášení texelů (rastrových elementů textury) na zobrazované pixely.
Při zvětšování a zmenšování textury můžeme pro zamezení některých vizuálních chyb využít filtraci. Nejrychlejšího zobrazení scény však dosáhneme vypnutím filtrace a výběrem vždy pouze jednoho nejbližšího texelu z textury pro každý vykreslovaný pixel:
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
Předchozí způsob je sice nejrychlejší, současně však nikterak neřeší vznik artefaktů, například moaré, způsobeného podvzorkováním obrazu. V OpenGL lze využít několika způsobů, které zamezují vznikům těchto artefaktů při zvětšování a zejména zmenšování textur. Pokud se vykresluje statická scéna, je možné použít pouze vhodné filtrace, pracující na principu výběru několika sousedních texelů a jejich bilineární interpolace při výpočtu barvy vykreslovaného pixelu:
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
Nevýhodou předchozího způsobu je určité zpomalení vykreslování, zejména díky faktu, že se musí provádět více přístupů do texturovací paměti, která v dnešní době představuje úzké hrdlo grafických akcelerátorů (rychlost použitých pamětí na grafických akcelerátorech roste mnohem pomaleji než výpočetní výkon použitých grafických procesorů – GPU).
V některých případech, zejména při požadavku na vyšší kvalitu obrázků, je možné použít antialiasing, který se v OpenGL provádí tak, že se scéna vykreslí ve vyšším rozlišení, než je rozlišení obrazovky, a poté se provede snížení rozlišení pomocí filtrace s vhodným jádrem filtru (kernelem). Rozdíl mezi scénou vykreslenou bez antialiasingu a s antialiasingem je patrný z prvního a druhého obrázku. Moderní grafické akcelerátory většinou umožňují provádět antialiasing celé scény. Tato technika je nazývaná FSAA – Full SceneAntialiasing. Nevýhodou je samozřejmě nižší rychlost vykreslování.
Obrázek 1: Trojrozměrná scéna vykreslená bez povoleného antialiasingu
Obrázek 2: Stejná scéna jako na prvním obrázku vykreslená se zapnutým antialiasingem
Při animacích však kromě moaré vznikají i další artefakty, z nichž nejviditelnější je „problikávání“ pixelů na příliš zmenšených texturách, tj. například texturách nanesených na vzdálených objektech. V těchto případech už běžná filtrace (pracující pouze pro těsné okolí nanášených texelů) není příliš prospěšná, protože s každým pohybem otexturovaného tělesa se může v rovině textury provést posun až o několik desítek texelů, čímž dochází k prudké změně vykreslované barvy.
Pro zamezení problikávání se používají textury uložené ve více rozlišeních (tj. úrovních detailu). Při vykreslování otexturovaného povrchu se nejdříve zjistí relativní velikost povrchu vůči celé textuře a poté se vybere vhodné rozlišení textury, která se posléze nanese. Tento postup má nevýhodu v tom, že při postupném zmenšování objektu by docházelo ke skokové změně textury (přešlo by se k menšímu rozlišení textury). Proto se zavádí další úroveň interpolace, kdy se vybere nejbližší větší a nejbližší menší textura a barva pixelů se vypočte interpolací mezi těmito dvěma texturami.
Nyní tedy zbývá pouze volba vhodného rozlišení textur. Z hlediska implementace interpolátoru na grafickém akcelerátoru je nejvýhodnější, aby se rozlišení textury v horizontálním i vertikálním směru snižovalo vždy na polovinu. Počet texelů je v každé následující textuře zmenšen na čtvrtinu až do dosažení textury o velikosti 1×1 pixel. Princip tvorby mipmapy je ukázán na třetím obrázku. Na čtvrtém obrázku je ukázka textur v mnoha rozlišeních, které vznikly například pomocí grafických editorů majících zabudovanou funkci Resample.
Obrázek 3: Princip tvorby mipmapy
Obrázek 4: Ukázka textur vhodných pro tvorbu mipmapy
Podpora mipmappingu v OpenGL
V grafické knihovně OpenGL jsou mipmapy samozřejmě také podporovány, protože se jedná o velmi často používanou renderovací pomůcku. Pro každé nastavované rozlišení textury je zapotřebí zavolat již dříve popsanou funkci:
void glTexImage2D(
GLenum target,
GLint level,
GLint components,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const GLvoid
*pixels
);
kde se do parametru level zadává úroveň textury v hierarchii. Nulou specifikujeme texturu se základním (tj. nejvyšším) rozlišením, jedničkou texturu s rozlišením polovičním atd. V texturovací paměti grafického akcelerátoru se vytvoří takzvaná mipmapa, tj. textura, ve které jsou uloženy všechny velikosti textur rozdělených na jednotlivé barevné složky RGB. Ukázka mipmapy je zobrazena na pátém obrázku. Z tohoto obrázku je patrné, že se jedná o hierarchickou strukturu, ve které se dají jednoduše adresovat korespondující pixely v různých rozlišeních. Při adresaci přitom vystačíme pouze s aditivními operacemi a bitovými posuny.
Obrázek 5: Ukázka interní reprezentace mipmapy uložené v texturovací paměti grafického akcelerátoru
Textury s více úrovněmi detailu můžeme buď vytvořit programově s použitím různých filtrů, nebo je možné použít funkce pro automatické generování mipmap. Tyto funkce jsou obsaženy v nadstavbové knihovně GLU a mají tvar:
int gluBuild1DMipmaps(
GLenum target,
GLint components,
GLint width,
GLenum format,
GLenum type,
const GLvoid * data
);
int gluBuild2DMipmaps(
GLenum target,
GLint components,
GLint width,
GLint height,
GLenum format,
GLenum type,
const GLvoid * data
);
Parametry těchto funkcí a jejich význam odpovídá parametrům funkcí glTexImage1D() a glTexImage2D().
Při použití mipmappingu je také možné specifikovat další typy filtrů použitých při zmenšování textur. Kromě výběru nejbližšího souseda (GL_NEAREST) a interpolace nejbližších sousedů (GL_LINEAR) jsou k dispozici i interpolace prováděné mezi dvěma texturami (vybírá se nejbližší menší a nejbližší větší textura):
- Při nastaveném filtru GL_NEAREST_MIPMAP_NEAREST je pro obarvení pixelu vybrán nejbližší texel z nejbližší větší nebo menší textury. Tento filtr poskytuje vizuálně nejhorší výsledky, vykreslovámí je však nejrychlejší.
- GL_NEAREST_MIPMAP_LINEAR – vybere dva nejbližší texely z obou textur a provede mezi nimi lineární interpolaci. Tímto filtrem se dají jednoduše odstranit nepříjemné skokové změny v obraze, které nastávají v případě, že se zobrazované těleso příliš zvětší nebo zmenší a provede se tak výběr jiné dvojice textur z mipmapy.
- GL_LINEAR_MIPMAP_NEAREST – provádí billineární interpolaci nejbližších texelů v jedné textuře. Při zmenšování nebo zvětšování tělesa mohou nastat skokové změny ve vykreslované textuře.
- GL_LINEAR_MIPMAP_LINEAR – nejprve se použije interpolace pro výpočet barev texelů v obou texturách a poté se výsledná barva spočte další interpolací mezi dvěma předešlými (ve skutečnosti se tedy provádí trilineární interpolace). Tento filtr je sice nejpomalejší, ale poskytuje nejlepší vizuální výsledky.
Příklad použití
V následujícím kódu bude ukázán způsob, jakým se mipmapa vytvoří. Ve skutečnosti však netvoříme korektní mipmapu, protože v každé úrovni detailu budou vykresleny textury s jinou barvou, což bude patrné také po spuštění níže uvedených demonstračních příkladů.
//---------------------------------------------------
// Vytvoření rastrového vzorku pro textury v
mipmapě
//---------------------------------------------------
void makeRasterTexture(void)
{
int i,j,c;
unsigned char
* P;
// textura na nulté úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT; j++) {
P=texture0[j][0];
for (i=0;
i<TEXTURE_WIDTH; i++) {
c=((((i&0x10)==0)^((j&0x10))==0)) * 255;
*P++=(unsigned char)c;
*P++=(unsigned char)c;
*P++=(unsigned char)c;
}
}
// textura na první úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT >> 1; j++) {
P=texture1[j][0];
for (i=0;
i<TEXTURE_WIDTH >> 1; i++) {
c=((((i&0x08)==0)^((j&0x08))==0)) * 255;
*P++=(unsigned char)c;
*P++=(unsigned char)c;
*P++=(unsigned char)0;
}
}
// textura na druhé úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT >> 2; j++) {
P=texture2[j][0];
for (i=0;
i<TEXTURE_WIDTH >> 2; i++) {
c=((((i&0x04)==0)^((j&0x04))==0)) * 255;
*P++=(unsigned char)c;
*P++=(unsigned char)0;
*P++=(unsigned char)c;
}
}
// textura na třetí úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT >> 3; j++) {
P=texture3[j][0];
for (i=0;
i<TEXTURE_WIDTH >> 3; i++) {
c=((((i&0x02)==0)^((j&0x02))==0)) * 255;
*P++=(unsigned char)0;
*P++=(unsigned char)c;
*P++=(unsigned char)c;
}
}
// textura na čtvrté úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT >> 4; j++) {
P=texture4[j][0];
for (i=0;
i<TEXTURE_WIDTH >> 4; i++) {
*P++=(unsigned char)c;
*P++=(unsigned char)c;
*P++=(unsigned char)c;
}
}
// textura na páté úrovni v mipmapě
for (j=0;
j<TEXTURE_HEIGHT >> 5; j++) {
P=texture5[j][0];
for (i=0;
i<TEXTURE_WIDTH >> 5; i++) {
*P++=(unsigned char)c;
*P++=(unsigned char)0;
*P++=(unsigned char)c;
}
}
// textura na šesté úrovni v mipmapě
// o velikosti 1 texel
texture6[0][0][0]=(unsigned char)0;
texture6[0][0][1]=(unsigned char)c;
texture6[0][0][2]=(unsigned char)0;
}
//------------------------------------------
// Nastavení parametrů textur
//------------------------------------------
void setTextures(void)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &textureName);
glBindTexture(GL_TEXTURE_2D,
textureName);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T,
GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
GL_NEAREST_MIPMAP_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, 64, 64, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture0);
glTexImage2D(GL_TEXTURE_2D, 1, 3, 32, 32, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture1);
glTexImage2D(GL_TEXTURE_2D, 2, 3, 16, 16, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture2);
glTexImage2D(GL_TEXTURE_2D, 3, 3, 8, 8, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture3);
glTexImage2D(GL_TEXTURE_2D, 4, 3, 4, 4, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture4);
glTexImage2D(GL_TEXTURE_2D, 5, 3, 2, 2, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture5);
glTexImage2D(GL_TEXTURE_2D, 6, 3, 1, 1, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture6);
glHint(GL_PERSPECTIVE_CORRECTION_HINT,
GL_NICEST);
glTexEnvf(GL_TEXTURE_ENV,
GL_TEXTURE_ENV_MODE,
GL_REPLACE);
glEnable(GL_TEXTURE_2D);
}
Pokračování
V dalším dílu si popíšeme některé formáty rastrových obrazových souborů, které lze použít pro ukládání a načítání textur.
Demonstrační příklady
V prvním demonstračním příkladu (zde najdete i obarvenou verzi pro prohlížení) je vykreslováno těleso s nanesenou 2D texturou. Textura je vytvořena jako mipmapa se základním rozlišením 64×64 texelů. Při vykreslování zmenšeného tělesa se použije výběr nejbližší velikosti textury v mipmapě a z této textury se vybere nejbližší texel. Při zvětšování vykreslovaného tělesa se taktéž vybírá nejbližší texel v textuře.
Obrázek 6: Screenshot z prvního demonstračního příkladu
Druhý demonstrační příklad (opět je zde obarvená verze pro prohlížení) navazuje na příklad předchozí s tím rozdílem, že pro výpočet barvy pixelu se při zvětšování i zmenšování tělesa používá bilineární interpolace.
Obrázek 7: Screenshot ze druhého demonstračního příkladu
Ve třetím demonstračním příkladu (obarvená verze pro prohlížení) se již při zmenšování tělesa využívají informace o texelech ze dvou nejbližších textur z mipmapy. Barevné údaje z těchto dvou textur jsou interpolovány a naneseny na vykreslovaný povrch tělesa.
Obrázek 8: Screenshot ze třetího demonstračního příkladu
Čtvrtý demonstrační příklad (obarvená verze pro prohlížení) používá interpolaci jak při výpočtu barev v jednotlivých texturách, tak i při interpolaci vypočtených barev z obou textur. Vizuální kvalita je tak nejvyšší, ovšem za cenu určitého zpomalení vykreslování. Toto zpomalení by se však projevilo až s rostoucím počtem vykreslovaných plošek ve scéně.
Obrázek 9: Screenshot ze čtvrtého demonstrační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.