Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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.

Graf závislosti jasu svetla na vzdialenosti - Tvorba shaderov v HLSL

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:

Zakrivenie a koeficient - Tvorba shaderov v HLSL

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

Point light v C# XNA - Tvorba shaderov v HLSL

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

Bodové svetlo v C# XNA - Tvorba shaderov v HLSL

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:

Uhol reflektorového svetla - Tvorba shaderov v HLSL

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:

Reflektorové svetlo v C# XNA - Tvorba shaderov v HLSL

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#

 

Predchádzajúci článok
XNA a HLSL - Svetlá druhýkrát
Všetky články v sekcii
Tvorba shaderov v HLSL
Preskočiť článok
(neodporúčame)
XNA a HLSL - postprocesorov
Článok pre vás napísal vodacek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Vodáček dělá že umí C#, naplno se již pět let angažuje v projektu ŽvB. Nyní studuje na FEI Upa informatiku, ikdyž si připadá spíš na ekonomice. Není mu také cizí PHP a SQL. Naopak cizí mu je Java a Python.
Aktivity