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


which rotates space about the y axis by the given angle.
RotateZ() returns a matrix which rotates space about the z axis by the given angle.
Scale() returns a matrix which scales space by the given scale factor.
You'll typically use the above functions to generate the world matrices, i.e. those that are used to move objects into position. The following functions are used to create DrawPrimitive's camera and view matrices:

ViewMatrix() accepts a camera position, target and roll and generates a matrix to represent that camera. This is handy if you know where the camera is located, since you can just pass the camera matrix directly into the transform function (see below). Another (and perhaps easier) method is to maintain the camera matrix yourself. If you user presses the "up" arrow then you can call Translate() to generate a matrix that moves space back along the z axis by a certain amount, and then post-mutiply the camera matrix by the translation matrix. In this manner you can specify almost all movement relative to the camera axis regardless of where the camera is positioned or how it's oriented.
ProjectionMatrix() sets up a matrix used to determine how points should be perspectively projected onto the screen. You pass in the distance to the near clipping (I use 0.01), the distance to the far clipping plane (I use a very high value of 2000000.0f) and the field of view (FOV) in radians. Sixty degrees is a nice FOV and the one I prefer to use. To convert from degrees to radians simply multiply by PI then divide by 180.
Here is the matrix code:

inline D3DMATRIX IdentityMatrix()
{
return D3DMATRIX(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
}

inline D3DMATRIX ZeroMatrix(void) // initializes matrix to zero
{
D3DMATRIX ret;
for (int i=0; i<4; i++)
for (int j=0; j<4; j++)
ret(i, j) = 0.0f;
return ret;
} // end of ZeroMatrix()

// Multiplies two matrices.
inline D3DMATRIX MatrixMult(const D3DMATRIX a, const D3DMATRIX b)
{
D3DMATRIX ret = ZeroMatrix();
for (int i=0; i<4; i++)
for (int j=0; j<4; j++)
for (int k=0; k<4; k++)
ret(i, j) += a(k, j) * b(i, k);
return ret;
} // end of MatrixMult()

D3DMATRIX Translate(const float dx, const float dy, const float dz)
{
D3DMATRIX ret = IdentityMatrix();
ret(3, 0) = dx;
ret(3, 1) = dy;
ret(3, 2) = dz;
return ret;
} // end of Translate()

D3DMATRIX RotateX(const float rads)
{
float cosine, sine;

cosine = (float)cos(rads);
sine = (float)sin(rads);
D3DMATRIX ret = IdentityMatrix();
ret(1,1) = cosine;
ret(2,2) = cosine;
ret(1,2) = -sine;
ret(2,1) = sine;
return ret;
} // end of RotateX()

D3DMATRIX RotateY(const float rads)
{
float cosine, sine;

cosine = (float)cos(rads);
sine = (float)sin(rads);
D3DMATRIX ret = IdentityMatrix();
ret(0,0) = cosine;
ret(2,2) = cosine;
ret(0,2) = sine;
ret(2,0) = -sine;
return ret;
} // end of RotateY()

D3DMATRIX RotateZ(const float rads)
{
float cosine, sine;

cosine = (float)cos(rads);
sine = (float)sin(rads);
D3DMATRIX ret = IdentityMatrix();
ret(0,0) = cosine;
ret(1,1) = cosine;
ret(0,1) = -sine;
ret(1,0) = sine;
return ret;
} // end of RotateZ()

D3DMATRIX Scale(const float size)
{
D3DMATRIX ret = IdentityMatrix();
ret(0, 0) = size;
ret(1, 1) = size;
ret(2, 2) = size;
return ret;
} // end of Scale()

inline D3DMATRIX ViewMatrix(const D3DVECTOR from, // camera location
const D3DVECTOR at, // camera look-at target
const D3DVECTOR world_up, // world’s up, usually 0, 1, 0
const float roll) // clockwise roll around
// viewing direction,
// in radians
{
D3DMATRIX view = IdentityMatrix();
D3DVECTOR up, right, view_dir;

view_dir = Normalize(at - from);
right = CrossProduct(world_up, view_dir);
up = CrossProduct(view_dir, right);
right = Normalize(right);
up = Normalize(up);

view(0, 0) = right.x;
view(1, 0) = right.y;
view(2, 0) = right.z;
view(0, 1) = up.x;
view(1, 1) = up.y;
view(2, 1) = up.z;
view(0, 2) = view_dir.x;
view(1, 2) = view_dir.y;
view(2, 2) = view_dir.z;
view(3, 0) = -DotProduct(right, from);
view(3, 1) = -DotProduct(up, from);
view(3, 2) = -DotProduct(view_dir, from);

if (roll != 0.0f)
{
// MatrixMult function shown below
view = MatrixMult(RotateZ(-roll), view);
}

return view;
} // end of ViewMatrix()

inline D3DMATRIX ProjectionMatrix(const float near_plane, // distance to near clipping plane
const float far_plane, // distance to far clipping plane
const float fov) // field of view angle, in radians
{
float c, s, Q;

c = (float)cos(fov*0.5);
s = (float)sin(fov*0.5);
Q = s/(1.0f - near_plane/far_plane);
D3DMATRIX ret = ZeroMatrix();
ret(0, 0) = c;
ret(1, 1) = c;
ret(2, 2) = Q;
ret(3, 2) = -Q*near_plane;
ret(2, 3) = s;
return ret;
} // end of ProjectionMatrix()


So we need to call the appropriate functions to generate the three matrices mentioned above. In this example I'll assume that the world matrix is 0 (i.e. we'll be specifying where vertices actually are in 3D space), the camera is positioned at the origin and is looking towards z positive with no roll, and the projection matrix is set to a 60 degree FOV. Here is the code I use:

