Generally, the most basic thing you can do in a 3D API is to draw a rotating 3D triangle. So our first step towards 3D graphics with XNA will be just that, a simple, constantly rotating, triangle.
Because XNA always uses shaders to draw geometry, this example will be slightly more complicated than your usual rotating triangle code. Don't worry, though, that what makes things slightly more complicated now will save you heaps of time in the future when you work on an actual game.
Creating a New Project
Begin by firing up XNA Game Studio / Visual C# Express and create a new XNA Game Project:

You will end up with a new project containing an empty game class
and the usual Program.cs with the application entry
method. The assistant has already added a
GraphicsComponent to your game that will initialize
Direct3D and clear the window background with it. When you run
this project, it looks like this:

Setting up the scene
Our first step will be to tell the Direct3D device where things are in our scene. This is done using three matrices respectively called world, view and projection matrix (with XNA's shader-based approach you could actually use any number of matrices, but until you tackle character animation, you can do pretty much everything with this design).
- Projection - The projection matrix is what transforms 3D coordinates into screen coordinates. It controls whether we do a perspective projection (things become smaller the farther away they are), an orthogonal projection (keeping objects at their original size as often seen in CAD and modelling tools) or some crazy other kind of projection.
- View - The view matrix defines the position and orientation of the viewer (commonly referred to as the camera).
- World - Before you draw a mesh in your scene, you will change the world matrix to control where the mesh is drawn. All meshes are (usually) modelled with coordinates around the coordinate system's center, so if you want to draw a tree and some specific position, you first set the world matrix to that position and then call the tree's drawing method.
So let's add some fields to our game class to store these matrices and initialize them:
public class Game1 : Microsoft.Xna.Framework.Game {
/// <summary>Initializes the game</summary>
public Game1() {
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize() {
// Create a new perspective projection matrix
this.projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, // field of view
(float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height, // aspect ratio
0.01f, 1000.0f // near and far clipping plane
);
this.world = Matrix.Identity;
this.view = Matrix.CreateLookAt(
new Vector3(0.0f, 0.0f, 3.0f), Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f)
);
base.Initialize();
}
/// <summary>
/// Load your graphics content. If loadAllContent is true, you should
/// load content from both ResourceManagementMode pools. Otherwise, just
/// load ResourceManagementMode.Manual content.
/// </summary>
/// <param name="loadAllContent">Which type of content to load.</param>
protected override void LoadGraphicsContent(bool loadAllContent) {
if(loadAllContent) {
// TODO: Load any ResourceManagementMode.Automatic content
}
// TODO: Load any ResourceManagementMode.Manual content
}
/// <summary>
/// Unload your graphics content. If unloadAllContent is true, you should
/// unload content from both ResourceManagementMode pools. Otherwise, just
/// unload ResourceManagementMode.Manual content. Manual content will get
/// Disposed by the GraphicsDevice during a Reset.
/// </summary>
/// <param name="unloadAllContent">Which type of content to unload.</param>
protected override void UnloadGraphicsContent(bool unloadAllContent) {
if(unloadAllContent == true) {
content.Unload();
}
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime) {
// Allows the default game to exit on Xbox 360 and Windows
if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>This is called when the game should draw itself.</summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime) {
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
/// <summary>Manages and initializes the XNA GraphicsDevice</summary>
private GraphicsDeviceManager graphics;
/// <summary>Loads and prepares resources for the game</summary>
private ContentManager content;
/// <summary>Matrix used to transform 3D to screen coordinates</summary>
private Matrix projection;
/// <summary>Matrix that defines the viewer's location in the scene</summary>
private Matrix view;
/// <summary>Matrix for controlling the location of rendered polygons</summary>
private Matrix world;
}
Next we need to create a vertex declaration that tells the graphics card how our vertex structure looks like. Theoretically, you could mix just about anything into your vertex structure, including data that will not be used by the graphics card, but graphics memory is limited, so you should consider this a fancy way to add texture coordinate pairs, model skinning weights and other components to a vertex when neccessary.
To do this, add another field to your game class:
private VertexDeclaration positionColorVertexDeclaration;
Then assign the following to his field in your Initialize() method:
this.positionColorVertexDeclaration = new VertexDeclaration(
this.graphics.GraphicsDevice, VertexPositionColor.VertexElements
);
VertexPositionColor is a predefined declaration for
vertices with only position and color components,
just like the name says.
Now that we can tell the graphics card what format our vertices are
in, we can already make it draw the triangle using the default
shader that's implemented in XNA (more on shaders later, just
accept that they're required for now :p). This default shader we
need is contained in the BasicEffect, so we'll
have to add an instance of that class to our game, too.
Add another field to your game class:
private Effect defaultEffect;
and initialize it in your Initialize() method just like the
VertexDeclaration we added before:
// vertex and pixel shaders that we can use to draw our triangle
this.defaultEffect = new BasicEffect(graphics.GraphicsDevice, null);
Now we can finally tell the graphics card to actually draw something.
Modify your Draw()method so it looks like this:
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime) {
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
this.graphics.GraphicsDevice.VertexDeclaration = this.positionColorVertexDeclaration;
this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
this.defaultEffect.Begin();
foreach(EffectPass pass in this.defaultEffect.CurrentTechnique.Passes) {
pass.Begin();
// Draw the triangle using the XNA default shader
this.graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
PrimitiveType.TriangleList,
new VertexPositionColor[] {
new VertexPositionColor(new Vector3(1, 0, 0), Color.Red),
new VertexPositionColor(new Vector3(0, 1, 0), Color.Green),
new VertexPositionColor(new Vector3(-1, 0, 0), Color.Blue)
},
0, // vertexOffset
1 // primitiveCount
);
pass.End();
}
this.defaultEffect.End();
// Let the GameComponents draw
base.Draw(gameTime);
}
If you would run your project now, you should see the following result:

