Drawing 2D Primitives with XNA

posted in: XNA | 2

As described on MSDN, drawing lines and other 3D Primitives with XNA involves defining vertices in world space, setting up an Effect, applying view and projection transformations, and finally making a call to the graphics card with a DrawUserPrimitives (or similar) call.

Pretty swish, but not very useful.

If you’re working in 2D, however, not only is this overkill, but if you’re going to use SpriteBatch it makes sense to be able to define primitives with pixel-based coordinates — where (0, 0) is the screen’s top-left corner and (ScreenWidth, ScreenHeight) is the bottom-right corner — just as you do with your sprites.

I recently implemented just such a system so I could render the 2D bounding primitives I’m using for Separating Axis Theorem-based collision detection (which I hope to write about soon) that are also defined in screen space in order to simply algorithms involving other sprites.

The SpriteBatch Shader on the Creators Club website details how to go about transforming vertices from screen/pixel space to clip space (the final transformation required by Direct3D/XNA before it can initiate rasterisation) as shown in the following image (minus the z-dimension for clip space):

Illustrates how vertices specified in pixel-coordinates must be transformed before Direct3D/XNA can perform rasterisation.

(1680×1050 is simply an example resolution, the principle remains the same for any values)

The following HLSL code performs the necessary calculations (where position is the float4 POSITION of the vertex passed into the vertex shader and ViewportSize is equal to the width and height of the screen/back buffer):

position.xy /= ViewportSize;
position.xy *= float2(2, -2);
position.xy -= float2(1, -1);

If you’d rather not use a custom shader, though, the following C# implementation achieves the same thing (albeit less efficiently, because the CPU is doing the work, not the GPU) where vertices is a Vector2 array of positions in screen space and verticesForGPU is a VertexPositionColor array.

for (int i = 0; i < vertices.Length; i++)
{
    verticesForGPU[i].Position = new Vector3(
        vertices[i].X / BackBufferWidth  *  2.0f - 1.0f,
        vertices[i].Y / BackBufferHeight * -2.0f + 1.0f, 0);
}

Manually sifting through the maths soon makes it clear how it all works. For x values between (0 and 1680): divide by 1680 to return values between (0 and 1), multiply by 2 to get values between (0 and 2) and subtract 1 to get the desired values between (-1 and 1). Transforming y values is synonymous.

I’ve uploaded a couple of sample files that demonstrate the process by rendering four lines defined in screen space: one uses a custom shader (source file, shader file) while the other does its calculations in the main Game class and uses an instance of BasicEffect for rendering (source file).

The output of both samples should look something like this:

Four 2D Lines defined in screen space, transformed to clip space and rendered. Exciting, no?

2 Responses

  1. Alex
    | Reply

    Hi, should this work in XNA 4.0?

    GraphicsDevice.VertextDeclaration, VertexPositionColor.VertexElements, Microsoft.Xna.Framework.Graphics.Effect.Begin() and End() cannot be resolved.

    I’ve set up a new project in Visual Studio 2010 with the AppHub pack (http://create.msdn.com/en-us/home/getting_started) and have added Microsoft.Xna.Framework.Graphics to my project references.

    • Karn Bianco
      | Reply

      Hey, I’ve not had a chance to try XNA 4.0, but I don’t see why it shouldn’t work unless they’ve fundamentally changed a lot of things. I’ll try and investigate at some point.

Leave a Reply