5. diel - XNA a HLSL - postprocesorov
Vitajte znova. V tomto dieli sa pozrieme na postprocesové efekty. Jedná sa vlastne o špeciálny shadery, ktoré nejako upravujú výsledný obraz vykreslené scény. Napríklad sa môže jednať o:
- rozostrenie obrazu
- farby na čiernobielu
- negatív
- korekcia farieb
- hmla
- toon shading
- glow efekt
- bloom
- ... a mnohé ďalšie
Efektov je nesmierna rad. Väčšinou sa jedná o korekcie farieb obrazu. Všetky majú tiež ďalšia vec spoločnú, sú to iba pixel shadery. Nepracujeme vôbec s vertexy. To okrem iného znamená kratšie kusy kódu a tiež menej písmeniek do článkov. Zlé vyhliadky.
Mašinéria
Pre realizáciu bude potrebné vykonať pár systémových udělátek, ktoré
nám značne zjednoduší prácu. Ale keďže tu nemá byť ani slovo o enginu a
vlastne som ho sem už napchal, tak to holt budeme musieť napísať všetko
individuálne. Prvá vec, ktorú je potrebné urobiť, je zachytiť všetko čo
vykreslíme do textúry a tú potom môžeme odovzdať shader na spracovanie.
Potom vykreslíme cez celé okno obdĺžnik a ten nám zaistí, že sa na
všetko shader aplikuje a tentoraz výsledok pošleme na výstup. Ako ale
vykresľovať do textúry? Aj túto možnosť nám grafická karta poskytuje,
použijeme na to takzvaný render target. Obdĺžnik pokrývajúci celú plochu
okna potom vykreslíme pomocou sprite Batch. Ovšem ponúka sa aj vlastné
riešenie, ale pre jednoduchosť takto. Poďme si teda všetko zrealizovať.
Najprv si vytvoríme novú triedu postprocesorov. Urobíme ju verejnú.
Konštruktor budeme odovzdávať aktuálne inštanciu triedy Game
a
tiež meno súboru s efektom:
public class Postprocesor{ protected Game Game; protected string EffectFileName; protected Effect Effect; public Postprocesor(string effect, Game game){ EffectFileName = effect; Game = game; } }
Štruktúra bude veľmi podobná komponentom z enginu, zas zo mňa vyšlo to
slovo. V metóde Load
nahráme efekt zo súboru:
public virtual void Load(){ Effect = Game.Content.Load<Effect>(EffectFileName); }
A rovnako ako spomínané komponenty pridáme ešte metódy
Update
a Draw
, zatiaľ prázdne:
public virtual void Update(){ } public virtual void Draw(Texture2D input){ }
Jediný rozdiel je, že v metóde Draw
odovzdáme textúru, v
ktorej budeme mať vykreslenú celú scénu. Pre vykreslenie si pridáme ešte
sprite batch:
protected SpriteBatch batch;
A v metóde Load
ju vytvoríme:
batch = new SpriteBatch(Game.GraphicsDevice);
Presunieme sa teraz do hlavnej triedy. Tam budeme potrebovať vytvoriť rendertarget, do ktorého scénu budeme vykresľovať.
RenderTarget2D target;
A v metóde LoadContent
ho vytvoríme:
target = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
Prvý parameter je jasný, druhý je šírka textúry, výška textúry,
zakážeme mipmapping. SurfaceFormat
je enum, kde máme všetky
možné formáty textúry, použijeme ten najľahšie a to teda farbu. A
posledná sú vlastnosti depth buffera (24 bitov), nechal som tam aj stencil
buffer (8 bitov). Na oboje sa dostane v ďalších dieloch a preto je ponecháme
zatiaľ bez väčšieho komentára. Rendertaget na začiatku metódy Draw
nastavíme:
protected override void Draw(GameTime gameTime){ if(postprocesor!=null)GraphicsDevice.SetRenderTarget(target); GraphicsDevice.Clear(Color.CornflowerBlue); ...
A na konci vykreslenie zas rendertarget odnastavíme:
if(postprocesor!=null)GraphicsDevice.SetRenderTarget(null);
Ak teraz program spustíme, tak uvidíme krásne fialové nič. To je všetko v poriadku, všetko sme vykreslili do textúry. Na výstupe nemáme nič. Ďalej si pridáme postprocesor.
Postprocesor postprocesor;
A v metóde LoadContent
ho nahráme:
if(postprocesor!=null)postprocesor.Load();
V metóde Update
taktiež zavoláme vhodnú metódu:
if(postprocesor!=null)postprocesor.Update();
A na záver v metóde Draw
na jej úplný koniec zavoláme
metódu Draw
postprocesoru:
if (postprocesor != null){ GraphicsDevice.SetRenderTarget(null); postprocesor.Draw(target); }
Posledné čo nám zostáva je metóda, ktorá obslúži vykreslenie efektu. A pomocou spritebatche vykreslíme na celú obrazovku ale s pomocou nášho shader:
batch.Begin(SpriteSortMode.Deferred, null, null, null, null, Effect); batch.Draw(input, new Vector2(), Color.White); batch.End();
A zakaždým, keď používame spritebatch, je potreba ustrážiť si stavy grafickej karty a tak je pre istotu vyresetuje:
Game.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
Game.GraphicsDevice.BlendState = BlendState.Opaque;
Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
A mašinéria je hotová. Teraz stačí už len vytvoriť príslušný efekt.
Čiernobiely efekt
Ten najjednoduchší efekt je prevedenie farieb na čiernobielu. A hlavne si na ňom predvedieme ako tieto shadery vyzerajú a ako ich písať. Prvá je sampler pre textúru:
sampler2D tex[1];
Je to vlastne pole s jedným prvkom. Máme len jednu textúru. Budeme mať iba pixel shader definovaný touto funkciou:
float4 PixelShaderFunction(float4 Position : POSITION0, float2 UV : TEXCOORD0) : COLOR0{ }
A tiež obvyklou technikou:
technique Technique1{ pass Pass1{ PixelShader = compile ps_2_0 PixelShaderFunction(); } }
Naozaj tam chýba vertexové časť. Tiež si všimnite, že nie sú prítomné žiadne štruktúry. Je to vcelku jedno, či ak ju použijeme a alebo len parametre vymenujeme. To by bola šablóna. Teraz pristúpme k samotnému shader. Čiernobiele spektrum dá vypočítať veľmi ľahko. Jednoducho len sčítame všetky tri kanály a vydelíme tromi a túto hodnotu použijeme vo výsledku.
float4 color = tex2D(tex[0], UV);
Vo funkcii použijeme nultý prvok poľa sa samplery a vypočítame intenzitu:
float intensity = (color.r+color.b+color.g)/3;
Namiesto rgb
možno použiť aj xyz
je to v zásade
jedno, len sa musí vždy použiť rovnaká sada. A do výsledku len vrátime
tieto hodnoty:
return float4(intensity, intensity, intensity, color.a);
Pridáme si taky novú triedu ja som ju nazval blackwhite, dedíme od postprocesoru a iba prepíšeme konštruktor:
public class BlackWhite : Postprocesor{ public BlackWhite(Game g) : base("postprocesory/blackwhite",g){ } }
A do metódy LoadContent
nad riadok, kde postprocesor nahrávame
ho priradíme:
postprocesor = new BlackWhite(this); if(postprocesor!=null)postprocesor.Load();
A máme hotovo, ak som na nič nezabudol. Ak teraz program spustíme, dostaneme čiernobiely obraz asi tak ako na nasledujúcom obrázku:
Tento prístup ale nie je úplne korektné. Ľudské oko vníma svetlo trochu inak. Hlavne zelenú farbu viac intenzívne než farby iné. Preto výpočet upravíme následujícně:
float intensity = 0.3f * color.r + 0.59f * color.g + 0.11f * color.b;
Všimnite si najväčšie váhy u zelenej a naopak najmenej u modrej. Výsledok sa ale príliš nelíši viď obrázok:
Tak a to by bolo pre dnešné diel všetko. Nabudúce sa pozrieme na sadu ďalších postprocesorov. Majú skrátka tú nevýhodu, že sú to krátke programy. Čakám ako vždy na komentáre, pripomienky, však to na mňa už dobre poznáte. 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é 111x (3.07 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#