Saturday, October 24, 2009

Read Between The Pixels - HLSL Kill Pixel Shader


A couple of days ago William Moore (@codenenterp) asked me via Twitter if I know a pixel shader that makes odd pixels black. I didn't know were such a shader is available, but I knew that it should be not hard to write one in HLSL. So I fired up the Shazzam tool and wrote two shaders.






I have started with a pixel shader that kills the color in every odd row of the texture and makes every second scanline transparent. If a black rectangle is overlaid with such a shaded image, each odd scanline will be seen as black, thus resulting in the desired black scanline effect.




An alternating line effect could be realized if an image instead of a black rectangle is overlaid.




After that I've extended the pixel shader to kill every odd pixel to produce the checkerboard effect William Moore wanted.




This is how a Silverlight Button could look like if the shader is applied to it:




How it works
The pixel shaders are pretty simple and small. As always, I've commented the HLSL code, but if you have any further questions, feel free to leave a comment.
Here is the shader that kills every odd scanline:
// Parameter
float2 TextureSize : register(C0);

// Sampler
sampler2D TexSampler : register(S0);

// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR 
{ 
   // Default color is fully transparent
   float4 color = 0;

   // Scale to int texture size
   float row = texCoord.y * TextureSize.y * 0.5f; 

   // Calc diff between rounded half and half to get 0 or 0.5
   float diff = round(row) - row;
   float diffSq = diff * diff;

   // Even or odd? Only even lines are sampled
   if(diffSq < 0.1)
   {
      color = tex2D(TexSampler, texCoord);
   } 
   return color;
}

And here the shader that kills every odd pixel:
// Parameter
float2 TextureSize : register(C0);

// Sampler
sampler2D TexSampler : register(S0);

// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR 
{ 
   // Default color is fully transparent
   float4 color = 0;

   // Scale to int texture size, add x and y
   float2 vpos = texCoord * TextureSize * 0.5f; 
   float vposSum = vpos.x + vpos.y;
 
   // Calc diff between rounded half and half to get 0 or 0.5
   float diff = round(vposSum) - vposSum;
   float diffSq = diff * diff;

   // Even or odd? Only even pixels are sampled
   if(diffSq < 0.1)
   {
      color = tex2D(TexSampler, texCoord);
   } 
   return color;
}

As you can see the only difference is that the kill pixel shader uses the sum of the texture coordinate's x and y component to determine if the pixel is even or odd.
For the effect of the uppermost blog post image (mandrill), I've used the product of x and y instead of the sum.
The size of the killed pixel group could be changed with the TextureSize parameter. The following example uses an eight of the original picture size as TextureSize.




Source code
I have also written a small Silverlight application that shows an image with the pixel shader attached. You can download the Visual Studio 2008 solution including the pixel shaders from here.

Photos from the USC-SIPI Image Database

3 comments:

  1. Wow that's amazing I believed that you just can do that using photoshop or coreldraw, thanks for share, i will try.

    ReplyDelete
  2. hello, i think that this post is very good beacause has useful information.

    ReplyDelete
  3. In my Dither shader for SweetFX I needed an odd or even checkerboard pattern too.

    I solved this with

    float grid_position = frac(dot(tex,(screen_size / 2.0))+0.25); //returns 0.25 and 0.75

    tex is my texcoords (a float2) and screen_size is a float2 with the size of the screen in pixels.

    I need the +0.25 to avoid problems with imprecise floating point math.

    It's extremely fast , compiles to only a DP2ADD and a FRC instruction.

    If adapted to your shader it would look like this :

    // Parameter
    float2 TextureSize : register(C0);

    // Sampler
    sampler2D TexSampler : register(S0);

    // Shader
    float4 main(float2 texCoord : TEXCOORD) : COLOR
    {
    // Default color is fully transparent
    float4 color = 0;

    float diff = frac(dot(texCoord ,(TextureSize / 2.0))+0.25); //returns 0.25 and 0.75


    // Even or odd? Only even pixels are sampled
    if(diff < 0.5)
    {
    color = tex2D(TexSampler, texCoord);
    }
    return color;
    }

    ReplyDelete