Introduction
This is the third in a series of tutorials which will allow you to create your own game engine (from initialization, to a fully rotatable, height mapped 3D world!).
In this tutorial, we will use Direct3D to create and render a primitive, as well as learn some more DirectX concepts.
Background
The code from these tutorials is taken from my own game engine: MAGEngine.NET, at different stages in development.
Using the code
You may use all of the code I provide as how you see fit, except to create another tutorial. You can use it as a sturdy(ish ;)) framework for your own applications, or print loads of copies off so that when I'm rich and famous you can sell them for $100 each ;)
Prerequisites
To do this series of tutorials, you will need:
- C# Compiler (preferably Visual C# 2005 Express)
- Managed DirectX 9.0 October SDK
OK, in the last tutorial, we spent a long time discussing various aspects of the Direct3D component, we created a good, solid sample framework, but we never got to render anything � which is the whole concept of Direct3D. In this tutorial, we will:
- Learn about primitives
- Initialize a triangle
- Set transforms
- Render the triangle
- Create a
cObject
class to use in our game
Theory
All 3D worlds - whether it is a character, terrain, or a box - are made out of triangles. And all these triangles are essentially three points, or vertices. So, to render our triangle, we will need to define three of these "points" and send them to be rendered in our OnPaint
function. In Managed DirectX, there is a set of structures made to represent different types of these points, known as the CustomVertex
class.
From this you, can choose a "point" structure to suit your needs - if you want a textured box which you could rotate, then you would choose PositionTextured
; if you want that box to be shaded, then you would choose PositionNormalTextured
. For today, we will be creating a static, colored triangle, so we will be using CustomVertex.TransformedColored
points.
Transformed indicates that the coordinates we define have been declared as screen coordinates, and do not need to be scaled, rotated, or otherwise.
Now, your initial reaction may be to jump straight into the code and render your triangle - but first, we should create a class to help us with this.
We will be making two classes, one called cObject
which contains an array of CustomVertex.TransformedColored
(s?), and a bool
indicating if we should render the triangle (IsActive
). We will then make a class representing a triangle. It will inherit cObject
, but will have two functions: an initialization function where it defines the array and any coordinates, and its own render function.
In Direct3D, the best way to render is to use the OnPaint
event, as we did in the previous tutorial. But, it will make life a lot easier in the future if we can set up all the framework in OnPaint
, and just call this render method. We could even do it within a for()
loop for arrays of triangle objects.
The above classes will be declared in a new file called cObject
, and are as follows:
public class cObject
{
public CustomVertex.TransformedColored[] itsVertices;
}
public class cTriangle : cObject
{
bool isActive;
public cTriangle(CustomVertex.TransformedColored[] itsVerts)
{
itsVertices = new CustomVertex.TransformedColored[3];
isActive = true; itsVertices[0] = itsVerts[0];
itsVertices[1] = itsVerts[1];
itsVertices[2] = itsVerts[2];
}
public void Render(Device device)
{
device.DrawUserPrimitives(PrimitiveType.TriangleList,
1, itsVertices;);
}
As you can see, the cObject
class is fairly simple - only containing an unassigned array of TransformedColored
vertices. The cTriangle
class only adds the isActive
member, and the initialization and render functions.
The Render
function, as you can see, calls the function DrawUserPrimitives()
. This is a function, obviously used to render primitives. You give it a PrimitiveType
- this simply tells DirectX 'how' to render the vertices. There are many examples, and displayed below you can see how they impact the positioning of the primitive:
The other parameters are fairly simple - how many primitives to render (=1) and then the source (or where the primitives to be rendered can be found). So, now that we have this class updated, we only have two more things to do to get our triangle rendered:
- Create a
cTriangle
object
- Initialize this object
- Update our
OnPaint
method to render the triangle
So, with this class created, it should be easy to create an instance of your class - make a public cTriangle
object in your window class (I will call mine "Tri
" in the samples). Now comes a slightly tricky part which we will resolve in the next tutorial.
We need to create a CustomVertex.TransformedColored
array to assign to the cTriangle
instance we will be creating and pass through its constructor. To do this, use the following code in your main()
method, nothing should be fairly new here:
CustomVertex.TransformedColored[] Verts =
new CustomVertex.TransformedColored[3];
Verts[0] = new CustomVertex.TransformedColored(new
Vector4(100.0f, 100.0f, 0.0f, 1.0f), Color.Red.ToArgb());
Verts[1] = new CustomVertex.TransformedColored(new
Vector4(200.0f, 100.0f, 0.0f, 1.0f), Color.Blue.ToArgb());
Verts[2] = new CustomVertex.TransformedColored(new
Vector4(150.0f, 200.0f, 0.0f, 1.0f), Color.Green.ToArgb());
OK, the only thing slightly strange here should be the Vector4
structure. The last parameter is known as the RHW, which is only used in transformed coordinates as it is used in perspectives. I would highly recommend to not bother about this - we will never be using it again after this tutorial when we use positioned coordinates, and it will always be 1.0 for this tutorial. For this triangle, the Z coordinate will always be 0.0f. The ToArgb()
function converts a .NET color to its Alpha-Red-Green-Blue value. For example, White, when converted to ARGB brings the value (0,255,255,255) (ignoring A which is Alpha, or transparency) because of course, white is a mixture of all the colors, whereas Red would convert to (0,255,0,0), and so on.
Now, make a call to your triangle's constructor and pass in the vertices defined above as the parameter. Then, go to the OnPaint
event of your form and we will finish this tutorial.
From OnPaint
, erase everything in it.
OnPaint
will be called in every single frame, every time the display needs to be updated. So, we need to do a number of things:
- Clear the display area
- Notify the GPU of what type of vertices we will be rendering
- Lock the GPU for rendering
- Render our primitives
- Unlock the GPU
- Swap the old frame with the new one
- Call the
Invalidate
function for .NET
The reason for locking and unlocking the GPU during rendering is to reduce errors at run time. When we render, we render to what is called a back buffer. This is an off-screen surface where any data can be stored until we call the device.Present()
function to copy it to the on screen surface.
To code, this translates as:
device.Clear(ClearFlags.Target,
System.Drawing.Color.Black, 1.0f, 0);
device.VertexFormat =
CustomVertex.TransformedColored.Format;
device.BeginScene();
Triangle.Render(device);
device.EndScene();
device.Present();
this.Invalidate();
This should make up the entire body of the OnPaint
function.
If you compile and run this code, you should see a blended-colour triangle on your screen.
X-Challenge
- Use the classes you created to render three triangles side by side
- Modify the
OnPaint
event so that it can render all initialized triangles without us needing to edit it every time we add or remove one from the array.
Contact
Please send all emails to xpyder@magclan.cwhnetworks.com. I do have MSN Messenger, my email address for this is jamespraveen@aol.com.
History
- 19/01/06: Submitted tutorials 1-3 to CodeProject.