Showing posts with label Pixel Shader. Show all posts
Showing posts with label Pixel Shader. Show all posts

Saturday, January 15, 2011

Toon pixel shader

How to make pixel shader which performs 'basic' toon shading operation on images ?

Algorithm is following:
1. Convert pixel from RGB to HSV color space.
2. Map H,S,V values to some pre-defined set of H,S,V values.
3. Convert back from HSV to RGB.
4. Calculate if pixel is on edge, if so - ignore above calculated pixel color and place some pre-defined edge color instead.

That's it. You will have image converted to toon-shaded variant :-)

Here is GLSL code, which performs toon shading on image:

#version 150
uniform sampler2D Texture0;
varying vec2 texCoord;

#define HueLevCount 6
#define SatLevCount 7
#define ValLevCount 4
float[HueLevCount] HueLevels = float[] (0.0,80.0,160.0,240.0,320.0,360.0);
float[SatLevCount] SatLevels = float[] (0.0,0.15,0.3,0.45,0.6,0.8,1.0);
float[ValLevCount] ValLevels = float[] (0.0,0.3,0.6,1.0);

vec3 RGBtoHSV( float r, float g, float b) {
float minv, maxv, delta;
vec3 res;

minv = min(min(r, g), b);
maxv = max(max(r, g), b);
res.z = maxv; // v

delta = maxv - minv;

if( maxv != 0.0 )
res.y = delta / maxv; // s
else {
// r = g = b = 0 // s = 0, v is undefined
res.y = 0.0;
res.x = -1.0;
return res;
}

if( r == maxv )
res.x = ( g - b ) / delta; // between yellow & magenta
else if( g == maxv )
res.x = 2.0 + ( b - r ) / delta; // between cyan & yellow
else
res.x = 4.0 + ( r - g ) / delta; // between magenta & cyan

res.x = res.x * 60.0; // degrees
if( res.x < 0.0 )
res.x = res.x + 360.0;

return res;
}

vec3 HSVtoRGB(float h, float s, float v ) {
int i;
float f, p, q, t;
vec3 res;

if( s == 0.0 ) {
// achromatic (grey)
res.x = v;
res.y = v;
res.z = v;
return res;
}

h /= 60.0; // sector 0 to 5
i = int(floor( h ));
f = h - float(i); // factorial part of h
p = v * ( 1.0 - s );
q = v * ( 1.0 - s * f );
t = v * ( 1.0 - s * ( 1.0 - f ) );

switch( i ) {
case 0:
res.x = v;
res.y = t;
res.z = p;
break;
case 1:
res.x = q;
res.y = v;
res.z = p;
break;
case 2:
res.x = p;
res.y = v;
res.z = t;
break;
case 3:
res.x = p;
res.y = q;
res.z = v;
break;
case 4:
res.x = t;
res.y = p;
res.z = v;
break;
default: // case 5:
res.x = v;
res.y = p;
res.z = q;
break;
}

return res;
}

float nearestLevel(float col, int mode) {
int levCount;
if (mode==0) levCount = HueLevCount;
if (mode==1) levCount = SatLevCount;
if (mode==2) levCount = ValLevCount;

for (int i =0; i<levCount-1; i++ ) {
if (mode==0) {
if (col >= HueLevels[i] && col <= HueLevels[i+1]) {
return HueLevels[i+1];
}
}
if (mode==1) {
if (col >= SatLevels[i] && col <= SatLevels[i+1]) {
return SatLevels[i+1];
}
}
if (mode==2) {
if (col >= ValLevels[i] && col <= ValLevels[i+1]) {
return ValLevels[i+1];
}
}
}
}

// averaged pixel intensity from 3 color channels
float avg_intensity(vec4 pix) {
return (pix.r + pix.g + pix.b)/3.;
}

vec4 get_pixel(vec2 coords, float dx, float dy) {
return texture2D(Texture0,coords + vec2(dx, dy));
}

