Platform SDK: DirectX |
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).
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; }
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