05 Rendering with a VertexBuffer

Last time we discussed how to create a VertexBuffer, which holds information about vertices. We need the VertexBuffer, since that’s the structure the Device accepts to actually do any rendering. In this article, we’ll talk about how to actually get our VertexBuffer to draw something on the screen.

To render a shape, we’ll need to modify our Render method. (Remember Render?) It doesn’t take much modification, though:

protected void Render() { device.Clear(ClearFlags.Target, Color.Bisque, 1.0F, 0); device.BeginScene(); // Two new lines of code here device.VertexFormat = CustomVertex.TransformedColored.Format;// It seems we need to set this // first or we keep getting InvalidCallException device.SetStreamSource(0, vertices, 0); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); device.EndScene(); device.Present(); }

You'll notice we've added two new lines of code. The first one doesn’t take much explanation:

device.SetStreamSource(0, vertices, 0);

SetStreamSource simply tells the Device, “When I tell you to render some stuff, make sure you grab the vertex data from this VertexBuffer.” We might have several VertexBuffers floating around, say one for each object we want to draw, and it’s important that we set the stream source before each rendering call or we might find ourselves drawing the wrong shape.

The second line of code is where all the action occurs:

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

It tells Direct3D to go ahead and actually draw something. Finally!

The first argument to DrawPrimitives is very important: it tells Direct3D what sort of thing we’re going to be drawing. There are basically six choices: PointList, LineList, LineStrip, TriangleList, TriangleStrip, and TriangleFan.

PointList is the simplest. It tells Direct3D that the vertices in the VertexBuffer are to be rendered as just that – a list of points. You wouldn’t use this too often, except maybe to render a star field in the background.

LineList is similar: the vertices in the buffer are in pairs, each pair containing the beginning and ending points of a line. Since we have to specify the beginning and ending points of each line, rendering a series of connected lines is inefficient, since we’d have to specify the start of the first line, the end of the first line, then specify the second point again as the starting point of the second line, and so on. Since efficiency is the watchword, Direct3D gives us the LineStrip.

LineStrip allows us to draw a series of interconnected lines, where the end of one line segment is the beginning of the next. If we pass PrimitiveType.LineStrip to DrawPrimitives, the Device will assume that the vertices in the VertexBuffer are specified like this: start, end of first segment, end of second segment, end of third segment, etc. There’s no need for the double-specification we had with LineList, but we can only draw connected segments. That’s the tradeoff.

Using points and lines, we can draw some nice, circa 1982 graphics, a la BattleZone . Of course, we’d rather do better – we’d rather draw solids. Well, solids in Direct3D are all made up of triangles – use enough of them and you can render just about anything. A cube, for example, can be made up of six squares, each of which is made up of two triangles.

As mentioned above, we have three choices when rendering triangles: TriangleList, TriangleStrip, and TriangleFan. TriangleList is the simplest of these. Specifying TriangleList tells Direct3D that we’re going to hand it sets of three vertices in the VertexBuffer, and for each triplet, it should draw the corresponding triangle.

Since we’re only drawing one triangle, that works fine for us:

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

However, had we been drawing lots of triangles, this choice might not be the best one. Imagine, for a moment, the cube I mentioned a minute ago. It’s made up of twelve triangles, but all of them share all of their vertices with other triangles. Having to specify these vertices over and over again would result in inefficiency. We can use TriangleStrips and TriangleFans to overcome this problem. (And actually, there’s something called an IndexBuffer that’s even better for this purpose, but we’ll leave that for another day.)

Much like a LineStrip starts with two points that define a line, a TriangleStrip starts with three points that define a triangle. Also like LineStrip, subsequent vertices build off of what’s already there. In the case of a TriangleStrip, each subsequent point adds a triangle made up of the new point and the two previous points. A TriangleFan is similar, but all the triangles have the first point in common. These figures might help clear that up:

TriangleList

TriangleStrip

Make sense?

OK! Coming back to Render, we’ve covered the first parameter of DrawPrimitives:

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

The second parameter is the offset into the VertexBuffer at which to start. In our case, that’s zero, since we want to start at the beginning. But if we had hundreds of vertices describing dozens of triangles, we might want to only draw numbers ten through seventy-seven, in which case we’d use a non-zero number here.

The third parameter is a bit tricky. It controls the number of primitives that should be drawn. When I first started with Direct3D, I thought it might mean the number of vertices that should be used. This is not the case – specifying the value three here in our code would actually cause an error, since it would try to draw three triangles, which for a TriangleList would require nine points. Our VertexBuffer only has three.

So what’s the result of all this? This:

Note that the color of each of the vertices is blended smoothly across the surface of the triangle. This happens not only for color data, but also for texture and normal data, two things we’ll talk about in an upcoming article.

There’s one more detail I need to mention, because if you start to play with this code, you might run into it. Because typical scenes in Direct3D contain hundreds if not thousands or millions of triangles, it’s important to only process those that might show up in the final scene. For this reason, Direct3D uses a process called culling to immediately eliminate many triangles from the scene.

Culling operates under the assumption that objects are solid – that they don’t have any holes in them. For objects that meet this criteria, we know that no part of any part of a triangle facing away from the viewer will ever be visible. Direct3D won’t even bother to consider these back-facing triangles. This process is sometimes known as backface culling.

Note that this is not true for objects with holes, like a hollow tube. In that case, you’ll need to tell Direct3D not to clip backfaces by adding the following line to Render:

device.RenderState.CullMode = Cull.None;

To return to the default behavior, specify

device.RenderState.CullMode = Cull.CounterClockwise;

The question you’re probably asking yourself is, “How does it know whether a triangle is facing backwards or forwards?” This last line of code gives us a clue: it looks at the order of the vertices that make up the triangle. By default, if they’re in clockwise order, the triangle faces forward. If they’re counterclockwise, the triangle faces backwards. Cull.Clockwise would give us the opposite interpretation and Cull.None means “don’t cull any triangles.”

Go ahead and experiment with these various settings by adding some more triangles, changing the order of vertices, and manipulating the CullMode.

Next time, we'll talk about how to go beyond transformed coordinates - we'll start to put the "3D" in Direct3D.