On the next page, we will create the shaders for the triangle!
Preparing the Shaders
You probably know about shaders already, little programs that are executed by the GPU on your graphics card to do fancy stuff.
A vertex shader is run once for each vertex you draw and has the ability to move it to another position. It is also responsible for transforming vertices by the matrices we talked about earlier in this article to translate the 3D coordinates we're feeding to the graphics card into flat 2D screen coordinates. A pixel shader is run once for each pixel drawn and is responsible for calculating the color of that pixel, often combining multiple textures and doing complex lighting calculations.

However, in a real world situation, you are often confronted with the choice to either write shaders for the lowest common denominator, eg. shader model 1.1 or to provide different shaders that are selected depending on the generation of the graphics card used. Older shaders might even require the graphics card to draw a mesh multiple times because not everything can be done on a single run.
This is solved by so-called effects. Effects were invented to get rid of this chaos: An effect basically contains everything needed to define how a mesh should look when rendered, including different vertex and pixel shaders that are selected depending on the class of graphics card that is used.
Let's add an effect to our project. Since Visual C# does not yet know what an effect file (.fx) is, we'll use the 'text file' type instead:

The shader for our rotating triangle will be a simple one:
struct VS_OUTPUT {
float4 position : POSITION;
float4 color : COLOR0;
};
VS_OUTPUT VertexShader(
float4 Pos : POSITION,
float4 Color : COLOR0
) {
VS_OUTPUT Out = (VS_OUTPUT)0;
Out.position = mul(Pos, WorldViewProj);
Out.color = Color;
return Out;
}
float4 PixelShader(float4 Color : COLOR0) : COLOR0 {
return Color;
}
technique TransformTechnique {
pass P0 {
vertexShader = compile vs_2_0 VertexShader();
PixelShader = compile ps_2_0 PixelShader();
}
}
Explaining the effect file language and HLSL (High-Level Shader Language), the language used to program the shaders in the effect files, would go too far here. As a beginner, it probably is best to just use predefined effects that you can obtain from sources such as the excellent ShaderX books or even create from within your modelling tool. If you really want to delve into effect file writing, browse the MSDN docs for more informations!
Make sure the .fx file is processed by the XNA content pipeline. If everything is right, it should carry the following settings:

