DirectX SDK

Rendering to Cubic Environment Maps

If you create a cubic environment map with the DDSCAPS_3DDEVICE capability, you can render to the individual faces of the cube map just like you would any other render-target surface. The most important thing to do before rendering to a face is set the transformation matrices such that the camera is positioned properly and points in the proper direction for that face: forward (+z), backward (-z), left (-x), right (+x), up (+y), or down (-y).

[C++]

The following C++ code prepares and sets a view matrix according to the face being rendered.

//
// The ppddsFaces variable is the address of an array of IDirectDrawSurface7 
// interface pointers, one for each face. 
//
void RenderFaces(LPDIRECTDRAWSURFACE7* ppddsFaces)
{
    // Loop through the six faces of the cube map.
    for( DWORD i=0; i<6; i++ )
    {
        DDSCAPS2 ddsc;
        ZeroMemory( (LPVOID)&ddsc, sizeof(DDSCAPS2) );

        // Get the surface caps (so we can find out which face this is).
        ppddsFaces[i]->GetCaps( &ddsc );

        // Standard view that will be overridden below
        D3DVECTOR vEnvEyePt = D3DVECTOR( 0.0f, 0.0f, 0.0f );
        D3DVECTOR vLookatPt, vUpVec;

        switch( ddsc.dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES )
        {
            case DDSCAPS2_CUBEMAP_POSITIVEX:
                vLookatPt = D3DVECTOR( 1.0f, 0.0f, 0.0f );
                vUpVec    = D3DVECTOR( 0.0f, 1.0f, 0.0f );
                break;
            case DDSCAPS2_CUBEMAP_NEGATIVEX:
                vLookatPt = D3DVECTOR(-1.0f, 0.0f, 0.0f );
                vUpVec    = D3DVECTOR( 0.0f, 1.0f, 0.0f );
                break;
            case DDSCAPS2_CUBEMAP_POSITIVEY:
                vLookatPt = D3DVECTOR( 0.0f, 1.0f, 0.0f );
                vUpVec    = D3DVECTOR( 0.0f, 0.0f,-1.0f );
                break;
            case DDSCAPS2_CUBEMAP_NEGATIVEY:
                vLookatPt = D3DVECTOR( 0.0f,-1.0f, 0.0f );
                vUpVec    = D3DVECTOR( 0.0f, 0.0f, 1.0f );
                break;
            case DDSCAPS2_CUBEMAP_POSITIVEZ:
                vLookatPt = D3DVECTOR( 0.0f, 0.0f, 1.0f );
                vUpVec    = D3DVECTOR( 0.0f, 1.0f, 0.0f );
                break;
            case DDSCAPS2_CUBEMAP_NEGATIVEZ:
                vLookatPt = D3DVECTOR( 0.0f, 0.0f,-1.0f );
                vUpVec    = D3DVECTOR( 0.0f, 1.0f, 0.0f );
                break;
        }

        D3DMATRIX matView;
        D3DUtil_SetViewMatrix( matView, vEnvEyePt, vLookatPt, vUpVec );
        g_pd3dDevice->SetTransform( D3DTRANSFORMSTATE_VIEW, &matView );
 

Remember, each face of a cubic-environment map represents a 90-degree field of view. Unless your application requires a different field of view angle (for special effects, perhaps), take care to set the projection matrix accordingly.

This code creates and sets a projection matrix for the most common case.

        // Use 90-degree field of view in the projection.
        D3DMATRIX matProj;
        D3DUtil_SetProjectionMatrix( matProj, g_PI/2, 1.0f, 0.5f, 1000.0f );
        g_pd3dDevice->SetTransform( D3DTRANSFORMSTATE_PROJECTION, &matProj );
 

Once the camera is in position, and the projection matrix set, you can render the scene. Each object in the scene should be positioned as you would normally position them. The following code, provided for completeness, outlines this task.

        // 
        // Render the scene.
        // 

        // Swap the depth-buffer to a face, then sets it as the render target.
        // This function is described in more detail later.
        ChangeRenderTarget( ppddsFaces[i] );

        // Clear the zbuffer.
        g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER, 0x000000ff, 1.0f, 0L );

        // Enable antialiasing, it makes the environment maps look better.
        g_pd3dDevice->SetRenderState( D3DRENDERSTATE_ANTIALIAS, 
                                      D3DANTIALIAS_TRANSLUCENTSORTINDEPENDENT );

        // Begin the scene.
        if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
        {
            // Position the geometry of the scene, as normal, by setting the 
            // world matrix for each object to be rendered.

            g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST,
                                         D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE3(0),
                                         lpVertices, VERTEX_COUNT, 0);

            // End the scene.
            g_pd3dDevice->EndScene();
        }
    }
}

