6. díl - XNA a HLSL - Negativ, embos, gamma, toonshading a sobel

C# .NET XNA game studio HLSL XNA a HLSL - Negativ, embos, gamma, toonshading a sobel

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

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

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

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

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

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

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

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


 

Stáhnout

Staženo 94x (3.08 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 (5 hlasů) :
4.84.84.84.84.8


 


Miniatura
Předchozí článek
XNA a HLSL - Postprocesory
Miniatura
Všechny články v sekci
Tvorba shaderů v HLSL

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!