10 min read

Drawing Triangles

Introduction

We have done quite a bit of 3D drawing in all of these tutorials. So far, though, everything we have drawn has gone through the Model class. By this point, you might be wondering how the Model class actually does its drawing. Or perhaps you have run into a need to be able to draw stuff without using the Model class. For instance, let’s say you wanted to randomly generate terrain in your game so that it is different every time. It is nearly impossible to hand off this data to the Model class because it is designed to be read in from a file and not modified after that point. Instead, you will have to use a different way of drawing your terrain.

This brings us to the primitives tutorial category, and ultimately, here. While the title of these tutorials is “Primitives”, that does not mean this is going to be extremely easy. I’m sure you’ll agree with me that rendering models are easier. But sometimes, it isn’t good enough. These tutorials are called “Primitives” because we are going to be drawing primitives. In graphics, primitives are the building blocks of rendering. By far the most common kind of primitive is a triangle. In fact, virtually everything that you’ve ever drawn in 3D has been made up of triangles. Lots and lots of triangles. So in this tutorial, we will try our hand at the first step of drawing primitives: drawing a simple triangle.

As usual, I’m putting my entire source code at the bottom of this tutorial, so if you get lost, feel free to look down at the code.

Preparing the Triangles

While I’m making these tutorials, I like to start with a brand new project whenever I can, just to make it easy for your guys. That’s what we’ll be doing in this tutorial, so if you want, go ahead and create a new project. Alternatively, you can just add this stuff into an existing project where you need it.

Also, one other note: we are going to be sticking to drawing with the BasicEffect class. If you are an expert with HLSL and effect files, then you can use your own shaders here instead. But HLSL isn’t a prerequisite to these tutorials, just an understanding of the basic 3D drawing that we’ve been doing.

Since we aren’t just reading in stuff from a file and stuffing them into a Model object, we are going to need to define everything about our triangles. With a model file, the content pipeline just takes care of all of this stuff for us. But we’re not going to have the content pipeline to help us.

The first thing we are going to need is a place to store our triangle. Actually, our data won’t even be stored as a triangle, but rather as a list of vertices. So let’s create a variable to store our vertex data. Also, at the same time, I’m going to create a few variables that we will use later for drawing. So add the following code as instance variables to your main game class:

VertexBuffer vertexBuffer;

BasicEffect basicEffect;
Matrix world = Matrix.CreateTranslation(0, 0, 0);
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 3), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);

I’m actually going to explain these in reverse order. The last three Matrix objects are all identical to the ones we’ve seen before. If matrices like these don’t look familiar to you, now is a good time to go back and look at the Basic Matrices tutorial. We will use these when we draw. Just before that is the line BasicEffect basicEffect;. You’ve probably used the BasicEffect class a lot by now. In the past, though, usually the Model class will create these for us, and we don’t need to create our own. However, here we won’t be using the Model class, so we will need to make our own BasicEffect.

The first line, though, is the most important to what we’re doing here: VertexBuffer vertexBuffer;. This is the data structure that we will use to store our vertex information.

We are now going to go ahead and set up our vertex buffer, as well as our BasicEffect object. Usually, we do this kind of thing in the LoadContent() method by calling Content.Load<Model>("...");. We aren’t loading a model this time (in a sense, we are creating our own from scratch), but the LoadContent() method is still a good place to do this. It might be a good idea to create a separate method called something like CreateVertices() or something, and call that from the LoadContent() method, but I’m just going to place mine directly in the LoadContent() method. (While I’m at it, it is probably worth mentioning that it probably makes the most sense to wrap all of this in a separate class, rather than dropping it directly into the main game class.) So add the following code there to prepare the effect and the vertices:

basicEffect = new BasicEffect(GraphicsDevice);

VertexPositionColor[] vertices = new VertexPositionColor[3];
vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Red);
vertices[1] = new VertexPositionColor(new Vector3(+0.5f, 0, 0), Color.Green);
vertices[2] = new VertexPositionColor(new Vector3(-0.5f, 0, 0), Color.Blue);

vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), 3, BufferUsage.WriteOnly);
vertexBuffer.SetData<VertexPositionColor>(vertices);

The first line here creates our BasicEffect.

After that, we set up an array to store our vertices (called vertices), which has three elements in it. This is because we are going to draw a single triangle, which has exactly three vertices. Depending on what you’re doing, you’ll want a different size. (We’ll play around with that a bit more in the next tutorial.)