Note  Most applications should enable antialiasing when rendering to the faces of a cubic environment map. Because each face is relatively small, enabling antialiasing when rendering to them can noticeably improve the appearance of the resulting environment map.

Notice the call to the ChangeRenderTarget function. When rendering to the cube map faces, you must of course assign the face as the current render-target surface. Applications that use depth buffers can explicitly create a depth-buffer for the render-target, or reassign an existing depth-buffer to the render-target surface. The following code uses the latter method.

HRESULT ChangeRenderTarget( LPDIRECTDRAWSURFACE7 pddsNewTarget)
{
    LPDIRECTDRAWSURFACE7 pddsOldRenderTarget = NULL;
    g_pd3dDevice->GetRenderTarget( &pddsOldRenderTarget );

    if( pddsOldRenderTarget )
    {
        LPDIRECTDRAWSURFACE7 pddsZBuffer = NULL;
        DDSCAPS2 ddscaps = { DDSCAPS_ZBUFFER, 0, 0, 0 };
        pddsOldRenderTarget->GetAttachedSurface( &ddscaps, &pddsZBuffer );

        if( pddsZBuffer )
        {
            pddsOldRenderTarget->DeleteAttachedSurface( 0, pddsZBuffer );
            pddsNewRenderTarget->AddAttachedSurface( pddsZBuffer );
            pddsZBuffer->Release();
        }
        pddsOldRenderTarget->Release();
    }

    g_pd3dDevice->SetRenderTarget( pddsNewRenderTarget, 0 );
    return S_OK;
}
[Visual Basic]

The following Visual Basic code prepares and sets a view matrix according to the face being rendered.

'
' The ddsFaces variable is the address of an array of DirectDrawSurface7
' objects, one for each face.
'
Sub RenderFaces(ddsFaces() As DirectDrawSurface7)
    Dim i As Integer
    
    ' Loop through the six faces of the cube map.
    For i = 0 To UBound(ddsFaces)
        Dim ddsc As DDSCAPS2
        Dim vEnvEyePt As D3DVECTOR, _
            vLookatPt As D3DVECTOR, _
            vUpVec As D3DVECTOR
        
        ' Get the surface caps (so we can find out which face this is).
        Call ddsFaces(i).GetCaps(ddsc)

        Select Case (ddsc.dwCaps2 And DDSCAPS2_CUBEMAP_ALLFACES)
            Case DDSCAPS2_CUBEMAP_POSITIVEX:
                vLookatPt.x = 1#: vUpVec.y = 1#
                
            Case DDSCAPS2_CUBEMAP_NEGATIVEX:
                vLookatPt.x = -1#: vUpVec = 1#

            Case DDSCAPS2_CUBEMAP_POSITIVEY:
                vLookatPt.y = 1#: vUpVec.z = -1#
                
            Case DDSCAPS2_CUBEMAP_NEGATIVEY:
                vLookatPt.y = -1#: vUpVec.z = 1#

            Case DDSCAPS2_CUBEMAP_POSITIVEZ:
                vLookatPt.z = 1#: vUpVec.y = 1#
                
            Case DDSCAPS2_CUBEMAP_NEGATIVEZ:
                vLookatPt.z = -1#: vUpVec.y = 1#
                
        End Select

        Dim matView As D3DMATRIX
        Call g_dx.ViewMatrix(matView, vEnvEyePt, vLookatPt, vUpVec)
        Call g_pd3dDevice.SetTransform(D3DTRANSFORMSTATE_VIEW, matView)
 

