<!-- Download Links -->
<!-- Article image -->
<!-- Main HTML starts here -->
This project was written using a same method as is recommended when using the Microsoft
DirectX Framework, although there is no library to build in DirectX 8 as there was in
DirectX 7, so the files have to be included in the project. These files are now located in the folder
mssdk\samples\multimedia\common which includes both a src and a include directory. The best way to use these is to add the include file to your DevStudio path through the
tools|options directories tab and the include the source files directly in your project.
One important note here is that
DirectX 8 is much stricter about what it will let you do due to its greater hardware dependency. In
DirectX 7 I was always able to run a debug app in full screen and then add break points with no problems at all. I could even run the code on a laptop with no
3D card present. This is possible with DirectX 8 but this project runs in a window at five frames a second so it would be hell to try and develop anything serious on it. This means that I'm going to have to recommend that all development takes place in a windowed environment and on a computer with a proper
3D card :(
This project is meant as an introduction to DirectX 8 and the classes in the project are intended to be reused with the minimum of fuss, while the demo code is clearly identified so as to be easily removable.
The code starts by inheriting from the framework library main class and simply adds a few get and set functions
class CNew3dApp : public CD3DApplication
The aim of this class is to override certain function in the base class and provide new implementations of some of the virtual functions. It also provides a few get and set functions to make life a bit easier.
The functions that are overridden are,
virtual HRESULT OneTimeSceneInit();
virtual HRESULT InitDeviceObjects();
virtual HRESULT DeleteDeviceObjects();
virtual HRESULT Render();
virtual HRESULT FrameMove( FLOAT fMove );
virtual HRESULT RestoreSurfaces();
virtual HRESULT FinalCleanup();
These are all pretty obvious when you've been staring at DirectX for a while but I'll just give a brief description of each here.
The virtual function
Is the first function to be called that the code accesses. This is to allow the code to set up the objects that will be required in the program. This function is used by the program to switch on, none major options in this case turning on the Stats function which will place a screen size read out and the frame rate int the top left hand corner of the screen.
The virtual function
is where the main objects for the code are set up. This code is reentrant so if the code is only required to do something once then it is necessary to make sure yourself that it is only done once. I know it seems a strange thing to say when the last function was called
but that function should not be used to change and major aspects such as the screen which is what I am doing here
if( !bInitialScreen )
bInitialScreen = true;
if( FAILED( SetScreenSize( 800, 600 ) ) )
See setting the screen size below fro a description of what this function does.
If you open the 3dApp.h file you'll notice a private member in the class
This is used in this project to show the stats that appear in the top left hand corner of the screen and if we trace it through the code we can see not only how it works but get a quick reprisal of the way that objects should be treated in the code.
In the constructor the font is initialized
m_pFont = new CD3DFont( _T( "Arial" ), 12, D3DFONT_BOLD );
In the InitDeviceObjects function where we set up the 3D objects for our scene the font is initialized and associated with the current D3DDevice
m_pFont->InitDeviceObjects( m_pd3dDevice );
function the font calls its own
function. This is done when the device needs to be deleted when the project is closing down.
The next time we see it is in the render function when it draws the application frame and device stats. It then calls its own version of
in the applications invalidate objects function, which is called when the current devices are changed for instance switching between windows and full screen or between the windowed application and the debugger. Again in
when the application once again has the main focus the font object calls its own version of the
function. Finally in the
function the font object is deleted.
While, hopefully, not being the most exciting class you'll ever come across it does provide a useful template for how to design classes to work within the framework provided by
DirectX. The CD3DFont class itself has three functions that are of interest apart from its use of the
DirectX Framework format. These are,
HRESULT DrawText( FLOAT x, FLOAT y, DWORD dwColor, TCHAR* strText, DWORD dwFlags=0L );
HRESULT DrawTextScaled( FLOAT x, FLOAT y, FLOAT z, FLOAT fXScale, FLOAT fYScale, DWORD dwColor, TCHAR* strText, DWORD dwFlags=0L );
HRESULT Render3DText( TCHAR* strText, DWORD dwFlags=0L );
In the current code we only use the
function but it might be interesting to look at what the others do in more detail at a later date.
Setting The Screen Size
function has had to be completely rewritten in the change to
DirectX 8. This is due to the introduction of the notion of adapters and the changes to the way the bit depth works.
HRESULT SetScreenSize(int nHoriz, int nVert,
BOOL bWindowed = FALSE, D3DFORMAT d3dFormat = D3DFMT_X8R8G8B8 );
The final parameter has been changed to a
parameter which is a value to represent the different types of ( in the case of the render target ) pixel format. We will only be concerned with two values here the
format which is a 32 bit RGB ( Red, Green, Blue )format where eight bits are reserved for each colour and the
format which is a 16 bit RGB format. The code is setup to default to use the 32 bit value.
The rest of the
HRESULT hResult = S_OK;
int nWidth, nHeight;
dwDevice = m_Adapters[m_dwAdapter].dwCurrentDevice;
for( int i=0; i < ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwNumModes; i++ )
if( m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Format == d3dFormat )
nWidth = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Width;
nHeight = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Height;
if( nWidth == nHoriz && nHeight == nVert )
m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwCurrentMode = i;
m_Adapters[ m_dwAdapter ].devices[ dwDevice ].bWindowed = bWindowed;
m_bWindowed = bWindowed;
if( m_pd3dDevice->Release() > 0L )
return DisplayErrorMsg( D3DAPPERR_NONZEROREFCOUNT, MSGERR_APPMUSTEXIT );
hResult = Initialize3DEnvironment();
if( FAILED( hResult ) )
return DisplayErrorMsg( hResult, MSGERR_APPMUSTEXIT );
The function changes the screen size to the requested size and sets it full screen or not depending on the value passed in
bWindowed, which must be
if running in debug mode. The main difference is the use of the notion of Adapters which to you and me means video card, though it is possible for a video card to have more than one adapter if it has built in multi monitor support. The devices that each adapter supports are the video modes eg 640/480 in 16 bit, 800/600 in 32 bit, etc., that the card can run.
Loading And Rotation
The object displayed on screen is an .x file that is basically a list of vectors for the
DirectX code to draw. This file does also contain texture information but for now just loading it and getting it to the screen correctly should be good enough. The object will be loaded into a D3DMesh class which is located in the header file d3dfile.h. This class will contain all the information needed for manipulating the x file that the code will load.
The mesh is loaded with the code,
HRESULT hResult = m_pObjectMesh->Create( m_pd3dDevice, _T( "dolphin.X" ) );
if( FAILED( hResult ) )
This code loads the dolphin.x file although there is nothing to prevent the name of any other file being added here it should be noted that at the moment the file has to be in the current directory and that due to the different sizes of the meshes it is possible that the object loaded will at first be too small. Big, or not visible at all which will mean that some changes to the view will have to be made which will be discussed in a while.
If you look through the code in the 3dapp.cpp file you can see that the control flow of the CD3DMesh is exactly the same as that for the font object in that in each of the overloaded functions a function is called by the
m_pObjectMesh object that will keep the object synchronised with the rest of the application.
There are three things that need to be covered now. At the moment we have a computer screen and an object that we want to draw on it in 3d but how does the computer know where to draw the object. Simple we tell it.
D3DXMatrixRotationY( &matWorld, timeGetTime()/550.0f );
m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp );
m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, -1.0f );
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
In the Render function we define three objects of type
D3DMATRIX, one for the world one for the view and one for the projection. We then set these matrices the way we want them by calling the
DirectX 8 helper functions and finally call
on the m_pd3dDevice ( which is the drawing device ).
The world matrix ( matWorld ) specifies the way the world will look, here the code tells it to set the matrix to rotate on the y access and because this is the Render function that gets called a number of times a second the code will tell it to rotate a bit more every single time through. Then when the
function is called any objects in the world will rotate on the y axis. Raising or lowering the value the divides the return of
( the number of seconds since windows started ) will slow down or speed up the rotation of the object respectively.
The view matrix ( matView ) specifies the way that the screen is looking in on the world. This calls the function
which sets the way the world is looked at in a left handed world. Meaning that the x axis of the screen will be 0 at the leftmost and its highest value at its rightmost point.
is then called with the
constant to tell
DirectX that this is the viewing style that is wanted.
The projection matrix ( matProj ) deals with the way that objects are perceived and scaled with the 3d world created. This is why things shrink when they are moved further away.
Changing The View
When the code for this project is run the object first of all comes from the left hand of the screen and then moves backwards and forwards a bit while sliding slightly to left as it moves forward and slightly to the right as it moves backwards. Well it Doesn't. The object remains perfectly stationary throughout the entire program. The only code that deals with moving the object in this example is the World matrix code that rotates it a little bit every time the render function is called. In fact when the code starts the object is behind you. As you move back and to the left the object comes into view and centers itself in the middle of your screen and then when it appears to move towards you and to your left it is because you are moving forwards and to the right. This is all done in the FrameMove function with the code,
static bool bForward = true;
if( bForward )
if( m_vecEye.z > 1000.0f )
bForward = false;
m_vecEye.x += 3.0f;
m_vecEye.z += 10.0f;
if( m_vecEye.z < 400.0f )
bForward = true;
m_vecEye.x -= 3.0f;
m_vecEye.z -= 10.0f;
The changes are made in the m_vecEye
( Note when I say vector I mean a
which inherits from a Vector and adds some functionality ) which is a class member that stores the eye position of the view when
matView matrix is created
D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp );
A vector contains the x, y and z coordinates for that position. With x being the position from the left of the center of the World position with a negative x moving to the left. y being the position from the base of the World position with a positive y value moving towards the top of the screen and a negative y value moving down towards the bottom of the screen and z being the depth. The vectors that are passed to the function above are the eye position which is the screen position within the World, which is currently the center of the screen. The at vector is the position which is the way that the screen is facing and the up position which tells the matrix where up is.
The whole effect is controlled by the value contained in m_vecEye.z value which when it goes above 1000 the view stops and starts to move forwards again until it reaches a value less than 400 where it stops and starts to go backwards again. These values should be experimented with to get the same effects when using different models.
Building The Sample
There should be no problems for any one building the sample as the cpp files are included in the zip file. This has been done due to me adding the line
In all of the cpp files. This is because I prefer to include MFC and I probably will use some of it some point. It's not done for technical reasons but because this is how I work. You can remove them and use the standard cpp files if you prefer. The only possible problem you should have is the need to include the path to the common header files in your tools\options\directories section of developer studio. These can be found under
The demo code will start at
and end with
By removing the code between these lines you will be left with a template to start developing you own code.