// returns pixel color
float IsEdge(in vec2 coords){
float dxtex = 1.0 /float(textureSize(Texture0,0)) ;
float dytex = 1.0 /float(textureSize(Texture0,0));
float pix[9];
int k = -1;
float delta;

// read neighboring pixel intensities
for (int i=-1; i<2; i++) {
for(int j=-1; j<2; j++) {
k++;
pix[k] = avg_intensity(get_pixel(coords,float(i)*dxtex,
float(j)*dytex));
}
}

// average color differences around neighboring pixels
delta = (abs(pix[1]-pix[7])+
abs(pix[5]-pix[3]) +
abs(pix[0]-pix[8])+
abs(pix[2]-pix[6])
)/4.;

return clamp(5.5*delta,0.0,1.0);
}

void main(void)
{
vec4 colorOrg = texture2D( Texture0, texCoord );
vec3 vHSV = RGBtoHSV(colorOrg.r,colorOrg.g,colorOrg.b);
vHSV.x = nearestLevel(vHSV.x, 0);
vHSV.y = nearestLevel(vHSV.y, 1);
vHSV.z = nearestLevel(vHSV.z, 2);
float edg = IsEdge(texCoord);
vec3 vRGB = (edg >= 0.3)? vec3(0.0,0.0,0.0):HSVtoRGB(vHSV.x,vHSV.y,vHSV.z);
gl_FragColor = vec4(vRGB.x,vRGB.y,vRGB.z,1.0);
}



So from this car


we will get this toon-car after shader is applied:


Have a fun with shaders !

Thursday, October 21, 2010

Thermal vision pixel shader

This is how we can get thermal vision pixel shader:
1. Make some gradient (here we will use blue-yellow-red gradient).
2. Make thermal map texture (here we will substitute pixel luminance value for temperature)
3. Get pixel's temperature from thermal map texture and map it to gradient value.

GLSL code



#version 120

uniform sampler2D tex;

void main()
{
vec4 pixcol = texture2D(tex, gl_TexCoord[0].xy);
vec4 colors[3];
colors[0] = vec4(0.,0.,1.,1.);
colors[1] = vec4(1.,1.,0.,1.);
colors[2] = vec4(1.,0.,0.,1.);
float lum = (pixcol.r+pixcol.g+pixcol.b)/3.;
int ix = (lum < 0.5)? 0:1;
vec4 thermal = mix(colors[ix],colors[ix+1],(lum-float(ix)*0.5)/0.5);
gl_FragColor = thermal;
}



Tank

and after thermal vision shader applied:

Sunday, July 11, 2010

Convolution Pixel Shader

What is convolution ? Simply speaking convolution is weighted sum of pixel values around target pixel. If those weights are put into vector W and pixel values around x,y are put in vector P, then in linear algebra terms convolution at x,y is nothing more than dot product of vectors W,P. More precisely:


Convolution(x,y) = (W*P)/denominator + offset


This W vector is called kernel. (Also formula can be re-written for matrix case, in that case W and P would be matrix). So by using different kernels we could achieve different image convolution effects. Several common convolution kernels:

Gaussian Blur kernel

1., 2., 1.,
2., 4., 2.,
1., 2., 1.

Sharpness kernel

-1., -1., -1.,
-1., 9., -1.,
-1., -1., -1.

Edge detection kernel

-1./8., -1./8., -1./8.,
-1./8., 1., -1./8.,
-1./8., -1./8., -1./8.

Emboss kernel

2., 0., 0.,
0., -1., 0.,
0., 0., -1.

Now, GLSL pixel shader code which performs convolution with all these kernels =>




#version 150

uniform sampler2D Texture0;

vec4 get_pixel(in vec2 coords, in float dx, in float dy) {
return texture2D(Texture0,coords + vec2(dx, dy));
}

float Convolve(in float[9] kernel, in float[9] matrix,
in float denom, in float offset) {
float res = 0.0;
for (int i=0; i<9; i++) {
res += kernel[i]*matrix[i];
}
return clamp(res/denom + offset,0.0,1.0);
}

float[9] GetData(in int channel) {
float dxtex = 1.0 / float(textureSize(Texture0,0));
float dytex = 1.0 / float(textureSize(Texture0,0));
float[9] mat;
int k = -1;
for (int i=-1; i<2; i++) {
for(int j=-1; j<2; j++) {
k++;
mat[k] = get_pixel(gl_TexCoord[0].xy,float(i)*dxtex,
float(j)*dytex)[channel];
}
}
return mat;
}

