0xDEADC0DE » Post-process filters in HLSL - part 1

Post-process filters in HLSL - part 1

Posted under Tutorials by Eclipse

As requested from Loover and some other guys around TIGSource forums, here’s a little tutorial on how to make post-process filters.
I’ll cover only a GPU implementation, and sadly only in HLSL for time purposes but knowing the bases of GLSL or CG you can do easily the same stuff also using an OpenGL rendering.
Also, keep in mind that I’m not an English speaker (not so fluent), so if you find something that sounds rough, hard to understand, or simply illogical, drop me a comment or a mail, thanks.

First of all, we need to setup a render-to-texture framework, I prefer to spend my time on this tutorial talking about HLSL so you have to do it on your own, because the Internet is already full of tutorials on render-to-texturing and there are very few good HLSL tutorials around, especially on postfilters.
But don’t despair, I’ll not let you alone here, so here for you a bunch of useful links on that argument:
Render to texture in DirectX and Creating A Post-Processing Framework by Gamasutra.

So basically we need to have our scene first rendered to a texture instead to the backbuffer, and then we need to render a fullscreen-sized quad with that texture on it. The Gamasutra article explains also some interesting options, like having a more or less texellated quad to do per vertex effects instead of per pixel ones.

render to texture scene
A simple 3d scene I did for this tutorial, rendered over my fullscreen-sized quad.

Let’s go deeper on HLSL

Create a .fx file using your favorite text-editor, Visual Studio, or FX Composer by NVidia, that I really love.
And start coding:


texture scene; //a texture variable for the render-to-textured frame


sampler sceneSampler = sampler_state
{
texture = <scene>;
AddressU = CLAMP;
AddressV = CLAMP;
AddressW = CLAMP;
MIPFILTER = NONE;
MINFILTER = NONE;
MAGFILTER = NONE;
};

A sampler is a structure the graphic card will use to deal with our textures, it’s only a way to tell the GPU we want linear or anisotropic filtering, mipmapping or how we want our uv to be tiled. In our case we want to preserve the original pixels of the scene texture so we don’t need any filtering.


struct VSIN
{
float2 pos: POSITION;
float2 uv: TEXCOORD0;
};


struct VSOUT
{
float4 pos: POSITION;
float2 uv: TEXCOORD0;
};

This two structures are respectively the format our quad vertices will have in input from the memory pool to the vertex shader and the one in output from the vertex to the pixel shader. A vertex shader REQUIRE at least a float4 position to work, so that’s the answer at why we need both of them.


//The simpler vertex shader we can do
VSOUT vs(VSIN IN)
{
VSOUT OUT;
OUT.pos=float4(IN.pos.xy,0,1);
OUT.uv=IN.uv;
return OUT;
}

Here we are to our vertex shader, if you’re now new to HLSL you will see that we’re not doing any world-space matrix multiplication to our vertices, that’s because we’re working directly in screen space, so your quad needs already to have screen-space vertex coordinates to work!
Basically this vs simply grab the incoming vertex data in the VSIN structure and traslate it into a VSOUT one before sending all that stuff to the pixel shader.


//a rather dumb pixel shader
float4 ps(VSOUT IN):COLOR
{
return tex2D(sceneSampler,IN.uv);
}

This pixel shader simply fetch the gpu with the sceneSampler and pick up a float4 pixel color from the current uv coordinates for every pixel renderized, in other words: it will simply display our texture on the quad.


technique PFbase
{
pass p0
{
VertexShader = compile vs_2_0 vs();
PixelShader = compile ps_2_0 ps();
}
}

A technique is a full description of a gpu rendering pipeline, it can feature various passes with many pixel and vertex shaders or simply, like in our case, a single pass one.

Rendering of the quad via shader

I assume we have already our fullscreen quad rendering our scene with the render-to-texture tecnique using the fixed pipeline, if not, check the link i posted before or ask for help on the comments.
Now we need to load our .fx file using D3DXCreateEffectFromFile function, after that we’ll have an ID3DXEffect pointer to our shader.

