Drawing Triangles

Introduction

We have done quite a bit of 3D drawing in 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 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. Handing off this data to the Model class is nearly impossible 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 category of tutorials, and ultimately, here. While the title of these tutorials is "Primitives," that does not mean this will be easy. I'm sure you'll agree that rendering models is easier. But sometimes, it isn't good enough. These tutorials are called "Primitives" because we will be drawing primitives. In graphics, primitives are the building blocks of rendering. By far, the most common primitive is a triangle. 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 making these tutorials, I like to start with a brand new project whenever possible to make it easy for you. That's what we'll be doing in this tutorial, so if you want, go ahead and create a new project. Alternatively, you can add this stuff to an existing project where you need it.

Also, one other note: we will be sticking to drawing with the BasicEffect class. If you are an expert with HLSL and effect files, then you can use your 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 will need to define everything about our triangles. With a model file, the content pipeline takes care of all this stuff for us. But we're not going to have the content pipeline to help us.

The first thing we will need is a place to store our triangle. 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, I will create a few variables that we will use later for drawing. So add the following code as instance variables to your main game class:

private VertexBuffer _vertexBuffer;
private BasicEffect _basicEffect;
private Matrix _world = Matrix.CreateTranslation(0, 0, 0);
private Matrix _view = Matrix.CreateLookAt(new Vector3(0, 0, 3), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
private Matrix _projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);

I'm 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;. By now, you've probably used the BasicEffect class. 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 is the most important: private VertexBuffer _vertexBuffer;. This is where our triangle's vertex data will be stored.

Next, we'll initialize our vertex buffer and basic effect. Usually, we do this in LoadContent() by calling Content.Load<Model>("…");. While we aren't loading a model through the content pipeline right now, the LoadContent() method is still a good place for this.

For what it is worth, I'm keeping things simple in the tutorial. In a larger program, you might consider putting all of this in another class rather than dropping it directly in 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 creates our BasicEffect instance.

After that, we set up an array to store our vertices (called vertices), which have three elements. We need three slots in the array because our triangle has three points. Adjust this to be whatever size you need for your circumstances. (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 are 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.

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

The last two lines of this code wrap our vertex array into a VertexBuffer object. 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 it contains, 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 MonoGame, 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 correct model, view, and projection matrices.

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 facing away from the camera) 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 by going through each pass in the effect, applying the pass's settings, and then drawing our primitives with it.

Let's look at that DrawPrimitives call in 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. Because of the way it stores the data on the graphics card, it is unsure of its exact format and how much data there is. 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:

DrawingPrimitives.png

What's Next?

We've drawn our first primitive. The next step is to play around with this a little more and draw more triangles, which we will do in the next tutorial. After that, we will get into some better ways to draw lots of triangles.


Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!