Saturday, September 13, 2008

Rendering a model from the XNA Content Pipeline

When starting a new project you begin with the "fundamentals" (especially if you suffer from the NIH syndrome), you build frameworks, toolkits and all kinds of fun stuff - what I mean is that I have not started working on my game yet :). The most recent of these tools is a Content Pipeline processor that renders a model to a texture. Before you say that this is useless keep reading. One day I was trying to render a terrain heightmap from Blender (there are tutorials here and here) and it hit me, why not render from the pipeline, this would streamline my workflow a little by eliminating the scene rendering step in Blender. All I needed to do was to draw the model using an orthographic projection with a shader that outputs the model height.
So here is the shader:

float4x4 World;
float4x4 View;
float4x4 Projection;

void Transform (float4 position : POSITION, out
float4 outPosition : POSITION,
out float3 outPixelPosition : TEXCOORD0) {
float4x4 worldViewProjection = mul(mul(World, View), Projection);
outPosition = mul(position, worldViewProjection);
outPixelPosition = outPosition.xyz;
}

float4 Heightmap(float3 pixelPosition : TEXCOORD0): COLOR {
float h = 1 - pixelPosition.z;
float4 height = float4(h, h, h, 1.0f);
return height;
}

technique BasicShader {
pass P0 {
VertexShader = compile vs_2_0 Transform();
PixelShader = compile ps_2_0 Heightmap();
}
}

Pretty simple stuff, just transform and then output the height of the vertex as the color. The projection is orthographic so the Z is already between 0 and 1, no need to scale the value or anything like that.

To the content processor now, (I'm not going to go through the moves of creating a content processor project, there is plenty of documentation on the internets), here is the class and the Process method. We create a graphics device and load the shader, then we set up a render target and draw the model using our shader and finally we take the pixel data and return a texture content with it.

The effect loading method employed below is only "slightly" INSANE, if anybody finds a better way of loading an effect in the content pipeline let me know - I tried, I really tried to load a compiled .xnb effect without using a ContentManager but it did not seem possible without recreating a lot of code from the ContentReader. Also, this method does a fair amount of work, it will first look for a .xnb file and if not found it will try to load and compile a .fx file and finally it will try to load the effect from the internal resources.

And here is the IGraphicsDeviceService implementation.

There is nothing special about the drawing method below, just that it took me some time to figure out how to properly get the vertex and index buffer from a ModelContent which is almost, but not quite, entirely unlike the ModelMesh.

And now the proof. This is the original mesh in Blender, notice the little axis icon on the bottom right corner - the Z is up, make sure the FBX exporter rotates the model to match the XNA coordinate system.

Now the processor options and the generated heightmap:


If you create a terrain using this heightmap and everything goes well you should get something very similar with your original model.

"The resemblance is striking"

Here is a tip, if you experience a stair stepping effect (you'll know when you see it) try to reduce the resolution of the image when creating the heightmap texture (see this explanation).
I think this processor could be used for some other things, like rendering sprites or UI elements, pretty much anything that you would model in 3d and then export as image would be a good use case if the rendering quality is not terribly important.

That's it for now, I hope this was useful. Questions? Suggestions? Go ahead, ask and suggest...

No comments: