English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform |
3D - Engine 10 - světlo a metody osvětlování 2 (stencil buffer)
Ahoj !
Tak zase jednou.. ani to moc netrvalo :-) Jak se vám líbil minulej díl ? Shadow-buffer ? Dobrý, ne ? No jo, ale trochu pomalý a ne moc realistický. Dneska se podíváme na jinej způsob jak udělat stíny no a tím je (jak jste mohli zjistit podle seznamu z minulého dílu) Raytracing Je to přesně to co to znamená - sledování paprsku. Na raytracingu může být založený i celý engine a dost lidí to tak dělá. Prostě nekreslí objekty, ale vysílá z kamery paprsky do prostoru a hledá průsečíky s objekty, které (jen ty nejbližší) pak zobrazuje. Potom objekty nejsou definované polygony (i když to taky jde ale pomaleji) ale jejich rovnicemi : potom můžete vidět raytracované koule, roviny, válce a kdovíco co lze nějak matematicky definovat. Rovnici roviny už známe : ax + by + cz + d = 0 Koule je takhle : (center.x - x)2 + (center.y - y)2 + (center.z - z)2 = radius2 Chápete ? Tomu se říká raytracing, obvykle to je hodně pomalé, ale existují i brutálně optimalizované systémy dosahující velice dobrých výsledků, třeba Trezebees .. Výhoda tohoto systému je, že nemáme hodně malých polygonů, ale třeba jen jednu kouli, nebo rovinu nebo nevímco, takže si můžeme hezky pohrát s vektory a počítat s ambient + diffuse + specular světlem, počítat odrazy objektů v sobě a podobně. Těžší je to zase s texturami. Buď se objekty kreslí jen v barvě, nebo se na ně nanese textura systémem xyz -> uvw. To se vezme normála objektu v tom místě a zjistí se její největší složka. Pokud je to x, potom u = y v tom bodě a v = z v tom bodě. Pokud y, pak u = x, v = z. No a pokud je největší z, pak u = x, v = y. Tohle se dá použít i pro náš model, pokud budete psát nějaký editor 3D, používala to většina editorů pro Quaka I, hlavně proto že textura všude bez obtíží navazuje. Spolu s raytracingem přišel taky pojem procedurální textury, to jsou textury, jejichž barva se vypočítá podle nějakého vzorečku (r = sin(x) * 256, g = sin(y) * 256, b = cos(z) * 256 nebo nějakého jiného) to se používá dodnes a hodí se to na šachovnice, nebe, různé šumy ... Taky si to zkusíme. Raytracing je jako zrozený pro C ++. Existuje třída CGeometry, která má možnost spočítat průsečík s paprskem, normálu v určitém bodě (funkce jsou jen virtuální a prázdné) Potom jsou klony - CSphere, CPlane, CTube, CTorus .. a nemusíte se starat o to s čím se paprsek protíná, C ++ to udělá za vás. Existuje ale ještě jedna metoda, vhodná pro náš polygonální svět a tou je raycasting spojený se stínovými paprsky. Pure raytracing vypadá nějak takhle : Krása, co ?
Raycasting
Raycasting jako takový je taky raytracing bez odrazů. Prostě se vyšle paprsek a kam se zapíchne, to se vykreslí. A něco podobného je ta věc se stínovými paprsky - vystřelíme paprsky z kamery (můžou být předpočítané podle úhlu klipovací pyramidy a rozlišení) a spočítáme průsečíky se světem (budeme používat bounding sphere aby to bylo rychlejší) a přichází řada na stínové paprsky - z každého bodu objektu (těch co jsou vidět) se vystřelí paprsky ke světlům a zjišťuje se jestli je světlo vidět, udělají se kejkle s normálami a můžeme spočítat diffuse (a specular) složku, kterou zmodifikujeme barvu pixelu. Kdyby jsme to dělali takhle pro každý pixel, asi by to bylo dost pomalé (kvůli těm průsečíkům) ale je tu jednoduchá optimalizace : Spočítá se to prostě třeba pro mřížku 64×64 a ta se na obraz roztáhne, na 320×200 bez nějákých kvantovacích problémů v pohodě. A kdo to chce mít přesnější, může ještě kontrolovat rozdíl v hodnotách, a pokud je velký (většinou na hranách objektů, spočítá prostě i to mezi nimi ve větším rozlišení a bude mít i ostré hrany objektů) Říkal jsem že budeme potřebovat rychlý algorytmus pro počítání průsečíků. Dá se použít BSP, jenže s tím by to bylo moc složité. Lepší by byl OcTree, který ještě neumíte, takže použiju bounding sphere, které se lehce vysvětlují. Bounding sphere algoritmus Bounding sphere je prostě koule (sphere), která obklopuje objekt (bounding) a když střílím paprsek, kontroluju napřed sphere a až potom vlastní objekt. Sphere má pozici a poloměr (radius) pozice je prostě průměr všech bodů objektu a radius je vzdálenost nejvzdálenějšího bodu od pozice sphere. To snad ani nebudu kreslit. My ale potřebujeme průsečík - na to si vezmeme jako u roviny - rovnici (ne roviny ale koule) : (center.x - x)2 + (center.y - y)2 + (center.z - z)2 = radius2 Pokud rovnice platí, máme bod [x, y, z], který leží na kouli [center, radius]. My ale potřebujeme průsečík s přímkou (přesněji polopřímkou) - paprskem. Když si to představíte, koule a přímka můžou mít jeden, dva nebo žádný průsečík. To odpovídá výsledkům kvadratické rovnice. Nám stačí (zatím) znát jen jestli průsečík je, nebo není. Pokud je průsečík jen jeden, znamená to že paprsek se koule jen dotýká a to můžeme s klidem zanedbat. Takže nám stačí znát, kdy je determinant ? ne ! denominátor ? ne. jo - diskriminant na to slovo jsem si nemoh vzpomenout, musel sem volat svýmu kámošovi ha ha, no jo, prázdniny. Pokud je diskriminant kladný, bude mít rovnice dva kořeny. Máme situaci :
Kde :
My teď potřebujeme vyjádřit x, y a z z té rovnice. Tak teď to zase jednou rozepíšu ať se matematici pořádně vyřádí :
a zároveň t > 0 (paprsek je až před kamerou)
No a to už vlastně máme rovnici, ze které můžeme vyextrahovat diskriminant. (kv. rovnice by potom měla tvar _At2 + _Bt + _C = 0)
My bychom mohli vypočítat t1 a t2 podle vzorce :
Můžeme to ještě trošku zjednudušit, pokud víme že vektor paprsku je jednotkový:
To se učí na všech středních a gymnáziích. Těmi t bysme vynásobili vektor v a přičetli k A (rovnice přímky) a dostali bychom průsečíky. Bližší průsečík (ve směru paprsku) je t1, protože jak víme, odmocnina vyjde vždy kladná, tzn. potřebujeme ji odečíst, aby t bylo co nejmenší. Nás ale zajímá jen ten diskriminant :
No a pokud to vyjde kladné tak máme průsečík. Huff .. To je hrůza, radši to ještě nechám zkontrolovat v excelu :-) Jo. Nas druhej pokus to vyšlo:
Je to funkce pro výpočet vzdálenosti bližšího průsečíku a druhá s výpočtem průsečík, vypadá že to bude i fungovat... Je tu ještě jedna možnost, a tou je trojúhelník - vektory u, v a kolmice na v, vedená ze středu koule. Pokud je úhel moc velký, parsek kouli prostě mine. dejme tomu že chybějící bod trojúhelníku bude X. My můžeme lehce spočítat stranu XC podle goniometrických funkcí. Pak stačí zjistit zda je větší nebo menší než poloměr koule a víme že ji mine / strefí. r / Len(v) = tg(fi) (fi je úhel který svírá vektor u a v) Tak, teď už vesele můžeme optimalizovat a odchytávat paprsky... Jen nevíme jak zjistit průsečík paprsku s face : Průsečíky paprsku s face Metod je určitě hodně, tady si ukážeme dvě. Ta první je jednodušší - okolo polygonu se vypočítá "ohrádka" z rovin (roviny, ležící na jeho hranách - obrázek) a potom průsečík paprsku s jeho normálovou rovinou (to už známe z klipování polygonů). Pokud průsečík leží uvnitř ohrádky, vyhráli jsme a paprsek protíná face. Ty průhledné věci mají být roviny
Ještě vám ukážu jak se ty roviny vytvoří (i když byste na to měli přijít pomalu už sami)
Funkce má dva parametry - začátek a konec paprsku, upravit to na začátek a vektor není
problém. Vrátí to 1 když je průsečík uvnitř, myslím...
Další, důmyslnější, i když ne tak přesná metoda je počítání úhlů. Vezme se zase průsečík paprsku s rovinou facu a vytvoří se tři vektory od průsečíku k vrcholům. Potom se sčítají úhly mezi těmito vektory (na obrázku psi1 psi2 psi3). Pokud součet bude 180°, je bod uvnitř (je potřeba dát určitou toleranci, která je původcem chyb)
Výhodou první metody je přesnost a možnost roviny předpočítat. Takže počet operací spadne
na několik násobení a tři cmp. Ve druhé metodě jsou to tři arccos, tři cmp, o tři násobení
míň a tři odmocniny navíc. Takže ta první je rychlejší a přesnější (pokud je dost paměti na
uchování těch rovin) Stejně vám k tomu zase můžu dát zdroják ať jsme komplet.
No.. to už by mělo stačit k napsání toho raycasteru .. Raycasting se shadow paprsky Tak, můžeme se na to vrhnout. Budeme kreslit scénu jako předtím, ale bude tam navíc jedna vrstva (ta matice 64×64) kde bude uložené (RGB!) diffuse light. Zájemci si můžou připsat i specular vrstvu a mít i lesklé objekty. Možná ... specular vrstva bude určitě v raytraceru. Tady zatím jen diffuse přes Lambertovu rovnici. Na začátku si přepočítáme sadu vektorů, které budeme střílet a při každém snímku tak zjistíme nejbližší průsečík s nějakým objektem. Potom musíme spočítat stínové paprsky ke všem světlům a určít zda něco světlo zastiňuje a podle toho reagovat. Nakonec se spočítá normální obraz a vynásobí se diffuse (a specular) vrstvou a máme další dynamické stíny za relativně nízkou cenu. Další možnost je nepoužívat matici, ale jen interpolovat x, y, z. To je o dost pomalejší, ale taky o dost přesnější. Když používáme S-Buffer a ne Z-Buffer jako většina grafického hardwaru (výjimkou je KYRO, používající tile-based rendering což je pravděpodobně něco podobného), máme možnost zjistit co bude na kterém místě v matici a přímo vypočítat průsečík. Potom ušetříme hodně času při hledání nejbližšího objektu. Ti co používají Z-Buffer / Hardware musí holt hledat. Takže k example. ... no asi vás moc nepotěším, ale nebude. Tahle metoda je už přeci jen trošku out, takže místo ní dáme něco trochu jiného. Stencil stíny Stencil buffer je záležitost nových (ne až tak nových, TNT už myslím stencil dávno měly). Jde o další buffer, který se dá použít pro různé účely. Jako takový není vidět, ale je možnost ovlivnit vykreslování scény na základě jeho obsahu. Ale ke stínům. Základem byly tzv. shadow volumes - stínové objemy. Vypadá to asi takhle : od každého facu se promítne jeho "jakoby stín", který jde teoreticky až do nekonečna. Cokoliv je vevnitř objemu, tvořeného plochami stínu je kupodivu ve stínu :
Stencil stíny fungují tedy tak, že stínový objem se vykreslí (poté, co už je scéna hotová)
a když se kreslí plocha, natočená ke kameře rubem, přičte se do stencilu jednička na pozici
odpovídajícího pixelu. Pokud je ke kameře lícem, jednička se odečte. Za předpokladu, že náš
stencil na začátku vynulujeme, místa kde je hodnota nula jsou osvětlené. (buď tam stín vůbec
nesahá, nebo je vidět přední i zadní strana stínu, takže pixel je za volume) Pokud je hodnota
nenulová, pixel bude někde uvnitř stínu. Je to docela jednoduché, ale má to jednu chybu :
co když je kamera uvnitř stínového objemu ? To je docela průšvih, protože se vynechá jeho
první stěna, takže stíny přejdou pravděpodobně do negativních (to co má být stín, bude světlo,
co má být světlo, bude stín) Zjišťovat, jestli kamera je uvnitř objemu je jednak neelegantní,
ale taky nepřesné na hranách, kdy by to stejně problikávalo. Nejjednodušší nápad je nakreslit
všechno bez testování průniků se světem (Z / S-bufferu), ale do stencilu se budou přičítat
opačné hodnoty - pro rub mínus jedna, pro líc jedna. Potom vlastně zjistíme, kde se volume
můžou kreslit a pokud předek není vidět, bude tam jednička. Když to ale vezmeme kolem a kolem :
Dá se to optimalizovat tak, že se nekreslí okraje každého facu, ale jen silueta objektu. Ta se dá vypočítat tak, že pro každý face určíte sousedy (přes jeho hrany) a zjišťujete, zda z pohledu světla je vidět face a potom podle sousedních faců (pokud nejsou vidět) se kreslí stěny stínu, promítnuté z odpovídajících hran. viz. obrázek :
Budeme zjišťovat, které hrany krychle náleží do siluety. Obrázek je viděný z pohledu světla.
Zkoušíme face 1. Je vidět z pohledu světla ? je. Teď - máme čtyři hrany. První hrana napojuje
sousední face 4. Ta vidět není, takže hrana je aktivní. Stejně je to s hranou dvě, jejíž soused
taky není vidět. Hrana tři spojuje face 2, která je vidět, takže se nic neděje. Stejné to je
i s hranou 4 a face 3. Takže z prvního facu povedou dvě stínové plochy.
Jen tak na okraj - pohled světla se samozřejmě nekreslí - zjišťuje se jen jestli světlo leží
před, nebo za rovinou facu.
siluety v naší scéně
Ještě taková věc - je potřeba, aby zadní část volume bylo úplné - tedy se musí kreslit i "čela",
nejen stěny. Tím prvním jsou vlastní facy a tím druhým jsou facy, posunuté na konec volume.
bacha na věc - velké polygony budou trošku blíž světlu
Teď už není problém. Jediná věc, která nám trochu stojí v cestě je S-Buffer, který moc dobře
nehandluje segmenty, které nejsou vidět. Řešením by bylo nakreslit i Z-Buffer a stěny objemů
kontrolovat pomocí něj, ale to by bylo pomalé, takže si musíme dát tu práci a napsat rutinu
pro kreslení neviditelných segmentů.
V example jsem to nakonec vyřešil dvěma rutinami, protože S-Buffer opravdu špatně řešil rovnoběžné plochy, takže se volume napřed kreslí celé bez testu hloubky a potom bez přední stěny, kterou by měl normálně zakrýt face, vrhající stín, jenže místo toho se vykreslí šum. Z-Buffer na 3D - kartách to vyřeší líp. Jediným obtížným úkolem může být hledání sousedů, které tu vysvětlím :
Funkce b_SameVertex() slouží jen k zjištění, jestli jsou dva vrcholy na stejném místě. (přibližně) Ve funkci Find_FaceNeighbours() se napřed všechny sousední facy nastaví na NULL a potom se berou další facy a zkouší se, jestli sousedí nějakou hranou a potom se ještě testuje, jestli jsou konvexní. Pokud by byly konkávní, nebudou se uvažovat za sousední. To je třeba příklad místnosti se stěnami přivrácenými dovnitř, samozřejmě. Všechny facy jsou vidět a všechny sousedi by byly vidět, takže podle algoritmu pro optimalizaci siluet by se nenakreslily žádné hrany - stěny místnosti by tedy nevrhaly stín. Ještě trochu ke světlu - v example se ve 3ds souboru počítá s jedním světlem, se kterým program hýbe po vodorovné kružnici okolo původního umístění světla. Na začátku se obvykle vykreslí ambient část světel, protože je potřeba mít něco v Z-bufferu (v našem případě S), abychom mohli kreslit stencil. Po vykreslení stencilu se vezme obraz a to, co je ve stínu se ztmaví na polovinu (v našem example). Normálně by se ale měl kreslit osvětlený svět, protnutý se stencilem a světlosti přičítat do obrazového bufferu. Pro další světla znovu a znovu, můžete klidně použít jen vertex shading. Tak, tady si to stáhněte a zdar ! Stencil engine
V další části budou ty lightmapy.
Nooo ... abych se přiznal tak jsem se při překládání tohodle tutorialu docela styděl, takže jsem to předělal aby to chodilo podle té rychlejší Carmackovy metody a navíc jsem přidal ještě smooth shading aby to vypadalo k světu. Stencil engine 2
... tady ho máte. Měli byste to bez problémů pochopit, kdyžtak pište. Zatím bye!
-tHE SWINe- |