float[9] GetMean(in float[9] matr, in float[9] matg, in float[9] matb) {
float[9] mat;
for (int i=0; i<9; i++) {
mat[i] = (matr[i]+matg[i]+matb[i])/3.;
}
return mat;
}

void main(void)
{
float[9] kerEmboss = float[] (2.,0.,0.,
0., -1., 0.,
0., 0., -1.);

float[9] kerSharpness = float[] (-1.,-1.,-1.,
-1., 9., -1.,
-1., -1., -1.);

float[9] kerGausBlur = float[] (1.,2.,1.,
2., 4., 2.,
1., 2., 1.);

float[9] kerEdgeDetect = float[] (-1./8.,-1./8.,-1./8.,
-1./8., 1., -1./8.,
-1./8., -1./8., -1./8.);

float matr[9] = GetData(0);
float matg[9] = GetData(1);
float matb[9] = GetData(2);
float mata[9] = GetMean(matr,matg,matb);

// Sharpness kernel
//gl_FragColor = vec4(Convolve(kerSharpness,matr,1.,0.),
// Convolve(kerSharpness,matg,1.,0.),
// Convolve(kerSharpness,matb,1.,0.),1.0);

// Gaussian blur kernel
//gl_FragColor = vec4(Convolve(kerGausBlur,matr,16.,0.),
// Convolve(kerGausBlur,matg,16.,0.),
// Convolve(kerGausBlur,matb,16.,0.),1.0);

// Edge Detection kernel
//gl_FragColor = vec4(Convolve(kerEdgeDetect,mata,0.1,0.),
// Convolve(kerEdgeDetect,mata,0.1,0.),
// Convolve(kerEdgeDetect,mata,0.1,0.),1.0);

// Emboss kernel
gl_FragColor = vec4(Convolve(kerEmboss,mata,1.,1./2.),
Convolve(kerEmboss,mata,1.,1./2.),
Convolve(kerEmboss,mata,1.,1./2.),1.0);

}




What's left ? Results of course :)
Here is original image

convolution with Gaussian Blur kernel

convolution with Sharpness kernel

convolution with Edge detection kernel

convolution with Emboss kernel

Friday, June 25, 2010

Steganography Pixel Shader

Steganography is method for hidding secret message in covert message. In this case I mean hidding secret image into covert image. So, sometimes What You See Is NOT What You Get :) How can we hide secret 3-bit image into other 24-bit image ?

Encoding procedure

1.
secret 3-bit image means that we can hide 2^3 = 8 color palette image.
So at first we need to map these 8 colors to 3-bit pattern. Lets use such mappings:

--------------------------
RGB | Bit pattern
--------------------------
2,2,2 | 0,0,0
38,38,38 | 0,0,1
74,74,74 | 0,1,0
110,110,110 | 1,0,0
146,146,146 | 0,1,1
182,182,182 | 1,1,0
218,218,218 | 1,0,1
254,254,254 | 1,1,1
--------------------------

2.
Now as we have color table, we just need to get 3-bit image required pixel and encode it's 3-bit pattern into covert RGB image. One way of doing this is to define secret bit meaning as follows: 0 means covert RBG byte is even, 1 means - odd.
So according to this definition we adjust covert image RGB bytes to be even or odd - depending to required 3-bit secret pattern. For example-
if 3-bit pattern is (1,0,1) and covert RGB pixel is (140,39,16) - then it will be converted to (141,40,17).

Decoding procedure
Secret image extraction from covert image procedure is a reversal of encoding:
1. Check covert RGB bytes are even or odd.
2. From that extract 3-bit pattern.
3. Map this 3-bit pattern to 8 color palette.

Properties of this image hidding method
1. Can be used in image formats which doesn't support alpha (transparency) channel.
2. Original image looses only 3/24 = 12.5 percents of quality, which means that it is hard or impossible to spot by eyes that something is wrong with covert image.
3. Regardless of good image quality after conversion, image histogram can show some signs of payload image. Because in some cases histogram is modulated after conversion.

Now code examples. Encoding part is left as exercise to the reader :) But here it is payload image extraction code (as always in GLSL shader language):
a) Vertex Shader (we need it for correct sampling of texels)