Back to our code. We need to somehow load this effect file.
This is where the XNA ContentManager class
comes into play. The ContentManager is responsible
for loading maintaining any assets such as textures, meshes and,
of course, effect files. To have the ContentManager
load our effect, we need to remove the BasicEffect construction in
the Initialize() method:
Add another field to your class:
private Effect colorFillEffect;
Then add this piece of code to your constructor in order to
compile the effect file and construct an Effect instance.
// vertex and pixel shaders that we can use to draw our triangle
//this.defaultEffect = new BasicEffect(graphics.GraphicsDevice, null);
// ^^ remove the above line or comment it out like shown here
Instead, let the ContentManager create the effect
by adding the following code into the
LoadGraphicsContent() method of your game:
/// Load your graphics content. If loadAllContent is true, you should
/// load content from both ResourceManagementMode pools. Otherwise, just
/// load ResourceManagementMode.Manual content.
/// </summary>
/// <param name="loadAllContent">Which type of content to load.</param>
protected override void LoadGraphicsContent(bool loadAllContent) {
if(loadAllContent) {
// TODO: Load any ResourceManagementMode.Automatic content
this.defaultEffect = this.content.Load<Effect>("ColorFill");
}
// TODO: Load any ResourceManagementMode.Manual content
}
The effect will, however, not show up yet because we didn't tell the graphics card to render the triangle with this effect. If you run the project now, the triangle would still be black.
To make use of the effect, you need to actiate it before drawing,
then repeat drawing your polygons as often as the effect requires
and ultimately terminate the effect again. The following
Draw() method does all this:
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime) {
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// Give the shader our concatenated world, view and projection
// matrix so it can transform the triangle into screen space
this.defaultEffect.Parameters["WorldViewProj"].SetValue(world * view * projection);
// Set the vertex declaration and prevent the graphics card from culling polygons
// which are facing away from the virtual camera
this.graphics.GraphicsDevice.VertexDeclaration = this.positionColorVertexDeclaration;
this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
this.defaultEffect.Begin();
foreach(EffectPass pass in this.defaultEffect.CurrentTechnique.Passes) {
pass.Begin();
// Draw the triangle using the XNA default shader
this.graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
PrimitiveType.TriangleList,
new VertexPositionColor[] {
new VertexPositionColor(new Vector3(1, 0, 0), Color.Red),
new VertexPositionColor(new Vector3(0, 1, 0), Color.Green),
new VertexPositionColor(new Vector3(-1, 0, 0), Color.Blue)
},
0, // vertexOffset
1 // primitiveCount
);
pass.End();
}
this.defaultEffect.End();
// Let the GameComponents draw
base.Draw(gameTime);
}

There it is. A nice, colorful triangle drawn using the shaders from the .fx file we created earlier. The final step will be to let the triangle rotate on the Y axis!
Adding Movement to the Scene
If you haven't looked at the
Game Loop Basics
yet, just keep in mind that the Update() method will be called
once for each 1/60th of a second that has passed. So this is the ideal
place to rotate the world matrix (which controls the position and
orientation of the triangle).
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime) {
// Allows the default game to exit on Xbox 360 and Windows
if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
// Rotate the world matrix by one degree per update
// Assuming updates are done at 60 Hz, one revolution will take 6 seconds
this.world *= Matrix.CreateRotationY(MathHelper.TwoPi / 360.0f);
base.Update(gameTime);
}
There we are. A colored triangle, drawn by a pixel shader, rotated by a vertex shader and spinning at the same speed, independent of the frame rate.
Of course, in a real game, you would not just throw all your code into the game class. Instead, you would design a framework to manage your shaders, meshes and vertex declarations, create the game logic to control your environment, actors and interactions between all those, gather data from input devices and so on. The game class itself would only make calls to the appropriate subsystems.

