Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DirectX 8 Template 2

0.00/5 (No votes)
18 Oct 2001 1  
An extension to the DirectX framework for adding backgrounds

Sample Image - dx8template.jpg

Introduction

In this article we will look at adding a class to the Directx Framework classes that will allow us to add any number of backgrounds that we want to the scene that we have created. This means that we will be looking at how to apply textures to the objects and how the texture is positioned so that it is displayed properly. I'll also be briefly describing the Flexible vertex format that is used in Directx Eight.

Adding a Class.

The reasons for adding a new class to the already provided framework are for reasons of flexibility and readability. As you can imagine from looking at the code if all the game code was run from the 3dApp.cpp file we would end up with a huge, mostly unreadable file, in which it would be difficult to keep track of all the things that are going on. For this reason we will encapsulate some of our functionality in a separate class and then move the code out so that instead of having something like

	HRESULT hResult;

	...  ///  40 lines deleted 

	hResult = pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
	if( FAILED( hResult ) )
	{
		SetError( _T( "DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 )" ) );
		SetLastResult( hResult );
		return hResult;
	}

We get

		m_bgBackGround.Render( m_pd3dDevice );

The class makeup is very simple and is written to mimic the way that the Directx Framework classes work although it should be noted that the classes have no direct relationship to the Framework classes themselves. As the class is mimicking the Framework classes it should have the familiar functions.

virtual HRESULT OneTimeSceneInit();

/// called next to initialize app objects 

virtual HRESULT InitDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, CString strTexture );
virtual HRESULT DeleteDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice );

/// render the scene

virtual HRESULT Render( LPDIRECT3DDEVICE8 pd3dDevice );

/// move the frame 

virtual HRESULT FrameMove( LPDIRECT3DDEVICE8 pd3dDevice ); 

/// called when device dependent objects are about to be lost 

virtual HRESULT InvalidateDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice ); 

/// restore the surfaces 

virtual HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, D3DXVECTOR3 vec1, D3DXVECTOR3 vec2, D3DXVECTOR3 vec3, D3DXVECTOR3 vec4 );
virtual HRESULT FinalCleanup();

Notice that the functions pass an LPDIRECT3DDEVICE8 as a parameter in the function call. This is so that the called code remains in step with the calling code and that both versions are using the same device to call their drawing functions on. The two functions that don't have the LPDIRECT3DDEVICE8 passed to them when called are InitDeviceObjects and FinalCleanup. This is because the InitDeviceObjects function is called before DirectX is fully initialized and should be used for loading outside data and the FinalCleanup function is used to release items loaded in InitDeviceObjects.

Using the Class

The class object is declared as a member variable in the 3DApp class

	CBackGround m_bgBackground;

This is then used like any normal part of the DirectX Framework classes. If you look through the 3DApp.cpp file you can see that it is used to call its own version of the framework functions throughout the code. The main function of interest in the CBackground class is the RestoreDeviceObjects function which is called whenever the surfaces that the D3DDevice is drawing on are lost. This means that this code isn't run every frame but only when it needs to be run. It is declared as

virtual HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, 
                                      D3DXVECTOR3 vec1, 
                                      D3DXVECTOR3 vec2, 
                                      D3DXVECTOR3 vec3, 
                                      D3DXVECTOR3 vec4 );

And it is called as

                                    /// top left x,y,z 

m_bgBackGround.RestoreDeviceObjects( m_pd3dDevice, 
                                     D3DXVECTOR3( 0.0f, /// start x position 

                                                  0.0f, /// start y position

                                                  0.5f ),/// start z position

                                    /// top right x,y,z

                                    D3DXVECTOR3( d3dViewPort.Width, /// top x position or width 

                                                   0.0f, /// still at y 0  

                                                  0.5f ), 
                                    /// bottom right x,y,z

                                    D3DXVECTOR3( d3dViewPort.Width, /// x position at the bottom   

                                                  d3dViewPort.Height, /// y position at the bottom 

                                                  0.5f ),
                                    /// bottom left x,y,z 

                                    D3DXVECTOR3( 0.0f, /// x at 0  

                                                  d3dViewPort.Height, /// y at bottom 

                                                  0.5f ) );

