Rendering 3D Scenes to Large Image Formats

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Related Articles of Interest:

Rendering Full Screen Images from Textures
Rendering to Multiple Windows
Saving a Screen Shot in Direct3D

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:

  1. 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.
     

  2. 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;
}

Related Articles of Interest:

Rendering Full Screen Images from Textures
Rendering to Multiple Windows
Saving a Screen Shot in Direct3D

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.