Intro to Effects and Post-Processing
March 9, 2008
Let’s learn how to use effects in our game to achieve a highly stylized, unique look to our game.
One of the coolest, and I think most inspirational, of the sample packs released on the XNA Creators Club website is the Non-Realistic Rendering sample. One of the coolest things, in my mind, about the oncoming onslaught of indie games for the X360 is going to be the huge amount of highly stylized games. If you’ve ever browsed Steam, you’ve probably noticed that the big studio games all fall into the same “awesome graphics FPS” category, while the indie games really stand out based on their unique look, combined with their unique gameplay. Games like Darwinia, Rag Doll Kung Fu and Gish immediately come to mind.
One of the ways to get a unique, highly stylized look to your game is through the clever and creative use of shaders, both as applied to your models, and as post-processing effects. I’ve recently showed you how you can take the colored crosshatching example from the Non-Realistic Rendering sample, combined with a grungy texture, to get a Silent Hill effect. What we’re going to do now is look at how shaders are used, pre- and post-, and how we can build up a nice little application that we can use to swap effects in and out and see what we’ll end up with.
In order to follow along, you’re going to need to know how to do some of the basics on your own. We’re going to start with a simple demo app that renders a textured model, renders a background, and accepts input (to allow you to rotate your model). If you don’t know how to do all of that, go get up to speed and come back.
For my example, I have used the Ship.fbx model that you’re all familiar with. Because I like to have my models, textures and effects in their own folder in the content pipeline, I’ve edited Ship.fbx to set the RelativePath of the ShipDiffuse.tga texture to ../Textures/ShipDiffuse.tga and you may want to do the same. I’ve also added a background texture, which is simply a 256×256 jpg named background.jpg. I included this texture in my project under the Textures folder, loaded it in LoadContent into a Texture2D object, then rendered it before rendering the ship model using the SpriteBatch. The Draw call specifies that I want that 256^2 texture stretched out to the dimensions of the viewport. So we’re not going to get the prettiest background, but its good enough to see what kind of effect any postprocessing will have on the scene.
I took a little shortcut here by calling SpriteBatch.Begin with the SaveStateMode.SaveState flag, although apparently this will cause a significant performance hit. For our purposes, it shouldn’t be an issue, but this is likely not how you’d want to handle drawing a background in a real game (Shawn Hargreaves has more on this here). Here’s the code I used to render both the background and then the model:
-
spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.BackToFront, SaveStateMode.SaveState);
-
spriteBatch.Draw(background, new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height), Color.White);
-
spriteBatch.End();
-
-
foreach (ModelMesh mesh in ship.Meshes) {
-
foreach (BasicEffect effect in mesh.Effects) {
-
effect.EnableDefaultLighting();
-
effect.View = view;
-
effect.Projection = projection;
-
effect.World = world;
-
}
-
-
mesh.Draw();
-
}
That’ll get our model up, with a nice little background up behind it.
Let’s take a quick look at what we’ve got so far:
Now that we’ve laid our base down, let’s talk about the two things we want to do with this:
- Apply some sort of effect to the ship to give it a particular kind of look
- Apply an effect to the entire scene, to give it a particular kind of look
Each of these will be handled differently. Applying an effect to the ship model is the simpler of the two, as it is such a common action that the means to do so is pretty much built in to XNA. The second requires a little more thought.
Let’s start with applying an effect to our model. First of all, we’re going to need an effect. We’ll look at writing effects in HLSL in a later article. You can scour the net for an effect to use, or you can use one from one of the XNA samples. I’m going to use a procedurally generated wood effect I found at the NVIDIA shader library. You can download the effect I chose here. I had to make a small modification to the HLSL file included because the shader relies on a separate file to generate noise called noise_3d.fxh. I just moved this from the includes folder where the shader expected it to be, into the same folder as the shader file. I had to edit the shader so that it knows where to find that file.
Whatever effect you decide to use, we’re going to need to know a little bit about how to use it. Here are the basics:
- Effect files contain one or more techniques
- Each technique contains one or more passes
In the case of my grayscale effect, the .fx file contains only one technique which has just one pass, but we’ll write the code generically enough that if it happened to have more than one pass, our logic would still work.
Since our effect is part of our content pipeline, we can add an Effect variable to our project and load it in our LoadContent method, just like any other asset.
First, declare the variable in our game class:
-
Effect woodEffect;
And then load the effect from the content pipeline into our effect variable:
-
woodEffect = content.Load<effect>("FX\\woodEffect");
Now we need to apply this effect to our model. We want to modify the original model drawing code we had before to use this effect when rendering, rather than the default effect used with the model.
A couple notes are in order. First, if you’re not very familiar with the Model class, you may want to have a look at my brief overview of it. Second, we need to be aware that effects aren’t a luxury, they are a necessity. Every model has effects associated with each of its parts (i.e. ModelMeshPart objects). Its just that if we don’t specifically choose an effect, we get a BasicEffect as a default. We no longer want that basic effect, now we want a good old Effect, which has an associated .fx file to go along with it.
So it should be reasonable that what we’re going to need to do is reassign all the effect values for our model to the new, custom Effect we’re using.
In our draw method, here’s how we do this:
-
// Reassign each mesh part’s effect to our custom effect
-
foreach (ModelMesh mesh in ship.Meshes) {
-
foreach (ModelMeshPart meshpart in mesh.MeshParts) {
-
meshpart.Effect = woodEffect;
-
}
-
}
We also need to set the technique we want to use from the shader file, and any parameters the shader expects. Sometimes the shader will allow you to pass in certain values that shape its behavior. Typically the shader will default to some typical values, so you won’t always need to pass those in. If you do though, here is where you do it. We don’t need to set anything other than our matrices, but the procedure is the same.
-
// Set the technique any parameters herewoodEffect.CurrentTechnique = woodEffect.Techniques["Main"];
Typically, you need to give the shader your world, view and projection matrices, but sometimes the shader will want you to do some calculations on your matrices in-game for performance reasons. We’ll set those matrices in the main drawing loop, but we could set them up there as well, since they don’t change in the drawing loop.
Let’s look at our new drawing loop:
-
// Draw the modelforeach (ModelMesh mesh in ship.Meshes) {
-
foreach (Effect effect in mesh.Effects) {
-
woodEffect.Parameters["WorldITXf"].SetValue(Matrix.Transpose(world));
-
woodEffect.Parameters["WvpXf"].SetValue(world*view*projection);
-
woodEffect.Parameters["WorldXf"].SetValue(world);
-
woodEffect.Parameters["ViewIXf"].SetValue(Matrix.Transpose(view));
-
-
mesh.Draw();
-
}
-
}
Now we’re good to go! If you are confused about what those WpvXf values are where we set the parameters, that’s how the author of the shader decided to name the matrices they needed. WpvXf is just the world * projection * view matrix.
Ok, so at this point, we’ve loaded a new shader, replaced the existing (default) effects in our model with our new, custom shader, configured it correctly, and rendered the model. What do we get?
A wooden spaceship! Ok, obviously we’re going to want to tweak those parameters we talked about earlier in order to get it to look like real, quality wood, but that’s easy stuff. We’ve got bigger plans here. Now we want to do some post-processing to get another effect going.
So as I was checking out the wood effect on the ship model, I started to think to myself: what would this look like if it was all post-processed into a sepia tone, old movie look? Well, we’ll do that in a later article, when we write our own shader. For now, let’s go overboard and get super dramatic. Let’s fly our spaceship on Mars!
This effect definitely fits my definition of highly stylized. Its extremely extreme, but effective. The specific effect isn’t important, but what it does is add a subtle blur to the scene, and bathe the whole thing in a brownish red tone.
Anyways, we are going to apply this effect in post-processing. Conceptually, post-processing is actually quite simple. Here’s how it works:
- Instead of rendering our scene to the backbuffer, as we have always done before, we render it to a custom render target. Basically, that’s just another place in memory that acts just like the back-buffer, but doesn’t get immediately shown to the player.
- Next, we feed our render target into the effect. Or, a better way of putting that is we apply our effect to the pre-rendered scene as if it were just another textured surface. Imagine if we rendered our frame to the edges of a cube, then rendered the cube using a custom effect. That’s pretty much what we’re doing here, except forget all the cube stuff. We just need to render a quad.
- The key thing here is that when we render the effected quad, we render that quad to the backbuffer. That way, our post-processed scene gets shown to the user.
So let’s see what we need to do to make this happen.
First, we need to define a couple things. We’ll need a SpriteBatch, which we’ve got. We’ll also need a render target and a texture to use to store our unprocessed scene. Let’s define those as part of our Game class:
-
RenderTarget2D renderTarget;Texture2D unprocessedScene;
Cool. Now what? Well, we need to actually create our render target, in our Initialize function:
-
1,
You can look up all that if you want. Basically we just created a render target using our existing graphics device, with the same width and height of our viewport, and the number of mipmaps to create for this texture. Mipmaps are an unrelated topic, and don’t really apply here, so google away if you’re curious. We also pass in the same SurfaceFormat the device is already using. Again, feel free to ignore this for now. Just use this constructor as-is.
As before, we need to load our effect. So let’s call our effect marsEffect and load it as the first one, in our LoadContent function. This is exactly the same process as in the woodEffect.
Now we’re ready to use the effect for post-processing. This is fairly straightforward. Let’s follow the three steps we outlined above.
First, we need to set the render target to our custom render target. This should be done before any drawing occurs, since we want all that drawing to happen on this new target:
Now just do all your scene rendering as before. If you build and run at this point, I hope you won’t be shocked to see absolutely nothing appear in your window.
Now, when your scene rendering is done, switch the render target back to the backbuffer. We’ll also grab a copy of the render into our Texture2D variable (note that you can’t do this until the backbuffer is selected as the render target again). Pretty simple really, setting the render target to null just says “use the backbuffer”.
-
unprocessedScene = renderTarget.GetTexture();
We’re now ready to render our lone sprite which, remember, contains the entire scene. We do this by wedging the spriteBatch.Draw call between the effect (and its passes) Begin calls. Our technique in this case is called draw.
-
marsEffect.CurrentTechnique = marsEffect.Techniques["draw"];
-
spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
-
marsEffect.Begin();
-
-
foreach (EffectPass pass in marsEffect.CurrentTechnique.Passes) {
-
pass.Begin();
-
pass.End();
-
}
-
-
marsEffect.End();
-
spriteBatch.End();
And that’s it. What do we end up with? Spaceship on Mars:
So get ready for next time. We’ll be implementing that sepia effect ourselves, and we’ll learn how to write our own shaders in HLSL.




[…] this project, I’ll be using the same code from the Intro to Effects and Post-Processing article. This code is already set up to drop effects on models and to use effects as […]
Help me please.
My effect techniques in this article dont work, what is whrong??
here is my code.
*snip*
Arnoldo, that’s way too much code. Please post just the relevant code and give me some idea of what is happening. “what is wrong” isn’t enough for me to begin with. Are you getting a compiler error? Is the effect simply not getting applied?
More info please.
thx nice tutorial, it works fine.
but i was trying to implemet the “carpaint texColor” from the NVIDIA Shader Library.
in the rendering, everything is black…
do you have any idea to fix that?
greets
thenude
If I’m not mistaken here, the return of the newly rendered image with effects has not been put back to screen.
How is that done?