Contents |
The following collection of effect snippets mimic old TV or camera effects. This can be useful to achieve flash back or old TV effects or low quality cameras that are used by the police.
Sepia tone is a color space that is used to give images an aged or antique feel. The following implementation follows [Ansari].
To achieve a good looking sepia tone effect, the original color space YIQ that was used to encode NTSC video in 1953 is used. To perform the conversion, the RGB values need to be transformed into YIQ color space and vice versa.
The matrix to convert an RGB sample into YIQ is

The first row of M computes the luminance of the input RGB samples, which corresponds to the Y component of YIQ. The second and third row encode the chromacity into I and Q.
The matrix to convert YIQ back to RGB is M's inverse.

To optimize the RGB to YIQ and vice versa conversion for Sepia, the RGB to YIQ conversion can be reduced to the luminance value; Y. This comes down to a dot product between the RGB value and the first row of the RGB-To-YIQ matrix.
To simplify the operation while converting back to RGB from Y -Sepia-, we can replace the I value with 0.2 and the Q value with 0.0 for a Sepia effect. This comes down to a vector consisting of the following values.

The values 0.2 and 0.0 were obtained by experimentation, and other values might look better in different applications.
The implementation to convert from RGB to Y can be achieved with the following line of HLSL code.
... float3 IntensityConverter={0.299, 0.587, 0.114}; Y = dot(IntensityConverter, Color); ...
Converting back to RGB can happen with the following code.
... float4 sepiaConvert ={0.191, -0.054,-0.221,0.0}; return Y + sepiaConvert; ...
A film grain filter can be used to mimic the bad reception of a TV sender. This filter can be achieved by fetching a wrapped 64x64x64 or 32x32x32 3D volume texture that holds random noise values by running through its z slices based on a time counter. The following source code shows how this can be achieved:
... // pos has a value range of -1..1 // the texture address mode is wrapped float rand = tex3D(RandomSampler, float3(pos.x, pos.y, counter)); float4 image = tex2D(ImageSampler, Screenpos.xy); return rand + image; ...
A border around the screen can be achieved by applying a power function to the extrema of the screen. The result is then clamped to 0..1 and used to multiply the end result of the other two filters like this:
... float f = (1-pos.x*pos.y) * (1-pos.y*pos.y); float frame = saturate(frameSharpness * (pow(f, frameShape) - frameLimit)); return frame * (rand + image); ...
The variable frameSharpness makes the transition between the frame and the visible scene softer. The variable frameshape makes the shape of the frame rounder or more edgy and the variable framelimit makes the frame bigger or smaller.
A median filter is usually used to remove "salt and pepper noise" from a picture [Mitchell]. For example if you have the following set of numbers: {9, 3, 6, 1, 2, 2, 8}, you can sort them to get {1, 2, 2, 3, 6, 8, 9} and select the middle value 3. Hence the median of these values is 3. This is better than taking the mean, because this value actually appears in the image. This filter can be also used to mimic MPEG compressed movies from a cheap camcorders.
As it turns out an approximation to a 2D median filter can be efficiently in a separable manner. For example with the following data we can get the median in figure 15:

An implementation picks out of three values the median by using the following algorithm.
float FindMedian(float a, float b, float c) { float Median; if(a < b) { if(b<c) Median = b; else Median = max(a, c); } else { if(a < c) Median = a; else Median = max(b, c); } return Median; }
This can be vectorized to the following code:
float3 FindMedian(float3 RGB, float3 RGB2, float3 RGB3) { return (RGB < RGB2) ? ((RGB2 < RGB3) ? RGB2 : max(RGB,RGB3) ) : ((RGB < RGB3) ? RGB : max(RGB2,RGB3)); }
Any interlace effect modifies every second line of the image differently than the other line. The lines are separated by taking the fraction of the unnormalized coordinates in the y direction like this:
float fraction = frac(UnnormCoord.y * 0.5); if (fraction) ... // do something with this line else ... // do something different with every second line