An image filter is an
algorithm that is executed to convert a picture from its original input
to some type of output, which is dependent on the algorithm itself.
Examples of some image filters are color inversion filters,
black-and-white (luminance) filters, sepia filters, edge detection
filters, blur filters, and so forth.
In this section we will look
at a few simple image filters and apply them to textures being rendered
on square surfaces to keep the code short and simple.
Color Inversion
Color
inversion is a simple effect to create when it comes to image filters.
To perform this effect we only have to use 1 minus the color in the
pixel shader. This makes colors that are 1 equal to 0 and colors that
are 0 equal to 1. In other words, it flips the colors around, so dark
becomes light and light becomes dark.
The effect shader file from the demo is shown in Listing 1. Figure 1 shows a screenshot of the effect.
Listing 1. Color Inversion Demo’s Effect Shader File
/* Color Inversion Filter Demo's HLSL Shader Ultimate Game Programming with DirectX 2nd Edition Created by Allen Sherrod */
Texture2D decal;
SamplerState DecalSampler { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; };
cbuffer cbChangesEveryFrame { matrix World; matrix View; };
cbuffer cbChangeOnResize { matrix Projection; };
struct VS_INPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD; };
struct PS_INPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; };
PS_INPUT VS(VS_INPUT input) { PS_INPUT output = (PS_INPUT)0;
output.Pos = mul(input.Pos, World); output.Pos = mul(output.Pos, View); output.Pos = mul(output.Pos, Projection);
output.Tex = input.Tex;
return output; }
float4 PS(PS_INPUT input) : SV_Target { return 1 - decal.Sample(DecalSampler, input.Tex); } technique10 ColorInversion { pass P0 { SetVertexShader(CompileShader(vs_4_0, VS())); SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_4_0, PS())); } }
|
Luminance Filter
The
algorithm for the luminance filter works by taking each color in the
pixel shader and multiplying each component by the luminance constant.
You then add the result of each multiplied component together to get the
value that will act as the grayscale pixel output. The luminance
constant is 0.30 for the red, 0.59 for the green, and 0.11 for the blue
components. Multiplying the color’s red, green, and blue components by
this luminance constant and then adding the results of all components
will create a result that appears black and white for all pixels. The
operations are shown as follows, where color is the original pixel color
and lum is the luminance constant (0.30, 0.59, 0.11).
Luminance = color.red * lum.red + color.green * lum.green + color.blue * lum.blue
This operation is essentially the dot product of two vectors, or in this case two colors. You can use the HLSL dot()
function to perform this operation, which will be discussed in more
detail in the next chapter for vectors. For now, know that the dot()
function takes each component from the two vectors (color and the
luminance constant in this example) and multiplies them together and
adds up the results. The resulting value is used for the output color.
The Luminance demo’s effect shader file is shown in Listing 2. Figure 2 shows a screenshot of the demo in action.
Listing 2. The Luminance Demo’s Effect Shader File
/* Luminance Filter Ultimate Game Programming with DirectX 2nd Edition Created by Allen Sherrod */
Texture2D decal;
SamplerState DecalSampler { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; };
cbuffer cbChangesEveryFrame { matrix World; matrix View; };
cbuffer cbChangeOnResize { matrix Projection; };
struct VS_INPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD; };
struct PS_INPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; };
PS_INPUT VS(VS_INPUT input) { PS_INPUT output = (PS_INPUT)0; output.Pos = mul(input.Pos, World); output.Pos = mul(output.Pos, View); output.Pos = mul(output.Pos, Projection);
output.Tex = input.Tex;
return output; }
float4 PS(PS_INPUT input) : SV_Target { float3 lumConst = float3(0.30, 0.59, 0.11);
float3 color = decal.Sample(DecalSampler, input.Tex); float dp = dot(color, lumConst);
return float4(dp, dp, dp, 1); }
technique10 LuminanceFilter { pass P0 { SetVertexShader(CompileShader(vs_4_0, VS())); SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_4_0, PS())); } }
|
The luminance filter is also known as the black-and-white transfer function. |
Sepia Tone Filter
The sepia tone effect is used to color a black-and-white image with a
brownish tone to give it the appearance of and old photograph. It does
this by taking the original color and a brown tone, and using the two to
color a black-and-white version of the image to create the effect. As
the description implies, the sepia tone builds off of the luminance
filter.
The sepia tone filter works in the following steps.
1. | Convert the image to black and white.
|
2. | Use
the luminance (black-and-white) value as a percent between a light and
dark set of colors to create the sepia constant, where 0% (0.0) means we
use the dark color, 100% (1.0) means we use the light color, and any
percentage between 0% and 100% will be a color value between the light
and dark colors.
|
3. | Take
the original color and the luminance color and find a color that is
halfway between the two. Let’s call this the half color.
|
4. | Use the half color and the sepia constant and find a color that is halfway between those two. This will be the output color.
|
In HLSL you can find a color that is between two colors by using the lerp() function. The lerp()
function takes as parameters the first vector (or color in this demo,
which is represented by a vector; i.e., they’re the same thing from a
structure point of view), the second vector, and a percentage to
interpolate between the two. Finding a color between two colors is
another way of mixing colors because the result will be a blend of the
two colors. When you find a color that is halfway between two other
colors, it is like you are equally mixing the colors together.
The term lerp
stands for linear interpolation. For a simple example, let’s say we are
interpolating between the values 0 and 100. The percentage is used to
find a value between these two, where a percentage of 0.0 will return 0,
and a percent of 1.0 will return 100. So if the percentage is 0.65,
then 65 will be returned since 65% into the range of 0 and 100 is 65.
The lerp() function does this operation,
but on two vectors to obtain a vector that is some percentage between
the two parameters. Therefore, to find a color between two colors using lerp(),
you just specify the two colors and a percentage between the two for
which you want to look. Since we are using colors, the result will be a
blend between the two.
The sepia tone effect is shown in Listing 3 in the demo’s shader file. This demo builds off of the Luminance demo and adds a few extra lines of code to the pixel shader. Figure 3
shows a screenshot of the demo. We recommend that you run the demo
applications to see the results in color to be able to fully appreciate
the differences between each image filter.
Listing 3. The Sepia Demo’s HLSL Effect Shader File
/* Sepia Filter Demo's HLSL Shader Ultimate Game Programming with DirectX 2nd Edition Created by Allen Sherrod */
Texture2D decal;
SamplerState DecalSampler { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; };
cbuffer cbChangesEveryFrame { matrix World; matrix View; };
cbuffer cbChangeOnResize { matrix Projection; };
struct VS_INPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD; };
struct PS_INPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; };
PS_INPUT VS(VS_INPUT input) { PS_INPUT output = (PS_INPUT)0;
output.Pos = mul(input.Pos, World); output.Pos = mul(output.Pos, View); output.Pos = mul(output.Pos, Projection);
output.Tex = input.Tex;
return output; }
float4 PS(PS_INPUT input) : SV_Target { float3 lumConst = float3(0.30, 0.59, 0.11); float3 light = float3(1, 0.9, 0.5); float3 dark = float3(0.2, 0.05, 0);
float3 color = decal.Sample(DecalSampler, input.Tex);
float luminance = dot(color, lumConst); float3 sepia = lerp(dark, light, luminance); float3 halfColor = lerp(color, luminance, 0.5); float3 final = lerp(halfColor, sepia, 0.5);
return float4(final, 1); }
technique10 SepiaFilter { pass P0 { SetVertexShader(CompileShader(vs_4_0, VS())); SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_4_0, PS())); } }
|