Hlavní navigace

Grafická knihovna OpenGL (19): Phongův osvětlovací model

11. 11. 2003
Doba čtení: 8 minut

Sdílet

V tomto dílu seriálu o grafické knihovně OpenGL si podrobněji popíšeme princip Phongova osvětlovacího modelu, který se v ní používá pro výpočet barvy na povrchu těles. Také si popíšeme způsoby definování normál (normálových vektorů) k povrchu těles, protože při použití Phongova osvětlovacího modelu je zapotřebí nějakým způsobem určit normálu ke každému bodu na povrchu tělesa.

Materiály, stínování – druhá část

V grafické knihovně OpenGL se pro výpočet barvy povrchu těles používá takzvaný Phongův osvětlovací model. Jak již bylo naznačeno v předchozím dílu, je tento model zvolen tak, aby byl výpočet osvětlení co nejrychlejší, ale současně, aby osvětlení výsledné scény působilo přirozeně. Výsledkem obou těchto snah je samozřejmě určitý kompromisní model, který však poskytuje pozoruhodně kvalitní výsledky. Odborníci na počítačovou grafiku samozřejmě mohou oprávněně namítnout, že existují i fyzikálně přesnější modely, například Cook-Torrancův, ty jsou však výpočetně složitější, proto se nehodí pro real-time grafiku, na kterou je knihovna OpenGL zaměřena. Tyto přesnější modely lze využít například v některých pokročilejších raytracerech (ale zdaleka ne ve všech).

Phongův osvětlovací model

V Phongově osvětlovacím modelu se světlo na povrchu tělesa rozkládá do tří světelných složek (ilustrační obrázky byly uvedeny v minulé části): ambientní složky (ambient light), difúzní složky (diffuse light) a odlesků (specular light). V tomto modelu se celková intenzita světla počítá zvlášt pro každou barevnou složku z barvového modelu RGB (red, green, blue). Pro výpočet se používá následující vztah, který je aplikován na každou barevnou složku zvlášť, ve skutečnosti tedy počítáme intenzity Ired , Igreen , Iblue :

I=ca Ia +cd Id (NL)+cIs (VR)n

Význam jednotlivých členů v tomto vztahu je následující:

  • I představuje celkovou intenzitu světla tak, jak je vnímána uživatelem. Ve skutečnosti se vždy spočítají tři na sobě nezávislé barevné složky R, G, B, takže se předchozí vztah počítá třikrát.
  • Ia představuje intenzitu ambientní složky světla, tj. té části světla, která na osvětlované těleso dopadá ze všech směrů rovnoměrně. Intenzita odraženého světla (tj. součinu caIa) je nezávislá na vzájemné poloze zdroje, tělesa a pozorovatele. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • Id představuje intenzitu difúzní složky světla, tj. té části světla, která dopadá na těleso z jednoho světelného zdroje (ať už bodového, či směrového) a odráží se do všech směrů rovnoměrně. Intenzita odraženého světla (tj. součinu cdId(NL)) je závislá na vzájemné poloze normály stěny tělesa N a vektoru dopadu světla L (viz další text). Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • Is představuje intenzitu odlesků, tj. té části světla, která dopadá na těleso z jednoho světelného zdroje (opět může jít buď o bodový, nebo o směrový zdroj světla) a odráží se převážně v jednom směru podle zákona odrazu světelných paprsků. Intenzita tohoto odraženého světla závisí na vzájemné poloze světelného zdroje, povrchu tělesa a pozice pozorovatele. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • ca je koeficient (resp. faktor) materiálu pro ambientní složku světla. Pro běžné materiály nabývá tento koeficient hodnot v rozmezí 0 až 1, kde nulová hodnota znamená, že se ambientní světlo od materiálu vůbec neodráží, hodnota 1 naopak nastavuje úplnou odrazivost. Hodnota tohoto členu v přechozí rovnici se nastavuje při vytváření materiálu.
  • cd je koeficient materiálu pro difúzní složku světla. Pro běžné materiály nabývá tento koeficient hodnot v rozmezí 0 až 1 s podobným významem jako u předchozího koeficientu. Ve většině případů lze ambientní a difúzní koeficient materiálu nastavit na stejnou hodnotu, což je v OpenGL samozřejmě podporováno. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření materiálu.
  • cs je koeficient odlesků s vlastnostmi podobnými jako u předchozích dvou koeficientů. Zde je nutné si uvědomit, že ve skutečnosti každý koeficient nastavujeme třikrát pro tři barevné složky RGB. Hodnota těchto koeficientů potom udává barvu materiálu při osvětlení bílým světlem, protože pouze bílé světlo je nastaveno na hodnotu (1.0, 1.0, 1.0). Při jiném osvětlení se mezi sebou pronásobí intenzity složek světelného zdroje a koeficienty materiálů, takže například sytě červený objekt bude v sytě modrém světle na obrazovce černý!
  • V součinu NL je použita normála k povrchu tělesa N a vektor ze světelného zdroje do bodu na povrchu tělesa, jehož barvu počítáme. Pokud jsou tyto vektory lineárně závislé (tj. rovnoběžné), je světelný zdroj umístěn kolmo nad povrchem tělesa a tím pádem je difúzní složka světla nejvíce intenzivní. Význam těchto dvou vektorů je zobrazen na prvním obrázku. Ještě před výpočtem skalárního součinu NL musí být oba vektory normalizovány, tj. jejich velikost musí být jednotková.
  • V součinu VR je použit vektor ideálně odraženého paprsku R a vektor z pozice pozorovatele do bodu na povrchu tělesa V, jehož barvu počítáme. Vektor R lze vypočítat ze znalosti vektorů L (vektor světla) a N (normála k povrchu) – postup je naznačen na druhém obrázku. Význam těchto dvou vektorů je zobrazen na prvním obrázku. Podobně jako u vektorů N a L, i vektory V a R musí být normalizovány.
  • n je exponent u výrazu (VR)n, který udává míru lesklosti tělesa. Difúzní těleso bude mít exponent nulový, vysoce lesklé těleso zde může mít hodnotu například 50. Čím vyšší je hodnota tohoto exponentu, tím jsou odlesky na tělese menší (plochou), ale intenzivnější. Vliv velikosti exponentu na lesklosti je zobrazený na třetím obrázku.

