Feb
1
GM6 D3D from the Ground Up - Chapter 3
2007 @ 10:16 AMChapter 3 - Drawing in 3D: Primitives
A Primitive?
A
primitive is the smallest "building block" of a 3D scene. You construct everything by organizing primitives. Think of them like atoms that construct our physical world of matter. You can create pretty much anything, not constrained by complexity, with primitives.
Game Maker's 3D Coordinate System
Game Maker uses a special coordinate system inside 3D mode. Since it still wants to use rooms and such, the z and y-axes are switched. The z-axis is vertical, while the y-axis goes in and out of the screen.
Drawing Points in 3D
At some point or another, you've made some type of graphic on the computer. If so, you're familiar with pixels. They are the smallest dot in an image. However, we have a different paradigm in Direct3D. We construct our dots in 3D space and it will project them into 2D pixels.
Defining our 3D Canvas
We are first going to set our projection to an orthographic projection. Don't worry, we'll eventually get to the perspective projection. But, for now, let's keep it simple with the orthographic projection.
Add the following code after your call to
d3d_start();:
d3d_set_perspective(false);. If you recall, this function will turn off the default perspective projection and turn on our orthographic projection. Now we need to actually set up our projection. In the Draw event, add the following code:
d3d_set_projection(x, y, 10, objVertices.x, objVertices.y, 10, 0, 0, 1);
Synopsis:
d3d_set_projection(xfrom, yfrom, zfrom, xto, yto, zto, xup, yup, zup);
xfrom, yfrom, and zfrom for a 3D-coordinate. This coordinate is where the "camera" is located. In our example, we put it at (x,y,10).
The projection starts from the object's position in the room and 10 units into the z-axis. xto, yto, and zto form another 3D-coordinate. This coordinate is the point the camera is looking towards. in our example, we set it to look at objVertices. (We are about to create this object which will draw some points on the screen.) So imagine the camera pointed to look towards our object. The xup, yup, and zup parameters define a 3D
vector. This vector describes the direction that the camera considers upwards. In this case, it's the z-axis.
A 3D Point
To construct a 3D point, we use the Game Maker function
d3d_vertex(x,y,z);. x,y, and z form a 3D coordinate as you should have assumed by now.
Let's Draw Something Finally!
A vertex is just not a 3D point. It's a point where two lines intersect. So, you have to wonder: is it part of a line, a cube, or some arbitrary polygon? We have to tell Game Maker what our vertex is part of.
Drawing Points
Create an object named "objVertices" and place it somewhere in the room. Add this to its draw event:
draw_set_color(c_red);
d3d_primitive_begin(pr_pointlist);
d3d_vertex(x,y,-150);
d3d_vertex(x-50,y,-150);
d3d_primitive_end();
d3d_primitive_begin tells Game Maker to start building a new primitive. it takes one argument, and this argument describes the type of primitive to start building. In our case, we just want some points to be drawn, so we used
pr_pointlist. It simply draws the vertices that are part of the primitive without doing anything special to them.
d3d_primitive_end() closes the current primitive. Go ahead and run your game now. You should see two red dots drawn on the screen. You can play with the x,y,and z values to become more acquainted with the coordinate system and 3D drawing.
Drawing a More Complex Set of Points
Let's do something a bit more practical rather than two simple dots on our screen. Our next example will use D3D to draw a "spring" of points on the screen.
draw_set_color(c_red);
d3d_primitive_begin(pr_pointlist);
zpos = -50;
for(angle = 0.0; angle <= (2*pi)*3; angle += 0.1)
{
xpos = 50*sin(angle);
ypos = 50*cos(angle);
d3d_vertex(xpos, ypos, zpos);
zpos += 0.5;
}
d3d_primitive_end();
This code calculates x and y for an angle that spins between 0 and 360 degrees 3 times, whilst increasing the height of the next point. You don't have to know the trigonometry behind this, but it would be very useful. Unfortunately, it is outside the scope of this tutorial; however it would be easy to find on the web.
Drawing 3D Lines
pr_pointlist is fairly straight-forward. But now let's move on to a more complex primitive:
pr_linelist
draw_set_color(c_red);
d3d_primitive_begin(pr_linelist);
d3d_vertex(x,y,150);
d3d_vertex(x-50,y,150);
d3d_primitive_end();
Here, for every 2 vertices specified, a line is drawn.
Drawing a More Complex Set of Lines
Again, let's see a practical example.
draw_set_color(c_red);
d3d_primitive_begin(pr_linelist);
ypos = 0;
for(angle = 0; angle <= pi; angle+=(pi/20))
{
xpos = 50*sin(angle);
zpos = 50*cos(angle);
d3d_vertex(x+xpos, y+ypos, zpos);
xpos = 50*sin(angle + pi);
zpos = 50*cos(angle + pi);
d3d_vertex(x+xpos, y+ypos, zpos);
}
d3d_primitive_end();
Line Strips
A line strip is very similar to a line list. The major difference is that in a line strip, one line is built continuously with each vertex that is drawn. You can specify the line strip primitive by passing the argument
pr_linestrip to
d3d_primitive_begin().
Drawing Triangles in 3D
So far we've been drawing only open surfaces composed of just lines. To draw a closed surface that can be shaded, we must use a
polygon. A polygon is a shape with an arbitrary number of sides.
The Triangle
This is the simplest polygon possible, with just three sides. To draw these, we must specify the
pr_trianglelist primitive when calling
d3d_primitive_begin()
draw_set_color(c_red);
d3d_primitive_begin(pr_trianglelist);
d3d_vertex(x,y,50);
d3d_vertex(x+50,y+50,5);
d3d_vertex(x+100,y-50,-100);
d3d_primitive_end();
This code draws a single triangle with vertices at different positions.
pr_trianglelist draws a triangle with every three vertices specified. If the number of vertices specified isn't divisible by three, the remainder is ignored.
Winding
There is an order in which you should specify vertices that composes a triangle primitive. This is called
winding. There are basically two types of winding: clockwise and counter-clockwise. In clockwise winding, vertices are supplied in the clockwise direction from the first vertex. The opposite is true for counter-clockwise winding.

