Rendering Primitives

  CU_Rendering.zip (25.2 KiB, 4,089 hits)

3d objects are all comprised of vertices. Each vertex can hold certain information such as position, color, texture coordinates, normals, fog data, etc. To tell DirectX what information our vertices contain, we fill out an array of D3DVERTEXELEMENT9 structures (If your familiar with FVF’s, in DirectX 9, D3DVERTEXELEMENT9 structures have replaced the flexible vertex format, although FVF’s are still used in certain cases suchs as when creating vertex buffers). Why doesn’t DirectX just have its own vertex type with all of this data? Well, straight from MSDN: “By using only the needed vertex components, your application can conserve memory and minimize the processing bandwidth required to render models.” We begin by creating our own vertex definition and initializing our three vertices that will form a triangle.

 
struct CUSTOMVERTEX {
  float x, y, z; // Position in 3d space 
  DWORD color; // Color 
}; 

CUSTOMVERTEX g_triangle[] = {
  {-2.0f, -2.0f, 5.0f, D3DCOLOR_XRGB( 255, 0, 0 )}, // Bottom left vertex 
  { 0.0f, 2.0f, 5.0f, D3DCOLOR_XRGB( 0, 0, 255 )}, // Top vertex 
  { 2.0f, -2.0f, 5.0f, D3DCOLOR_XRGB( 0, 255, 0 )} // Bottom right vertex 
};

Our vertex definition will hold a position in 3d space and a color. To define our triangle, we need to create 3 points. Here, we create an array of 3 vertices. For each vertex, we specify the x, y, and z coordinates along with a color using the D3DCOLOR_XRGB macro. If you draw these points out on paper, keeping in mind a left-handed coordinate system, you’ll notice the vertices are specified in clockwise order. This is known as the winding order of the polygon. The winding order is used to determine whether or not the vertex is facing the camera. If you imagine a triangle on the back side of my current triangle facing away from you and using the same vertex declaration, the winding order would be counter-clockwise with respect to the camera. This would prevent the back triangle from being rendered. The process of eliminating backfacing polygons is known as backface culling. If the triangle were to rotate, it would disappear once the back side faces the camera because the back triangle is never rendered. This reduces the processing load on the GPU.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: 
  This callback function will be called immediately after the Direct3D device has been created. This is 
  the best location to create D3DPOOL_DEFAULT resources. Resources created here should be released in 
  the OnLostDevice callback. 
Parameters: 
  [in] pDevice – Pointer to a DIRECT3DDEVICE9 instance 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
