6. diel - XNA a HLSL - Negatív, embos, gamma, toonshading a Sobel
Vitajte znova. V tomto dieli pridáme ďalšie ľahké postprocesové efekty. Ale ešte ktoré začneme, tak pre istotu zopakujem kostru shader, do ktorej budeme pridávať všetok obsah, ak nebude povedané inak:
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(); } }
Negatív
Ďalším veľmi ľahkým efektom je vytvorenie negatívu. Vlastne len farby otočíme a to takto:
1-barva
To len aby ste nevypadli zo vzorčekov Vezmeme kostru shader a otrocky vzorček aplikujeme na všetky farebné zložky okrem alfa kanál:
return float4(1-color.r,1-color.g,1-color.b,1);
Shader do programu nasaďte ako minule a výsledok by mohol vyzerať nejako takto.
Všetko sa dá vyriešiť aj efektívnejšie pomocou takzvaného swizlingu. Je to špecialita jazyka ako takého, kiež by niečo také bolo možného aj inde:
return float4(1-color.rgb, color.a);
Operácia sa vykoná pre všetky komponenty oddelene. Super vlastnosť
uľahčujúce zápis. A výsledok je úplne rovnaký. Ešte by som podotkol, že
zložky musia byť z rovnakej sady teda buď z xyzw
a alebo z
rgba
. Kombinovať je navzájom nedajú. Pomocou inverzie si
vytvoríme ďalší efekt a tým bude embos.
Embos
Efekt embosu dovedie zdôrazniť prechody farieb. Principiálne sa skladá originálna farba s negatívom posunutým v určitom smere o niekoľko pixelov. Vzorček je nasledujúci:
(barva[u,v] + (1- barva[u+1,v+1]))/2
Pre posun o jeden pixel doprava dole. Dvoma delíme, aby bola výsledná farba v korektnom farebnom rozsahu. Posun musí byť pre dosiahnutie daného efektu vždy na diagonále. Implementáciu prevedieme ako vždy do kostry:
float3 ret=(color.rgb+(1-tex2D(tex[0],UV+offset).rgb))/2; return float4(ret, color.a);
Kde offset je premenná, ktorú som si vystrčil von ako parameter.
float offset=0.001;
Hodnotu možno zistiť buď pokusne a alebo výpočtom. Keďže máme hodnoty pre textúrovanie v rozsahu 0-1, tak budeme musieť trošku kúzliť. Poznáme rozmer okna 800x480 (aspon myslím). Rozmer jedného pixelu zistíme 1/800. Pomerne sa to zhoduje s pokusnú hodnotou.
Gamma korekcia
Ďalšie shader, tentokrát ale už bude používanie. Gama korekcia (anglicky gamma correction) je úprava obrazu, predtým používaná pre zobrazovanie na CRT obrazovkách, ktoré nezobrazovali lineárne. Preto bolo potrebné tento vplyv kompenzovať. Možno ju použiť aj na opravu expozície, kedy je požadovaný objekt schovaný v príliš tmavé alebo príliš svetlé časti obrazu a týmto spôsobom ju možno vytiahnuť von. Opäť ako vždy rovnica:
barva^gamma
Rovnica ako vždy jednoduchá a ak sa zdá niekomu v niečom známa, tak sa ani veľmi nepletie. Rovnakú rovnicu sme mali pre moduláciu jasu bodového svetla. Opäť aj tu môžu nastať dva prípady. Potrebujeme utlmiť príliš jasný obraz a alebo naopak jas zdvihnúť. Exponentmi medzi 0-1 jas zvýši a naopak exponentmi 1-nekonečno znižujú. Na nasledujúcom grafe vidíte výsledný priebeh:
Implementácia algoritmu je rovnaká ako u predchádzajúcich shaderov:
float3 ret=pow(color.rgb,gamma);
return float4(ret, color.a);
A premennú gamma dávame ako vstupný parameter:
float gamma=2.2;
Na výsledok s pôvodným obrázkom, obrázkom s gamma na 1 / 2,2 as gamma 2,2 sa môžete pozrieť nižšie:
Nie, nie je to montáž, výstup mi už takto generuje shader. Nie je to nič zložité, stačilo iba 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 pre väčšinu obrazoviek sa pohybuje v rozmedzí 1,8 až 2,4. To len pre kompletnú informáciu.
Toonshading
Toonshading je mierne zložitejšie technika. Podľa jasu vyberieme farbu z druhej pomocnej textúry a tak farbu zameníme. Už je na nás akú paletu zvolíme. Predovšetkým si vytvoríme textúru s novou paletou farieb. Ja som vzal desať náhodných farbičiek, šírka textúry teda bude 10 a výška 1.
A textúru pridáme do projektu. Poďme na shader. Najprv musíme pridať jednu textúru do poľa samplerov:
sampler2D tex[2];//2 textury
A vypočítame si intenzitu pre daný pixel, napríklad tým zložitejším spôsobom s váhami:
float intensity = 0.3f * color.r + 0.59f * color.g + 0.11f * color.b;
A túto hodnotu použijeme pre určenie novej farby z druhého samplera. Druhá súradnice bude vždy nula, pretože textúra má vlastne len jeden rozmer.
float3 toon=tex2D(tex[1],float2(intensity,0));
A všetko pošleme na výstup:
return float4(toon, color.a);
A to je celý shader. Jeho obsluha bude veľmi jednoduchá. V triede (je rovnaká ako ostatné) si iba konštruktor odovzdáme meno textúry s paletou:
Texture2D Toon; string toontexture; public ToonShading(Game g, string toontexture): base("postprocesory/toon",g) { this.toontexture = toontexture; }
Prepíšeme metódu Load
a textúru nahráme:
public override void Load(){ base.Load(); Toon = Game.Content.Load<Texture2D>(toontexture); }
A v metóde Draw
nastavíme textúru do druhého samplera:
public override void Draw(Texture2D input){ Game.GraphicsDevice.Textures[1] = Toon; base.Draw(input); }
Tým sme nastavili textúru ako aktívny. A to je celé. Výsledok bude pre moju textúru vyzerať nasledovne:
Detekcia hrán
Občas je potrebné tiež detekovať hrany v obraze. Je to napríklad jedna z metód anti-aliasingu. A určite je dobré tento shader poznať. Metód je veľa, ale ja použijem takzvaný sobelův filter. Je to matica (áno zas matice) 3x3 o nasledujúcom rozložení:
-1 0 1 -1 -2 -1 -2 0 2 0 0 0 1 0 1 1 2 1
Potrebujeme matice dve, jedna je pre horizontálne detekcie a druhá pre vertikálne. Avšak je možné maticu rotovať po 45% a zisťovať tak i ďalšie prechody. Predovšetkým si nadefinujeme konštanty. Prvý bude hodnota, ktorá nám určí hranicu medzi hranou a nehraný:
float treshold=0.05;
Nasledujú dve premenné pre veľkosti jedného pixelu:
float pixelx=0.001; float pixely=0.002;
Pre zjednodušenie si napíšeme ľahkú funkciu, ktorá nám vypočíta jas na danom bodu. HLSL rovnako ako ďalšie programovacie jazyky podporuje písanie funkcií. Tá musí však byť deklarovaná pred tým, než ju použijeme, teda rovnako ako v C-čku. Funkcia je to ľahká, takže bez komentára:
float Jas(float2 uv){ float3 color= tex2D(tex[0], uv); return (color.r+color.g+color.b)/3; }
Použil som jednoduchú variantu, ale môžeme použiť aj tú s váhami. Sobelovy matica 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 pre vertikálne smer:
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áver len rozhodneme, či je na danom mieste prechod alebo nie. A to tak, že sčítame druhej mocniny oboch vypočítaných hodnôt a porovnáme ich s hranicou:
if((valx*valx+valy*valy)<treshold)return tex2D(tex[0],UV); return float4(0,0,0, 1);
A to je všetko Výsledok vyzerá nejako takto:
Tak a to by bolo pre dnešné diel všetko. Postprocesové efekty sú pekné tému, ale veľmi to nevydrží. Nepoznám ich totiž toľko a pretože sú naozaj len na pár riadkov, tak sa ich do jedného dielu vojde naozaj dosť. Snáď vás detekcia hrán neodradila, je už pravda zložitejšie. Budem sa tešiť ako vždy na komentáre, otázky, nápady, sťažnosti a na videnie zase nabudúce.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 99x (3.08 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#