The m_pd3dDevice member is passed to the function call along with four vectors that plot the screen co-ordinates in a clockwise direction. The first vector is passed at an x position of 0 and a y position of 0 which is the top left hand part of the screen. Then the top right position is sent as a the view port width for the x position and 0 again for the y position. And so on round the screen. The z position is left constant at 0.5. Which means that all the corners are at the same depth but there is nothing to stop the third vector, representing the bottom right of the screen being set to -100.0f. In fact if the image is wanted to appear just on the left of the screen just add /2 where ever the code reads d3dViewPort.width. The CBackGround::RestoreDeviceObjects function is implemented as

	BACKGROUNDVERTEX cvVertices[] =
	{
	        {  vec1.x, vec1.y, vec1.z, 1.0f, 0.0f, 0.0f, }, 
	        {  vec2.x, vec2.y, vec2.z, 1.0f, 1.0f, 0.0f, },
	        {  vec3.x, vec3.y, vec3.z, 1.0f, 1.0f, 1.0f, },
	        {  vec4.x, vec4.y, vec4.z, 1.0f, 0.0f, 1.0f  },
	};

	m_dwSize = sizeof(cvVertices);

	    /// Create the vertex buffer

	if( FAILED( pd3dDevice->CreateVertexBuffer( m_dwSize, 0, D3DFVF_BACKGROUNDVERTEX,
                                                       D3DPOOL_MANAGED, &m_pvbVertexBuffer ) ) )
	        return E_FAIL;

       	VOID* pVertices;
	if( FAILED( m_pvbVertexBuffer->Lock( 0, m_dwSize, (BYTE**)&pVertices, 0 ) ) )		        return E_FAIL;
	memcpy( pVertices, cvVertices, m_dwSize );
	m_pvbVertexBuffer->Unlock();

The BACKGROUNDVERTEX type is an example of the flexible vertex buffer which will be discussed later for now we can see that each section of the array holds six floats with the first three floats being the values that were passed during the function call.

The fourth float in each section of the array is the rhw value this tells the DirectX pipeline that the transformation and lighting to this vertex is being handled by the user and that it doesn't need to do anything to it.

The final two parameters are the texture coordinates.

Adding A Texture

There is so much ground that can be covered on textures that I'm just going to stick to talking about what is going on in the code and not what it is possible to do.

The texture for the background is loaded in the CBackGround::InitDeviceObjects function which is called from the CNew3dApp::InitDeviceObjects function passing the LPDIRECT3DDEVICE8 pointer and the name of the texture file to load.

if( FAILED( D3DUtil_CreateTexture( pd3dDevice, strTexture.GetBuffer( 0 ), 
                                   &m_ptBackGroundTexture ) ) )
       	return D3DAPPERR_MEDIANOTFOUND;

The code uses the D3DUtil_CreateTexture function to load the passed in file name and store it in the LPDIRECT3DTEXTURE8 m_ptBackGroundTexture. Which is next mentioned in the CBackGround::Render function when it passed to the SetTexture function.

	hResult = pd3dDevice->SetTexture( 0, m_ptBackGroundTexture );

This says that the first texture stage will use the texture we loaded earlier. Most devices now support eight textures at once, which are then individually set using the SetTextureStageState.

	hResult = pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

This function is a staple part of texturing with DirectX, so learn to love it now. The function sets the drawing requirements for each texture, with a maximum of eight textures possible per surface requiring the first parameter to go through 0-7 for each textured object, you'll be seeing a lot of this function. In the first argument above we indicate which texture we want the arguments to apply to. As we are only using one texture this should be zero. The D3DTSS_COLORARG1 argument specifies that this is the first colour argument for the texture and the D3DTA_TEXTURE specifies that the code should use the colors in the texture. The second call to SetTextureStageState again uses a stage of zero and uses the argument D3DTSS_COLOROP which tells DirectX to perform certain colour operations on this texture stage. The D3DTOP_SELECTARG1 tells DirectX that it should use the texture without modifying it in any way. Basically the two calls to SetTextureStageState say just draw the texture.

If you look at the texture formatting part of the code

       0.0f, 0.0f  /// top left 

       1.0f, 0.0f  /// top right

       1.0f, 1.0f  /// bottom right

       0.0f, 1.0f  /// bottom left

You can see here that the format follows the same clockwise format as the drawing of the vertices above. This is of course for drawing to a flat surface that is straight on to the screen drawing things differently can be more complicated. An easy example of texturing an object is to change the 1.0 values to 5.0 This will draw 5 copies of the image in both the horizontal and the vertical direction.

