Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

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:

Čiernobiely efekt v C# .NET XNA - Tvorba shaderov v HLSL

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:

Čiernobiely efekt v C# .NET XNA - Tvorba shaderov v HLSL

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#

 

Predchádzajúci článok
XNA a HLSL - Svetlá tretíkrát
Všetky články v sekcii
Tvorba shaderov v HLSL
Preskočiť článok
(neodporúčame)
XNA a HLSL - Negatív, embos, gamma, toonshading a Sobel
Článok pre vás napísal vodacek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
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