Lekce 3 - XNA a HLSL - Světla podruhé
V minulé lekci, XNA a HLSL - Textury, jsme se podívali na to, jak na trojúhelníky pomocí shaderů nanášet textury.
Vítejte znova. V tomto dílu si přidáme ambientní a directional (směrové) světlo a také odlesky. Aby ale výsledky byly vidět, potřebujeme jiný testovací model. Nejlépe něco kulatého. Nakonec jsem se pochlapil a stvořil v cinemě novou nádheru. Takže si model a textury do projektu importujte. Druhou maličkostí, kterou je potřeba provést, je aplikovat měřítko, aby byl model větší a lépe se nám s ním pracovalo. Zvolil sem 2,5x a potřebujeme takto zvětšit všechny meshe. Proto tedy modifikujeme World matici. Nezapomeňme na pořadí násobení:
part.Effect.Parameters["World"].SetValue(transforms[mesh.ParentBone.Index]*Matrix.CreateScale(2.5f)*rotace);
Ambientní světlo
První skutečné světlo, které bude přítomno vždycky je ambientní světlo. Tedy jakýsi světelný základ. Já ho používám jako jakési světlo stínu. Tedy pouze ním budou „osvětleny“ neosvícené plochy. Budeme potřebovat pouze jeho barvu. Žádné další informace nejsou potřeba. Přidáme do shaderu nový parametr:
float3 AmbientColor;
Pixel shader bude potřebovat poměrně přeorganizovat, protože budeme sčítat dohromady všechny světelné složky a ty pak aplikujeme na texturu. Proto si vytvoříme proměnnou lights:
float3 lights=AmbientColor;
pro světla a druhou color. A rovnou ji nastavíme na ambientní složku. Pro barvu povrchu uděláme druhou:
float3 color=DiffuseColor;
A tu nastavíme na diffusní barvu. Je-li povolena textura, tak ji s diffusní barvou smícháme vynásobením:
if(TextureEnabled){
color*=tex2D(TextureSampler,input.UV);
}
Posledním krokem, co nám zbývá, je obě barvy jak světel tak z modelu smíchat dohromady a poslat na výstup:
float3 output = saturate(lights) * color; return float4(output,1);
Dovolil jsem si použít funkci saturate. Ta nedělá nic jiného, než že nám jakékoliv číslo ořízne do rozsahu 0 – 1. Je tam, protože se může snadno stát, že nasčítáme víc světel a hodnoty by se dostaly mimo tento rozsah a za tu jistotu, že bude vše v pořádku, mi to prostě stojí. Shader je hotov, nyní stačí pouze přidat do hlavního programu proměnnou pro barvu světla a do shaderu ji poslat:
effect.Parameters["AmbientColor"].SetValue(AmbientColor.ToVector3());
Hotovo. Jak scéna vypadá se světlem a bez něj se můžete podívat na následujícím obrázku.
Světlo nezdůrazňuje hrany a proto pod ním vypadají všechny objekty placatě. Proto si přidáme trochu realičnosti dalším světlem.
Directional – Směrové světlo
Směrové světlo nám onu větší realičnost přidá. Jeho výpočet je ovšem složitější. Je zapotřebí znát normálu. Co to je normála? Normála je vektor, který je kolmý k ploše a ještě nejlépe, když má velikost jedna. Jak normály vypadají si můžete prohlédnout na následujícím obrázku. Jsou to ty žluté čáry.
Ty potřebujeme dostat do našeho shaderu. Přidáme si do vstupní struktury novou položku, sémantiku použijeme NORMAL0, celá struktura bude vypadat takto:
struct VertexShaderInput{
float4 Position : POSITION0;
float2 UV:TEXCOORD0;
float3 Normal:NORMAL0;
};
Taktéž přidáme do výstupní struktury, ovšem zde použijeme sémantiku TEXCOORD1, tedy pro souřadnice textury. Moc na výběr zde totiž nemáme. Celá struktura bude vypadat takto:
struct VertexShaderOutput{
float4 Position : POSITION0;
float2 UV:TEXCOORD0;
float3 Normal:TEXCOORD1;
};
Ve vertex shaderu musíme normálu transformovat maticí World. Dostane tak rotace a pozici:
output.Normal=mul(input.Normal,World);
Bohužel také měřítko, proto jej budeme muset později normalizovat. Přidáme ještě dva parametry. Směr světla a jeho barvu:
float3 DirectionalLight; float3 DirectionalLightColor;
Nyní máme vše připraveno pro samotný výpočet. Jak ho ale provést? Jak jinak, než snadno. Směrové světlo se jinak nazývá také Lambertian light, a jeho rovnice je následující:
diff=max(l x n,0)
kde l je směrnice světla, n je normála a x není nic jiného než takzvaný dot product. To je skalární součin dvou vektorů. O ten se naštěstí nemusíme starat, protože na něj máme připravenou funkci. Diff je číslo jakou měrou se má světlo na danou plochu projevit. Funkci max jistě znáte, ta nám zde nepovolí záporná čísla o které nestojíme. Pojďme si to napsat do kódu. Předně si normalizujeme oba vektory:
float3 lightDir = normalize(DirectionalLight); float3 normal = normalize(input.Normal);
a provedeme samotný výpočet světla.
lights += saturate(dot(lightDir, normal)) * DirectionalLightColor;
Funkce dot nám provede onen skalární součin. Vynásobíme, abychom dostali výslednou barvu a je to. V hlavním programu pak nastavíme obě hodnoty kupříkladu na:
effect.Parameters["DirectionalLight"].SetValue(new Vector3(0,0,0)-new Vector3(-1,0,0)); effect.Parameters["DirectionalLightColor"].SetValue(Color.Yellow.ToVector3());
A kocháme se výsledkem:
Světlo přidalo výsledku plastičnost, tvary nám pěkně vystoupily. Něco výsledku ale ještě schází. Realičnost podtrhneme odlesky.
Specular light
Nebo-li odlesky. Jsou doplňujícím světlem. Ono to vlastně není tak docela nové světlo, ale jen jedna složka ostatních světel. Odlesky mohou generovat všechna světla. Rovnice je takováto:
kspec=max(r x v,0)^n
Kde r je vektor odražený od zdroje světla (ten vypočteme s pomocí normály, kterou máme z minula), x je opět dot product, v je vektor, kterým se na dané místo dívá kamera. Opět vše ořízneme a záporná čísla zanedbáváme funkcí max. Na závěr je ale ještě toto vše umocněno. Tím lze měnit velikost odlesku. Čím větší mocnina, tím je odlesk menší. Rovnice je velmi podobná té předchozí. Ona vlastně funguje velmi podobně. Pro realizaci tohoto efektu potřebujeme znát pouze jednu doplňující informaci - pozici kamery, na které je vlastně celý postup založen. Potřeba je pak samozřejmě i ona mocnina a barva odlesku:
float3 CameraPosition; float SpecularPower=32; float3 SpecularColor=float3(1,1,1);
Pozici, ze které se dívá na ono místo kamera, budeme potřebovat vypočítat ve vertex shaderu. Přidáme si do výstupní struktury novou proměnnou:
float3 ViewDirection : TEXCOORD2;
Již jste si všimli, že do texcoordu jde narvat prakticky cokoliv, ano, je to tak Vypočteme směrnici vektoru. Jistě si ze školy pamatujete, že to je koncový bod mínus počáteční, takže tedy:
output.ViewDirection=worldPosition-CameraPosition;
A pak ještě celé světlo:
lights+=pow(saturate(dot(reflect(lightDir,normal),normalize(input.ViewDirection))),SpecularPower)*SpecularColor;
To je onen vzoreček přepsaný do podoby programové. Funkce reflect nám provede onen zrcadlový odraz paprsku a funkce pow není nic jiného než mocnina. Náš materiál nastavíme o další dvě hodnoty SpecularPower a SpecularColor:
public Vector3 SpecularColor{ get; set; } public float SpecularPower{ get; set; }
A v metodě SetEffectParametrs pak pouze nastavíme hodnoty o shaderu:
ef.Parameters["SpecularColor"].SetValue(SpecularColor); ef.Parameters["SpecularPower"].SetValue(SpecularPower);
Obě hodnoty lze získat z BasicEffectu. Kam jsou nahrány přímo z modelu. Takže záleží jen na kvalitě grafika jak tyto hodnoty nastaví. Takže do metody LoadContent přidáme hned pod vytvoření nové instance efektu následující přiřazení:
mat.SpecularColor = ef.SpecularColor; mat.SpecularPower = ef.SpecularPower;
Ve výsledku můžeme spatřit drobná kulatá zesvětlení bílého světla. Model má jednu chybu, že jsou hodnoty odrazivosti nastavené velmi nízko. Ale nevadí. Snad do příště přijdu na to, jak to provést. Celek vypadá tedy následovně:
Tak a to by bylo pro dnešní díl všechno.
Příště, XNA a HLSL - Světla potřetí, se podíváme na zbylá světla. A mě nezbývá nic jiného, než těšit na komentáře a případné připomínky či dotazy.
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 127x (3.05 MB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET