Topic : DrawPrimitive
Author : Mark Feldman
Page : << Previous 2  Next >>
Go to page :


position, but it does include information about the camera's field of view (FOV) as well as front and back clipping planes.

The DirectX documentation provides more information on setting up a viewport, I use the following code which is supplied with one of the demos:

// Create a viewport
D3DVIEWPORT viewport;
ZeroMemory(&viewport, sizeof(viewport));
viewport.dwSize = sizeof(viewport);
viewport.dwWidth = SCREEN_WIDTH;
viewport.dwHeight = SCREEN_HEIGHT;
viewport.dvScaleX = SCREEN_WIDTH / 2.0f;
viewport.dvScaleY = SCREEN_HEIGHT / 2.0f;
viewport.dvMaxX = D3DVAL(1.0);
viewport.dvMaxY = D3DVAL(1.0);
if (m_pD3D->CreateViewport(&m_pViewport, NULL) != D3D_OK)
return FALSE;
if (m_pDevice->AddViewport(m_pViewport) != D3D_OK)
return FALSE;
if (m_pViewport->SetViewport(&viewport) != D3D_OK)
return FALSE;
if (m_pDevice->SetCurrentViewport(m_pViewport) != D3D_OK)
return FALSE;


Rendering A Scene


With the device and viewport created, we are now ready to render primitives. Before doing this though you'll probably want to clear the back buffer to black. The fact that DrawPrimitive doesn't do this for you is an advantage, because it allows you to perform custom rendering in the background first if you wish. Here's a code fragment to clear the back-buffer surface:

// Clear the back buffer
DDBLTFX bltfx;
ZeroMemory(&bltfx, sizeof(bltfx)); // Sets dwFillColor to 0 as well
bltfx.dwSize = sizeof(bltfx);
m_pBackBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&bltfx);


To start rendering a D3D scene you call the BeginScene() member function:

m_pDevice->BeginScene();
When you are finished you call the EndScene() member function:

m_pDevice->EndScene();
Your rendering calls should be sandwhiched in between these two calls. Primitives are rendered with two functions: DrawPrimitive() and DrawIndexedPrimitive(). The main difference between these two is how vertices are passed in. With DrawPrimitive you pass in a pointer to an array of vertices. If you are rendering 4 triangles (say) then the array must have 4*3=12 elements, i.e. 3 discrete vertex structures for each triangle. DrawIndexedPrimitive is similar, but you instead pass in a pointer to a master vertex array, and each triangle is instead defined by 3 WORDs which are indices into that array. This is slightly faster, since duplicate vertices only have to go through the transformation/projection process once (well...that's the theory anyway. In practice it seldom works like that, for reason which we'll see later on). In this article I'll only be discussing DrawPrimitive(), since it's slightly easier to work with.

Primitive Types


The first parameter you pass into DrawPrimitive() is the type of primitive you wish to draw. There are currently 6 primitive types supported: points, lines, polylines, triangles, triangle strips and triangle fans.

Point lists are vertices rendered as single-pixels on the screen. Point lists are specified with the D3DPT_POINTLIST identifier.

Lines are defined by two endpoints, each is projected onto the screen and a stright line is drawn between them (passing in 4 vertices to DrawPrimitive will cause two lines to be drawn). Line lists are specified with the D3DPT_LINELIST identifier.

Polylines are similar to line lists, but instead of passing in pairs of endpoints you pass in the coordinates for a single long line. If you use this primitive to render unfilled polygons then don't forget to pass the first point in again at the end of the list, otherwise the last edge won't be drawn. Polylines are specified with the D3DPT_LINESTRIP identifier.

Triangle lists are specified with vertex triplets. If you want to render 10 triangles then you would pass in an array of 30 vertices. Note that all triangles in a single call are drawn with the same render state (shading method, fill texture etc). Triangle lists are specified with the D3DPT_TRIANGLELIST identifier.

Triangle strips are handy for rendering complex mesh models. To visualize how triangle strips are rendered, think of the vertex array as specifying a polyline. Now imagine that extra edges are formed between all the even numbered vertices (ie elements 0 and 2, 4 and 6, 8 and 10 etc) and also between all the odd numbered vertices (1 and 3, 5 and 7). The resulting triangles are what DrawPrimitive renders. Check the DirectX documentation for more information on this type. Triangle lists are specified with the D3DPT_TRIANGLESTRIP identifier.

Triangle fans are handy for rendering general convex polygons. You pass in the points of the polygon and DrawPrimitive breaks it down into triangles and renders each one seperately. Check the DirectX documentation for more information on this type. Triangle fans are specified with the D3DPT_TRIANGLEFAN identifier.

Vertex Types


The second parameter you pass into DrawPrimitive allows you to specify the type of structures in the vertex array. This in turn implies how DrawPrimitive should use them. DrawPrimitive currently accepts 3 types of vertex structures:

D3DVT_TLVERTEX: Specifies that the vertices are of type D3DTLVERTEX. This type allows you to pass in vertex coordinates in screen space. You pass in the screen coordinates relative to <0,0> in the upper left hand corner, as well as lighting information, texture coordinates and a few other things.

D3DVT_LVERTEX: Specifies that the vertices are of type D3DLVERTEX. This type allows you to pass in the vertex coordinates in world space. You still have to assign lighting to each point yourself, but DrawPrimitive will tranform the point by the world, view and projection matrices (see below for more details on this). Personally I find this type to be the most useful for most of the work I do. You specify the world coordinates, the lighting and the texture coordinates where appropriate.

D3DVT_VERTEX: Specifies that the vertices are of type D3DTLVERTEX. DrawPrimitive will both transform and light vertices of this type itself. You specify the world coordinates, the normal vector to use for lighting and the texture coordinates where appropriate.

Rendering a 2D Primitive


Let's start things off simple and draw a colored 2D triangle on the screen. I'll assume we've set up for 320x240 mode, the following code will render a single triangle between the coordinates <160,50>, <240,200> and <80,200>:

// Render a triangle
D3DTLVERTEX v[3];
v[0] = D3DTLVERTEX(D3DVECTOR(160, 50,0),1,D3DRGB(1,0,0),D3DRGB(0,0,0),0,0);
v[1] = D3DTLVERTEX(D3DVECTOR(240,200,0),1,D3DRGB(0,1,0),D3DRGB(0,0,0),0,0);
v[2] = D3DTLVERTEX(D3DVECTOR( 80,200,0),1,D3DRGB(0,0,1),D3DRGB(0,0,0),0,0);
m_pDevice->DrawPrimitive(D3DPT_TRIANGLELIST,D3DVT_TLVERTEX,(LPVOID)v,3,NULL);
Note: in order to use overloaded functions to create vertices "on-the-fly" like this you need to add the following line before including d3d.h:

#define D3D_OVERLOADS


Also notice that I'm lazy and don't bother casting my values to D3DVAL (which is type float), doing so makes the code longer and a bit more difficult to read anyway. In a commercial application you'd probably want to do this though, just to be safe.

The code starts off by declaring an array of 3 D3DTLVERTEX structures, thus the points we pass in will be treated as pre-lit pre-transformed vertices in screen space.

The first element, a D3DVECTOR, specifies the coordinates in screen-space. Note that the z component isn't used, so I set it to 0.

The second element is the rhw field. If you've pre-transformed and projected the points of a triangle and you wish to map a texture onto it then you still need to provide some depth-information so that the rasterization gets the perspective mapping correct (I'll be discussing this is detail further on). For this simple test we're rendering a gouraud-shaded triangle (the default), so depth information isn't used. Set this field to 1 for the time being.

The next field is the diffuse color component. The D3DRGB macro accepts 3 color components ranging from 0-1. As you can see, I've assigned each corner of the triangle a different primary color. DrawPrimitive will gouraud shade the pixels in between as it rasterizes.

The next field is the specular color component used for phong shading. This example uses gouraud shading, so this field isn't used. Even so, I set it to <0,0,0> just to be safe.

The last two elements specify the UV coordinates for texture mapping. I'll be going into detail about this later on, but for the time being I set them to 0.

Matrices


Before continuing any further I suggest you brush up on my atricle on matrices. One important thing to keep in mind is that in my article I represent matrices opposite to the way Direct3D does, ie with the rows and collumns swapped around.

DrawPrimitive requires you to set three matrices: the world matrix, the view matrix and the projection matrix. The world matrix is used to position objects in 3D space, so you usually set it once for each object you render so as to move it into position. The view matrix represents the camera location, you typically set this once at the start of each frame to position the camera. The view matrix contains information about how points should be perspectively projected onto the screen, you usually only need to set this once right after creating the device (unless your camera has a zoom function, in which case you change it as you need to).

The DirectX help file provides a number of useful functions for creating and manipulating matrices. The code is spread across a few articles, but I've collected it all into one code segement which I'll show next. I've made a few minor modifications, e.g. inlined each function for the sake of speed. Most functions should be pretty self-explanatory, but here's a brief summary anyway:

IdentityMatrix() returns a D3DMATRIX set to the the identity matrix.
ZeroMatrix() returns a D3DMATRIX with all elements set to 0.
MatrixMult() multiplies two matrices matrix a by matrix b.
Translate() returns a matrix which translates space by the given vector.
RotateX() returns a matrix which rotates space about the x axis by the given angle.
RotateY() returns a matrix

Page : << Previous 2  Next >>