void CGameApp::OnResetDevice(LPDIRECT3DDEVICE9 pDevice) {
  D3DVERTEXELEMENT9 vertexDeclaration[] = {
    {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, 
    {0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, 
    D3DDECL_END() 
  }; 

  LPDIRECT3DVERTEXDECLARATION9 _vertexDeclaration = 0; 
  pDevice->CreateVertexDeclaration(vertexDeclaration, &_vertexDeclaration); 
  pDevice->SetVertexDeclaration(_vertexDeclaration); 

  // Set up the render states 
  pDevice->SetRenderState(D3DRS_FILLMODE, m_pFramework->GetFillMode());
  pDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD );
  pDevice->SetRenderState(D3DRS_LIGHTING, FALSE); 

  // Set the world and view matrices 
  D3DXMATRIX identity; 
  D3DXMatrixIdentity(&identity); 
  pDevice->SetTransform(D3DTS_WORLD, &identity); 
  pDevice->SetTransform(D3DTS_VIEW, &identity); 

  // Set the projection matrix 
  D3DXMATRIX projection; 
  float aspect = (float)m_pFramework->GetWidth() / (float)m_pFramework->GetHeight(); 
  D3DXMatrixPerspectiveFovLH(&projection, D3DX_PI / 3, aspect, 1.0f, 1000.0f); 
  pDevice->SetTransform(D3DTS_PROJECTION, &projection); 
}

This is where we fill out the D3DVERTEXELEMENT9 array. Our vertices have a position (D3DDECLUSAGE_POSITION), which is comprised of 3 floats (D3DDECLTYPE_FLOAT3), and a color (D3DDECLTYPE_D3DCOLOR and D3DDECLUSAGE_COLOR). Notice at the end of the array there is a call to the macro D3DDECL_END, which tells DirectX that we’re done filling out the array. After we have defined the vertex element array, we create and set the vertex declaration with IDirect3DDevice9::CreateVertexDeclaration and IDirect3DDevice9::SetVertexDeclaration respectively.

You can see that I am also telling DirectX how the geometry should be displayed by setting some DirectX render states with IDirect3DDevice9::SetRenderState. You can see that I set the fill mode depending on whether wireframe is toggled (hit F2), a gouraud shade mode, which interpolates the vertex colors over the surface of the polygon, and I disable lighting.

The next step is to initialize all the transform matrices. These matrices are used in the calculations that determine where in 3d space the geometry is rendered (world transform), where the virtual camera is located (view transform), and how the geometry is projected into 2d space (projection transform). To initialize the world and view matrices, we’ll set a D3DXMATRIX to an identity matrix with D3DXMatrixIdentity and then we’ll tell DirectX to use these matrices with IDirect3DDevice9::SetTransform. Having identity matrices in the world and view transforms creates the effect of a camera sitting at the origin of the 3d world, looking straight down the positive Z-axis. To create the projection matrix, we need to calculate the aspect ratio. If you look at the coordinates of the triangle, you’ll notice that the triangle will fit perfectly in a 4×4 square. However, since monitors and windows may not be perfectly square, if we just copied the geometry straight to the screen, it would appear stretched or squashed because the image fills up the entire viewport and the entire viewport is not a perfect square. The aspect ratio fixes this problem by altering all the geometry prior to transforming the geometry into projection space. This makes all the geometry appear as we would expect it to be. Once we have the aspect ratio, we can create the actual matrix with D3DXMatrixPerspectiveFovLH. The field of view parameter is usually set to around 60 degrees (PI / 3).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Renders the current frame. 
Parameters: 
  [in] pDevice – Pointer to a DIRECT3DDEVICE9 instance 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
void CGameApp::OnRenderFrame(LPDIRECT3DDEVICE9 pDevice) {
  pDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); 
  pDevice->BeginScene(); 

  pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 1, g_triangle, sizeof(CUSTOMVERTEX)); 

  pDevice->EndScene(); 
  pDevice->Present(0, 0, 0, 0); 
}

To render the triangle, all we need to do is call IDirect3DDevice9::DrawPrimitiveUP. Notice that we are rendering using a triangle strip. Using triangle strips or triangle fans is often more efficient than using triangle lists because fewer vertices are duplicated. DirectX has a few other primitive types to choose from. The UP in DrawPrimitiveUP stands for “User Pointer,” which means that we are using vertex information straight from a specified pointer. You’ll normally never want to do this since using vertex buffers is a lot faster, however using a user pointer is an easy way to learn about vertex declarations and basic rendering.

If you look at the WM_SIZE event in the window procedure in CFramework, you’ll notice that we update the window size variables and reset the device. If we didn’t do this, then the geometry would be squashed and stretched when we resize the window as shown below.

Squished

Now we have a pretty triangle to stare at all day.

One thought on “Rendering Primitives”

Holosim January 29, 2015 at 9:44 am

Great tutorials on DX9, Chad. I particularly like the modular framework you build through each step.

I’m getting a notice when the app closes, and I’m hoping you might shed some light on it for me. When the program exits a dialog pops up saying the program “triggered a break point” in “crt0dat.c”. I’ve seen this before, but I’m not entirely sure what this means or how to avoid it. It let’s me click CONTINUE, and everything functions normally. I don’t think it’s something wrong with code. It might be something subtle, like changing dxerr9.lib to dxerr.lib in the properties, or switching from Unicode to Multi-byte character sets. I’ve learned as much from overcoming these kinds of obstacles as much as anywhere else.

I’m getting the dialog in both Debug and Release mode (using Microsoft Visual Studio 2010 & DirectX SDK release June2010), and naturally everything works without complaint if I simply run the EXE file directly. I’m just hoping to learn how to mitigate that annoying dialog box, and maybe learn something about the C run-time init/term routines. The breakpoint occurs at Line 393 of crt0dat.c, which is the void __cdecl exit function.

Have a great day, and thanks again for the tutorials!

Comments are closed.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.