3. diel - XNA a HLSL - Svetlá druhýkrát
Vitajte znova. V tomto dieli si pridáme ambientnej a directional (smerové) svetlo a tiež odlesky. Aby ale výsledky boli vidieť, potrebujeme iný testovací model. Najvyššie niečo okrúhleho. Nakoniec som sa pochlapili a stvoril v číne novú nádheru. Takže si model a textúry do projektu importujte. Druhou maličkostí, ktorú je potrebné vykonať, je aplikovať mierka, aby bol model väčší a lepšie sa nám s ním pracovalo. Zvolil sem 2,5x a potrebujeme takto zväčšiť všetky meshe. Preto teda modifikujeme World maticu. Nezabudnime na poradie násobenie:
part.Effect.Parameters["World"].SetValue(transforms[mesh.ParentBone.Index]*Matrix.CreateScale(2.5f)*rotace);
Ambientnej svetlo
Prvé skutočné svetlo, ktoré bude prítomné vždy je ambientné svetlo. Teda akýsi svetelný základ. Ja ho používam ako akési svetlo tieňa. Teda iba ním budú "osvetlené" neosvietenej plochy. Budeme potrebovať iba jeho farbu. Žiadne ďalšie informácie nie sú potrebné. Pridáme do shader nový parameter:
float3 AmbientColor;
Pixel shader bude potrebovať pomerne preorganizovať, pretože budeme sčítavať dohromady všetky svetelné zložky a tie potom aplikujeme na textúru. Preto si vytvoríme premennú lights:
float3 lights=AmbientColor;
pre svetlá a druhú color. A rovno ju nastavíme na ambientnej zložku. Pre farbu povrchu urobíme druhú:
float3 color=DiffuseColor;
A tu nastavíme na diffusní farbu. Ak je povolená textúra, tak ju s diffusní farbou zmiešame vynásobením:
if(TextureEnabled){
color*=tex2D(TextureSampler,input.UV);
}
Posledným krokom, čo nám zostáva, je obe farby ako svetiel tak z modelu zmiešať dohromady a poslať na výstup:
float3 output = saturate(lights) * color; return float4(output,1);
Dovolil som si použiť funkciu Saturate. Tá nerobí nič iné, než že nám akékoľvek číslo oreže do rozsahu 0 - 1. Je tam, pretože sa môže ľahko stať, že sčítajú viac svetiel a hodnoty by sa dostali mimo tohto rozsahu a za tú istotu, že bude všetko v poriadku, mi to jednoducho stojí. Shader je hotový, teraz stačí len pridať do hlavného programu premennú pre farbu svetla a do shader ju poslať:
effect.Parameters["AmbientColor"].SetValue(AmbientColor.ToVector3());
Hotovo. Ako scéna vyzerá so svetlom a bez neho sa môžete pozrieť na nasledujúcom obrázku.
Svetlo nezdôrazňuje hrany a preto pod ním vyzerajú všetky objekty placaté. Preto si pridáme trochu realičnosti ďalším svetlom.
Directional - Smerové svetlo
Smerové svetlo nám onú väčšie realičnost pridá. Jeho výpočet je však zložitejšia. Je potrebné poznať normálu. Čo to je normála? Normal je vektor, ktorý je kolmý k ploche a ešte najlepšie, keď má veľkosť jedna. Ako normály vyzerajú si môžete prezrieť na nasledujúcom obrázku. Sú to tie žlté čiary.
Tie potrebujeme dostať do nášho shader. Pridáme si do vstupnej štruktúry novú položku, sémantiku použijeme NORMAL0, celá štruktúra bude vyzerať takto:
struct VertexShaderInput{
float4 Position : POSITION0;
float2 UV:TEXCOORD0;
float3 Normal:NORMAL0;
};
Taktiež pridáme do výstupnej štruktúry, avšak tu použijeme sémantiku TEXCOORD1, teda pre súradnice textúry. Moc na výber tu totiž nemáme. Celá štruktúra bude vyzerať takto:
struct VertexShaderOutput{
float4 Position : POSITION0;
float2 UV:TEXCOORD0;
float3 Normal:TEXCOORD1;
};
Vo vertex shader musíme normálu transformovať maticou World. Dostane tak rotácie a pozíciu:
output.Normal=mul(input.Normal,World);
Bohužiaľ tiež mierka, preto ho budeme musieť neskôr normalizovať. Pridáme ešte dva parametre. Smer svetla a jeho farbu:
float3 DirectionalLight; float3 DirectionalLightColor;
Teraz máme všetko pripravené pre samotný výpočet. Ako ho ale vykonať? Ako inak, než ľahko. Smerové svetlo sa inak nazýva aj Lambertian light, a jeho rovnice je nasledujúci:
diff=max(l x n,0)
kde l je smernica svetla, n je normála ax nie je nič iné ako takzvaný dot product. To je skalárny súčin dvoch vektorov. O ten sa našťastie nemusíme starať, pretože na neho máme pripravenú funkciu. Diff je číslo akou mierou sa má svetlo na danú plochu prejaviť. Funkciu max iste poznáte, tá nám tu nepovolí záporné čísla o ktoré nestojíme. Poďme si to napísať do kódu. Predovšetkým si normalizujú obaja vektory:
float3 lightDir = normalize(DirectionalLight); float3 normal = normalize(input.Normal);
a vykonáme samotný výpočet svetla.
lights += saturate(dot(lightDir, normal)) * DirectionalLightColor;
Funkcia dot nám vykoná onen skalárny súčin. Vynásobíme, aby sme dostali výslednú farbu a je to. V hlavnom programe potom nastavíme obe hodnoty napríklad 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 sa výsledkom:
Svetlo pridalo výsledku plastickosť, tvary nám pekne vystúpili. Niečo výsledku ale ešte chýba. Realičnost podtrhneme odlesky.
Specular light
Alebo ak odlesky. Sú doplňujúcim svetlom. Ono to vlastne nie je tak celkom nové svetlo, ale len jedna zložka ostatných svetiel. Odlesky môžu generovať všetky svetlá. Rovnica je takáto:
kspec=max(r x v,0)^n
Kde r je vektor odrazený od zdroja svetla (ten vypočítame s pomocou normály, ktorú máme z minula), x je opäť dot product, v je vektor, ktorým sa na dané miesto pozerá kamera. Opäť všetko orežeme a záporné čísla zanedbávame funkcií max. Na záver je ale ešte toto všetko umocnené. Tým je možné meniť veľkosť odlesku. Čím väčšia mocnina, tým je odlesk menšie. Rovnica je veľmi podobná tej predchádzajúcej. Ona vlastne funguje veľmi podobne. Pre realizáciu tohto efektu potrebujeme poznať iba jednu doplňujúcu informáciu - pozíciu kamery, na ktorej je vlastne celý postup založený. Potreba je potom samozrejme aj ona mocnina a farba odlesku:
float3 CameraPosition; float SpecularPower=32; float3 SpecularColor=float3(1,1,1);
Pozíciu, z ktorej sa pozerá na ono miesto kamera, budeme potrebovať vypočítať vo vertex shader. Pridáme si do výstupnej štruktúry novú premennú:
float3 ViewDirection : TEXCOORD2;
Už ste si všimli, že do texcoordu ide narvať prakticky čokoľvek, áno, je to tak Vypočítame smernicu vektora. Iste si zo školy pamätáte, že to je koncový bod mínus počiatočné, takže teda:
output.ViewDirection=worldPosition-CameraPosition;
A potom ešte celé svetlo:
lights+=pow(saturate(dot(reflect(lightDir,normal),normalize(input.ViewDirection))),SpecularPower)*SpecularColor;
To je onen vzorček prepísaný do podoby programovej. Funkcia reflect nám vykoná onen zrkadlový odraz lúča a funkcia pow nie je nič iné ako mocnina. Náš materiál nastavíme o ďalšie dve hodnoty SpecularPower a SpecularColor:
public Vector3 SpecularColor{ get; set; } public float SpecularPower{ get; set; }
A v metóde SetEffectParametrs potom iba nastavíme hodnoty o shader:
ef.Parameters["SpecularColor"].SetValue(SpecularColor); ef.Parameters["SpecularPower"].SetValue(SpecularPower);
Obe hodnoty možno získať z BasicEffectu. Kam sú nahraté priamo z modelu. Takže záleží len na kvalite grafika ako tieto hodnoty nastavia. Takže do metódy LoadContent pridáme hneď pod vytvorenie novej inštancie efektu nasledujúce priradenie:
mat.SpecularColor = ef.SpecularColor; mat.SpecularPower = ef.SpecularPower;
Vo výsledku môžeme uvidieť drobná guľatá zosvetlenie bieleho svetla. Model má jednu chybu, že sú hodnoty odrazivosti nastavené veľmi nízko. Ale nevadí. Snáď do nabudúce prídem na to, ako to urobiť. Celok vyzerá teda nasledovne:
Tak a to by bolo pre dnešné diel všetko. Nabudúce sa pozrieme na zvyšná svetla. A ma nezostáva nič iné, než tešiť na komentáre a prípadné pripomienky či otázky.
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é 127x (3.05 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#