4. diel - XNA a HLSL - Svetlá tretíkrát
Vitajte v ďalšom dieli. Dnes sa pozrieme na bodové (point) svetlo a reflektor (spot). Výpočty spojené s realizáciou oboch svetiel sú už trochu náročnejšie, ale dúfam, že nebudú robiť problém.
Point light
Bodové svetlo sa najviac približuje skutočnému zdroju svetla. Má svoje centrum, kde je jas najvyššia, ten sa potom znižuje postupne do stratena. Má teda tvar gule. Na nasledujúcom diagrame je zanesená závislosť jasu na vzdialenosti.
Intenzita jasu nebude klesať lineárne. Ako ste si už zvykli, môžeme to tiež vyjadriť rovnicou:
katt=1 – (d/r)^f
Kde katt je onen jas, d je vzdialenosť stredu svetla od bodu, ktorý vykresľuje. r je polomer kružnice, ktorú svetlo vytvára. Jednotku odčítame kvôli prevráteniu, takto by bolo najviac jasno na okrajoch. Posledné, čo zostáva osvetliť, je f, čo je koeficient, ktorý udáva ako bude prevodné krivka z obrázku hore zakrivená. Ako je zakrivenie ovplyvnené koeficientom sa môžete pozrieť na graf nižšie:
Dosť ale teórie, poďme si toto svetlo napísať. Začneme ako vždy novými parametrami:
float3 PointLightPosition; float3 PointLightColor; float PointLightAttenuation; float PointLightFalloff=2;
Prvá je pozícia svetla, jeho farba, polomer dosahu a onen koeficient. Pre výpočet vzdialenosti vykreslovaného bodu od stredu svetla budeme potrebovať poznať pozíciu tohto bodu. Bude preto potrebné túto hodnotu odovzdať až do pixel shader. Upravíme si výstupné štruktúru:
float4 WorldPosition:TEXCOORD3;
Sémantika opäť tradičnú, do ktorej sa zmestí jednoducho všetko. Vo vertex shader túto hodnotu doplníme na už vypočítanú hodnotu:
output.WorldPosition=worldPosition;
A to bude všetko. Vertex shadery, ako ste si už všimli, sú veľmi jednoduché. Všetka mágia sa odohráva až pri výpočtoch jednotlivých pixlov. Nebolo to tak vždy, ale to už zas odbočujem od témy. Pixel shader nám musí pre každý pixel vykonať výpočet osvetlenia podľa rovnice spomínané hore. V kóde to bude vyzerať nasledovne. Najprv si vypočítame vzdialenosť svetla od bodu, ktorý vykresľuje:
float d=distance(PointLightPosition,input.WorldPosition);
Používame funkciu distance, ktorá sa o výpočet postará, zvyšok rovnice potom vyzerá nasledovne:
lights+=(1-pow(saturate(d/PointLightAttenuation),PointLightFalloff))*PointLightColor;
Shader je tým hotový. Shadery sú dobré v tom, že ich kód je naozaj krátky. Ešte musíme v našom programe nastaviť hodnoty pre svetlo. Pokusne som zvolil tieto:
effect.Parameters["PointLightPosition"].SetValue(new Vector3(50,40,0)); effect.Parameters["PointLightColor"].SetValue(Color.White.ToVector3()); effect.Parameters["PointLightAttenuation"].SetValue(100);
Svetlo bude mať teda bielu farbu, jeho stred je na (50,40,0) a polomer je 100. Malo by byť všetko hotové. Skúsený čitateľ už ale veľmi dobre vie, že skoro zakaždým, keď sa vytasím s touto vetou, tak všetko hotové rozhodne nie je. Je tomu aj teraz. Poďme sa ale najskôr pozrieť na výsledok. Nutné podotknúť že som sa zbavil všetkých ostatných svetiel aby vyniklo len svetlo nové:
Žlto je vyznačená guľa, na ktorú svetlo pôsobí. Ovšem scéna je
nasvetlenie zle. Krásne je to vidieť na kocku. Tá má nasvietený aj vršok,
aj keď je zdroj svetla nachádza nižšie. Výpočet nie je kompletný, bude
potrebné do neho započítať aj normály povrchov, rovnako akoby sa jednalo o
smerové svetlo. Vrátime sa teda opäť do pixel shader. Zrecykloval by som
premennú lightDir
, pretože jej význam bude rovnaký:
lightDir=normalize(PointLightPosition-input.WorldPosition);
Pre pripomenutie smerový vektor znormalizuje. To ako moc na dané miesto získame potom rovnako ako u smerového svetla:
float diffuse=saturate(dot(normal,lightDir));
Normal je stále rovnaká a tak ju veselo recyklujeme. Týmto potom stačí vynásobiť to, čo už máme. Celok bude teda vyzerať nasledovne:
lights=(1-pow(saturate(d/PointLightAttenuation),PointLightFalloff))*PointLightColor*diffuse;
Ako sa zmenil výsledok môžete sledovať na nasledujúcom obrázku:
Zmizli iba vršky, ktoré osvetlené byť nemajú. Bodové svetlo je hotovo.
Spot light
Reflektorové svetlo sa výpočtom od bodového príliš nelíši. Má bod, z ktorého vychádza, smernicu ktorú svieti a tiež uhol. Uhol určuje ako široký bude výsledný kotúč. Pozri nasledujúci obrázok:
A ako vždy nemôže chýbať rovnice:
katt=1-(dot(p-lp,ld)/cos(a))^f
Kde lp je pozícia svetla, p je pozícia vykreslovaného pixelu, ld je smernica svetla, a je uhol reflektora af je rovnaké ako u rovnice hore. Systém je rovnaký ako u všetkých predchádzajúcich svetiel. Predovšetkým horda parametrov:
float3 SpotLightPosition; float3 SpotLightColor; float3 SpotLightDirection; float SpotLightAngle; float SpotLightFalloff=20;
Nejako sa nám množia. Vo vertex shader máme už všetko, ďalšie informácie nepotrebujeme. Zaoberať sa teda budeme iba druhou časťou. Ako u predošlého svetla si vypočítame ako veľmi svetlo ovplyvňuje opísaná povrch:
lightDir=normalize(SpotLightPosition-input.WorldPosition); diffuse=saturate(dot(normal,lightDir));
Premenné môžeme z recyklovať, ich význam je rovnaký. Ďalej si vypočítame prvú časť zo vzorca:
d=dot(-lightDir, normalize(SpotLightDirection));
float a=cos(SpotLightAngle);
Všimnite si mínusu pred premennú lightDir
, pre správny
výpočet potrebujeme opačný vektor k už získanému vektora. Jeho otočenie
vykonáme práve tým mínusom. A ak je pixel vnútri kužeľa, tak ho
osvetlíme:
if(a<d) lights+=1-pow(saturate(a/d),SpotLightFalloff)*diffuse*SpotLightColor;
Ak nie je, tak je intenzita svetla nula a nie je potreba sa zvyšným výpočtom zaoberať. A to je celý shader. Do programu si potom už len pridáme nastavenie parametrov, hoci takéto:
effect.Parameters["SpotLightPosition"].SetValue(new Vector3(200,150,0)); effect.Parameters["SpotLightDirection"].SetValue(new Vector3(-200,-150,0)); effect.Parameters["SpotLightAngle"].SetValue(MathHelper.ToRadians(30/2)); effect.Parameters["SpotLightColor"].SetValue(Color.White.ToVector3());
Uhol nezabudneme vydeliť dvoma. Samotné svetlo bude vyzerať nasledovne:
Tak a to by sme mali. Máme kompletný súbor svetiel. Nabudúce sa pozrieme na niečo trochu iné, budú to post procesové efekty. Tie sú ešte jednoduchšie ako osvetlenie, takže ak ste HLSL zatiaľ na chuť neprišli a robí vám problém pochopiť čo sa to tam deje, tak tu bude vaša posledná šanca. Čakám tiež ako vždy na komentáre, otázky a spol. Takže dovidenia nabudúce.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 158x (3.06 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#