You might be able to guess from the name of this data structure that each element in this array will store position and color information for a vertex. This is what we will use in this tutorial, since it is pretty simple, but there is also defined structures called VertexPositionColorTexture, VertexPositionNormalTexture, and VertexPositionTexture, which store different properties of the vertex.

It might also interest you to know that you can create your own structures that BasicEffect or other effects will be able to work with, too. We’ll discuss this in a later tutorial.

Going back to the code, in the next three lines, we create the vertices with specific locations and colors. The values, of course, determine exactly what the vertices will look like.

Continuing on, the last two lines of this code wrap our vertex array into a VertexBuffer object (which we added to the main game class as an instance variable, previously). To create the VertexBuffer object, we need to supply a reference to the graphics device, and then supply some information about what we expect to store inside it. This includes the type of data and the number of elements we are going to store, and finally, we state how we want to use the vertex buffer, which we specify as WriteOnly.

By the way, BufferUsage.WriteOnly is our only real option here. There’s BufferUsage.None, but what good is that? If I’m not mistaken, this class is more or less vestigial–the remains of a previous version of XNA, where other choices were available. At any rate, BufferUsage.WriteOnly is good enough for what we need it for and is a good reminder that once we send data off to the graphics card, we don’t just tweak it, we have to throw the whole thing out and start from scratch.

Drawing the Triangles

Drawing our triangle is a little bit of work. To do this, go down to your Draw() method and add the following code:

basicEffect.World = world;
basicEffect.View = view;
basicEffect.Projection = projection;
basicEffect.VertexColorEnabled = true;

GraphicsDevice.SetVertexBuffer(vertexBuffer);

RasterizerState rasterizerState = new RasterizerState();
rasterizerState.CullMode = CullMode.None;
GraphicsDevice.RasterizerState = rasterizerState;

foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
    pass.Apply();
    GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
}

The first step is to set up our BasicEffect object with the right matrices for the model, view, and projection.

We also turn on vertex coloring, which the BasicEffect class uses.

In the next line, we tell the graphics device which vertex buffer we want to draw. In this tutorial, we’ve only got one available, but in practice, there could easily be plenty of others.

We then create a RasterizerState object, which sets up a bunch of options for how to “rasterize” our triangles (the process of turning the geometry into pixels on the screen). The middle of those three lines is optional. It turns off culling. It is very typical to cull backfaces (triangles that are facing away from the camera) in an effort to speed up the drawing, but for the moment, we’re going to turn it off–it helps with troubleshooting problems. (I can’t count the number of times that I thought my geometry wasn’t being drawn when, in fact, it was being drawn, but because I was seeing the backside of them, and it was getting culled, I just didn’t see it.)

We then finish things up by going through each pass in the BasicEffect and applying it and then drawing our primitives with it.

Let’s look at that DrawPrimitives call in a bit more detail. Remember that at this point, the program/graphics device already knows what data it is supposed to draw. That was determined a few lines earlier when we called SetVertexData. But because of the way it stores the data on the graphics card, it is unsure of the exact format of the data, and how much data there is to draw. So we need to provide some extra information to the DrawPrimitives call.

We first state that it is simply a list of triangles (PrimitiveType.TriangleList), meaning that each set of three vertices in the list forms a single triangle, and the next three vertices would be the next triangle, and so on. We then state the index of the vertex to start at (this will typically be 0 if you are going to draw the whole thing), and finally, the number of primitives that you want to draw. In our case, that is 1, but in the next tutorial, we’ll work with more triangles.

With these changes, you should be able to run your game and see your triangle, which should look something like the image below:

Screenshot 1

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace DrawingTriangles
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        VertexBuffer vertexBuffer;

        BasicEffect basicEffect;
        Matrix world = Matrix.CreateTranslation(0, 0, 0);
        Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 3), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
        Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <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()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            basicEffect = new BasicEffect(GraphicsDevice);

            VertexPositionColor[] vertices = new VertexPositionColor[3];
            vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Red);
            vertices[1] = new VertexPositionColor(new Vector3(+0.5f, 0, 0), Color.Green);
            vertices[2] = new VertexPositionColor(new Vector3(-0.5f, 0, 0), Color.Blue);

            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), 3, BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexPositionColor>(vertices);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <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 game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            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)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.VertexColorEnabled = true;

            GraphicsDevice.SetVertexBuffer(vertexBuffer);

            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            GraphicsDevice.RasterizerState = rasterizerState;

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
            }

            base.Draw(gameTime);
        }
    }
}