m_World = IdentityMatrix();
m_View = ViewMatrix(D3DVECTOR(0,0,0), D3DVECTOR(0,0,1), D3DVECTOR(0,1,0), 0);
m_Projection = ProjectionMatrix(1.0f, 1000.0f, (float)(60*PI/180)); // 60 degree FOV
Once you've created these matrices you should set them with calls to the SetTransform() function:

m_pDevice->SetTransform(D3DTRANSFORMSTATE_WORLD, &m_World);
m_pDevice->SetTransform(D3DTRANSFORMSTATE_VIEW, &m_View);
m_pDevice->SetTransform(D3DTRANSFORMSTATE_PROJECTION, &m_Projection);


Rendering a 3D Primitive


Rendering a 3D triangle is very similar to the 2D case, but you specify the triangle's coordinates in 3D world space rather than screen space. DrawPrimitive will transform the points by the world matrix to position them, then by the view matrix to find where they are relative to the camera, then by the projection matrix to project them onto the screen (actually D3D undoubtedly combines all three matrices into one and performs only one matrix transformation, but the net result is the same).

With the 3 matrices set above we can again call DrawPrimitive(), this time passing in an array of D3DLVERTEX instead:

D3DLVERTEX v[3];
v[0] = D3DLVERTEX(D3DVECTOR( 0, 2,10),D3DRGB(1,0,0),D3DRGB(1,0,0),0,0);
v[1] = D3DLVERTEX(D3DVECTOR( 2,-2,10),D3DRGB(0,1,0),D3DRGB(1,0,0),0,0);
v[2] = D3DLVERTEX(D3DVECTOR(-2,-2,10),D3DRGB(0,0,1),D3DRGB(1,0,0),0,0);
m_pDevice->DrawPrimitive(D3DPT_TRIANGLELIST,D3DVT_LVERTEX,(LPVOID)v,3,NULL);


Note that we are now in world space, so x is positive going right, y is positive going up and z is positive going into the screen. Also the origin point <0,0,0> is centered on the screen. This is all very important to remember, because DrawPrimitive by default performs counter-clockwise back-face culling. Get your axis' mixed up, and the triangle will be culled.

To construct the D3DLVVERTEX variables above we need to pass in 5 values: the coordinate in 3D world space, the diffuse color component, the specular color component and the UV texture coordinates (not used yet). The result you see on the screen at this point should look fairly similar to the 2D triangle, but try playing with the coordinate values above (particularly z) to move points around.

The Render State


The SetRenderState() function is used to modify various DrawPrimitive rendering parameters. You pass in the state variable you wish to render as well as the value you wish to set it to (dependent on the parameter you're setting). The DirectX documentation covers this function in detail, but here are a few of the more important parameters:

D3DRENDERSTATE_TEXTUREHANDLE: Sets the handle of the current texture used to map triangles. Set it to NULL to remove texture mapping.

D3DRENDERSTATE_TEXTUREPERSPECTIVE: Toggles perspective correct texture mapping. Set it to TRUE to enable perspective correction, and FALSE to disable it.

D3DRENDERSTATE_SUBPIXEL: When set to TRUE, this parameter enables subpixel accurate texture mapping. Leaving this disabled can result in a significant speed increase, but vertical texture lines "warble" up and down as the viewpoint changes. I hate the effect myself, and always enable this feature, but some people with slow machines may prefer you to at least give them the option.

D3DRENDERSTATE_FILLMODE: Specifies how triangles should be filled. You'll usually want to set this to D3DFILL_SOLID to fill in the triangle with a color or texture.

D3DRENDERSTATE_SHADEMODE: Allows you to specify flat, gouraud or phong shading. Pass in D3DSHADE_FLAT, D3DSHADE_GOURAUD or D3DSHADE_PHONG.

D3DRENDERSTATE_MONOENABLE: When set to TRUE, this enables the monochromatic lighting model. When monochromatic lighting is enabled the blue channel is used to interpolate the shade intensity across a face. Setting this parameter to FALSE enables RGB rendering in which each of the 3 RGB components is interpolated across the face and used to shade their respective color components in the texture. In software rendering RGB is significantly slower than monochromatic, so you'll usually want to set this parameter to TRUE.

D3DRENDERSTATE_SPECULARENABLE: Enables specular highlights. This tends to look somewhat more realistic but it's much slower to render, particularly during software rendering.

D3DRENDERSTATE_CULLMODE: Sets the backface culling mode. Passing in D3DCULL_NONE will cause triangles to always be rendered regardless of their orientation. D3DCULL_CW will cause triangles with clockwise vertices (after projection onto the screen) to be ignored. D3DCULL_CCW will cause triangles with counter-clockwise vertices to be ignored. Like most other apps, I define the vertices of my triangles in clockwise order, so I set D3DCULL_CCW (which also happens to be the default).

D3DRENDERSTATE_DITHERENABLE: Toggles dithering. Dithering doesn't seem to slow rendering down too much on my machine, but both the software renderer and my hardware accelerated card apparently use some kinf of Bayer dithering which results in a noticable (and annoying) dither pattern across the face of my triangles. I usually disable this.

The next three parameters allow you adjust how and if DrawPrimitive performs z-buffering. If you want to z-buffer your primitives then you must create a z-buffer surface and attach it to the surface you're rendering to. Providing control over how z-buffering is performed allows us to perform high-speed rendering of dynamic objects into a scene. Quake, for example, renders all it's static geometry at the start with no overdraw. During this rendering phase pixels depths are not compared, but it does write to the z-buffer as it goes along. When all static polygons have been rendered they are left with a scene of all the static objects along with a corresponding

Page : << Previous 3  Next >>