Polygons with clockwise winding are considered
front-facing by Direct3D, while polygons with counter-clockwise winding are considered
back-facing by Direct3D. This is important because you sometimes want to give the back of a polygon certain characteristics such as a different color. Therefore it is important to keep the winding of polygons in a scene consistent if possible. Most 3D models that are saved from a 3D suite program usually automatically do this for you. So you may not have to worry about it in some cases.
Triangle Strips
A triangle strip is a primitive similar to the
pr_trianglelist primitive. You can create one by passing
pr_trianglestrip to
d3d_primitive_begin(). This primitive allows you to draw connected triangles. By convention, once the first three vertices are drawn, you only have to add one more to get a whole other triangle. This is the bandwith saving caveat of using triangle strips. You can minimize data bandwith and memory usage when defining large amounts of triangles.
Triangle Fans
A triangle fan is a primitive similar to the
pr_trianglelist and
pr_trianglestrip primitives. You can create one by passing
pr_trianglefan to
d3d_primitive_begin().
The difference with this primitive is that you define triangles that "fan" around a central point. You could, for example, create 3 triangles by specifying four vertices, the first of which is the central vertex.
Drawing a More Complex Set of Triangles
Let's do something a bit more practical with our new triangle primitives and create a
solid object.
draw_set_color(c_red);
colorChange = 0;
d3d_primitive_begin(pr_trianglefan);
d3d_vertex(x,75,y);
for(angle = 0; angle < (2*pi); angle+=(pi/9))
{
xpos = 50*sin(angle);
ypos = 50*cos(angle);
if((colorChange mod 2) == 0)
{
draw_set_color(c_red);
}
else {
draw_set_color(c_green);
}
colorChange += 1;
d3d_vertex(x+xpos,75,y+ypos);
}
d3d_primitive_end();
d3d_primitive_begin(pr_trianglefan);
d3d_vertex(x,0,y);
for(angle = 0; angle < (2*pi); angle+=(pi/9))
{
xpos = 50*sin(angle);
ypos = 50*cos(angle);
if((colorChange mod 2) == 0)
{
draw_set_color(c_red);
}
else {
draw_set_color(c_green);
}
colorChange += 1;
d3d_vertex(x+xpos,75,y+ypos);
}
d3d_primitive_end();
This large piece of code may look intimidating at first, but it's really not that complex. What we are accomplishing here is drawing a cone with two triangle fan primitives. The first draws the flat bottom part of the cone. It starts off with a single central vertex and creates 8 around it in a circle. The second triangle fan starts off with the tip of the cone and creates 8 points around it in a circle matching up with the bottom of the cone. Simple, huh?
Polygon Culling
Polygon culling is a Direct3D optimization where primitives that are not visible are not drawn at all. It seems pretty logical that this is enabled, but sometimes you want to see the back of something. However, in our cone example, we never see the inside of the cone. So let's enable culling to tell D3D to not even bother drawing the inside of the cone.
d3d_set_culling(true); will enable backface culling. To disable it, just call
d3d_set_culling(false);
<< Previous Chapter Next Chapter >>
Well done!
A) Triangle Strips drawing above: Vertex 4 should be connected with Vertex 2, not with Vertex 1.
B) It seems that one cannot maintain a determined winding direction while applying Triangle Strips.