English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform
box_cz 3D - Engine 09 - Světlo a metody stínování box_cz

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, základní RGB operace
  • Trošku fyziky
    • Lambertova rovnice
    • Tři druhy světla
    • Shrnutí a optimalizace
  • Flat shading (Xamlpe - Hra "life")
  • Smooth shading (Xample - volumetric fog - mlha)
  • Phong shading
    • teorie - phongův model
    • simulace pomocí enviroment map
    • jednoduchý enviro-map Xample
    • bump-map Xample
  • Shadow buffer (dynamické stíny)
    • Xample s jedním světlem
    • Jak na více světel
  • Raytracing
    • Zase trocha teorie
    • Jednoduchý raycasting (jen teoreticky)
    • Stencil - malá změna programu
    • Super raytracer
  • Lightmapy
    • Xample z Quake ]I[ Arena mapama
    • Xample s raycastingem
  • Radiosita
    • teorie radiosity, globální osvětlování
    • Progerssive refinement - vylepšení radiosity
    • Xample na patche - statická, dynamická
    • Xample s lightmapama
  • Vylepšené lightmapy
    • animování
    • volumetrická mlha
Tak.. to by bylo. Je toho opravdu požehnaně, nějak to rozdělím až to budu psát na dva nebo tři díly. No a vzhledem k tomu že jsou prázdniny to asi nebude tak brzo. Tož, dem na to :


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 :


texture × multiplicative tex = mult result


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 :

texture + additive_tex = q3 jumppad

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ů :


Vector Light, Point, Normal; // pozice svetla, bodu kde urcujeme jeho intenzitu a normala
float f_LightIntensity = 255; // intenzita svetla
float f_PointIntensity; // intenzita svetla v nasem bode
Vector u; // pomocny vektor

u = Point - Light;
_Normalize(u);
// vektor od svetla do bodu

f_PointIntensity = f_LightIntensity * (u.x * Normal.x + u.y * Normal.y + u.z * Normal.z);
// vypocet svetlosti

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 :

ambient



    Diffuse light

Je to světlo, odrážené povrchem všemi směry (tudíž je jedno odkud se díváte) :

diffgraph

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 :

diffuse



    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 :

specugraph

Potom by to vypadalo nějak takhle :

specular


    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 :

amb+diff+spec



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í :


fi = (mx_len - len + min_len) / mx_len;
fi = (fi < 0)? 0 : fi;

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á :
  • Každá buňka s méně nebo jedním sousedem chcípne
  • Každá buňka s více než třemi sousedy chcípne
  • Každá buňka se dvěma nebo třemi sousedy přežije
Pro buňku, která je chcaplá :
  • Každá buňka se třemi sousedy se "narodí"
(vymyslel to John Conway v roce 1970) To se pořád dokola iteruje a buňky tvoří obrazce a rodí se a chcípou, sranda.. V paskalu bylo použité 2D pole a my to uděláme jako 3D pole, kde buňka bude jen jedna, ale pokaždé se bude kreslit s jinou transformační maticí. V tom bude lítat naše milá kamera, která bude zároveň světlo. Pole má velikost 64×64×64 a pravidla pro populaci buňek jsou v souboru (obsahuje čtyři čísla - min. počet sousedů kdy přežije, max. počet sousedů kdy přežije a počet sousedů od-do, při kterém se zrodí nová buňka). V dalším souboru je startovní pozice buněk (1.číslo = počet buněk, potom jsou trojice čísel, udávající pozice jednotlivých buněk) Tady se můžete kouknout na prvních pár screenshotů a stáhnout si example. Example má přiložený soubor example.txt pomocí kterého můžete měnit mřížku ve které se life hraje, dál ve složce Cells jsou tři modely které můžete zkusit přejmenovat na cell.3ds (na obrázcích je cell_cube.3ds) a pak ještě soubory Life.ini, kde je nastavení živých a mrtvých buněk a Rules.ini kde jsou počty sousedů pro určité akce. Tož tady :

life 1

life 2

life 3

 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 :

hmla

 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 :


x = cur_face->vertex[j]->normal.x;
y = cur_face->vertex[j]->normal.y;
z = cur_face->vertex[j]->normal.z;
//
tx = x * m[0][0] + y * m[1][0] + z * m[2][0];
ty = x * m[0][1] + y * m[1][1] + z * m[2][1];
//
face.vertex[j]->u = (tx + 1) / 2;
face.vertex[j]->v = (ty + 1) / 2;

Tohle je kus funkce DrawObject(), který se stará o počítání souřadnic env-mapy. A tady je example :

q3 envmaps

reflection map
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 :


Offset[x][y] = (bump[x - 1][y] - bump[x + 1][y]) +
               (bump[x][y - 1] - bump[x][y + 1]) * bump_width;

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

sandman/solstice 00

sandman/solstice 01

sandman/solstice 02
Obrázky z dema Solstice od Sandmana

far cry bump 01

far cry bump 02
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 :

matrix/spotilte

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 :


struct Vertex {
    float x, y, z; // normalni souradnice
    float u, v;    // souradnice textury
    //
    float i, j, k; // souradnice v lightspace
    //
    Vector normal;  // normala vertexu
};

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's
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's, light shape
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 :

shadowbuffer-max
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á :


void ShadowObject(Matrix *m_object, Matrix *m_camera, Object *p_object)
{
    Face *cur_face, face;
    int n_material_id;
    float tx, ty, tz;
    float x, y, z;
    Polygon poly;
    Vertex v[3];
    int i, j;

    face.vertex[0] = &v[0];
    face.vertex[1] = &v[1];
    face.vertex[2] = &v[2];
    // nastaví vertexy pro naši dočasnou face

    Matrix_Inverse(m_camera, &m);
    Matrix_Multiply(&m, m_object, &m);
    Matrix_Inverse(&m, &im);
    // spočítá transformační matice

    for(i = 0; i < p_object->n_face_num; i ++){
        if((p_object->face[i].normal.c * im[3][2] +
           p_object->face[i].normal.a * im[3][0] +
           p_object->face[i].normal.b * im[3][1] +
           p_object->face[i].normal.d) > 0.1)
            continue;
        // byckface culling

        cur_face = &p_object->face[i];
        n_material_id = cur_face->n_material;
        for(j = 0; j < 3; j ++){
            x = cur_face->vertex[j]->x;
            y = cur_face->vertex[j]->y;
            z = cur_face->vertex[j]->z;
            //
            tx = x * m[0][0] + y * m[1][0] + 
                 z * m[2][0] +     m[3][0];
            ty = x * m[0][1] + y * m[1][1] + 
                 z * m[2][1] +     m[3][1];
            tz = x * m[0][2] + y * m[1][2] + 
                 z * m[2][2] +     m[3][2];
            //
            face.vertex[j]->x = tx;
            face.vertex[j]->y = ty;
            face.vertex[j]->z = tz;

            cur_face->vertex[j]->i = tx;
            cur_face->vertex[j]->j = ty;
            cur_face->vertex[j]->k = tz;
            // uloží se souřadnice zpátky do face
        }

        poly = Clip_Face(&face, &pyramid);
        // prožene face pyramidou

        if(poly.n_vertex_num) { // pokud je face vidět, bude to nenulové číslo
            for(j = 0; j < poly.n_vertex_num; j ++) {
                if(poly.vertex[j].z < 0.1) {
                    poly.n_vertex_num = 0;
                    break;
                }
                // pokud je z - souřadnice moc malá, zahodíme polygon

                poly.vertex[j].x = (n_Width / 2) + (poly.vertex[j].x *
                    z_delta) / poly.vertex[j].z;
                poly.vertex[j].y = (n_Height / 2) + (poly.vertex[j].y *
                    z_delta) / poly.vertex[j].z;
                // perspektivní korekce
            }

            ShadowPolygon(&poly, cur_face->n_id);
            // vykreslí polygon na obrazovku
        }
    }
}

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 :


int Compute_Interpolation_Consts(Polygon *pp, int nWidth, int nHeight)
{
    float x[3], y[3], z[3];
    float i[3], j[3], k[3];
    float u[3], v[3];
    float c;
    int l;

    for(l = 0; l < 3; l ++) {    
        z[l] = 1 / pp->vertex[l].z; 
        u[l] = pp->vertex[l].u * z[l] * nWidth;
        v[l] = pp->vertex[l].v * z[l] * nHeight;
        x[l] = pp->vertex[l].x;
        y[l] = pp->vertex[l].y;
        //
        k[l] = pp->vertex[l].k * z[l];
        i[l] = pp->vertex[l].i * z[l];
        j[l] = pp->vertex[l].j * z[l];
    }
    .
    .

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í :


void Draw_Segment(int c, int l, float z, float u, float v,
    float i, float j, float k, unsigned __int32 *video)
{
    int xs = (n_Width >> 1) * 0x10000;
    int ys = (n_Height >> 1) * 0x10000;
    int du, u1, u2;
    int dv, v1, v2;
    int di, i1, i2;
    int dj, j1, j2;
    float z1, zs;

    z1 = (0x10000 / z);
    // převod zpátky na lineární, do 16:16 fp

    u1 = (int)(u * z1);
    v1 = (int)(v * z1);
    // u a v se převede trošku jinak

    zs = (0x10000 * z_delta) / k;
    i1 = xs + (int)(i * zs);
    j1 = ys + (int)(j * zs);
    // shadow proměnné

    while(c >= 16) {
        z += dzdx * 16;
        u += dudx * 16;
        v += dvdx * 16;
        //
        i += didx * 16;
        j += djdx * 16;
        k += dkdx * 16;
        // pro optimalizaci přepočítáme jen kazdý 16. pixel

        z1 = (0x10000 / z);
        // určení dalšího lineárního z
        
        u2 = (int)(u * z1);
        v2 = (int)(v * z1);
        // u a v se převede trošku jinak

        zs = (0x10000 * z_delta) / k;
        i2 = xs + (int)(i * zs);
        j2 = ys + (int)(j * zs);
        // shadow proměnné

        du = (u2 - u1) / 16;
        dv = (v2 - v1) / 16;
        di = (i2 - i1) / 16;
        dj = (j2 - j1) / 16;
        // spočítá delty

        Interpolate_Segment(u1, du, v1, dv, i1, di, j1, dj, video, video + 16);
        // kreslící smyčka

        c -= 16;
        // sníží počet pixelů o 16
        
        video += 16;
        // posune se dál

        u1 = u2;
        v1 = v2;
        i1 = i2;
        j1 = j2;
        // využije předpočítané hodnoty
    }

    if(c > 0) { // kreslí zbytek lajny (pokud délka není násobkem 16)
    .
    .

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() :


static void _fastcall Interpolate_Segment_Bilerp(int u1, int du, int v1, int dv,
    int i1, int di, int j1, int dj, unsigned __int32 *video, unsigned __int32 *v2)
{
    unsigned __int32 texel;
    int frac, tex;
    int i, j;

    do {
        tex = ((u1 >> 16) & A_1) + ((v1 >> B_1) & A_2);
        // adresa texelu

        frac = ((u1 >> 10) & 0x3f) + ((v1 >> 4) & 0xfc0);
        // desetinna cast adresy (6 bitu z V + 6 bitu z U)

        texel = _palette[(_texture[tex] << 6) + multab[0][frac]] +
                _palette[(_texture[(tex + 1) & A_3] << 6) + multab[1][frac]] +
                _palette[(_texture[(tex + W_0) & A_3] << 6) + multab[2][frac]] +
                _palette[(_texture[(tex + W_1) & A_3] << 6) + multab[3][frac]];
        // bilinearni interpolace

        i = i1 >> 16;
        j = j1 >> 16;
        if(i > 1 && i < Width - 1 && j > 1 && j < Height - 1) {
            if(_sh_buffer[i + 1 + Width * (j + 1)] != _cur_id &&
               _sh_buffer[i + 1 + Width * (j - 1)] != _cur_id &&
               _sh_buffer[i + 1 + Width * j] != _cur_id &&
               _sh_buffer[i + Width * j] != _cur_id &&
               _sh_buffer[i - 1 + Width * (j + 1)] != _cur_id &&
               _sh_buffer[i - 1 + Width * (j - 1)] != _cur_id &&
               _sh_buffer[i - 1 + Width * j] != _cur_id &&
               _sh_buffer[i + Width * (j + 1)] != _cur_id &&
               _sh_buffer[i + Width * (j - 1)] != _cur_id) {
                texel = ((((texel & 0xff00ff) * n_ambient) & 0xff00ff00) |
                         (((texel & 0x00ff00) * n_ambient) & 0x00ff0000)) >> 8;
            }
        } else {
            texel = ((((texel & 0xff00ff) * n_ambient) & 0xff00ff00) |
                     (((texel & 0x00ff00) * n_ambient) & 0x00ff0000)) >> 8;
        }
        // zeslabi svetlo podle stinu

        *video ++ = texel;
        // nova pozice

        u1 += du;
        v1 += dv;
        i1 += di;
        j1 += dj;
        // interpolace souradnic
    } while(video < v2);
}

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 example

 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-

zpět


Valid HTML 4.01!
Valid HTML 4.01