varying vec2 texCoord;

void main(void)
{
gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0 );
texCoord = 0.5 * gl_Position.xy + vec2(0.5);
}




b) Pixel Shader (real program doing payload image extraction)



#version 120
uniform sampler2D Texture0;
varying vec2 texCoord;

int binaryToDecimal(in int d1, in int d2, in int d3) {
return 4*d1+2*d2+d3;
}

void fillColors(inout float[8] colors) {
colors[0] = 2./255.;
colors[1] = 38./255.;
colors[2] = 74./255.;
colors[3] = 146./255.;
colors[4] = 110./255.;
colors[5] = 218./255.;
colors[6] = 182./255.;
colors[7] = 254./255.;
}

int Odd(in float num) {
return int(mod(num,2.)!=0.);
}

void main()
{
float colTable[8];
fillColors(colTable);
vec4 col = texture2D(Texture0, texCoord);
int d1 = Odd(col.r*255.);
int d2 = Odd(col.g*255.);
int d3 = Odd(col.b*255.);
float level = colTable[binaryToDecimal(d1,d2,d3)];
gl_FragColor = vec4(level,level,level,1.0);
}



NOTE: When using this GLSL shader for image below - make sure that covert texture is rendered to screen aligned quad of EXACTLY 512x512 pixel dimensions. Otherwise you will not get hidden image, but instead you will get just noise because of incorrect pixel/texel samplings !!!

Finally,- results. This is covert image:

and this is payload image extracted from above image (after extraction GLSL shader applied):


Have fun in making/analyzing covert images !

Thursday, June 17, 2010

Binary filter Pixel Shader

Binary image is image composed only from 2 colors. Typical conversion algorithm to binary image is this -> If pixel's averaged intensity is greater than threshold - draw pixel in first color, otherwise- draw it in second color. But you can define other methods of conversion to binary as well.
For example another method can be - If pixel's color is near target color (within error bounds) - draw pixel in first color, otherwise - draw it in second. This conversion rule is implemented in following GLSL pixel shader code:



uniform sampler2D tex;

bool nearColor(in vec4 col, in vec4 tcol, in float error) {
return abs(col.r-tcol.r) < error &&
abs(col.g-tcol.g) < error &&
abs(col.b-tcol.b) < error ;
}

void main()
{
vec4 xcol = texture2D(tex, gl_TexCoord[0].xy);
vec4 scol = vec4(0.737,0.506,0.404,1.0);
if (nearColor(xcol,scol,0.085))
gl_FragColor = scol;
else
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}



Image

converted to binary mode:

Tuesday, June 15, 2010

Self-projection Pixel Shader

Image is several times scaled and projected on itself. This concrete GLSL pixel shader projects image on itself 2 times:



uniform sampler2D tex;

bool inRectangle(in vec2 xloc, in vec2 loc, in vec2 size) {
return xloc[0] >= loc[0] &&
xloc[1] >= loc[1] &&
xloc[0] <= loc[0]+size[0] &&
xloc[1] <= loc[1]+size[1];
}

void main()
{
vec2 start1 = vec2(0.19,0.11);
vec2 size1 = vec2(0.495,0.285);
vec2 start2 = start1 + start1*size1;
vec2 size2 = size1*size1;
if (inRectangle(gl_TexCoord[0].xy, start2, size2))
gl_FragColor = texture2D(tex, (gl_TexCoord[0].xy - start2)/size2);
else if (inRectangle(gl_TexCoord[0].xy, start1, size1))
gl_FragColor = texture2D(tex, (gl_TexCoord[0].xy - start1)/size1);
else
gl_FragColor = texture2D(tex, gl_TexCoord[0].xy);
}



Image

and self-projected version of it

Monday, June 14, 2010

Pixelation Pixel Shader

Pixelation is process when pixel at x,y is duplicated into x+dx,y+dy rectangle. Pixelation GLSL fragment code:



uniform sampler2D tex;

void main()
{
float dx = 15.*(1./512.);
float dy = 10.*(1./512.);
vec2 coord = vec2(dx*floor(gl_TexCoord[0].x/dx),
dy*floor(gl_TexCoord[0].y/dy));
gl_FragColor = texture2D(tex, coord);
}