Full Code
Here's the full code for the final stage of the game class again. You can also download the finished Visual C# project from this link: XNA Rotating Triangle Example Source Code
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
namespace RotatingTriangle {
/// <summary>Shows a rotating triangle on the screen</summary>
public class Game1 : Microsoft.Xna.Framework.Game {
/// <summary>Initializes the game</summary>
public Game1() {
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize() {
// Create a new perspective projection matrix
this.projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, // field of view
(float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height, // aspect ratio
0.01f, 1000.0f // near and far clipping plane
);
this.world = Matrix.Identity;
this.view = Matrix.CreateLookAt(
new Vector3(0.0f, 0.0f, 3.0f), Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f)
);
// Declare the vertex format we will use for the rotating triangle's vertices
this.positionColorVertexDeclaration = new VertexDeclaration(
this.graphics.GraphicsDevice, VertexPositionColor.VertexElements
);
// Create a new default effect containing universal
// vertex and pixel shaders that we can use to draw our triangle
//this.defaultEffect = new BasicEffect(graphics.GraphicsDevice, null);
base.Initialize();
}
/// Load your graphics content. If loadAllContent is true, you should
/// load content from both ResourceManagementMode pools. Otherwise, just
/// load ResourceManagementMode.Manual content.
/// </summary>
/// <param name="loadAllContent">Which type of content to load.</param>
protected override void LoadGraphicsContent(bool loadAllContent) {
if(loadAllContent) {
// TODO: Load any ResourceManagementMode.Automatic content
this.defaultEffect = this.content.Load<Effect>("ColorFill");
}
// TODO: Load any ResourceManagementMode.Manual content
}
/// <summary>
/// Unload your graphics content. If unloadAllContent is true, you should
/// unload content from both ResourceManagementMode pools. Otherwise, just
/// unload ResourceManagementMode.Manual content. Manual content will get
/// Disposed by the GraphicsDevice during a Reset.
/// </summary>
/// <param name="unloadAllContent">Which type of content to unload.</param>
protected override void UnloadGraphicsContent(bool unloadAllContent) {
if(unloadAllContent == true) {
content.Unload();
}
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime) {
// Allows the default game to exit on Xbox 360 and Windows
if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
// Rotate the world matrix by one degree per update
// Assuming updates are done at 60 Hz, one revolution will take 6 seconds
this.world *= Matrix.CreateRotationY(MathHelper.TwoPi / 360.0f);
base.Update(gameTime);
}
/// <summary>This is called when the game should draw itself.</summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime) {
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// Give the shader our concatenated world, view and projection
// matrix so it can transform the triangle into screen space
this.defaultEffect.Parameters["WorldViewProj"].SetValue(world * view * projection);
// Set the vertex declaration and prevent the graphics card from culling polygons
// which are facing away from the virtual camera
this.graphics.GraphicsDevice.VertexDeclaration = this.positionColorVertexDeclaration;
this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
this.defaultEffect.Begin();
foreach(EffectPass pass in this.defaultEffect.CurrentTechnique.Passes) {
pass.Begin();
// Draw the triangle using the XNA default shader
this.graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
PrimitiveType.TriangleList,
new VertexPositionColor[] {
new VertexPositionColor(new Vector3(1, 0, 0), Color.Red),
new VertexPositionColor(new Vector3(0, 1, 0), Color.Green),
new VertexPositionColor(new Vector3(-1, 0, 0), Color.Blue)
},
0, // vertexOffset
1 // primitiveCount
);
pass.End();
}
this.defaultEffect.End();
// Let the GameComponents draw
base.Draw(gameTime);
}
/// <summary>Manages and initializes the XNA GraphicsDevice</summary>
private GraphicsDeviceManager graphics;
/// <summary>Loads and prepares resources for the game</summary>
private ContentManager content;
/// <summary>Default Vertex- & PixelShader for the graphics card</summary>
private Effect defaultEffect;
/// <summary>Vertex type that stores a position and color in each vertex</summary>
private VertexDeclaration positionColorVertexDeclaration;
/// <summary>Matrix used to transform 3D to screen coordinates</summary>
private Matrix projection;
/// <summary>Matrix that defines the viewer's location in the scene</summary>
private Matrix view;
/// <summary>Matrix for controlling the location of rendered polygons</summary>
private Matrix world;
}
}
an InvalidOperationException?
Thank you for the tutorial, I get an error in the graphics.GraphicsDevice.DrewUserPrimitives I get "InvalidOperationException: both a valid vertex shader and pixel shader (or valid effect) must be set on the device before draw operations may be performed" I have like zero 3D knowlage so I couldnt fix it ...... where did I go wrong? Thank you :) so much for the XNA on non-supporting cards work around
My Fault!
Sorry!
The exception is actually an error in the example code. I forgot to copy the
BasicEffectinstance into the document when I upgraded the article from XNA Beta1 to XNA 1.0 Final.It's fixed now and I double-checked the entire tutorial from start till end to work on my system. Again, sorry for any frustration I might have caused :)
colorFillEffect field?
The tutorial declares colorFillEffect field of type Effect but this field does not get used anywhere.
Thanks for the great tutorial
I am currently trudging the very long path towards being a game programmer, thanks for a very nice tutorial to help me on my way.
Great tutorial, thanks for
Great tutorial, thanks for that one.
Error loading "ColorFill". File not found.
I created ColorFill.fx as you said, and its in the correct place in my project (and by now, several wrong places as well), but I still keep getting this runtime error.
The properties of ColorFill in my XNA GS2 version are slightly different to those in the tutorial, perhaps that is where the problem is, any ideas?
This is the only tutorial I have found on the Net that is completely self contained and just draws a triangle - the "Hello World" of 3D. Geez I wish it worked for me!
Great tutorial though!
Post new comment