English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform |
3D - Engine 09 - Světlo a metody stínování
Hmmno .. Tak už sem tady zase. Zatím sem ještě pořád živej :-) ale pořád čekám tu atomovku,
co mi má ten hodnej pán poslat :-). No nic, pustíme se do toho.. Dneska máme díl o světlech,
osvětlování, osvětě, a sexu a tak, co to kecám.. ? Napřed jsem myslel že to spojím všechno
do jednoho dílu, ale ono by to vyšlo asi >64 kB, což můj milý Volkov Kchomándr nezvládá.
Takže tady je předběžnej obsah :
Míchání barev Na klasických PC máme tzv. RGB systém (jak už si mnozí všimli) pracuje to tak že v jednom čísle jsou uložené hodnoty červené, modré a zelené, pomocí nichž lze namíchat (téměř) jakou- koliv barvu. Ti co hrajou hry určitě taky znají pojem "barevná hloubka", to je velikost toho čísla. My máme 15, 16 a 32 bpp (bits per pixel). Při patnácti je 5 bitů od každé barvy, ten poslední bit (nejvyšší) zůstane prázdný. Při šestnácti je to 5 bitů červené, 6 bitů zelené a 5 bitů modré. (oko je totiž nejvíc citlivé na zelenou ! ne červenou - ta ho jen dráždí.) No a těch 32 bpp je 8 bitů od každé složky, vrch je zase prázdný. Taky se občas objevuje 24 bpp, což je to samé, ale nedá se to vzít jako jedno číslo, v c-čku máme jen 8-mi, 16-ti a 32-ou bitová čísla - byte, short a long. DirectX modul používá víc kvalitní režim 32bpp, a nic jiného (to proto že pro jinou barevnou hloubku by se musely přepsat všechny rutiny kde se mixujou barvy.) Teď když už víme jak vypadá barva v programu, můžeme se podívat co s tím jde provádět. Nejjednodušší je násobení barev : Násobení barev Násobení barev používá většina her jako Quake, Unreal a tak.. Je to přesně to jak se to jmenuje - RGB složky obrázku se vynásobí s RGB složkami barevného kanálu (to je obrázek, který symbolizuje světlo, dopadající na ten první obrázek) a potom se ještě vydělí jeho maximální hodnotou (u nás je to osm bitů na kanál => max. hodnota 2^8 = 256) Musí se násobit každá složka zvlášť, ne jen jako dvě čísla. Výsledek vypadá takhle : Tady si můžete stáhnout jednoduchý example, snad to ani nebudu vysvětlovat ..
Sčítání barev
Taky additive transparency - aditivní průhlednost. Funguje tak že se všechny složky zase sečtou, pokud některá z nich přeteče (> 256) je upravená na maximum (256). Takhle se dělají všelijaké efekty jako oheň, lens flares ("odrazy v objektivu"), ruzné průhledné věci (sklo) dost se používá v Quake3 :
Potom ještě existuje substraktivní míchání, to je to samé, jen se barva odečítá a kontroluje
se 0 místo maxima. Používá se na kouř, zase na sklo, občas na fleky po stěnách, kref.
Ještě jsem zapomněl dodat že se zase složky sčítají odděleně.
Trošku fyziky Možná si řeknete na co fyziku ? Ale ono by to bez ní nevypadalo moc dobře. V tomhle díle asi budou jen techniky co nemají moc společného s fyzikou (ale přesto jsou použitelné a vypadají docela dobře) Tak se na to vrhneme : Lambertova rovnice Pan Lambert sice zřejmě nebyl programátor, ale přišel na hezkou věc : Čím větší úhel svírá normála povrchu a vektor, jdoucí od světla (světlo je bod) do bodu, pro který chceme zjistit osvětlení, tím méně světla na něj dopadne. To jste se asi všichni učili už na základce, ale v trocu jiném kontextu - prostě když je plocha šikmo tak paprsky dopadají dál od sebe než když je na ně kolmo. Pan Lambert to shrnul do rovnice : I = Imx × cos(fi) kde: I = světlost bodu Imx = síla světla fi = úhel, svírající normála a vektor od světla do toho bodu (ne naopak!) Když se na to podíváte, uvidíte, že nemusíme počítat kosinus, ale jen skalární součin obou vektorů :
Snad je to z toho pseudo-c pochopitelné :-). Takhle se prostě spočítá světlo, které povrch pohltí. Potom bude mít pořád stejnou světlost pokud se nepohne světlo. Chcete mít takový povrch, kde bude záviset i na pozici kamery (v praxi lesklé povrchy) ? Spočítejte tuhle světlost a pak ještě jednou, jenže intenzita světla bude předchozí výsledek a pozice světla bude pozice kamery. Chápete ? Prostě se započítá úhel při dopadu a úhel pohledu. Z pohledu fyziky to nedává žádný smysl, ale funguje to dobře. Tři druhy světla Možná si řeknete že to s fyzikou taky nemá nic společného, ale nebudete mít docela pravdu. Jako všechno je to optimalizace přesného fyzikálního modelu. (a taky trochu ulehčení práce testerům a designérům) Ve skutečnosti (jak asi víte) existují matné (diffuse) a lesklé (specular) materiály. Proto byla zavedena diffuse a specular složka světla. Ta třetí se jmenuje ambient a je to světlo, rozptýlené v prostředí. Ambient light Světlo, rozptýlené v prostředí. Co si pod tím představit ? Ve skutečnosti něco takového neexistuje - je to světlo, dopadající na všechny povrchy ze všech stran rovnoměrně. (takže v našem engine bylo ambient light nastavené na 255 - všechno mělo plný jas) Používají to designéři map, aby se jim chyby neskrývaly ve tmě a tak.. Jinak to vypadá nějak takhle :
Diffuse light
Je to světlo, odrážené povrchem všemi směry (tudíž je jedno odkud se díváte) :
Kdo to nepochopil - je to vyzařovací diagram difůzního povrchu : paprsek dopadne pod nějakým
úhlem (na tom ještě záleží), ale pak se rozptýlí do všech stran - tudíž nezáleží na tom,
odkud se díváte. To už se dá docela dobře použít, používá to většina 3D-editorů pro hry
(většinou je možnost si to zapnout) Vypadá to asi takhle :
Specular light
To je zase světlo, které dopadá na lesklý povrch a odráží se jen jedním směrem. Takže záleží odkud se díváte, čím je úhel svíraný s odraženým paprskem menší, tím víc světla vidíte. Vyzařovací diagram lesklého povrchu vypadá nějak takhle :
Potom by to vypadalo nějak takhle :
A ve skutečnosti...
V praxi samozřejmě není žádný povrch dokonale lesklý nebo dokonale difůzní, proto se zavedly tyhle složky, aby se mohly mixovat mezi sebou. Ve 3D-enginu se většinou obyčejně sečtou :
Shrnutí a optimalizace
Většina starších enginů používala jen diffuse a ambient složky, protože specular složka je závislá na pohledu a proto se musí každý snímek přepočítávat. Přišlo sice pár optimalizací (fake Phong shadihg), ale jak uvidíte, nejsou moc použitelné pro herní engine a tak. Většina engine nepočítá jen s Lambertovou rovnicí, ale v úvahu se bere i vzdálenost (taky ukážeme) a viditelnost - stíny (to taky ukážeme ;-)). Většina enginů se snaží aspoň část osvětlení předpočítat a ušetřit tak čas při kreslení. Tak a teď už jdeme na věc. Flat shading Flat shading jsme tu už jednou měli, jenže bez textur a navíc jsem neobjasnil, co se to tam vlastně děje. Flat shading znamená, že se určí hodnota světlosti pro jeden celý polygon, tudíž je hodně vidět z jakých polygonů je scéna složená a vypadá hodně počítačově. Jak ji určit ? Jednoduše použijte Lambertovu rovnici - možná to zní trošičku složitě, ale není : Většinou se ale světlo uvažuje v kameře, takže stačí natransformovat normálový vektor s transformační maticí příslušného polygonu a jeho z-ová složka je světlost. (v obou případech je to číslo od nuly do jedné, tudíž to znamená vynásobit jej ještě sílou světla (256), nebo může být ke každému světlu přiřazená RGB barva a bude se násobit každá složka, takže polygony budou osvícené barevně) Pro víc světel se budou RGB hodnoty sčítat, ale musí se zase kontrolovat aby nám nepřetekly. Potom ještě můžeme započítat vzdálenost světla, tj. vynásobit ty kosinusy ještě rovnicí :
Tak - mx_len je vzdálenost, kam světlo už nedosáhne, len je vzdálenost světla a středu polygonu a min_len je vzdálenost, kde jas světla neklesá. Výsledek musí být vždy kladný a ne větší než jedna. Je to vlastně jednoduše odvoditelná rovnice a vzhledem k tomu že ji píšu z hlavy doufám že jsem ji nesplet. Jak jste si mohli přečíst, example je hra "life". Kdo se učil paskal, téměř určitě ji musí znát. Jde o to že máme nějaké pole prvků, na začátku jsou všechny nastaveny na "mrtvé", jen pár na "živé". Potom jsou stanoveny podmínky, za kterých má buňka potomky - pokud je jich málo vedle sebe tak "chcípne" na osamělost, pokud moc tak na nedostatek jídla a v ideálním případě má potomka. Přesně je to : Pro buňku, která je živá :
Download Life
Určitě si s tím zkuste pohrát, ten 2D life taky stojí za to (Example.txt) ..
Smooth shading Smooth shading je o krok pokročilejší technika. Barva se neurčuje pro jednotlivé polygony, ale pro jejich vrcholy. Vyžaduje to znát normály vertexů, což by se mohlo zdát jednoduché a taky by bylo, kdyby na objektech nebyla textura. Dělá se to tak, že se vezme vrchol a jeho normála bude průměr normál všech faců, které jsou jím definovány. (To je nejkostrbatější věta za půl roku mýho života :0) Ono by stačilo uchovávat id vertexu, ale kvůli textuře se stane že v jednom vertexu jsou dvě různé souřadnice textury, a proto se musí vytvořit další vertex. Takže nestačí porovnávat id / adresu v paměti, ale musí se porovnávat pozice, aby bylo všechno správně hladké. Dál se normálně spočítá pomocí Lambertova vzorce (a) vzdálenosti světel barva světla v jednotlivých vrcholech a ta se dál interpoluje stejně jako souřadnice textury. Ale je potřeba aby to bylo o dost přesnější, takže se nepoužije naše rychlejší metoda s rovinami, ale souřadnice se interpolují podél hran polygonu. To nám zaručí že hodnota nikde nepřeteče a nezpůsobí tak šum na hranách. Jinak je to docela prosté a jednoduché. Jedinou zákeřností smooth shadingu je, že (samozřejmě) nemůžeme mít místo s vyšší světlostí uprostřed polygonu, ale jen na kraji. Takže pokud chcete, aby to vypadalo dobře, nesmíte nechávat velké polygony, ale přerozdělit je na menší. Tahle metoda nemusí být použitá jen na světlo, ale třeba i na mlhu a ostatní efekty. Používá ji třeba Morrowind : The elder scrolls - na mlhu i na světlo. Když už mluvím o mlze - je to stejné jako se světlem, jen nezáleží na normále. Prostě se určí hranice kam až kamera prohlédne a jak daleko od ní se začne mlha projevovat (být vidět) a spočítá se hodnota, která se interpoluje po polygonu a určuje váhu barva mlhy / (textura × světlo), je to jednoduché. Example je místnost se světly a právě takovou mlhou : Fog xample
Phong shading
Už jsem se o phongovi zmiňoval v souvislosti s odlesky a lesklými materiály. Přesně na to se phongovo stínování hodí. Pan N. H. Phong objevil to samé co pan Lambert, ale z trochu jiného pohledu : Čím blíž je vektor paprsku světla, odraženého od bodu kde chceme zjišťovat světlost vektoru, kudy "se dívá kamera" tím větší světlost bude. Ovšem je to pakárna, ale jde přece o optimalizaci. To je sice hezké, ale sežere to nejmíň odmocninu na pixel, a to je už nepoužitelné (realtime). Malé ulehčení vykoumal pan Gouraud a tím je smooth shading, kterému se někdy taky říká Gouraud shading. Tím se ale (jak už jsem se zmiňoval) okrademe o hot-spot (odlesk, kde je intenzita světla největší), a nebo o čas protože objekt bude muset být (vzhledem k tomu že odlesk bývá dost malý) rozdělený na hodně moc polygonů. Pak přišel někdo chytrý a spojil obě metody dohromady a vznikl geniální Enviroment - mapping. Enviroment mapping Enviroment mapping je metoda, kde máme hotspot předpočítaný do tzv. enviroment mapy. Ta se potom na polygon natáhne, ale ne podle předpočítaných souřadnic, ale podle normál vertexů. (ty musí být roznásobené na velikost enviroment-mapy). Je to velice jednoduché a dá se s tím dělat hromada věcí (tím mi to připomíná mou holku :-)) záleží jen co nasypete do enviroment mapy a co s ní budete dělat v texturovací smyčce. Můžete v tom mít předpočítaný spot světla a tím násobit texturu. Nebo v tom můžete mít přímo texturu v barvě kovu nebo něčeho se světlým středem. Nebo tam (a podle toho se to jmenuje) můžete mít předpočítaný obrázek zase se světlým středem a dostanete úžasný odraz okolí v objektu :0 !! Má to jen pár nevýhod. Nejde s tím dělat target spoty (třeba baterka nebo něco co nesvítí dokola, ale jen určitým směrem) a je problém mít víc světel, nebo kontrolovat stíny. Samozřejmě to jde, ale bude to nelidsky pomalé. Tady je example :
Tohle je kus funkce DrawObject(), který se stará o počítání souřadnic env-mapy. A tady je example : To nahoře jsou envmapy z Quake ]I[ a to dole je printscreen ;-) (všimněte si jak se tam odráží můstek, světla...) Enviroment mapping example
Bump mapping
Variací je spousta a navíc je tu možnost použít to pro 3D-Textury - Bump Mapping. Vysvětlím vám to někdy jindy, teď jen krátce. Bump mapa je mapa výšky textury (tedy její reliéf). Tím že textura není placka, ale má výstupky, se vlastně mění normála toho bodu na který je mapa namapovaná, takže jen stačí trochu poposunout enviroment-mapu. To se může do bump tabulky předpočítat, pokud znáte rozměr enviroment - mapy :
Tohle se pak jen přičte k sopuřadnicím enviroment-mapy a pokud je to dobře zfiltrované (musí se zfiltrovat bump-mapa a potom ještě jednou env-mapa), vypadá to úúúúžasně : Obrázky z dema Solstice od Sandmana Obrázky ze hry Far Cry - ukázky šikovně použitého bump-mappingu
Shadow buffer
Jak název napovídá, shadow buffer je technika kde se pro zjišťování stínů použije buffer. Je to v podstatě jednoduchá technika, která na GF3 bude šlapat perfektně a bude dávat perfektní výsledky. Jde o to že se vytvoří buffer určité velikosti, do kterého se vykreslí scéna z pohledu světla, ale ne texturovaně, alébrž ID polygonů. Každému polygonu se potom taky uloží souřadnice, které měl v tom bufferu (2D) a když se kreslí z pohledu kamery, natáhne se i ten buffer (říká se tomu taky Poly Id buffer) a porovnává se s id právě kresleného polygonu. Pokud je stejné, znamená to že polygon je od světla vidět a tudíž je osvětlený (ale jen v aktuálním pixelu). Potom je ještě potřeba určit nějakou světlost. Světlost stínu je ambient light a světlost světla je ambient + něco (třeba Gouraud = Smooth) Stíny generované touhle metodou jsou ostré jako břitva :-) a nejdou odfiltrovat (špatně), takže když má buffer nízké rozlišení, stíny jsou hranaté. Tahle technika byla perfektně použitá v demu The Fulcrum (Matrix, 1997) a taky v jedné zapomenuté hře (každopádně - někdo si ji pamatoval a připomenul, byla to hra "Seed"), která byla dafault nastavená na software rendering a můj stroj při ní zcela zatuhl. (P233 MMX cha cha - dejte mi na novej :-)) Říkalo se že to je Lara vs. Quake, ale už opravdu nevím. Ale pak na voodoo šlapala perfektně - hromada stínů, efektů, krve, maso. Ještě musím dodat že buffer se musí nějak zaoblit (nechcete přece mít světlo ve tvaru čtverce :-)), buďto se do něj vykreslí okolí kruhu, nebo něaký nápis nebo cokoli. (Taky třeba buffer pro barevné filtry - Mafie) Tady je obrázek z dema spotlite (to byl Matrixův starší počin - mám dojem že první ... Novější The Fulcrum se mi nepodařilo printscreenovat :-( ). Na jednom světle je namapovanej obrázek perfektního grafitti :
Pak to taky bylo perfektně použité ještě v několika demech. Takže trošku přesněji : každý
vertex má o tři souřadnice víc (x, y, z v lightspace - ne perspektivně korektní, prostě
jen to, co vyleze z transformace :-)) takže to bude vypadat nějak takhle :
Na tom nic moc není, prostě když kreslíme objekt tak ještě zaznamenáme to co jsme spočítali. Potom, když už jsme vykreslili celý svět, nevygenerujeme RGB obrázek, ale jen id polygonů : Poly-Id buffer
Pak se musí udělat operace, kdy se část bufferu smaže (nahradí -1 = tady černá) takže je
vidět jen to co má být osvětlené :
Poly-Id buffer s naznačeným tvarem kužele
Potom už nám stačí jen vykreslit polygony (znovu - zpomalení) z pohledu kamery a spolu se
souřadnicemi textury se interpoluje i souřadnice v shadow-bufferu. Při kreslení se musí
kontrolovat, zda id na bufferu je shodné s id kresleného polygonu. Pokud je stejné, tak to
znamená, že polygon je osvětlený (to už jsme si řekli), ale právě tady je zádrhel - máme
souřadnice ve 3D-světě a my je potřebujeme srovnat do bufferu. Ještě ve fixed-pointu k x, y
přičteme polovinu šířky a výšky bufferu (taky ve fp), takže získáme pozici v bufferu bez
posunutí. Potom to nekreslíme perspektivně korektně podle z kamery, ale podle z z pohledu
světla, které navíc interpolujeme perspektivně korektně podle z kamery. No, je to celé
zamotané ale snad to pochopíte z examplu. Bod který spočítáme (x a y) navíc může ležet mimo
náš buffer, takže musíme kontrolovat hranice. Hrúúza - bez 3D karty se stíhá tak 1 světlo,
víc ani náhodou. Ale výsledek stojí za to :
Výsledek (3D-Max)
Tak se koukneme dál na example. Obsahuje pár nových funkcí. V Raster.cpp přibyla funkce pro
kreslení shadow-bufferu (ReleaseShBuffer()), která funguje úplně stejně jako její předchůdce
(ReleaseScreen()) jen nekreslí textury ale barvy. Taky přibyla optimalizovaná funkce pro
kreslení polygonů do shadow-bufferu (nepočítá se s texturou) a funkce pro kreslení objektů
do shadow bufferu. Ta už nás zajímá :
Tady se uloží ta pozice natransformovaných vrcholů. Vidíte že se s ní opravdu nic neprovádí. Na konci se pak volá ShadowPolygon() - to je ta optimalizovaná funkce pro kreslení bez textury. Parametrem je id fejsu, které se naplní při počítání normálových rovin v load_3ds (každá face musí mít jiné id !!) Dál nás bude zajímat počítání interpolačních konstant :
To je úsek funkce, kde se počítá se souřadnicemi shadow-bufferu. Vidíte že x, y, i z v lightspace (= i, j, k) jsou perspektivně korektní podle z z cameraspace. Zbytek je už stejný (stejné výpočty jako předtím + stejné výpočty pro i, j, k) Dál jdou naše milé souřadnice do s-bufferu a vytáhnou se až při kreslení :
Tak, tady se už pracuje zvlášť s x,y,z v lightspace a x,y,z v cameraspace. Je to skoro bez rozdílu, až na zpětný převod z v lightspace (k) - započítá se ještě perspektivní zkreslení. No a zbytek funkce je už domyslitelný. Teď ještě Interpolate_Segment() :
Na začátku se vezme textura a spočítá se texel (jako vždycky). Potom se převedou souřadnice i a j (x a y v lightspace) z fixed pointu, takže teď už ukazují do shadow bufferu a zbývá jen zjistit jestli je polygon vidět, nebo není. Zjišťuje se to trochu nepřesně (a pomalu), ale kdyby se kontrolovalo jednoduše jen _sh_buffer[i + j * Width], na krajích polygonů by byly stíny, protože tahle interpolace není vůbec přesná. Mohl by tady být třeba ještě nějaký výpočet světlosti nebo jen násobení s maskou světla jako ve spotlite. To může udělat hladké okraje, ale stíny vrhané předměty budou vždycky ostré. No, vlastně tak mě napadá že by to šlo ... Vezme se Poly-Id buffer a hledají se hrany. To se dělá tak, že se vezme ještě další buffer stejné velikosti a pixel po pixelu se prochází. Pro každý pixel se vezmou okolní pixely (z Poly-Id) a pokud nejsou stejné, pixel leží na hraně :-) Potom se buffer se hranami odfiltruje - rozmaže (vezme se hrana = černá, ostatek = bílá) a použije pro modulaci světlosti. Trochu problém bude, že trošku stínu bude i na hraně osvíceného objektu, ale jinak to asi bude vypadat moc cool ... No vidíte, co člověk nevymyslí při kontrole prasopisu :-) Zkusím to napsat a hodím to někam na stránky (asi do sekce Q&A) Teď už zbývá jen si to stáhnout na disk : Shadow - Buffer xample Shadow - Buffer bez směrových světel Pokud nechcete mít jen spot-světla (reflektory), ale i normální světla, která svítí do všech stran, budete muset udělat několik (ideálně 6) spot světel, která jsou vzájemně natočená o 90°. Asi nemusím říkat jak rychlé to bude ... Shadow - Buffer s více světly Více světel znamená více bufferů, takže to musíme kreslit vícekrát. Připravíme si paměť pro světlost, vždycky vykreslíme jeden buffer z jednoho světla a potom vykreslíme obraz z pohledu kamery do té paměti, ale jen světlosti (stín = 0, světlo = volitelné) a znovu pro každé světlo. Potom k tomu připočteme ambient factor (až na konec, ne pro každé světlo) a kreslíme textury do jiného bufferu. Pak zbývá jen ty dva buffery vynásobit a dostanete obrázek s více světly. Takhle se ale celá scéna překresluje pořád dokola a dá rozum že se to nedá stíhat. NVidia měla hezké demo se třemi světly kde se všechno dělalo na jeden průchod (připravily se tři shadowbuffery) a potom se interpolovalo třikrát víc souřadnic. Nemusím říkat jaký by to byl binec kdyby jsme měli interpolovat tolik souřadnic... Na to jsou jiné metody o kterých si povíme příště. Takže programování zdar !!! Shadow - Buffer na 3D kartách Na 3d-kartách se to dělá trošku jinak. Nepoužívá se poly-id-buffer, ale z-buffer světla, natažený jako textura. Potom se pro každý polygon počítá vzdálenost od světla (ne per-vertex, ale per-pixel, protože pro polygony, které mají světlo nad svým středem by vznikaly chyby) a potom se porovnává s nataženým z-bufferem. Výhoda téhle metody je, že můžete přidat nějakou toleranci v porovnávání jestli je povrch ve stínu, nebo ne, čímž se spraví šum okolo hran a navíc stačí udělat jedno porovnávání místo devíti které musíme dělat my. -tHE SWINe- |