Purpose of this tutorial is to introduce a seldom metioned technique:
Even so I find this technique useful for example for situations where you normally display only one mesh on the screen, like for example in a game part where you describe mechanisms or game parts like technologies.
To understand this tutorial you should have managed to compile the Direct3D-EmptyProject from the Microsoft DirectX SDK, as this is the base of my tutorial.
For the general algorithm to render a mesh to a surface you need to solve the following steps:
For generating your D3DDevice we use the Direct3D-EmptyProject from the Microsoft DirectX SDK. There you will find all you need to go to the next step of this tutorial.
INT WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
{
// Set the callback functions
DXUTSetCallbackDeviceCreated( OnCreateDevice );
DXUTSetCallbackDeviceReset( OnResetDevice );
DXUTSetCallbackDeviceLost( OnLostDevice );
DXUTSetCallbackDeviceDestroyed( OnDestroyDevice );
DXUTSetCallbackMsgProc( MsgProc );
DXUTSetCallbackFrameRender( OnFrameRender );
DXUTSetCallbackFrameMove( OnFrameMove );
// Initialize DXUT and create the desired Win32 window and Direct3D device for the application
DXUTInit( true, true, true ); // Parse the command line, handle the default hotkeys, and show msgboxes
DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
DXUTCreateWindow( L"RenderToSurface" );
DXUTCreateDevice( D3DADAPTER_DEFAULT, true, 800, 600, IsDeviceAcceptable, ModifyDeviceSettings );
// Start the render loop
DXUTMainLoop();
return DXUTGetExitCode();
}
|
To display a mesh we need first to load it fom a file. In good style I have created a small class which loads and renders a mesh:
For loading we use the convenience routine D3DXLoadMeshFromX which takes a mesh saved in the x-file format.
The first paramter is the filename expected as widechar. For this we convert our sting using the ACL Makros:
As second parameter we define where to where to store the mesh.
Finally we provide some variables for used materials of the mesh and a pointer to the mesh itself.
After loading the mesh we still need to load all textures for materials used by the mesh. For this we iterate through the materials and load each by using D3DXCreateTextureFromFile.
void D3DMesh::loadFromFile( IDirect3DDevice9* d3dDevice )
{
LPD3DXBUFFER d3dxMtrlBuffer;
// Load the mesh from the specified file
USES_CONVERSION;
if( FAILED( D3DXLoadMeshFromX( A2W( fileName_.c_str() ), D3DXMESH_SYSTEMMEM,
d3dDevice, NULL,
&d3dxMtrlBuffer, NULL, &numMaterials_,
&mesh_ ) )
)
{
// on failure we are down in this tutorial, normally you would show an error message
// or write a message to a log-file
return;
}
// We need to extract the material properties and texture names from the
// pD3DXMtrlBuffer
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)d3dxMtrlBuffer->GetBufferPointer();
meshMaterials_ = new D3DMATERIAL9[ numMaterials_ ];
if( meshMaterials_ == NULL )
{
// on failure we are down in this tutorial, normally you would show an error message
// or write a message to a log-file
return;
}
meshTextures_ = new LPDIRECT3DTEXTURE9[ numMaterials_ ];
if( meshTextures_ == NULL )
{
// on failure we are down in this tutorial, normally you would show an error message
// or write a message to a log-file
return;
}
for( DWORD i=0; i < numMaterials_; i++ )
{
// Copy the material
meshMaterials_[i] = d3dxMaterials[i].MatD3D;
// Set the ambient color for the material (D3DX does not do this)
meshMaterials_[i].Ambient = meshMaterials_[i].Diffuse;
meshTextures_[i] = NULL;
if( d3dxMaterials[i].pTextureFilename != NULL &&
strlen( d3dxMaterials[i].pTextureFilename ) > 0 )
{
// Create the texture
USES_CONVERSION;
if( FAILED( D3DXCreateTextureFromFile( d3dDevice,
A2W( d3dxMaterials[i].pTextureFilename ),
&meshTextures_[i] ) ) )
{
return;
}
}
}
// Done with the material buffer
d3dxMtrlBuffer->Release();
}
|
We iterate again through the materials and set for each material itself the corresponding texture and then draw the assigned subset of the mesh.
void
D3DMesh::draw( IDirect3DDevice9* d3dDevice )
{
for( DWORD i=0; i
|
To render a mesh to a surface, those surface nees to be created as a RenderTarget.
Our first step will be to get the current RenderTarget with GetRenderTarget for two purposes. First to restore it after we are done with rendering our mesh and second to take some of its values to create our RenderTarger correctly. Those values we acquire by using the Method GetDesc of IDirect3DSurface9.
Now we can use CreateRenderTarget with its size, color format and the parameters taken from the original RenderTarget.
IDirect3DSurface9* surface_ = NULL;
IDirect3DSurface9* origTarget_ = NULL;
// store orginal rendertarget
pd3dDevice->GetRenderTarget( 0, &origTarget_ );
D3DSURFACE_DESC desc;
origTarget_->GetDesc( &desc );
// create our surface as render target
pd3dDevice->CreateRenderTarget( 320, 240, D3DFMT_X8R8G8B8,
desc.MultiSampleType, desc.MultiSampleQuality,
false, &surface_, NULL );
|
Next we set our surface as the RenderTarget. Then we first clear it. After that we start to set a rotation matrix, viewing vectors, perspective projection and finally an ambient light source.
// now all rendering will be directed to our surface
pd3dDevice->SetRenderTarget( 0, surface_ );
// clear surface
pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,30), 1, 0 );
// Prepare Transformations so that our mesh rotates slowly
D3DXMATRIXA16 trans;
D3DXMatrixRotationY( &trans, timeGetTime()/5000.0f );
D3DXMATRIXA16 trans2;
D3DXMatrixRotationZ( &trans2, timeGetTime()/5000.0f );
trans *= trans2;
// our whole world is rotating around the mesh
pd3dDevice->SetTransform( D3DTS_WORLD, &trans );
// prepare view vectors
D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
// set view vectors
pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
// prepare projection matrix
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH( &matProj,
D3DX_PI/4, 1.0f, 1.0f, 100.0f );
pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
// finally we need some light
pd3dDevice->SetRenderState( D3DRS_AMBIENT, D3DXCOLOR( 1.0, 1.0, 1.0, 1.0 ) );
|
By this we have prepared the world on our Rendertarget so that we can now draw our mesh.
The mesh is here for simplicity realised by a static variable.
// load mesh, here for convenience into a static variable
static D3DMesh mesh = D3DMesh( "tiger.x" );
static bool loaded = false;
if (!loaded )
{
mesh.loadFromFile( pd3dDevice );
loaded = true;
}
mesh.draw( pd3dDevice );
|
Note: That throguh the set rotation matrix our mesh will slowly rotate around it's X and Z axis.
After all this we restore the origibal RenderTarget, so that we can draw our surface onto it.
//restore original render target pd3dDevice->SetRenderTarget( 0, origTarget_ ); |
Now we get the Backbuffer of the current RenderTarget. Create a rectangle as target for our surface and draw then our surface to the Backbuffer.
// target rect for our surface
RECT DstRect;
DstRect.left = 40;
DstRect.top = 30;
DstRect.right = 360;
DstRect.bottom = 270;
IDirect3DSurface9* backBuffer = NULL;
// draw our our surface to screen
if( !FAILED( pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer ) ) )
{
pd3dDevice->StretchRect( surface_, NULL, backBuffer, &DstRect, D3DTEXF_NONE );
}
|
On EndScene the Backbuffer will then be drawn onto the screen and we are done.
V( pd3dDevice->EndScene() ); |
So intead of a creating a texture you just creat a surface as RenderTarget.
Instead of drawing a dummy object for the texture you draw the suface directly to the Backbuffer.
I think this is worth it to keep your code more simple.