Vektory použité v Phongově osvětlovacím modelu
Obrázek 1: Vektory použité v Phongově osvětlovacím modelu

Postup při výpočtu odraženého paprsku - vektoru RObrázek 2: Postup při výpočtu odraženého paprsku – vektoru R

Vliv velikosti exponentu na lesklost materiáluObrázek 3: Vliv velikosti exponentu na lesklost materiálu

Normály

Při výpočtu osvětlení je bezpodmínečně nutné zadávat u každého vrcholu jeho normálu, jinak by nebylo možné spočítat barvu na povrchu tělesa (viz předchozí vzorec, kde se vyskytuje vektor N). Pokud není normála nastavena, bere se implicitní hodnota normály N=(0.0, 0.0, 1.0) (tj. normála míří ve směru osy z), což není ve většině případů korektní. Příkaz pro nastavení normály je velmi podobný příkazu pro nastavení barvy nebo polohy vertexu, avšak narozdíl od těchto příkazů, které existovaly ve více verzích s různým počtem parametrů, se vždy zadávají tři složky normálového vektoru: nx,ny a nz. Normála vrcholu je, podobně jako barva, stavová veličina, takže je možné jednu hodnotu normály použít i pro více vrcholů. V závislosti na typu dat použitých pro reprezentaci normály je možné zvolit jednu z těchto funkcí:

void glNormal3b(
  GLbyte nx,
  GLbyte ny,
  GLbyte nz
);

void glNormal3s(
  GLshort nx,
  GLshort ny,
  GLshort nz
);

void glNormal3i(
  GLint nx,
  GLint ny,
  GLint nz
);

void glNormal3f(
  GLfloat nx,
  GLfloat ny,
  GLfloat nz
);

void glNormal3d(
  GLdouble nx,
  GLdouble ny,
  GLdouble nz
);

Další možností je použití funkcí, které jako svůj parametr akceptují pole o třech prvcích:

void glNormal3bv(
  const GLbyte *v
);

void glNormal3sv(
  const GLshort *v
);

void glNormal3iv(
  const GLint *v
);

void glNormal3fv(
  const GLfloat *v
);

void glNormal3dv(
  const GLdouble *v
);

Pro korektní funkčnost výpočtu osvětlení musí být normálový vektor normalizován, tj. délka tohoto vektoru musí být jednotková. Normalizaci je možné provést buď programově, nebo automaticky pomocí funkcí OpenGL.

Programová normalizace spočívá v tom, že se vypočte délka normálového vektoru podle vzorce L=sqrt(nx2+n2+nz2)a touto délkou se vydělí každá složka normálového vektoru:
nx‚=nx / L
ny‘=ny / L
nz'=nz / L