If, however, you want to draw two different images on the screen you would need to use two versions of the CBackground class with each one loading a different image file and then draw them to the screen by altering the x positions of the vectors. The x positions of the vectors for the left side would be

	0.0f /// top left, 

	d3dViewPort.Width/2 /// top right, 

	d3dViewPort.Width/2 /// bottom right, 

	0.0f /// bottom left 

And the right side would be

	d3dViewPort.Width/2 /// top left, 

	d3dViewPort.Width /// top right, 

	d3dViewPort.Width /// bottom right, 

	d3dViewPort.Width/2 /// bottom left 

Flexible Vertex Format

When the Flexible Vertex Format is first mentioned it sounds and looks scary, mostly because it usually takes a couple of attempts to find any information about what it is so I'll attempt to explain it here,

Take a structure

struct FVF
{
	float x;   
	float y;	   
	float z;    
	float rhw; 
	float tu;
	float tv;
};

This is the data that we used in the BACKGROUNDVERTEX array above, 6 floats, three to specify the location of the object and one to tell the DirectX pipeline that we want the vertices drawn as they are and to specify the float co-ordinates. Then say that we want the ability to change our minds. Now we want to add a specular colour and another texture. Now our structure looks like

	struct FVF
	{
		float x;   
		float y;	   
		float z;    
		float rhw;
		DWORD dwSpec; 
		float tu;
		float tv;
		float tu2;
		float tv2;
	};

Now we have a problem in that if we want to rewrite our app so that it uses both the structure types at some point its going to crash unless we distinguish between the two types. This would lead to us defining a type to pass to the DirectX pipeline.

#define STRUCT_TYPE_1
#define STRUCT_TYPE_2

We could just use the two structure types and everything would be fine until we decided we wanted to use the specular colour but not the second texture type. This means that we have to define another two different structures and another two different structure types, then we remember that DirectX allows up to eight textures at any one time, which would mean defining another six structures and structure types. There has to be an easier way to tell the DirectX pipeline what we want it to draw and the way Microsoft have done this is to specify a set data structure. I term it in that way because I don't think it is defined as a structure anywhere but as a set of rules for the way in which data is passed to the DirectX pipeline. This is what it would look like if it was defined as a structure ( Taken from the diagram in "About Vertex Formats" in the DirectX Eight help file )

struct VERTEXDEMOSTRUCT
{
	/// position

	float x;
	float y;
	float z;
	/// rhw 

	float rhw;
	/// Blending Weight data

	float b1;
	float b2;
	float b3;
	/// Vertex Normal 

	float normalx;
	float normaly;
	float normalz;
	/// Vertex Point Size

	float vpSize;
	/// Diffuse Colour

	DWORD dwDiffuse;
	/// Specular colour

	DWORD dwSpecular
	/// Text Cordinates 1-8 each containing 4 floats

};

Now when you pass you Vertex format to the DirectX pipeline you are not going to use all the available parameters so Microsoft use defines to tell the DirectX pipeline which sections of the data you are using and these sections of data must be defined in the same order as above, as they are in the BACKGROUNDVERTEX structure, which is defined in the Background.cpp file as

	/// Our custom FVF, which describes our custom vertex structure

	#define D3DFVF_BACKGROUNDVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1)

This define is then passed to

pd3dDevice->CreateVertexBuffer( m_dwSize, 0, D3DFVF_BACKGROUNDVERTEX, 
                                D3DPOOL_MANAGED, &m_pvbVertexBuffer )

Which creates vertex buffer the size of the defined BACKGROUNDVERTEX before copying the defined array to the to the allocated vertex buffer. This gives a block of memory, containing the information that we want to draw to the screen, which we will do in the CBackground::Render function.

Firstly we call the

hResult = pd3dDevice->SetStreamSource( 0, m_pvbVertexBuffer, sizeof( BACKGROUNDVERTEX ) );

which tells the DirectX pipeline the block of memory that we want to draw and the size of the block. We then call

	hResult = pd3dDevice->SetVertexShader( D3DFVF_BACKGROUNDVERTEX );

which tells the DirectX pipeline the definition of the stream or memory block of data. Once the DirectX pipeline has all the information it needs we can tell DirectX to draw the stream to the screen.

The example project was developed mostly in Devstudio 7 but includes project files for Devstudio 6. Because Devstudio 7 isn't out of beta yet and not everyone is going to switch immediately, all projects from now on will be released in both formats where ever possible.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here