Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 5 - XNA a HLSL - Postprocesory

V minulé lekci, XNA a HLSL - Světla potřetí, jsme si vytvořili bodové světlo a reflektor.

Vítejte znovu. V tomto dílu se podíváme na postprocesové efekty. Jedná se vlastně o speciální shadery, které nějak upravují výsledný obraz vykreslené scény. Kupříkladu se může jednat o:

  • rozostření obrazu
  • barvy na černobílou
  • negativ
  • korekce barev
  • mlha
  • toon shading
  • glow efekt
  • bloom
  • … a mnohé další

Efektů je nesmírná řada. Většinou se jedná o korekce barev obrazu. Všechny mají také další věc společnou, jsou to pouze pixel shadery. Nepracujeme vůbec s vertexy. To mimo jiné znamená kratší kusy kódu a také méně písmenek do článků. Špatné vyhlídky.

Mašinerie

Pro realizaci bude potřeba provést pár systémových udělátek, které nám značně zjednoduší práci. Ale jelikož zde nemá být ani slovo o enginu a vlastně sem ho sem už nacpal, tak to holt budeme muset napsat vše individuálně. První věc, kterou je potřeba udělat, je zachytit vše co vykreslíme do textury a tu pak můžeme předat shaderu ke zpracování. Pak vykreslíme přes celé okno obdélník a ten nám zajistí, že se na vše shader aplikuje a tentokrát výsledek pošleme na výstup. Jak ale vykreslovat do textury? I tuto možnost nám grafická karta poskytuje, použijeme k tomu takzvaný render target. Obdélník pokrývající celou plochu okna pak vykreslíme pomocí sprite batche. Ovšem nabízí se i vlastní řešení, ale pro jednoduchost takto. Pojďme si tedy vše zrealizovat. Prvně si vytvoříme novou třídu Postprocesor. Učiníme ji veřejnou. Konstruktorem budeme předávat aktuální instanci třídy Game a také jméno souboru s efektem:

public class Postprocesor{
  protected Game Game;

  protected string EffectFileName;

  protected Effect Effect;

  public Postprocesor(string effect, Game game){
    EffectFileName = effect;
    Game = game;
  }
}

Struktura bude velmi podobná komponentám z enginu, zas ze mě vyšlo to slovo. V metodě Load nahrajeme efekt ze souboru:

public virtual void Load(){
  Effect = Game.Content.Load<Effect>(EffectFileName);
}

A stejně jako zmíněné komponenty přidáme ještě metody Update a Draw, prozatím prázdné:

public virtual void Update(){

}

public virtual void Draw(Texture2D input){

}

Jediný rozdíl je, že v metodě Draw předáme texturu, ve které budeme mít vykreslenou celou scénu. Pro vykreslení si přidáme ještě sprite batch:

protected SpriteBatch batch;

A v metodě Load ji vytvoříme:

batch = new SpriteBatch(Game.GraphicsDevice);

Přesuneme se nyní do hlavní třídy. Tam budeme potřebovat vytvořit rendertarget, do kterého scénu budeme vykreslovat.

RenderTarget2D target;

A v metodě LoadContent jej vytvoříme:

target = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);

První parametr je jasný, druhý je šířka textury, výška textury, zakážeme mipmapping. SurfaceFormat je enum, kde máme všechny možné formáty textury, použijeme ten nejsnažší a to tedy barvu. A poslední jsou vlastnosti depth bufferu (24 bitů), nechal jsem tam i stencil buffer (8 bitů). Na obojí se dostane v dalších dílech a proto je ponecháme prozatím bez většího komentáře. Rendertaget na začátku metody Draw nastavíme:

protected override void Draw(GameTime gameTime){
  if(postprocesor!=null)GraphicsDevice.SetRenderTarget(target);
  GraphicsDevice.Clear(Color.CornflowerBlue);
...

A na konci vykreslení zas rendertarget odnastavíme:

if(postprocesor!=null)GraphicsDevice.SetRenderTarget(null);

Pokud nyní program spustíme, tak uvidíme krásné fialové nic. To je vše v pořádku, vše jsme vykreslili do textury. Na výstupu nemáme nic. Dále si přidáme postprocesor.

Postprocesor postprocesor;

A v metodě LoadContent jej nahrajeme:

if(postprocesor!=null)postprocesor.Load();

V metodě Update taktéž zavoláme příslušnou metodu:

if(postprocesor!=null)postprocesor.Update();

A na závěr v metodě Draw na její úplný konec zavoláme metodu Draw postprocesoru:

if (postprocesor != null){
  GraphicsDevice.SetRenderTarget(null);
  postprocesor.Draw(target);
}

Poslední co nám zbývá je metoda, která obslouží vykreslení efektu. A pomocí spritebatche vykreslíme na celou obrazovku ale s pomocí našeho shaderu:

batch.Begin(SpriteSortMode.Deferred, null, null, null, null, Effect);
batch.Draw(input, new Vector2(), Color.White);
batch.End();

A pokaždé, když používáme spritebatch, je potřeba ohlídat si stavy grafické karty a tak je pro jistotu vyresetujeme:

Game.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
Game.GraphicsDevice.BlendState = BlendState.Opaque;

Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

A mašinerie je hotova. Nyní stačí už jen vytvořit příslušný efekt.

Černobílý efekt

Ten nejsnadnější efekt je převedení barev na černobílou. A hlavně si na něm předvedeme jak tyto shadery vypadají a jak je psát. První je sampler pro texturu:

sampler2D tex[1];

Je to vlastně pole s jedním prvkem. Máme pouze jednu texturu. Budeme mít pouze pixel shader definovaný touto funkcí:

float4 PixelShaderFunction(float4 Position : POSITION0, float2 UV : TEXCOORD0) : COLOR0{

}

A také obvyklou techniku:

technique Technique1{
  pass Pass1{
    PixelShader = compile ps_2_0 PixelShaderFunction();
  }
}

Opravdu tam chybí vertexová část. Také si povšimněte, že nejsou přítomny žádné struktury. Je to vcelku jedno, zda-li ji použijeme a nebo jen parametry vyjmenujeme. To by byla šablona. Nyní přistupme k samotnému shaderu. Černobílé spektrum lze vypočíst velmi snadno. Prostě jen sečteme všechny tři kanály a vydělíme třemi a tuto hodnotu použijeme ve výsledku.

float4 color = tex2D(tex[0], UV);

Ve funkci použijeme nultý prvek pole se samplery a vypočteme intenzitu:

float intensity = (color.r+color.b+color.g)/3;

Místo r g b lze použít i x y z je to v zásadě jedno, pouze se musí vždy použít stejná sada. A do výsledku pouze vrátíme tyto hodnoty:

return float4(intensity, intensity, intensity, color.a);

Přidáme si taky novou třídu já jsem ji nazval BlackWhite, dědíme od postprocesoru a pouze přepíšeme konstruktor:

public class BlackWhite : Postprocesor{
  public BlackWhite(Game g) : base("postprocesory/blackwhite",g){

  }
}

A do metody LoadContent nad řádek, kde postprocesor nahráváme jej přiřadíme:

postprocesor = new BlackWhite(this);
if(postprocesor!=null)postprocesor.Load();

A máme hotovo, pokud jsem na nic nezapomněl. Pokud nyní program spustíme, dostaneme černobílý obraz asi tak jako na následujícím obrázku:

Černobílý efekt v C# .NET XNA - Tvorba shaderů v HLSL

Tento přístup ale není úplně korektní. Lidské oko vnímá světlo poněkud jinak. Hlavně zelenou barvu více intenzivně než barvy jiné. Proto výpočet upravíme následujícně:

float intensity = 0.3f * color.r + 0.59f * color.g + 0.11f * color.b;

Povšimněte si největší váhy u zelené a naopak nejméně u modré. Výsledek se ale příliš neliší viz obrázek:

Černobílý efekt v C# .NET XNA - Tvorba shaderů v HLSL

Tak a to by bylo pro dnešní díl všechno.

Příště, XNA a HLSL - Negativ, embos, gamma, toonshading a sobel, se podíváme na sadu dalších postprocesorů. Mají zkrátka tu nevýhodu, že jsou to krátké programy. Čekám jako vždy na komentáře, připomínky, však to na mě už dobře znáte. Na shledanou příště.


 

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 111x (3.07 MB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET

 

Předchozí článek
XNA a HLSL - Světla potřetí
Všechny články v sekci
Tvorba shaderů v HLSL
Přeskočit článek
(nedoporučujeme)
XNA a HLSL - Negativ, embos, gamma, toonshading a sobel
Článek pro vás napsal vodacek
Avatar
Uživatelské hodnocení:
4 hlasů
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