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;
...
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();
virtual HRESULT InitDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, CString strTexture );
virtual HRESULT DeleteDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice );
virtual HRESULT Render( LPDIRECT3DDEVICE8 pd3dDevice );
virtual HRESULT FrameMove( LPDIRECT3DDEVICE8 pd3dDevice );
virtual HRESULT InvalidateDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice );
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
m_bgBackGround.RestoreDeviceObjects( m_pd3dDevice,
D3DXVECTOR3( 0.0f,
0.0f,
0.5f ),
D3DXVECTOR3( d3dViewPort.Width,
0.0f,
0.5f ),
D3DXVECTOR3( d3dViewPort.Width,
d3dViewPort.Height,
0.5f ),
D3DXVECTOR3( 0.0f,
d3dViewPort.Height,
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);
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
1.0f, 0.0f
1.0f, 1.0f
0.0f, 1.0f
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
d3dViewPort.Width/2
d3dViewPort.Width/2
0.0f
And the right side would be
d3dViewPort.Width/2
d3dViewPort.Width
d3dViewPort.Width
d3dViewPort.Width/2
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
{
float x;
float y;
float z;
float rhw;
float b1;
float b2;
float b3;
float normalx;
float normaly;
float normalz;
float vpSize;
DWORD dwDiffuse;
DWORD dwSpecular
};
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
#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.