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:

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:

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