IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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 6 - XNA a HLSL - Negativ, embos, gamma, toonshading a sobel

V předchozí lekci, {PEVIOUS}, jsme si napsali systém pro postprocesorové efekty.

Vítejte znovu. V tomto dílu přidáme další snadné postprocesové efekty. Ale ještě něž začneme, tak pro jistotu zopakuji kostru shaderu, do které budeme přidávat veškerý obsah, nebude-li řečeno jinak:

sampler2D tex[1];

float4 PixelShaderFunction(float4 Position : POSITION0, float2 UV : TEXCOORD0) : COLOR0
{
    float4 color = tex2D(tex[0], UV);
    //dalsi kod semhle
}

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

Negativ

Dalším velmi snadným efektem je vytvoření negativu. Vlastně jen barvy otočíme a to takto:

1-barva

To jen abyste nevypadli ze vzorečků :-) Vezmeme kostru shaderu a otrocky vzoreček aplikujeme na všechny barevné složky vyjma alfa kanál:

return float4(1-color.r,1-color.g,1-color.b,1);

Shader do programu nasaďte jako minule a výsledek by mohl vypadat nějak takto.

Shader pro negativ v XNA - Tvorba shaderů v HLSL

Vše lze vyřešit také efektivněji pomocí takzvaného swizlingu. Je to specialita jazyka jako takového, kéž by něco takového bylo možného i jinde:

return float4(1-color.rgb, color.a);

Operace se provede pro všechny komponenty odděleně. Super vlastnost usnadňující zápis. A výsledek je naprosto stejný. Ještě bych podotkl, že složky musí být ze stejné sady tedy buď z x-y-z-w a nebo z r-g-b-a. Kombinovat je navzájem nelze. Pomocí inverze si vytvoříme další efekt a tím bude embos.

Embos

Efekt embosu dovede zdůraznit přechody barev. Principiálně se skládá originální barva s negativem posunutým v určitém směru o několik pixelů. Vzoreček je následující:

(barva[u,v] + (1- barva[u+1,v+1]))/2

Pro posun o jeden pixel doprava dolů. Dvěma dělíme, aby byla výsledná barva v korektním barevném rozsahu. Posun musí být pro dosažení daného efektu vždy na diagonále. Implementaci provedeme jako vždy do kostry:

float3 ret=(color.rgb+(1-tex2D(tex[0],UV+offset).rgb))/2;
return float4(ret, color.a);

Kde offset je proměnná, kterou jsem si vystrčil ven jako parametr.

float offset=0.001;

Hodnotu lze zjistit buď pokusně a nebo výpočtem. Jelikož máme hodnoty pro texturování v rozsahu 0-1, tak budeme muset trošku kouzlit. Známe rozměr okna 800x480 (aspoň myslím). Rozměr jednoho pixelu zjistíme 1/800. Poměrně se to shoduje s pokusnou hodnotou.

Shader pro embos v XNA - Tvorba shaderů v HLSL

Gamma korekce

Další shader, tentokráte ale už bude použitelnější. Gama korekce (anglicky gamma correction) je úprava obrazu, dříve používaná pro zobrazování na CRT obrazovkách, které nezobrazovaly lineárně. Proto bylo potřeba tento vliv kompenzovat. Lze ji použít také pro opravu expozice, kdy je požadovaný objekt schován v příliš tmavé nebo příliš světlé části obrazu a tímto způsobem ji lze vytáhnout ven. Opět jako vždy rovnice:

barva^gamma

Rovnice jako vždy snadná a pokud se zdá někomu v něčem známá, tak se ani moc neplete. Stejnou rovnici jsme měli pro modulaci jasu bodového světla. Opět i zde mohou nastat dva případy. Potřebujeme utlumit příliš jasný obraz a nebo naopak jas zvednout. Exponenty mezi 0-1 jas zvýší a naopak exponenty 1-nekonečno snižují. Na následujícím grafu vidíte výsledný průběh:

Graf gamma korekce - Tvorba shaderů v HLSL

Implementace algoritmu je stejná jako u předchozích shaderů:

float3 ret=pow(color.rgb,gamma);
return float4(ret, color.a);

A proměnnou gamma dáváme jako vstupní parametr:

float gamma=2.2;

Na výsledek s původním obrázkem, obrázkem s gamma na 1/2,2 a s gammou 2,2 se můžete podívat níže:

Obrázek s gamma korekcí v XNA - Tvorba shaderů v HLSL

Ne, není to montáž, výstup mi už takto generuje shader. Není to nic složitého, stačilo pouze zapodmínkovat výpočet asi takto:

float3 ret=color.rgb;
if(UV.x>0.33)ret=pow(color.rgb,1/gamma);
if(UV.x>0.66)ret=pow(color.rgb,gamma);

Obvyklá hodnota pro většinu obrazovek se pohybuje v rozmezí 1,8 až 2,4. To jen pro kompletní informaci.

Toonshading

Toonshading je mírně složitější technika. Podle jasu vybereme barvu z druhé pomocné textury a tak barvu zaměníme. Už je na nás jakou paletu zvolíme. Předně si vytvoříme texturu s novou paletou barev. Já jsem vzal deset náhodných barviček, šířka textury tedy bude 10 a výška 1.

