Live
The intensity of the scratches and the noise is controlled with the Sliders. The shader could be disabled with the "Bypass" checkbox and it's also possible to load a WMV clip with the "Load" Button.
William Moore made some short video clips with After Effects for me and I have uploaded them to my Dropbox. Just copy a URL to the TextBox and press "Load" to try them with the shader:
- Grass.wmv
- MooreFamily.wmv
How it works
Here's the pixel shader written in HLSL:
// Parameters float ScratchAmount : register(C0); float NoiseAmount : register(C1); float2 RandomCoord1 : register(C2); float2 RandomCoord2 : register(C3); float Frame : register(C4); // Static static float ScratchAmountInv = 1.0 / ScratchAmount; // Sampler sampler2D TexSampler : register(S0); sampler2D NoiseSampler : register(S1); // Shader float4 main(float2 uv : TEXCOORD) : COLOR { // Sample texture float4 color = tex2D(TexSampler, uv); // Add Scratch float2 sc = Frame * float2(0.001f, 0.4f); sc.x = frac(uv.x + sc.x); float scratch = tex2D(NoiseSampler, sc).r; scratch = 2 * scratch * ScratchAmountInv; scratch = 1 - abs(1 - scratch); scratch = max(0, scratch); color.rgb += scratch.rrr; // Calculate random coord + sample float2 rCoord = (uv + RandomCoord1 + RandomCoord2) * 0.33; float3 rand = tex2D(NoiseSampler, rCoord); // Add noise if(NoiseAmount > rand.r) { color.rgb = 0.1 + rand.b * 0.4; } // Convert to gray + desaturated Sepia float gray = dot(color, float4(0.3, 0.59, 0.11, 0)); color = float4(gray * float3(0.9, 0.8, 0.6) , 1); // Calc distance to center float2 dist = 0.5 - uv; // Random light fluctuation float fluc = RandomCoord2.x * 0.04 - 0.02; // Vignette effect color.rgb *= (0.4 + fluc - dot(dist, dist)) * 2.8; return color; }
The 1st effect adds some random scratches, which are moving slowly each frame.
Unfortunately the Shader Model 2 doesn't support any random function and the noise() intrinsic could not be used for real-time effects. That's a problem if you want to make pseudorandom-based shaders, but one key element of restricted shader development is pre calculation and texture lookup. The randomness, which is used in the shader, comes from a WriteableBitmap, that is filled with random color in the initialization step of the application. The shader in turn samples random values from that texture.
The 2nd effect adds a bit random noise to the image. If the texture coordinates with the frame counter would only be used, a static, moving pattern would be seen and it wouldn't look noisy at all. So actually a new random noise texture is needed every frame to achieve the desired effect. Generating a random texture with a WriteableBitmap in every frame would be too expensive. Instead of this, a little trick is used and only four random numbers are generated every frame and passed to the shader as parameters. These random seeds are then used as texture coordinates for a random lookup in the random colored texture. Therefore each pixel in every frame samples a different random value. Although it is not highly random, the values are sufficient enough to produce changing noise.
The 3rd step converts the pixel to grayscale and colors it afterwards with a desaturated Sepia tone.
The 4th and last effect computes the distance to the center and multiplies it with the color. This generates the vignette effect as a fadeout to black towards the edge. A random light flickering of an old projector is also simulated.
That's it and it's all about cheating.
Source code
Feel free to download the Visual Studio 2008 solution including the pixel shader from here.
Update 12-29-2009
I refactored the code behind shader class and separated it completly from the calling code (MainPage.xaml.cs). Now the shader class generates a default noise texture and random coordinates itself.
The linked source code was updated.
Update 12-31-2009
See this blog post if you want to try the shader in real time with your own webcam.