Remember, each face of a cubic-environment map represents a 90-degree field of view. Unless your application requires a different field of view angle (for special effects, perhaps), take care to set the projection matrix accordingly.

This code creates and sets a projection matrix for the most common case.

        ' Use 90-degree field of view in the projection.
        Dim matProj As D3DMATRIX
        Call g_dx.ProjectionMatrix(matProj, 0.5, 1000#, 3.1415926535 / 2)
        Call g_d3dDevice.SetTransform(D3DTRANSFORMSTATE_PROJECTION, matProj)
 

Once the camera is in position, and the projection matrix set, you can render the scene. Each object in the scene should be positioned as you would normally position them. The following code, provided for completeness, outlines this task.

        '
        ' Render the scene.
        '

        ' Swap the depth-buffer to a face, then sets it as the render target.
        ' This function is described in more detail later.
        Call ChangeRenderTarget(ddsFaces(i))

        Dim rec(1) As RECT
        rec.Top = 0: rec.Left = 0
        
        ' This assumes surface dimensions of 64 by 64
        rec.Bottom = 64: rec.Right = 64

        ' Clear the zbuffer.
        Call g_d3dDevice.Clear(0, rec(), D3DCLEAR_ZBUFFER, _
                               g_dx.CreateColorRGBA(0, 0, 256, 0), 1#, 0)

        ' Enable antialiasing, it makes the environment maps look better.
        Call g_d3dDevice.SetRenderState(D3DRENDERSTATE_ANTIALIAS, _
                                        D3DANTIALIAS_TRANSLUCENTSORTINDEPENDENT)

        ' Begin the scene.
        Call g_d3dDevice.BeginScene
        If Err.Number = DD_OK Then
            ' Position the geometry of the scene, as normal, by setting the
            ' world matrix for each object to be rendered.

            Call g_pd3dDevice.DrawPrimitive(D3DPT_TRIANGLELIST, D3DFVF_XYZ Or D3DFVF_NORMAL Or _
                                            D3DFVF_TEX1 Or D3DFVF_TEXCOORDSIZE3(0), _
                                            g_Vertices(0), VERTEX_COUNT, 0)

            ' End the scene.
            Call g_d3dDevice.EndScene
        End If
    Next i
End Sub

Note  Most applications should enable antialiasing when rendering to the faces of a cubic environment map. Because each face is relatively small, enabling antialiasing when rendering to them can noticeably improve the appearance of the resulting environment map.

Notice the call to the ChangeRenderTarget subroutine. When rendering to the cube map faces, you must of course assign the face as the current render-target surface. Applications that use depth buffers can explicitly create a depth-buffer for the render-target, or reassign an existing depth-buffer to the render-target surface. The following code uses the latter method.

Sub ChangeRenderTarget(ddsNewTarget As DirectDrawSurface7)
    Dim ddsOldRenderTarget As DirectDrawSurface7
    
    Set ddsOldRenderTarget = g_pd3dDevice.GetRenderTarget

    If (ddsOldRenderTarget Is Not Nothing) Then
        Dim ddsZBuffer As DirectDrawSurface7
        Dim ddscaps As DDSCAPS2
        
        ddscaps.lCaps = DDSCAPS_ZBUFFER
        Set ddsZBuffer = ddsOldRenderTarget.GetAttachedSurface

        If (ddsZBuffer Is Not Nothing) Then
            Call ddsOldRenderTarget.DeleteAttachedSurface(ddsZBuffer)
            Call ddsNewRenderTarget.AddAttachedSurface(ddsZBuffer)
        End If
    End If

    Call g_pd3dDevice.SetRenderTarget(pddsNewRenderTarget)
End Sub