Tooning efekt v XNA - Tvorba shaderů v HLSL

A texturu přidáme do projektu. Pojďme na shader. Prvně musíme přidat jednu texturu do pole samplerů:

sampler2D tex[2];//2 textury

A vypočteme si intenzitu pro daný pixel, kupříkladu tím složitějším způsobem s váhami:

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

A tuto hodnotu použijeme pro určení nové barvy z druhého sampleru. Druhá souřadnice bude vždy nula, protože textura má vlastně jen jeden rozměr.

float3 toon=tex2D(tex[1],float2(intensity,0));

A vše pošleme na výstup:

return float4(toon, color.a);

A to je celý shader. Jeho obsluha bude velmi snadná. Ve třídě (je stejná jako ostatní) si pouze konstruktorem předáme jméno textury s paletou:

Texture2D Toon;
string toontexture;

public ToonShading(Game g, string toontexture): base("postprocesory/toon",g) {
  this.toontexture = toontexture;
}

Přepíšeme metodu Load a texturu nahrajeme:

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

A v metodě Draw nastavíme texturu do druhého sampleru:

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

Tím jsme nastavili texturu jako aktivní. A to je celé. Výsledek bude pro mou texturu vypadat následovně:

Toon efekt v XNA - Tvorba shaderů v HLSL

Detekce hran

Občas je potřeba také detekovat hrany v obraze. Je to kupříkladu jedna z metod anti-aliasingu. A určitě je dobré tento shader znát. Metod je mnoho, ale já použiji takzvaný sobelův filtr. Je to matice (ano zas matice) 3x3 o následujícím rozložení:

-1 0 1      -1 -2 -1
-2 0 2       0  0  0
 1 0 1       1  2  1

Potřebujeme matice dvě, jedna je pro horizontální detekce a druhá pro vertikální. Avšak je možné matici rotovat po 45% a zjišťovat tak i další přechody. Předně si nadefinujeme konstanty. První bude hodnota, která nám určí hranici mezi hranou a nehranou:

float treshold=0.05;

Následují dvě proměnné pro velikosti jednoho pixelu:

float pixelx=0.001;
float pixely=0.002;

Pro zjednodušení si napíšeme snadnou funkci, která nám vypočte jas na daném bodu. HLSL stejně jako další programovací jazyky podporuje psaní funkcí. Ta musí však být deklarována před tím, než ji použijeme, tedy stejně jako v C-čku. Funkce je to snadná, takže bez komentáře:

float Jas(float2 uv){
  float3 color= tex2D(tex[0], uv);
  return (color.r+color.g+color.b)/3;
}

Použil jsem snadnou variantu, ale můžeme použít i tu s váhami. Sobelovy matice aplikujeme následujícně:

float valx=-1*Jas(float2(UV.x-pixelx,UV.y-pixely))+Jas(float2(UV.x+pixelx,UV.y-pixely));
valx+=-2*Jas(float2(UV.x-pixelx,UV.y))+2*Jas(float2(UV.x+pixelx,UV.y));
valx+=-1*Jas(float2(UV.x-pixelx,UV.y+pixely))+Jas(float2(UV.x+pixelx,UV.y+pixely));

A pro vertikální směr:

float valy=-1*Jas(float2(UV.x-pixelx,UV.y-pixely))-2*Jas(float2(UV.x,UV.y-pixely))-1*Jas(float2(UV.x+pixelx,UV.y-pixely));
valy+=1*Jas(float2(UV.x-pixelx,UV.y+pixely))+2*Jas(float2(UV.x,UV.y+pixely))+1*Jas(float2(UV.x+pixelx,UV.y+pixely));

A na závěr pouze rozhodneme, zda-li je na daném místě přechod či nikoliv. A to tak, že sečteme druhé mocniny obou vypočtených hodnot a porovnáme je s hranicí:

if((valx*valx+valy*valy)<treshold)return tex2D(tex[0],UV);
return float4(0,0,0, 1);

A to je vše :-) Výsledek vypadá nějak takto:

Detekce hran pomocí shaderu v XNA - Tvorba shaderů v HLSL

Tak a to by bylo pro dnešní díl vše. Postprocesové efekty jsou pěkné téma, ale moc to nevydrží. Neznám jich totiž tolik a protože jsou opravdu jen na pár řádků, tak se jich do jednoho dílu vejde opravdu dost. Snad vás detekce hran neodradila, je už pravda složitější.

Budu se těšit jako vždycky na komentáře, otázky, nápady, stížnosti a na viděnou zase příště, XNA a HLSL - Postprocesory Sepia, Alfa masking a Noise.


 

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

 

Předchozí článek
XNA a HLSL - Postprocesory
Všechny články v sekci
Tvorba shaderů v HLSL
Přeskočit článek
(nedoporučujeme)
XNA a HLSL - Postprocesory Sepia, Alfa masking a Noise
Článek pro vás napsal vodacek
Avatar
Uživatelské hodnocení:
5 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