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 7 - XNA a HLSL - Postprocesory Sepia, Alfa masking a Noise

V předchozí lekci, XNA a HLSL - Negativ, embos, gamma, toonshading a sobel, jsme si připravili hned několik postprocesorových shaderů.

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 - Tvorba shaderů v HLSL

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č - Tvorba shaderů v HLSL

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č - Tvorba shaderů v HLSL

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 - Tvorba shaderů v HLSL

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 - Tvorba shaderů v HLSL

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 - Tvorba shaderů v HLSL

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ě.


 

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

 

Předchozí článek
XNA a HLSL - Negativ, embos, gamma, toonshading a sobel
Všechny články v sekci
Tvorba shaderů v HLSL
Článek pro vás napsal vodacek
Avatar
Uživatelské hodnocení:
3 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