Simple Meshes
February 3rd, 2007
The XNA game system is specially designed to make it easier to import models from 3d editing applications. That’s fine if you actually know how to use these applications, or have the time to learn. I’m betting that most XNA coders want to dive in and get their hands dirty with code that throws models around the screen, without actually having to spend the time designing these models. Models can be created later, once you know XNA works for you. If this seems to describe you, then read on.
Even if you already have a designer beavering away with blender, you’ll still need to understand how meshes can be created dynamically. Without this kind of knowledge height maps will stay as height maps, never to be rendered as a beautiful rolling landscape; the craft you’re flying won’t be able to decompose into fragments when it gets hit by a rocket. You get the idea.
XNA is only capable of rendering four things. Points, lines, sprites [2d images] and triangles. As the game you’re writing basically comes down to line after line of C#, the images your program will be creating comes down to triangles. Thousands upon thousands of texture-mapped triangles.
Some may even be bump-mapped. But they will still be triangles.
To start with, we’re going to display a simple cube. A cube appears to be made of 8 vertices:
To render this we need to use 4 separate vertices per side. This makes 24 vertices:

You can imagine from this diagram how the faces are decomposed into triangles. No faces shares a vertex with any other face, even though every vertex shares the same (x,y,z) position as two other vertices. The reason for doing this is that the vertex structure does not just contain its location. It also contains a texture coordinate and a vertex normal. This allows us to give each face a unique texture, and also to ensure all its vertices point in the same direction (useful for lighting). For a smooth object, such as a sphere, we would share vertices between faces, as they would need to share normals and texture coordinates to make the object appear uniform.
This program uses the same camera movement technique as detailed in the Camera Movement tutorial . Also, if your mouse has a scroll wheel it will be the view up and down.
Creating the cube
The cube is created in the call to InitialiseCube(). This takes a Vector3 object as a parameter, which specifies the size (in three dimensions) of the “cube”. It should perhaps be more accurately named “cuboid”.
This code first creates a few objects necessary for the rendering process.
vertexShaderDeclaration = new VertexDeclaration(graphics.GraphicsDevice,
VertexPositionNormalTexture.VertexElements);
vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,
typeof(VertexPositionNormalTexture),
24,
ResourceUsage.WriteOnly,
ResourceManagementMode.Automatic);
vertexArray = new VertexPositionNormalTexture[24];
The VertexDeclaration object instructs the shader how to interpret any vertex data it is passed. This is passed an array of elements which describe where the shader can find the position, normal and texture within the vertex data stream it is to process. It also indicates that each vertex will have a position, a normal (used for lighting) and a texture coordinate. This is an example of a flexible vertex format – referred to as an FVF. Other examples are@VertexPositionColor@ and VertexPositionColorTexture.
The VertexBuffer object will contain the array of vertices, and is initialised with the type of vertex being used, the number of vertices it should expect, and a few resource-management enumerations. The array to contain the vertices is then created.
Eight locally scoped Vector3 objects are initialised to the eight cube corners, and then four vertices per face are initialised. Each face has its vertices in clock-wise order. Any faces where, once all transformations have taken place,the vertex order is anti-clockwise, are marked as facing away from the viewer point and culled. Think about the cube: if the face nearest the camera has its corners in a clockwise order, once the cube is rotated by 180 (through its Y-axis), and that face is no longer visible, its corners would appear to be anti-clockwise 1.
Each call to SetVertex() sets an individual vertex for one of the six faces. As well as the position of the vertex it also sets the face normal (the direction the face is facing – shared by all vertices on a face) and the texture coordinate (a two-dimension texture, with a two-dimension vector coordinate).
vertexBuffer.SetData<VertexPositionNormalTexture>(vertexArray);
This code then passes the vertex array into the vertex buffer. The vertex buffer is what is eventually passed to the renderer.
Next a vertex index array is created, to contain the vertex number of the series of triangles that will be passed to the renderer. This is simply an array of shorts. This array has one element per triangle vertex – and refers to the vertex buffer that was just created. So, the first face is composed of two triangles – it needs six vertices. These are the vertices numbered 0,1,2 and 0,2,3.

