Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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.

Graf závislosti jasu světla na vzdálenosti - Tvorba shaderů v HLSL

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:

Zakřivení a koeficient - Tvorba shaderů v HLSL

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

Point light v C# XNA - Tvorba shaderů v HLSL

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

Bodové světlo v C# XNA - Tvorba shaderů v HLSL

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:

Úhel reflektorového světla - Tvorba shaderů v HLSL

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

Reflektorové světlo v C# XNA - Tvorba shaderů v HLSL

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

 

Předchozí článek
XNA a HLSL - Světla podruhé
Všechny články v sekci
Tvorba shaderů v HLSL
Přeskočit článek
(nedoporučujeme)
XNA a HLSL - Postprocesory
Článek pro vás napsal vodacek
Avatar
Uživatelské hodnocení:
4 hlasů
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