Then we can call ourshader->SetTecnique(”PFbase”), that’s optional if we have only one tecnique on the file or if we wants to use the first one.
Now, instead of using SetTexture on the direct3d device we need to call ID3DXEffect::SetTexture to send our rendered scene to the shader (remember the variable “scene” inside the fx file? yes!) like this shader->SetTexture(”scene”, rttscene); and last but not least, we need to call ID3DXEffect::Begin and ID3DXEffect::BeginPass before the quad draw call and then ID3DXEffect::EndPass and ID3DXEffect::End right after that.

Now that we have the full power of modifying the screen-space pixels on our 3d or 2d scene we can have fun!

Color toning

One of the easiest thing we can do now is changing the brightness or overall toning of our scene.
Let’s go over the pixel shader again and try to modify it like this


return tex2D(sceneSampler,IN.uv)*2.0f;

and, OUCH so bright, it’s burning my eyes, we basically multiplied all the pixels by 2.
Now try that:


float4 c=tex2D(sceneSampler,IN.uv);
c.r+=0.3f;
return c;

color toning

We added a 0.3 value to the image red channel, resulting in a more warm scene.
HLSL let us modify color values in a lot of different cool ways:

- Accessing only one channel by c.r, c.g, c.b, c.a
- Changing multiple channels with c.rgb, c.rb ect (eg. c.rg=0.5f)
- Changing the entire color using another float4 variabile, like c=color2

Crazy filters!


float4 c=tex2D(sceneSampler,IN.uv);
c.r *= 0.5f+sin(IN.uv.y*50)*2;
c.g *= 0.5f+cos(IN.uv.y*2)*2;
c.b *= 0.5f+sin(IN.uv.x*15)*2;
return c;

We’re now multiplying a sin\cos of the quad uv coordinate and a bunch of variables to make some fancy colored horizontal and vertical bands over the screen, note that the color values goes from 0 to 1 so multiplying for another 0 to 1 value we get a darker scene, so we can add something to bright it up a bit.

crazy hlsl filter

Working with pixel coordinates

Ok, we’re able to change pixel color values, but what if we also work with the UVs?


IN.uv.x*=0.5f;
float4 c=tex2D(sceneSampler,IN.uv);
return c;

What’s that? We stretched the scene horizontally dividing the U coordinate by two.
To access the single u or v value we can use uv.x, uv.y or also uv[0], uv[1].So now we can for example invert them like that


float temp=IN.uv.x;
IN.uv.x=IN.uv.y;
IN.uv.y=temp;
float4 c=tex2D(sceneSampler,IN.uv);
return c;

or we can simply do that


float4 c=tex2D(sceneSampler,IN.uv.yx);
return c;

cool uh?

coordinate sampling in hlsl

Now let’s try to make an underwater-like effect modifying colors and coordinates:


float2 wave;
wave.y = IN.uv.y + (sin((IN.uv.x*25))*0.01);
wave.x = IN.uv.x + (sin((IN.uv.y*25))*0.01);
float4 c=tex2D(sceneSampler,wave);
c.b+=0.7f;
c.r-=0.25f;
return c;

Now I can, for example, do another technique called “underwater” and use it instead of the default one when my camera goes under the water level, to have something like that:

wave \ under-water effect in hlsl

But our filter is not finished, if you try that you will see that those distortions are “static”, so when the camera is not moving our underwater effect doesn’t work so good.
We need the wave-like effect moving during time so, to fix that, we need to pass a timer to the shader.Define a global float variable somewhere, for example right under the texture definition.float timer;

then inside our pixel shader we need to add the time value to our “wave” variable


float2 wave;
wave.y = IN.uv.y + (sin((IN.uv.x*15)+timer)*0.01);
wave.x = IN.uv.x + (sin((IN.uv.y*5)+timer)*0.01);
float4 c=tex2D(sceneSampler,wave);
c.b+=0.7f;
c.r-=0.25f;
return c;

I also tweaked a bit the values to have a nicer effect.
Now just add a timer to your code and call shader->SetFloat(”timer”, timer) to have something like that:

wave \ under-water effect in hlsl
Obviously with a better animation, this gif is made of only 4 frames ;)

Wow, the tutorial is already longer than I expected to be the entire one, so I’ll cut it in two parts, in the next one we’ll see how to do effects like Gaussian Blur, Motion Blur, Bloom and also some methods of non-photorealistic rendering to achieve some effects like the one in the game Okami.

Stay tuned and let me comments\suggestions\critics for this tutorial here!