7. diel - XNA a HLSL - postprocesorov Sepia, Alfa masking a Noise
Vitajte znovu, v dnešnom diele si pridáme ďalšie postprocesorové efekty. Začneme zľahka shader nazývaným sépia. Pridáme si tiež alfa masking a potom taky výsledný obraz zaneřádíme náhodným šumom. Práca veľa, začnime.
Sépia
Sepia je jednoduchý shader, skrze ktorý je možné obraz pozmeniť do podoby staré fotografie. Taky určite poznáte tento efekt z mobilov, kde sa v rôznych obmenách objavuje. Vzorec vyzerá nasledovne:
R=r*0.393 + g*0.769 + b*0.189 G=r*0.349 + g*0.686 + b*0.168 B=r*0.272 + g*0.534 + b*0.131
Ako sa k tomu prišlo sa ma nepýtajte, neviem to. Shader je potom jednoduchý ako facka:
float4 color = tex2D(tex[0], UV); float4 ret = color; ret.r = (color.r * 0.393) + (color.g * 0.769) + (color.b * 0.189); ret.g = (color.r * 0.349) + (color.g * 0.686) + (color.b * 0.168); ret.b = (color.r * 0.272) + (color.g * 0.534) + (color.b * 0.131); return ret;
Iba som prepísal konštanty spomínané vyššie. Výsledný efekt vyzerá nasledovne:
Tak to by sme sa rozohriali po kratšej odmlke a teraz už niečo poriadneho a užitočného.
Alfa masking
Alfa masking je shader, ktorý nám umožní prekryť celý obraz obrázkom iným - maskou. Tento efekt určite poznáte zo strieľačiek, keď zameriavate. Ukážeme si dva prístupy, jeden s maskou čierno-bielu a druhý, ktorý používa alfa kanál. Ale čo budeme potrebovať vždy, tak nahrať textúru s maskou a prepašovať ju do shader. Preto v súbore sa triedou pridáme premenné:
Texture2D Mask;
string texture;
V konstruktoru si nastavíme meno súboru s textúrou:
public AlfaMask(Game g, string texture): base("postprocesory/alfamask",g){ this.texture = texture; }
A ako obyčajne v metóde Load
textúru z mena nahráme:
public override void Load(){ base.Load(); Mask = Game.Content.Load<Texture2D>(texture); }
Pred vykreslením nesmieme zabudnúť textúru nastaviť ako aktívny. Vykonáme to rovnako ako minule nastavením:
public override void Draw(Texture2D input){ Game.GraphicsDevice.Textures[1] = Mask; base.Draw(input); }
Poradie úkonov som si nepomýlil, musí byť iba takto, inak sa veľmi pravdepodobne nič nevykreslí. Poďme si pripraviť tiež shader. Predovšetkým zvýšime počet samplerov na dva:
sampler2D tex[2];
A môžeme pristúpiť k samotnému shader. Ako vždy si vyberieme farbu:
float4 color = tex2D(tex[0], UV);
a tiež vyberieme farbu z masky:
float4 mask=tex2D(tex[1],UV);
Výslednú farbu získame vynásobením oboch takto získaných farieb.
return color*mask;
Tento jednoduchý postup bude fungovať trebárs pre túto masku:
Jednoduchá čierno-biela maska. Všetko čo je čierne na maske prekryje obraz. Vyplýva to už z kódu shader. Čierna farba sú vlastne samé nuly a ak čokoľvek násobíme nulou, tak ako iste vieme je výsledok zase nula, teda čierna. Biela farba naopak prenesie všetku farbu pôvodnej. Pokiaľ ale budeme chcieť použiť nasledujúce masku:
Ktorá používa okrem čiernej a bielej tiež ďalšie farbičky, mierne narazíme. Aj keď ako kedy. Záleží, aký efekt potrebujeme. Ostatné farby budú viac či menej priehľadné. Bude záležať na ich jasu. Niekedy je to efekt žiaduce, ale trebárs u zameriavača pušky, ktorý je napevno farbou natlačený, to chcieť nebudeme. A práve na tieto prípady budeme potrebovať textúru s alfa kanálom, ktorý nám určí ako veľmi je daný pixel priehľadný. Alfa kanál do nej dostaneme cez nejaký lepší editor na fotky. Ja som použil Photo Filtre, ktorý je podľa mňa jednoduchý na pochopenie. Nie je to síce taká mašinka ako obchod s fotkami, ale všetko čo som kedy potreboval sa mi s ním nejako podarilo urobiť. Pre predstavu ako alfa maska vyzerá viď nasledujúci obrázok:
Zelené čiary mimo kruh sú pevné, vnútri priehľadnej a červený kruh je tiež mierne priehľadný. Do shader si to prenesieme miernu úpravou. Vzorec pre tento druh alfa blending je nasledujúci:
c=color*(1-mask.afa)+mask
A ten potom iba prevedieme do shader následujícně:
return color*(1-mask.a)+mask;
Ako vzorec ale funguje? Vyložíme si to na príklade. Dajme tomu, že máme pixel, ktorý je úplne nepriehľadný. Má teda alfu jedna. Tú odpočítame od jednotky a získame tak v zátvorke nulu. Takže vo výsledku bude len a len obsiahnutá farba z masky. A to je to čo presne chceme. Podobne tomu je aj u ďalších hodnôt.
A ako vidno aj výsledok tomu zodpovedá.
Noise - šum
Tento shader využijeme na zašumenia celého obrazu. Celý princíp je veľmi jednoduchý, Shader vypočítame akýsi virtuálny a náhodný offset as pomocou neho len zo samplera vyberieme výslednú farbu. Ako však generovať náhodné číslo. Náhodné číslo nikdy nebude veľmi dobre náhodné, ale pre naše veľmi skromné účely ho vytvoríme z funkcie sínus a súradníc daného bodu. Treba podotknúť, že tento shader som našiel tu a budem sa originálu držať.
Pridáme si teda do shader premennú Seed
, ktorá nám bude
reprezentovať semienko:
float Seed;
V pixel shader si vypočítame náhodné číslo zo súradníc za použitia semienka:
float noiseX=Seed*sin(UV.x*UV.y);
Túto "náhodnou" hodnotu upravíme ešte za pomocou funkcie
fmod
. Tá rovnako pracuje rovnako ako modulo akurát je aj pre
čísla s plávajúcou rádovou čiarkou. Vracia nám teda zvyšok po
delení.
noiseX=fmod(noiseX,8)*fmod(noiseX,4);
A potom vypočítame offset pre súradnice. Opäť za pomocou zabudovanej
funkcie fmod
:
float2 dis=float2(fmod(noiseX,NoiseAmount),fmod(noiseX,NoiseAmount+0.002));
Nesmieme zabudnúť pridať premennú NoiseAmount
ako parameter
shader. Tá nám bude hovoriť ako veľmi sa má šum prejavovať. Ja som ju
nastavil rovno na hodnotu 0.01 ktorá sa ukázala pomerne pekná:
float NoiseAmount=0.01;
A na záver offset aplikujeme v samplera a výslednú farbu vraciame ako finálna:
float4 color = tex2D(tex[0], UV+dis); return color;
Shader je hotový. V triede sa Shader prepíšeme metódu Draw
a
nastavíme semienko na nami zvolenú hodnotu. Videl by som ju v rádoch stoviek
až tak dvetisíc. Ja som použil 523.
float Seed; public Noise(Game g) : base("postprocesory/noise",g){ Seed = 523; } public override void Draw(Microsoft.Xna.Framework.Graphics.Texture2D input){ Effect.Parameters["Seed"].SetValue(Seed); base.Draw(input); }
A máme hotovo. Ak teraz program spustíme, dostaneme nádherne zašmudlaný
obraz. A to by bolo pre dnes ... moment. A čo keď si zastavím animáciu. Tak
to šumieť prestane. A to my nechceme. Budeme musieť do shader pridať tiež
vplyv času. Bohužiaľ na to náš systém nie je stavaný, ale to sa dá
rýchlo napraviť. V triede s všeobecným postprocesorov upravíme metódu
Update
a pridáme jej parameter a herným časom, takže bude
vyzerať nasledovne:
public virtual void Update(GameTime time){ }
A do volanie tejto metódy v hlavnej hre tento parameter nezabudnite odovzdať. V našom shader potom už len pridáme premennú pre čas:
float Time;
A do funkcie sínus ju premietneme nasledovne:
float noiseX=Seed*Time*sin(UV.x*UV.y+Time);
Do triedy postprocesoru pridáme metódu Update
kde si čas
upravíme:
public override void Update(GameTime time){ base.Update(time); Time += time.ElapsedGameTime.Milliseconds/500.0f; }
A na úplný záver v metóde Draw
čas nastavíme a sme
hotoví.
Effect.Parameters["Time"].SetValue(Time);
Teraz sa bude šum hýbať aj pri statickej scéne.
To by bolo naozaj pre tento diel všetko. Nabudúce sa pozrieme na možnosti rozmazávaniu obrazu, bez ktorých sa v hrách jednoducho nezaobídete. Teším sa na otázky, názory, nápady a tak ďalej v komentároch. 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é 120x (3.1 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#