Druhou možností je povolení automatické normalizace, což se provede funkcí glEnable(GL_NOR­MALIZE). Zakázání se provede příkazem glDisable(GL_NOR­MALIZE). Při provádění automatické normalizace se však buď programově, nebo na grafickém akcelerátoru provádí složité matematické operace, jako jsou odmocniny a podíly, což může výrazně zpomalovat vykreslování celé trojrozměrné scény. Proto je výhodnější normalizaci provést programově před začátkem vykreslování nebo mít již normály uložené v normalizované podobě tak, jak to dělají některé 3D modelovací programy.

Typ stínování

V OpenGL lze používat dva typy stínování: konstantní a Gouraudovo. Pomocí speciálních programovacích technik však lze dosáhnout i Phongova stínování (pozor, neplést s Phongovým osvětlovacím modelem, který byl popsán výše).

Při použití konstantního stínování je pro každou plošku (tj. například trojúhelník) spočítána jedna barva a celá ploška je při vykreslování touto barvou vykreslena. Výsledkem je, že jsou na zobrazovaném tělese jasně patrné všechny hrany mezi ploškami, což je nepříjemné zvláště u oblých tvarů.

Problém viditelnosti hran částečně řeší Gouraudovo stínování, u kterého se spočítají barvy jednotlivých vrcholů (normály jsou zadané pro vrcholy, takže se přímo použije dříve uvedený vzorec) a vykreslovaná ploška mezi těmito vrcholy je vyplněna přechodem mezi těmito barvami. Výsledkem jsou jemnější hrany, které jsou méně viditelné. Gouraudovo stínování samozřejmě způsobuje také vizuální chyby, protože se osvětlení počítá pouze ve vrcholech a ignorují se světelné podmínky na ploškách. To například znamená, že zejména na velkých plochách jsou jasně patrné absence odlesků.

Konstantní stínování se zapne příkazem glShadeModel(GL_FLAT), Gouraudovo stínování potom příkazem glShadeModel(GL_SMO­OTH). Ukázky obou typů stínování jsou předvedeny na demonstračních příkladech.

Pokračování

V dalším, už jubilejním dvacátém pokračování tohoto seriálu si řekneme další informace o nastavování pozic a orientace světelných zdrojů. Také si popíšeme reflektorové světlo, které je sice výpočetně nejnáročnější, ale na druhou stranu s ním lze vytvářet velmi zajímavé efekty.

Demonstrační příklady

Po přeložení a spuštění prvního demonstračního příkladu (zde je HTML verze tohoto příkladu) se zobrazí klasická čajová konvička. V tomto příkladu je nastaveno a povoleno zobrazování odlesků spolu s ambientní a difúzní světelnou složkou. Je však zapnutý konstantní (flat) režim stínování, což znamená, že vždy celá ploška je vyplněna konstantní barvou. Při použití tohoto režimu jsou jasně patrné všechny plošky, ze kterých se povrch čajové konvice skládá (i když se jedná o poměrně pečlivě vymodelované těleso obsahující několik set plošek).

Druhý testovací příklad (opět HTML verze) zobrazuje čajovou konvičku osvětlenou stejně jako v příkladu přechozím. Jediný rozdíl spočívá v tom, že místo konstantního stínování je použito stínování Gouraudovo, u kterého se barva jednotlivých plošek vypočítá interpolací barev ve vrcholech těchto plošek. Zejména na velkých ploškách, od kterých by se mělo odrážet světlo (nebo při velkém přiblížení) jsou vidět vizuální chyby vzniklé použitím tohoto typu stínování.

Ve třetím demonstračním příkladu (zde přikládám i HTML verzi s obarvenou syntaxí) je ukázán způsob definice normál pro každý vrchol tělesa. Normály jsou v tomto příkladu již normalizované (tj. mají jednotkovou délku), proto může být automatická normalizace vypnuta. Můžete si sami vyzkoušet, co se stane, když některá normála nebude mít jednotkovou délku, tedy např. glNormal3f(5.0f, 0.0f, 0.0f).

UX DAy - tip 2

V dnešním posledním příkladu (na HTML verzi s obarvenou syntaxí jsem samozřejmě také myslel) je předchozí příklad změněn tak, že normály ve vrcholech povrchu tělesa nejsou normalizovány. Proto je zapotřebí zapnout automatickou normalizaci, která však může značně zpomalovat vykreslování. Proto je vždy lepší normalizaci provést ještě před prvním vykreslením trojrozměrné scény.

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ý?