Written by Robert
Dunlop
Microsoft DirectX MVP |
|
Rendering of scenes in Direct3D is limited by the size of the frame
buffer, but in a production environment it is often necessary to render
the scene to larger formats. A common example is providing artwork
for printed advertising - a normal screen shot blown up to poster size is
not a pretty thing. In this article we will take a look at a
technique for rendering very large images in Direct3D, with a sample
function designed to call an existing scene rendering function, for easy
integration with existing code. |
|
Rather than trying to extend the limits of the frame buffer,
which is limited by supported resolutions and the amount of available video
memory, we can instead tackle this task by breaking it down into smaller pieces.
By subdividing the screen to be rendered into a grid, and then rendering each
region separately, we can generate a series of images that can be tiled onto a
larger image surface to form a single, seamless image.
To do this, we have to modify the projection matrix prior to
rendering each tile, so that the desired portion of the grid will be expanded
and and aligned with the viewport rectangle. This requires the region to
be scaled and offset such that X and Y values after projection will fall into a
range of -1.0 to 1.0. For example, if we wanted to break the image into 2
x 2 tiles, resulting in an image twice the width and height of the original, we
would have to render the following ranges:
-1.0 < X < 0.0
0.0 < Y < 1.0 |
|
|
0.0 < X < 1.0
0.0 < Y < 1.0 |
-1.0 < X < 0.0
-1.0 < Y < 0.0 |
|
|
0.0 < X <1.0
-1.0 < Y < 0.0 |
The coordinates shown above for each quadrant represent the
range of each section if they were transformed and projected as a complete
scene. To allow the individual tiles to be rendered to the entire
viewport, two adjustments must be made to the projection matrix:
-
Scaling must be applied to the X and Y coordinates, effectively
zooming in on the scene. Multiplying the _11 and _22 members of the
projection matrix by the number of subdivisions will scale the image such that
each tile is the size of the viewport.
-
Prior to rendering each tile, an appropriate offset must be
applied to the X and Y axis to align the tile with the viewport rectangle.
Note that this offset is applied in the third row of the projection matrix so
that the offset is multiplied by depth, resulting in the offset being preserved
after projection.
Note: These changes
to the projection matrix will have no effect on the rendering of pre-transformed
vertices. Such will have to be dealt with as a special case, which is
beyond the scope of this article.
Before we begin
rendering the tiles, an image surface is created that will contain the final
image. The system memory pool is used, so that image size is not limited
by available video memory.
After each tile is rendered, the image on the back buffer is copied to the
corresponding image on the image buffer. The tiles combine to form a
single continuous image, which is then saved as a bitmap file using the
D3DXSaveSurfaceToFile() introduced in DirectX 8.1.
|
Below is a function that performs a tile based rendering using a rendering
function provided by the application. Provided that your application
already has a rendering function that is separate from any movement or other
changes to the scene, this should be fairly easy to integrate.
HRESULT RenderTiled(LPCSTR fileName,
// name out output file
LPDIRECT3DDEVICE8
pDev, // D3D device
int numTiles,
// divisions per axis
HRESULT renderFunc()) // pointer
to render function
{
HRESULT hr;
// get the backbuffer description
LPDIRECT3DSURFACE8 backbuf;
if(FAILED(hr=pDev->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&backbuf)))
return hr;
D3DSURFACE_DESC desc;
hr=backbuf->GetDesc(&desc);
backbuf->Release();
if(FAILED(hr))
return hr;
// calculate final image size
int imageWidth=desc.Width*numTiles;
int imageHeight=desc.Height*numTiles;
// create the image buffer
LPDIRECT3DSURFACE8 surf;
if (FAILED(hr=pDev->CreateImageSurface(imageWidth,
imageHeight,
desc.Format,
&surf)))
return hr;
// get the current projection matrix and save a copy
D3DXMATRIX oldProj,newProj;
pDev->GetTransform(D3DTS_PROJECTION,&oldProj);
newProj=oldProj;
// scale the projection matrix on x and y axis
newProj._11*=numTiles;
newProj._22*=numTiles;
// loop through the tiles in X
for (int i=0;i<numTiles;i++) {
// offset x coordinates
newProj._31=(numTiles-1)-i*2.0f;
// loop through the tiles in Y
for (int j=0;j<numTiles;j++) {
// offset y coordinates
newProj._32=-((numTiles-1)-j*2.0f);
// set the modified projection matrix
pDev->SetTransform(D3DTS_PROJECTION,&newProj);
// call the rendering function
if (FAILED(hr=renderFunc())) {
surf->Release();
return hr;
}
// get the back buffer pointer
if(FAILED(hr=pDev->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&backbuf))) {
surf->Release();
return hr;
}
// copy the tile to the image buffer and release the back buffer
RECT destRect;
destRect.left=desc.Width*i;
destRect.right=destRect.left+desc.Width;
destRect.top=desc.Height*j;
destRect.bottom=destRect.top+desc.Height;
hr=D3DXLoadSurfaceFromSurface(surf,NULL,&destRect,
backbuf,NULL,NULL,D3DX_FILTER_NONE,0);
backbuf->Release();
if (FAILED(hr)) {
surf->Release();
return hr;
}
// show current tile
pDev->Present(0,0,0,0);
}
}
// restore projection matrix
pDev->SetTransform(D3DTS_PROJECTION,&oldProj);
// save the image to specified file
hr=D3DXSaveSurfaceToFile(fileName,D3DXIFF_BMP,surf,NULL,NULL);
surf->Release();
// return status of save to caller
return hr;
}
|