[apologies for my handwriting here. Still getting used to the tablet, and my handwriting sucks even on paper. Perhaps this tablet is for right-handed people only?]
The code loops through the array, calculating the vertex numbers, The reason this can be done is that when the vertices were created above (in the vertex buffer) the order was very specific. When any face is rotated to be at the front, facing the camera, the corners would be in the same position in the same order.
indexBuffer = new IndexBuffer(graphics.GraphicsDevice,
sizeof(short) * vertexIndices.Length,
ResourceUsage.None,
ResourceManagementMode.Automatic,
IndexElementSize.SixteenBits);
indexBuffer.SetData<short>(vertexIndices);
This first creates the index buffer, then passes the index array is to it. This index buffer is passed to the renderer when Draw() is called.
The texture
All polygons, apart from very boring ones, need to be texture-mapped. To do this an image is needed. To prevent you having to download a texture-map of my creating, or creating your own, the program will create its own. For the moment this will be very simple, just two diagonal lines. However the code to create the texture will show you how to generate your own, programmatically. Later we will be creating more interesting textures.
protected void CreateTexture()
{
faceTexture = new Texture2D(graphics.GraphicsDevice,
64,
64,
1,
ResourceUsage.Dynamic,
SurfaceFormat.Bgr565,
ResourceManagementMode.Manual);
ushort [] data=new ushort[4096];
for (int i = 0; i < 64; i++)
{
SetTexturePixel(ref data, 64, 64, i, i, Color.SlateBlue);
SetTexturePixel(ref data, 64, 64, 63 - i, i, Color.SlateGray);
}
faceTexture.SetData<ushort>(data, 0, 4096, SetDataOptions.Discard);
}
When creating the texture, because we are going to dynamically alter it, we have to use pass ResourceUsage.Dynamic and ResourceManagementMode.Manual. Also we are using 16 bit colours – with a surface format of 5 blue bits, 6 green bits, and 5 red bits. The texture created here is 64 pixels by 64 pixels, so we initialise an 4096 (64*64) element array of unsigned shorts (16 bit numbers).
The call to SetTexturePixel() is implemented in the code too. It sets the points passed – here (i,i) and (63-i,i), to the colour passed.
The faceTexture object is then passed this array. This texture object is then passed to the BasicEffect object when it is initialised.
The shader
To make this easier, we are going to use an XNA-provided shader, the BasicEffect class. This allows us to specify lighting and texture-mapping without having to write shader code. The method InitialiseShader(), called during initialisation (just before calling InitialiseCube()) creates a BasicEffect object, specifying a light that is above, to the left, and behind the viewer when the program starts. The direction of the light remains fixed, the viewer is free to move.
A texture is also passed to the shader here, that was created by a previous call to CreateTexture()
Rendering
As always, all rendering is done inside the Draw() method. In there you will find the following code:
GraphicsDevice device = graphics.GraphicsDevice;
device.VertexDeclaration = vertexShaderDeclaration;
shaderEffect.Begin(SaveStateMode.None);
shaderEffect.World = sceneWorldTransformation;
shaderEffect.View = viewMatrix;
shaderEffect.Projection = projectionMatrix;
foreach (EffectPass pass in shaderEffect.CurrentTechnique.Passes)
{
pass.Begin();
device.Indices = indexBuffer;
device.Vertices[0].SetSource(vertexBuffer,
0,
VertexPositionNormalTexture.SizeInBytes);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0,
0,
24,
0,
12);
pass.End();
}
shaderEffect.End();
First, the vertex shader is passed to the graphics device – this lets it know what format the vertices will be in. The shader is started without saving the render state and the world transformation (the cubes position and the way it is rotating), as well as the view and the projection are set.
The loop uses the BasicEffect object, shaderEffect, created in the InitialiseShader() method. Although this code will allow multi-pass rendering, in actual fact only one pass is performed here. It then passes the device the index buffer and the vertex buffer. DrawIndexedPrimitives is told it has to draw a list of indexed triangles, 12 to be exact, using 24 vertices.
1 This clockwise/anti-clockwise magic is done using the vector cross-product operation.
Dream Build Play, Contest Registration
February 1st, 2007
The XNA contest is now open to registration. Well, nearly. Its open for registration to people who live in America, and who don’t receive an error when they go to here
Hopefully they’ll add international registration soon!
New tablet!
January 31st, 2007
Ok, my next two tutorials on simple objects are nearly finished. However I’m now the proud owner of a Wacom tablet, and it’s really hard to use. I guess I could go back to my tablet PC, but I should try and use this. So once I get the pictures done, they’ll be some more articles. Promise!
However, apart from that I have a huge deadline at work, I’m technically reviewing my friends Ruby on Rails book., and I’m learning Go, and tonnes of other stuff.
There is still no word on DreamBuildPlay and its now the end of January. Perhaps MS have some internal processes slowing it down, or maybe they just forgot?
Or perhaps they meant January 2008?
