Hlavní navigace

Grafická knihovna OpenGL (25): mipmapping

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

Sdílet

Dnešní díl seriálu o grafické knihovně OpenGL je věnovaný technice mipmappingu, která je často používaná pro zamezení vzniku vizuálních chyb vznikajících v dynamicky se měnících scénách a animacích.

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 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
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 3: Princip tvorby mipmapy

Obrázek 4: Ukázka textur vhodných pro tvorbu mipmapyObrá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
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):

  1. Při nastaveném filtru GL_NEAREST_MIP­MAP_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ší.
  2. GL_NEAREST_MIP­MAP_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.
  3. GL_LINEAR_MIP­MAP_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.
  4. GL_LINEAR_MIP­MAP_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

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
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
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
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ě.

CS24 tip temata

Obrázek 9: Screenshot ze čtvrtého demonstračního příkladu
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.

Autor článku

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