Smart

and pixelated version of it

Friday, June 11, 2010

Fog Pixel Shader

Each pixel is blended with fog color. The bigger pixel distance from observer location - the more pixel color approaches fog color. GLSL fragment code:





uniform sampler2D tex;

void main()
{
float FogDensity = 10.;
vec4 FogColor = vec4(0.4,0.2,0.2,1.0);
vec4 CurrentColor = texture2D(tex, gl_TexCoord[0].xy);

// distance to target
float FogDistance = distance(vec2(0.49,0.46),gl_TexCoord[0].xy);

// fog factor
float FogFactor = exp(-abs(FogDistance * FogDensity));

// linear blend between fog color and pixel color
gl_FragColor = mix(FogColor,CurrentColor,FogFactor);
}




Image

and after fog shader applied:

Wednesday, June 9, 2010

Frosted glass Pixel Shader

We can get frosted glass effect by shifting pixel location with pseudo-random vector, such as after shift respective pattern emerges. Frosted glass GLSL pixel shader code:




uniform sampler2D tex;

float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(92.,80.))) +
cos(dot(co.xy ,vec2(41.,62.))) * 5.1);
}

void main()
{
vec2 rnd = vec2(rand(gl_TexCoord[0].xy),rand(gl_TexCoord[0].xy));
gl_FragColor = texture2D(tex, gl_TexCoord[0].xy+rnd*0.05);
}




Original image

and processed with frosted glass filter

Monday, June 7, 2010

Explosion and implosion Pixel Shader

Explosion/Implosion filter can be modeled by shifting pixel outwards/towards the center of image by value which is proportional to pixel distance from the center of image.
Explosion/Implosion pixel shader code in GLSL:




uniform sampler2D tex;

void main()
{
vec2 cen = vec2(0.5,0.5) - gl_TexCoord[0].xy;
vec2 mcen = - // delete minus for implosion effect
0.07*log(length(cen))*normalize(cen);
gl_FragColor = texture2D(tex, gl_TexCoord[0].xy+mcen);
}




By applying this filter to image below

we get such explosion effect

and such implosion effect

Thursday, June 3, 2010

Edge detection Pixel Shader

This time i will write short edge detection tutorial for OpenGL GLSL language. Suppose we have 9 pixels such as below :

We want to find out intensity of pixel I_x after applying edge detection filter. In simple edge detection model intensity I_x can be defined as:

That is - for being able to calculate pixel intensity we need to find out intensity differences between neighboring pixels and average them. Pixel shader program which implements this idea is given below (In GLSL language):



uniform sampler2D tex;

float threshold(in float thr1, in float thr2 , in float val) {
if (val < thr1) {return 0.0;}
if (val > thr2) {return 1.0;}
return val;
}

// averaged pixel intensity from 3 color channels
float avg_intensity(in vec4 pix) {
return (pix.r + pix.g + pix.b)/3.;
}

vec4 get_pixel(in vec2 coords, in float dx, in float dy) {
return texture2D(tex,coords + vec2(dx, dy));
}

// returns pixel color
float IsEdge(in vec2 coords){
float dxtex = 1.0 / 512.0 /*image width*/;
float dytex = 1.0 / 512.0 /*image height*/;
float pix[9];
int k = -1;
float delta;

// read neighboring pixel intensities
for (int i=-1; i<2; i++) {
for(int j=-1; j<2; j++) {
k++;
pix[k] = avg_intensity(get_pixel(coords,float(i)*dxtex,
float(j)*dytex));
}
}

// average color differences around neighboring pixels
delta = (abs(pix[1]-pix[7])+
abs(pix[5]-pix[3]) +
abs(pix[0]-pix[8])+
abs(pix[2]-pix[6])
)/4.;

return threshold(0.25,0.4,clamp(1.8*delta,0.0,1.0));
}

void main()
{
vec4 color = vec4(0.0,0.0,0.0,1.0);
color.g = IsEdge(gl_TexCoord[0].xy);
gl_FragColor = color;
}



So by using this shader this image =>

is transformed into this =>


For more advanced algorithms on edge detection - start here.

Have fun with pixel shaders !