CU_Rendering.zip (25.2 KiB, 2,744 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.
{
DWORD color; // Color
};
CUSTOMVERTEX g_triangle[] =
{
{ 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 )
{
{
{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->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.
Now we have a pretty triangle to stare at all day.

