Shades of Sepia


Last week, I put effort into making modern, full-color rendering look like an old TV. This time around, we’ll go even further into the past and make the screen resemble an old, faded photo. To produce this effect, I use a pixel shader to apply a sepia tone to the image as well produce a vignette effect by darkening the edges of the image.

Using the previous grayscale conversion from my earlier shader, it is trivially easy to apply a sepia tone to the screen by multiplying my grayscale values with a sepia RGB value. Naturally, this greyscale staining technique can work with any other color. Do old photos tint red on Mars? I don’t know, make a game and tell me!

    float greyscale = dot(tex.rgb, float3(0.3, 0.59, 0.11));
    tex.rgb = greyscale * float3(0.9, 0.8, 0.6);

The other component of the shader is to use the distance of every pixel from the center of the screen to determine how dark it should be. By telling pixels to be darker the further they are from the center, you can create the characteristic corner darkening associated with the vignette effect.

Now, how to go about calculating the distance of each pixel from the center of the screen? If I wanted to get the actual screen position of each pixel (using the VPOS semantic), I would need to use HLSL Shader Model 3.0. Luckily, because I’m already rendering my scene to an intermediate texture and drawing it to the screen with the pixel shader, I can use texture coordinates (which are available in all shader models) to determine the location of each pixel on the screen.

Just using the magnitude of the pixel distance from center by itself would produce an evening darkening from the center of the screen. However, since I still want most of the screen to be readily visible, I use the square of the pixel distance so the fall-off into darkness happens closer to the edge of the screen but with a steeper cliff. Using the dot product of the pixel distance with itself gives me just the number I’m looking for.

With a scalar determined by the distance of the current pixel from the center of the screen in hand, I can apply it to pixel color like so with an additional multiplier for tweaking the intensity of the effect:

    float greyscale = dot(tex.rgb, float3(0.3, 0.59, 0.11));
    float2 dist = input.TexCoord - 0.5f;
    tex.rgb = greyscale * float3(0.9, 0.8, 0.6) * (1 - dot(dist, dist) * VignetteIntensity);

Using the shader code above, I get the following effect:

And there we are. A nice sepia toned screen with a vignette effect for that old-timey photo look. As an added bonus, you can also apply a random modifier to the vignette intensity to create an approximation of the flickering you get from old movie reels.

Shader code for the sepia pixel shader is below:

sampler TextureSampler : register(s0);
float VignetteIntensity;

struct VertexShaderOutput
{
    float4 Position : POSITION0;
	float2 TexCoord : TEXCOORD0;
};

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float4 tex = tex2D(TextureSampler, input.TexCoord);
	
	float greyscale = dot(tex.rgb, float3(0.3, 0.59, 0.11));
    float2 dist = input.TexCoord - 0.5f;
    tex.rgb = greyscale * float3(0.9, 0.8, 0.6) * (1 - dot(dist, dist) * VignetteIntensity);

	return tex;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
Share this Article:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • Print