Lekce 4 - XNA a HLSL - Světla potřetí
V předchozí lekci, XNA a HLSL - Světla podruhé, jsme si vytvořili ambientní a směrové světlo.
Vítejte v dalším dílu. Dnes se podíváme na bodové (point) světlo a reflektor (spot). Výpočty spojené s realizací obou světel jsou už trochu náročnější, ale doufám, že nebudou dělat problém.
Point light
Bodové světlo se nejvíce přibližuje skutečnému zdroji světla. Má svoje centrum, kde je jas nejvyšší, ten se pak snižuje postupně do ztracena. Má tedy tvar koule. Na následujícím diagramu je zanesena závislost jasu na vzdálenosti.

Intenzita jasu nebude klesat lineárně. Jak jste si už zvykli, můžeme to také vyjádřit rovnicí:
katt=1 – (d/r)^f
Kde katt je onen jas, d je vzdálenost středu světla od bodu, který vykreslujeme. r je poloměr kružnice, kterou světlo vytváří. Jedničku odečítáme kvůli převrácení, takto by bylo nejvíce jasno na okrajích. Poslední, co zbývá osvětlit, je f, což je koeficient, který udává jak bude převodní křivka z obrázku nahoře zakřivená. Jak je zakřivení ovlivněno koeficientem se můžete podívat na graf níže:

Dost ale teorie, pojďme si toto světlo napsat. Začneme jako vždy novými parametry:
float3 PointLightPosition; float3 PointLightColor; float PointLightAttenuation; float PointLightFalloff=2;
První je pozice světla, jeho barva, poloměr dosahu a onen koeficient. Pro výpočet vzdálenosti vykreslovaného bodu od středu světla budeme potřebovat znát pozici tohoto bodu. Bude proto potřeba tuto hodnotu předat až do pixel shaderu. Upravíme si výstupní strukturu:
float4 WorldPosition:TEXCOORD3;
Sémantika opět tradiční, do které se vleze prostě všechno. Ve vertex shaderu tuto hodnotu doplníme na již vypočtenou hodnotu:
output.WorldPosition=worldPosition;
A to bude všechno. Vertex shadery, jak jste si už povšimli, jsou velmi snadné. Veškerá magie se odehrává až při výpočtech jednotlivých pixelů. Nebylo to tak vždycky, ale to už zas odbočuji od tématu. Pixel shader nám musí pro každý pixel provést výpočet osvětlení podle rovnice zmíněné nahoře. V kódu to bude vypadat následovně. Nejprve si vypočteme vzdálenost světla od bodu, který vykreslujeme:
float d=distance(PointLightPosition,input.WorldPosition);
Používáme funkci distance, která se o výpočet postará, zbytek rovnice pak vypadá následovně:
lights+=(1-pow(saturate(d/PointLightAttenuation),PointLightFalloff))*PointLightColor;
Shader je tím hotov. Shadery jsou dobré v tom, že jejich kód je opravdu krátký. Ještě musíme v našem programu nastavit hodnoty pro světlo. Pokusně jsem zvolil tyto:
effect.Parameters["PointLightPosition"].SetValue(new Vector3(50,40,0)); effect.Parameters["PointLightColor"].SetValue(Color.White.ToVector3()); effect.Parameters["PointLightAttenuation"].SetValue(100);
Světlo bude mít tedy bílou barvu, jeho střed je na (50,40,0) a poloměr je 100. Mělo by být vše hotovo. Zkušený čtenář už ale moc dobře ví, že skoro pokaždé, když se vytasím s touto větou, tak vše hotovo rozhodně není. Je tomu i nyní. Pojďme se ale nejdříve podívat na výsledek. Nutno podotknout že jsem se zbavil všech ostatních světel aby vyniklo jen světlo nové:

Žlutě je vyznačena koule, na kterou světlo působí. Ovšem scéna je
nasvětlena špatně. Krásně je to vidět na krychli. Ta má nasvícený i
vršek, i když je zdroj světla nalézá níže. Výpočet není kompletní,
bude potřeba do něj započítat i normály povrchů, stejně jakoby se jednalo
o směrové světlo. Vrátíme se tedy opět do pixel shaderu. Zrecykloval bych
proměnnou lightDir
, protože její význam bude stejný:
lightDir=normalize(PointLightPosition-input.WorldPosition);
Pro připomenutí směrový vektor znormalizujeme. To jak moc na dané místo získáme pak stejně jako u směrového světla:
float diffuse=saturate(dot(normal,lightDir));
Normála je stále stejná a tak ji vesele recyklujeme. Tímto pak stačí vynásobit to, co již máme. Celek bude tedy vypadat následovně:
lights=(1-pow(saturate(d/PointLightAttenuation),PointLightFalloff))*PointLightColor*diffuse;
Jak se změnil výsledek můžete sledovat na následujícím obrázku:

Zmizely pouze vršky, které osvětlené být nemají. Bodové světlo je hotovo.
Spot light
Reflektorové světlo se výpočtem od bodového příliš neliší. Má bod, ze kterého vychází, směrnici kterou svítí a také úhel. Úhel určuje jak široký bude výsledný kotouč. Viz následující obrázek:

A jako vždy nemůže chybět rovnice:
katt=1-(dot(p-lp,ld)/cos(a))^f
Kde lp je pozice světla, p je pozice vykreslovaného pixelu, ld je směrnice světla, a je úhel reflektoru a f je stejné jako u rovnice nahoře. Systém je stejný jako u všech předcházejících světel. Předně horda parametrů:
float3 SpotLightPosition; float3 SpotLightColor; float3 SpotLightDirection; float SpotLightAngle; float SpotLightFalloff=20;
Nějak se nám množí. Ve vertex shaderu máme již vše, další informace nepotřebujeme. Zaobírat se tedy budeme pouze druhou částí. Jako u předešlého světla si vypočteme jak moc světlo ovlivňuje vykreslovaný povrch:
lightDir=normalize(SpotLightPosition-input.WorldPosition); diffuse=saturate(dot(normal,lightDir));
Proměnné můžeme z recyklovat, jejich význam je stejný. Dále si vypočteme první část ze vzorce:
d=dot(-lightDir, normalize(SpotLightDirection));
float a=cos(SpotLightAngle);
Povšimněte si mínusu před proměnnou lightDir
, pro správný
výpočet potřebujeme opačný vektor k již získanému vektoru. Jeho
otočení provedeme právě tím mínusem. A pokud je pixel uvnitř kuželu, tak
jej osvětlíme:
if(a<d) lights+=1-pow(saturate(a/d),SpotLightFalloff)*diffuse*SpotLightColor;
Pokud není, tak je intenzita světla nula a není potřeba se zbylým výpočtem zaobírat. A to je celý shader. Do programu si pak už jen přidáme nastavení parametrů, třebas takové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());
Úhel nezapomeneme vydělit dvěma. Samotné světlo bude vypadat následovně:

Tak a to bychom měli. Máme úplný soubor světel.
Příště, XNA a HLSL - Postprocesory, se podíváme na něco trochu jiného, budou to post procesové efekty. Ty jsou ještě snazší než osvětlení, takže pokud jste HLSL zatím na chuť nepřišli a dělá vám problém pochopit co se to tam děje, tak zde bude vaše poslední šance. Čekám také jako vždycky na komentáře, dotazy a spol. Takže na viděnou příště.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 158x (3.06 MB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET