7. díl - XNA a HLSL - Postprocesory Sepia, Alfa masking a Noise

C# .NET XNA game studio HLSL XNA a HLSL - Postprocesory Sepia, Alfa masking a Noise

Vítejte znovu, v dnešním díle si přidáme další postprocesorové efekty. Začneme zlehka shaderem nazývaným sepia. Přidáme si také alfa masking a pak taky výsledný obraz zaneřádíme náhodným šumem. Práce mnoho, začněme.

Sepia

Sepia je jednoduchý shader, skrze který je možné obraz pozměnit do podoby staré fotografie. Taky určitě znáte tento efekt z mobilů, kde se v různých obměnách objevuje. Vzorec vypadá následovně:

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

Jak se k tomu přišlo se mě neptejte, nevím to. Shader je pak snadný jako 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;

Pouze jsem přepsal konstanty zmíněné výše. Výsledný efekt vypadá následovně:

Efekt sépie v XNA

Tak to bychom se rozehřáli po kratší odmlce a nyní už něco pořádného a užitečného.

Alfa masking

Alfa masking je shader, který nám umožní překrýt celý obraz obrázkem jiným – maskou. Tento efekt určitě znáte ze stříleček, když zaměřujete. Ukážeme si dva přístupy, jeden s maskou černo-bílou a druhý, který používá alfa kanál. Ale co budeme potřebovat vždycky, tak nahrát texturu s maskou a propašovat ji do shaderu. Proto v souboru se třídou přidáme proměnné:

Texture2D Mask;
string texture;

V konstruktoru si nastavíme jméno souboru s texturou:

public AlfaMask(Game g, string texture): base("postprocesory/alfamask",g){
  this.texture = texture;
}

A jako obyčejně v metodě Load texturu ze jména nahrajeme:

public override void Load(){
  base.Load();
  Mask = Game.Content.Load<Texture2D>(texture);
}

Před vykreslením nesmíme zapomenout texturu nastavit jako aktivní. Provedeme to stejně jako minule nastavením:

public override void Draw(Texture2D input){
  Game.GraphicsDevice.Textures[1] = Mask;
  base.Draw(input);
}

Pořadí úkonů jsem si nespletl, musí být pouze takto, jinak se velmi pravděpodobně nic nevykreslí. Pojďme si připravit také shader. Předně zvýšíme počet samplerů na dva:

sampler2D tex[2];

A můžeme přistoupit k samotnému shaderu. Jako vždy si vybereme barvu:

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

a také vybereme barvu z masky:

float4 mask=tex2D(tex[1],UV);

Výslednou barvu získáme vynásobením obou takto získaných barev.

return color*mask;

Tento jednoduchý postup bude fungovat třebas pro tuto masku:

Alfa masking v XNA jako zaměřovač

Jednoduchá černo-bílá maska. Vše co je černé na masce překryje obraz. Vyplývá to už z kódu shaderu. Černá barva jsou vlastně samé nuly a pokud cokoliv násobíme nulou, tak jak jistě víme je výsledek zase nula, tedy černá. Bílá barva naopak přenese všechnu barvu původní. Pokud ale budeme chtít použít následující masku:

Alfa masking v XNA jako zaměřovač

Která používá krom černé a bílé také další barvičky, mírně narazíme. I když jak kdy. Záleží, jaký efekt potřebujeme. Ostatní barvy budou více či méně průhledné. Bude záležet na jejich jasu. Někdy je to efekt žádoucí, ale třebas u zaměřovače pušky, který je napevno barvou natištěn, to chtít nebudeme. A právě na tyto případy budeme potřebovat texturu s alfa kanálem, který nám určí jak moc je daný pixel průhledný. Alfa kanál do ní dostaneme přes nějaký lepší editor na fotky. Já jsem použil Photo Filtre, který je podle mě snadný na pochopení. Není to sice taková mašinka jak obchod s fotkami, ale vše co jsem kdy potřeboval se mi s ním nějak podařilo udělat. Pro představu jak alfa maska vypadá viz následující obrázek:

Maska s alfa kanálem

Zelené čáry mimo kruh jsou pevné, uvnitř průhledné a červený kruh je také mírně průhledný. Do shaderu si to přeneseme mírnou úpravou. Vzorec pro tento druh alfa blendingu je následující:

c=color*(1-mask.afa)+mask

A ten pak pouze převedeme do shaderu následujícně:

return color*(1-mask.a)+mask;

Jak vzorec ale funguje? Vyložíme si to na příkladu. Dejme tomu, že máme pixel, který je naprosto neprůhledný. Má tedy alfu jedna. Tu odečteme od jedničky a získáme tak v závorce nulu. Takže ve výsledku bude jen a pouze obsažena barva z masky. A to je to co přesně chceme. Obdobně tomu je i u dalších hodnot.

Maska s alfa kanálem

A jak vidno i výsledek tomu odpovídá.

Noise - šum

Tento shader využijeme k zašumění celého obrazu. Celý princip je velmi snadný, shaderem vypočteme jakýsi virtuální a náhodný offset a s pomocí něj jen ze sampleru vybereme výslednou barvu. Jak ovšem generovat náhodné číslo. Náhodné číslo nikdy nebude moc dobře náhodné, ale pro naše velmi skromné účely jej vytvoříme z funkce sinus a souřadnic daného bodu. Nutno podotknout, že tento shader jsem našel zde a budu se originálu držet.

Přidáme si tedy do shaderu proměnnou Seed, která nám bude reprezentovat semínko:

float Seed;

V pixel shaderu si vypočteme náhodné číslo ze souřadnic za použití semínka:

float noiseX=Seed*sin(UV.x*UV.y);

Tuto „náhodnou“ hodnotu upravíme ještě za pomocí funkce fmod. Ta stejně pracuje stejně jako modulo akorát je i pro čísla s plovoucí řádovou čárkou. Vrací nám tedy zbytek po dělení.

noiseX=fmod(noiseX,8)*fmod(noiseX,4);

A pak vypočteme offset pro souřadnice. Opět za pomocí zabudované funkce fmod:

float2 dis=float2(fmod(noiseX,NoiseAmount),fmod(noiseX,NoiseAmount+0.002));

Nesmíme zapomenout přidat proměnnou NoiseAmount jako parametr shaderu. Ta nám bude říkat jak moc se má šum projevovat. Já jsem ji nastavil rovnou na hodnotu 0.01 která se ukázala poměrně pěkná:

float NoiseAmount=0.01;

A na závěr offset aplikujeme v sampleru a výslednou barvu vracíme jako finální:

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

Shader je hotov. Ve třídě se shaderem přepíšeme metodu Draw a nastavíme semínko na námi zvolenou hodnotu. Viděl bych ji v řádech stovek až tak dva tisíce. Já jsem 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. Pokud nyní program spustíme, dostaneme nádherně zašmudlaný obraz. A to by bylo pro dneš... moment. A co když si zastavím animaci. Tak to šumět přestane. A to my nechceme. Budeme muset do shaderu přidat také vliv času. Bohužel na to náš systém není stavěný, ale to se dá rychle napravit. Ve třídě s obecným postprocesorem upravíme metodu Update a přidáme jí parametr a herním časem, takže bude vypadat následovně:

public virtual void Update(GameTime time){

}

A do volání této metody v hlavní hře tento parametr nezapomeňte předat. V našem shaderu pak už jen přidáme proměnnou pro čas:

float Time;

A do funkce sinus ji promítneme následovně:

float noiseX=Seed*Time*sin(UV.x*UV.y+Time);

Do třídy postprocesoru přidáme metodu 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ávěr v metodě Draw čas nastavíme a jsme hotoví.

Effect.Parameters["Time"].SetValue(Time);

Nyní se bude šum hýbat i při statické scéně.

Efekt šumu v XNA

To by bylo opravdu pro tento díl všechno. Příště se podíváme na možnosti rozmazávání obrazu, bez kterých se ve hrách prostě neobejdete. Těším se na otázky, názory, nápady a tak dále v komentářích. Na shledanou příště.


 

Stáhnout

Staženo 111x (3.1 MB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET

 

  Aktivity (1)

Článek pro vás napsal vodacek
Avatar
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.

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 



 

 

Komentáře

Avatar
Michael Olšavský:

To si všechny ty filtry pamatuješ? Jestli ano, tak to tě opravdu obdivuji. (y)

 
Odpovědět 2.11.2013 21:50
Avatar
Luboš Běhounek (Satik):

Shaderová mlha a blur (+ motion blur) budou příště? :)

Odpovědět 2.11.2013 21:59
:)
Avatar
vodacek
Redaktor
Avatar
Odpovídá na Luboš Běhounek (Satik)
vodacek:

strejda gaus a radial blur
a mlha bude ve článku s dalšíma shaderama co potřebujou hloubku
ale chci aj ssao a pak deferred shading/lighting sem na to ted našel fajnový pdf-ko

Editováno 2.11.2013 22:09
 
Odpovědět 2.11.2013 22:07
Avatar
vodacek
Redaktor
Avatar
Odpovídá na Michael Olšavský
vodacek:

všecky né ale ty principy sou snadný a daj se vymyslet na místě

 
Odpovědět 2.11.2013 22:10
Avatar
Odpovídá na vodacek
Michael Olšavský:

Sépie, gamma, negativ ano, ale zvyraznění hran na místě nevymyslím. Stejně tak šum by mě nenapadl.

 
Odpovědět 2.11.2013 22:14
Avatar
vodacek
Redaktor
Avatar
Odpovídá na Michael Olšavský
vodacek:

tak google pomůže vždycky :-D

 
Odpovědět  +3 2.11.2013 22:15
Avatar
magic44
Redaktor
Avatar
magic44:

Ahoj super články o postprocesorech :). Můžu se zeptal, jestli nevíš jak udělat efekt "akvarel"/"wa­tercolor"??

Odpovědět 14.12.2014 21:42
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
O.S.DV.F
Člen
Avatar
Odpovídá na vodacek
O.S.DV.F:

Fešné, a kdy bylo by pokračování?

Odpovědět 24. ledna 11:47
Jo! Zkompilovalo se to!
Avatar
vodacek
Redaktor
Avatar
Odpovídá na O.S.DV.F
vodacek:

zatím nic neplánuju, ale když se vás ozve víc... proč by ne!

 
Odpovědět 24. ledna 16:52
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 9 zpráv z 9.