//----------------------------------------------------------------------------- // File: ShadowBuffer.cpp // // Desc: Illustrates how to do shadow buffering on the XBox. // // Hacked up in 2002 by Thatcher Ulrich at Oddworld Inhabitants, tu@tulrich.com, to experiment with perspective shadow buffers. // // Hist: 06.06.01 - Adding start/stop capability // // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #include #include #include #include "resource.h" #include "xpath.h" #include "xmenu.h" #include "xobj.h" // #include //----------------------------------------------------------------------------- // ASCII names for the resources used by the app //----------------------------------------------------------------------------- XBRESOURCE g_ResourceNames[] = { { "Floor.bmp", resource_Floor_OFFSET }, { "Wings.bmp", resource_Wings_OFFSET }, { "BiHull.bmp", resource_BiHull_OFFSET }, { NULL, 0 }, }; extern CXBPackedResource* g_pModelResource; const char* c_modelName[] = { "Models\\Airplane.xbg", "Models\\Airplane.xbg", "Models\\teapot.xbg", "Models\\Airplane.xbg", "Models\\teapot.xbg", "Models\\Airplane.xbg", "Models\\teapot.xbg", "Models\\Airplane.xbg", "Models\\teapot.xbg", "Models\\Airplane.xbg", "Models\\teapot.xbg", }; const int c_modelNameCount = (sizeof(c_modelName) / sizeof(c_modelName[0])); //----------------------------------------------------------------------------- // Shadowbuffer types //----------------------------------------------------------------------------- #define SHADOWBUFFERTYPE_D16 0 #define SHADOWBUFFERTYPE_D24S8 1 #define SHADOWBUFFERTYPE_F16 2 #define SHADOWBUFFERTYPE_F24S8 3 // Z ranges for all buffer types float g_fShadowBufferZRange[4] = { D3DZ_MAX_D16, D3DZ_MAX_D24S8, D3DZ_MAX_F16, (float)D3DZ_MAX_F24S8 }; // Descriptions WCHAR g_fShadowBufferDesc[4][8] = { L"D16", L"D24S8", L"F16", L"F24S8" }; // Shadow buffer width and height. const int SHADOWBUFFERWIDTH = 512; const int SHADOWBUFFERHEIGHT = 512; // View near & far clipping distance. const float c_near = 1.0f; const float c_far = 200.f; //----------------------------------------------------------------------------- // Projection frustrum //----------------------------------------------------------------------------- struct LINEVERTEX { FLOAT x, y, z; DWORD color; }; D3DXVECTOR4 g_vHomogenousFrustum[8] = { D3DXVECTOR4( 1.0f, 1.0f, 0.0f, 1.0f ), D3DXVECTOR4( 1.0f, 1.0f, 1.0f, 1.0f ), D3DXVECTOR4(-1.0f, 1.0f, 0.0f, 1.0f ), D3DXVECTOR4(-1.0f, 1.0f, 1.0f, 1.0f ), D3DXVECTOR4(-1.0f,-1.0f, 0.0f, 1.0f ), D3DXVECTOR4(-1.0f,-1.0f, 1.0f, 1.0f ), D3DXVECTOR4( 1.0f,-1.0f, 0.0f, 1.0f ), D3DXVECTOR4( 1.0f,-1.0f, 1.0f, 1.0f ), }; LINEVERTEX g_vFrustumLines[8] = { { 1.0f, 1.0f, 0.0f, 0xffffffff }, { 1.0f, 1.0f, 1.0f, 0xffffffff }, {-1.0f, 1.0f, 0.0f, 0xffffffff }, {-1.0f, 1.0f, 1.0f, 0xffffffff }, {-1.0f,-1.0f, 0.0f, 0xffffffff }, {-1.0f,-1.0f, 1.0f, 0xffffffff }, { 1.0f,-1.0f, 0.0f, 0xffffffff }, { 1.0f,-1.0f, 1.0f, 0xffffffff }, }; LINEVERTEX g_vCameraFrustumLines[8] = { { 1.0f, 1.0f, 0.0f, 0xffffffff }, { 1.0f, 1.0f, 1.0f, 0xffffffff }, {-1.0f, 1.0f, 0.0f, 0xffffffff }, {-1.0f, 1.0f, 1.0f, 0xffffffff }, {-1.0f,-1.0f, 0.0f, 0xffffffff }, {-1.0f,-1.0f, 1.0f, 0xffffffff }, { 1.0f,-1.0f, 0.0f, 0xffffffff }, { 1.0f,-1.0f, 1.0f, 0xffffffff }, }; //----------------------------------------------------------------------------- // Help screen //----------------------------------------------------------------------------- XBHELP_CALLOUT g_HelpCallouts[] = { { XBHELP_LEFTSTICK, XBHELP_PLACEMENT_1, L"Slide camera" }, { XBHELP_RIGHTSTICK, XBHELP_PLACEMENT_1, L"Rotate camera" }, { XBHELP_DPAD, XBHELP_PLACEMENT_2, L"Take screenshot" }, { XBHELP_A_BUTTON, XBHELP_PLACEMENT_1, L"Toggle rotation" }, { XBHELP_BLACK_BUTTON, XBHELP_PLACEMENT_1, L"Toggle big light view" }, { XBHELP_WHITE_BUTTON, XBHELP_PLACEMENT_2, L"Change\nZ-buffer format" }, { XBHELP_LEFT_BUTTON, XBHELP_PLACEMENT_1, L"Zoom Out" }, { XBHELP_RIGHT_BUTTON, XBHELP_PLACEMENT_1, L"Zoom In" }, { XBHELP_START_BUTTON, XBHELP_PLACEMENT_1, L"Pause" }, { XBHELP_BACK_BUTTON, XBHELP_PLACEMENT_1, L"Display help" }, }; #define NUM_HELP_CALLOUTS (sizeof(g_HelpCallouts)/sizeof(g_HelpCallouts[0])) static void SaveBuffer(const char* filename, IDirect3DSurface8* buffer); // for screenshots. //----------------------------------------------------------------------------- // Name: class CXBoxSample // Desc: Main class to run this application. Most functionality is inherited // from the CXBApplication base class. //----------------------------------------------------------------------------- class CXBoxSample : public CXBApplication { CXBFont m_Font; CXBHelp m_Help; BOOL m_bDrawHelp; CXBPackedResource m_xprResource; D3DXMATRIX m_matProj; D3DXVECTOR3 m_vCameraPos; D3DXVECTOR3 m_vCameraDir; D3DXMATRIX m_matView; D3DXMATRIX m_matViewPrime; // camera view as used for light rendering. May not match real camera view D3DXMATRIX m_matProjPrime; // camera proj as used for light rendering. May not match real camera proj D3DXMATRIX m_matViewProjPrime; // camera view*proj as used for light rendering. May not match real camera view*proj D3DXMATRIX m_viewProjToShadowProj; D3DXVECTOR3 m_lightDir; D3DXVECTOR3 m_vLightPos; D3DXMATRIX m_matTexture; // Texture projection matrix D3DXMATRIX m_matShadowBuffer; // projection *and* view from world to shadow-buffer space. CXObject m_FloorObj; CXObject** m_Objects; int m_objectCount; CXObject m_LightObj; LPDIRECT3DSURFACE8 m_pShadowBufferTarget; LPDIRECT3DTEXTURE8 m_pShadowBufferDepth; // Shadow buffer depth texture LPDIRECT3DTEXTURE8 m_pShadowBufferColor; // Shadow buffer color texture LPDIRECT3DSURFACE8 m_pRenderTarget; LPDIRECT3DSURFACE8 m_pZBuffer; // Back buffer depth surface D3DSurface m_FakeTarget; DWORD m_dwShadowBufVS; // Shadow buffer vertex shader DWORD m_dwShadowBufPS; // Shadow buffer pixel shader DWORD m_dwShadowGenVS; // vshader for perspective shadow buffer generation DWORD m_dwShadowDrawVS; // vshader for drawing using a perspective shadow buffer DWORD m_dwShadowBufferType; // Shadowbuffer type float m_fZOffset; // Shadowbuffer z offset float m_fZSlopeScale; // Shadowbuffer z slope scale BOOL m_bRotateFlag; // Object rotate flag BOOL m_bDrawFrustum; // Draw frustum flag bool m_shadowDepthTestInverted; // reverse the sense of the shadow buffer test, for inverted light. bool m_bigLightView; // for debugging; when true, display the shadowbuffer large on-screen. public: HRESULT Initialize(); HRESULT FrameMove(); HRESULT Render(); HRESULT InitPixelShader(); HRESULT DisplaySBObject(CXObject *obj); void RenderForShadowBuffer( CXObject* pObject ); VOID ShowTexture(LPDIRECT3DTEXTURE8 pTexture, float sizePixels); D3DXVECTOR3 TransformWorldToScreen(const D3DXVECTOR4& v); CXBoxSample(); }; //----------------------------------------------------------------------------- // Name: main() // Desc: Entry point to the program. Initializes everything, and goes into a // message-processing loop. Idle time is used to render the scene. //----------------------------------------------------------------------------- void __cdecl main() { CXBoxSample xbApp; if( FAILED( xbApp.Create() ) ) return; xbApp.Run(); } //----------------------------------------------------------------------------- // Name: CXBoxSample() // Desc: Constructor //----------------------------------------------------------------------------- CXBoxSample::CXBoxSample() :CXBApplication() { m_vCameraPos = D3DXVECTOR3(0.0f, 20.0f, -30.0f); m_vCameraDir = D3DXVECTOR3(1.0f, 0.0f, 0.0f); m_fZOffset = 4.0f; m_fZSlopeScale = 2.0f; m_bRotateFlag = true; m_bDrawFrustum = false; m_bDrawHelp = false; m_bigLightView = false; } //----------------------------------------------------------------------------- // Name: Initialize() // Desc: Initialize device-dependant objects. //----------------------------------------------------------------------------- HRESULT CXBoxSample::Initialize() { D3DXVECTOR3 v; // Create a font if( FAILED( m_Font.Create( m_pd3dDevice, "Font.xpr" ) ) ) return XBAPPERR_MEDIANOTFOUND; // Create the help if( FAILED( m_Help.Create( m_pd3dDevice, "Gamepad.xpr" ) ) ) return XBAPPERR_MEDIANOTFOUND; // Create our vertex shader DWORD vdecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(0, D3DVSDT_FLOAT3), // v0 = XYZ D3DVSD_REG(1, D3DVSDT_FLOAT3), // v1 = normals D3DVSD_REG(2, D3DVSDT_FLOAT2), // v2 = TEX1 D3DVSD_END() }; if( FAILED( XBUtil_CreateVertexShader( m_pd3dDevice, "shaders\\vshader.xvu", vdecl, &m_dwShadowBufVS ) ) ) return E_FAIL; if( FAILED( XBUtil_CreateVertexShader( m_pd3dDevice, "shaders\\shadowgen.xvu", vdecl, &m_dwShadowGenVS ) ) ) return E_FAIL; if( FAILED( XBUtil_CreateVertexShader( m_pd3dDevice, "shaders\\shadowdraw.xvu", vdecl, &m_dwShadowDrawVS ) ) ) return E_FAIL; if( FAILED( XBUtil_CreatePixelShader( m_pd3dDevice, "shaders\\shadwbuf.xpu", &m_dwShadowBufPS ) ) ) return E_FAIL; // Create the resources if( FAILED( m_xprResource.Create( m_pd3dDevice, "Resource.xpr", resource_NUM_RESOURCES, g_ResourceNames ) ) ) return XBAPPERR_MEDIANOTFOUND; // Set the resource globally so the CXModel can access textures g_pModelResource = &m_xprResource; // Set base path to point to our data. All the _FNA commands use this XPath_SetBasePath( _T("d:\\media\\") ); // Floor m_FloorObj.m_Model = new CXModel; m_FloorObj.m_Model->GridXZ(16, 16, 80.0f, 80.0f, FVF_XYZNORMTEX1, "Floor.bmp", 10.0f, 10.0f); // Objects m_objectCount = 64; m_Objects = new CXObject*[m_objectCount]; // Objects from resource files. for (int i = 0; i < c_modelNameCount; i++) { CXBMesh *xbm = new CXBMesh; xbm->Create(g_pd3dDevice, (CHAR*) c_modelName[i], &m_xprResource); m_Objects[i] = new CXObject; m_Objects[i]->SetXBMesh(xbm); // m_Objects[i]->SetPosition(0.0f, 4.0f, 0.0f); m_Objects[i]->SetPosition((rand() / float(RAND_MAX) - 0.5f) * 50.0f, (rand() / float(RAND_MAX)) * 15.0f, (rand() / float(RAND_MAX) - 0.5f) * 50.0f); m_Objects[i]->SetRotation(-1.5708f, 0.0f, 0.0f); } // Some more procedural objects. {for (int i = c_modelNameCount; i < m_objectCount; i++) { m_Objects[i] = new CXObject; m_Objects[i]->m_Model = new CXModel; if (i % 3 == 0) { m_Objects[i]->m_Model->Sphere( (rand() / float(RAND_MAX)) * 2.5f + 1.f, // radius 20, 20, FVF_XYZNORMTEX1, "Bihull.bmp", 2.0f); } else if (i % 3 == 1) { m_Objects[i]->m_Model->Cylinder( (rand() / float(RAND_MAX)) * 0.5f + 1.f, 0xFFFFFFFF, // radius, color (rand() / float(RAND_MAX)) * 0.5f + 1.f, 0xFFFFFFFF, // radius, color (rand() / float(RAND_MAX)) * 2.f + 3.f, // length 20, FVF_XYZNORMTEX1, "Wings.bmp", 2.0); } else { m_Objects[i]->m_Model->GridXZ(2, 2, (rand() / float(RAND_MAX)) * 10.0f + 1.f, (rand() / float(RAND_MAX)) * 10.0f + 1.f, FVF_XYZNORMTEX1, "Wings.bmp", 4.0f, 4.0f); } m_Objects[i]->SetPosition((rand() / float(RAND_MAX) - 0.5f) * 50.0f, (rand() / float(RAND_MAX)) * 15.0f, (rand() / float(RAND_MAX) - 0.5f) * 50.0f); m_Objects[i]->SetRotation(-1.5708f, 0.0f, 0.0f); }} // Light m_LightObj.m_Model = new CXModel; m_LightObj.m_Model->Cylinder( 0.2f, 0xc0ffffff, 0.5f, 0x00ffffff, 0.6f, 16, FVF_XYZDIFF, NULL, 1.0f ); m_LightObj.SetPosition( 10.0f, 100.0f, 0.0f ); // Create shadow buffer m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_D16, 0, &m_pShadowBufferDepth ); m_dwShadowBufferType = SHADOWBUFFERTYPE_D16; // Make a color buffer for the light's view, for debugging. // Normally we wouldn't need a color buffer for shadow-buffer // rendering. m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_R5G6B5, 0, &m_pShadowBufferColor ); m_pShadowBufferColor->GetSurfaceLevel(0, &m_pShadowBufferTarget); // // Setup dummy color buffer (bad things will happen if you write to it). // ZeroMemory( &m_FakeTarget, sizeof(m_FakeTarget) ); // XGSetSurfaceHeader( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, D3DFMT_LIN_R5G6B5, // &m_FakeTarget, 0, 0 ); // m_pShadowBufferTarget = &m_FakeTarget; // Get original color and z-buffer. m_pd3dDevice->GetDepthStencilSurface( &m_pZBuffer ); m_pd3dDevice->GetRenderTarget( &m_pRenderTarget ); m_bigLightView = false; return S_OK; } float VectorDot(const D3DXVECTOR3& a, const D3DXVECTOR3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } //----------------------------------------------------------------------------- // Name: FrameMove() // Desc: Called once per frame, the call is the entry point for animating // the scene. //----------------------------------------------------------------------------- HRESULT CXBoxSample::FrameMove() { D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX mat; // Toggle help if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_BACK ) m_bDrawHelp = !m_bDrawHelp; // // Toggle frustum // if( m_DefaultGamepad.bPressedAnalogButtons[XINPUT_GAMEPAD_BLACK] ) // m_bDrawFrustum = !m_bDrawFrustum; // Toggle big light view. if( m_DefaultGamepad.bPressedAnalogButtons[XINPUT_GAMEPAD_BLACK] ) m_bigLightView = !m_bigLightView; // Toggle object rotation if( m_DefaultGamepad.bPressedAnalogButtons[XINPUT_GAMEPAD_A] ) m_bRotateFlag = !m_bRotateFlag; // Rotate the objects if( m_bRotateFlag ) { {for (int i = 0; i < m_objectCount; i++) { m_Objects[i]->m_vRotation.y += 0.57f * m_fElapsedAppTime * ((10.f + i) / 20.f); m_Objects[i]->m_vRotation.x = 0.0f; }} } // Check for buffer change if( m_DefaultGamepad.bPressedAnalogButtons[XINPUT_GAMEPAD_WHITE] ) { // Release existing depth buffer m_pShadowBufferDepth->Release(); switch( m_dwShadowBufferType ) { case SHADOWBUFFERTYPE_D16: m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_D24S8, 0, &m_pShadowBufferDepth ); m_dwShadowBufferType = SHADOWBUFFERTYPE_D24S8; break; case SHADOWBUFFERTYPE_D24S8: m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_F16, 0, &m_pShadowBufferDepth ); m_dwShadowBufferType = SHADOWBUFFERTYPE_F16; break; case SHADOWBUFFERTYPE_F16: m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_F24S8, 0, &m_pShadowBufferDepth ); m_dwShadowBufferType = SHADOWBUFFERTYPE_F24S8; break; case SHADOWBUFFERTYPE_F24S8: m_pd3dDevice->CreateTexture( SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 1, 0, D3DFMT_LIN_D16, 0, &m_pShadowBufferDepth ); m_dwShadowBufferType = SHADOWBUFFERTYPE_D16; break; } } #if 0 // Adjust z offset if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_UP ) m_fZOffset += 0.5f; if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_DOWN ) m_fZOffset -= 0.5f; // Adjust z offset slope scale if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_LEFT ) m_fZSlopeScale += 0.1f; if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_RIGHT ) m_fZSlopeScale -= 0.1f; #endif // 0 // Adjust light position based on dpad. bool dpadMoved = false; if( m_DefaultGamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) { m_LightObj.m_vPosition.z -= m_fElapsedTime*8.0f; dpadMoved = true; } if( m_DefaultGamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) { m_LightObj.m_vPosition.z += m_fElapsedTime*8.0f; dpadMoved = true; } if( m_DefaultGamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) { m_LightObj.m_vPosition.x += m_fElapsedTime*8.0f; dpadMoved = true; } if( m_DefaultGamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT ) { m_LightObj.m_vPosition.x -= m_fElapsedTime*8.0f; dpadMoved = true; } // Take a screenshot whenever the dpad moves. if (dpadMoved) { SaveBuffer("t:\\shadowscreen.bmp", m_pRenderTarget); } // // Adjust camera position // D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f); D3DXVECTOR3 vRight; D3DXVec3Cross(&vRight, &m_vCameraDir, &vUp); D3DXVec3Cross(&vUp, &vRight, &m_vCameraDir); D3DXVec3Normalize(&vUp, &vUp); // Rotate the camera. D3DXMATRIX matRotate; D3DXMatrixRotationAxis(&matRotate, &vUp, m_DefaultGamepad.fX2 * m_fElapsedTime); D3DXVec3TransformCoord(&m_vCameraDir, &m_vCameraDir, &matRotate); D3DXMatrixRotationAxis(&matRotate, &vRight, m_DefaultGamepad.fY2 * m_fElapsedTime); // Cheesy angle limits... if ((m_DefaultGamepad.fY2 < 0 && m_vCameraDir.y <= -0.95) || (m_DefaultGamepad.fY2 > 0 && m_vCameraDir.y >= 0.95)) { // Don't rotate -- we're almost at the limit & we don't want to get gimbal-locked... } else { D3DXVec3TransformCoord(&m_vCameraDir, &m_vCameraDir, &matRotate); } // Renormalize the camera dir/up. D3DXVec3Cross(&vRight, &m_vCameraDir, &vUp); D3DXVec3Cross(&vUp, &vRight, &m_vCameraDir); D3DXVec3Normalize(&vUp, &vUp); D3DXVec3Normalize(&m_vCameraDir, &m_vCameraDir); // In/out based on triggers. float fIn = (m_DefaultGamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] / 255.0f); float fOut = (m_DefaultGamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] / 255.0f); if( fIn > 0.1f ) { m_vCameraPos += m_vCameraDir * 30.0f * fIn * m_fElapsedTime; } if( fOut > 0.1f ) { m_vCameraPos -= m_vCameraDir * 30.0f * fOut * m_fElapsedTime; } // Slide camera based on left stick. m_vCameraPos -= vRight * 20.0f * m_fElapsedTime * m_DefaultGamepad.fX1; m_vCameraPos += vUp * 20.0f * m_fElapsedTime * m_DefaultGamepad.fY1; D3DXVECTOR3 vAt(m_vCameraPos); vAt += m_vCameraDir; // Compute view transform. D3DXMatrixLookAtLH( &m_matView, &m_vCameraPos, &vAt, &vUp ); // Compute projection transform. D3DXMatrixPerspectiveFovLH( &m_matProj, D3DX_PI/4, 640.0f/480.0f, c_near, c_far ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProj ); // Light orientation (looks at center of scene) D3DXVECTOR3 zero(0.f, 0.f, 0.f); D3DXMatrixLookAtLH( &m_LightObj.m_matOrientation, &m_LightObj.m_vPosition, &zero, &up ); D3DXMatrixInverse( &m_LightObj.m_matOrientation, NULL, &m_LightObj.m_matOrientation ); // Light direction -- for shadowbuffering. m_lightDir = m_LightObj.m_vPosition - zero; D3DXVec3Normalize(&m_lightDir, &m_lightDir); // Compute camera view & proj matrices as used by the shadowbuffer // mapping. Does not necessarily have to match true view & proj. { // Use normal camera view. m_matViewPrime = m_matView; float viewSlideBack = 0.f; // Measure how similar the light & camera directions are. float dot = m_lightDir.x * m_matViewPrime._13 + m_lightDir.y * m_matViewPrime._23 + m_lightDir.z * m_matViewPrime._33; { // Some not-very-principled hackery (but the results are // very nice): tweak the view to make the projection more // orthographic, when the light is aligned with the view // vector. In the aligned situation, we don't want so // much perspective effect. viewSlideBack += 50.0f * fabsf(dot); } // More aggressive clipping planes for the shadow buffer. float sbNear = 5.0f; float sbFar = 50.0f; if (dot < 0) { // light pointed at camera: we don't need sb info from behind the camera... viewSlideBack += 5.f; sbNear += viewSlideBack; } else { // light pointed along the camera (light is behind the // camera). Need info from behind the camera, but the SB // doesn't need much perspective correction... viewSlideBack += 5.f; sbNear += viewSlideBack; viewSlideBack = viewSlideBack * 2.f - 5.f; // xxxxxx } m_matViewPrime._43 += viewSlideBack; // // Use normal camera projection. // m_matProjPrime = m_matProj; // // Use orthographic camera projection... this should degenerate to normal shadow mapping. // D3DXMatrixOrthoLH(&m_matProjPrime, 50.f, 50.f, sbNear, sbFar); float angle = atanf(sbFar * tanf(D3DX_PI/4) / (sbFar + viewSlideBack)); // Make a new projection, with more aggressive c_near/c_far. D3DXMatrixPerspectiveFovLH(&m_matProjPrime, angle, 640.0f/480.0f, sbNear, sbFar + viewSlideBack); // Combine the matrices. D3DXMatrixMultiply(&m_matViewProjPrime, &m_matViewPrime, &m_matProjPrime); // \todo more options here... } // Generate the shadow buffer transform matrix and the // corresponding texture transform matrix (the shadow buffer // matrix maps from screen space to shadow-buffer pre-projection // space; the texture transform matrix just tacks on the extra // texture scaling.) // // Note: if you are using multiple lights & shadow buffers, this needs // to be calculated for each light. { // Light position in post-perspective screen space. D3DXVECTOR3 lightPos; // For a directional light, basically we want to look at the // screen-space cube from some point on the "infinite plane" // (past the end of zFar). float infinityZ = c_far / (c_far - c_near); // // Pick an arbitrary light pos. \todo: make this depend on infinite light direction. // D3DXVECTOR3 lightPos(0.f, 100.f, infinityZ); // light position in *screen* space. // // // // // xxx very approximate // float fRadius = (c_far - c_near) / 2.f; // float fDist = D3DXVec3Length(& (lightPos - D3DXVECTOR3(0, 0, c_far / 2.f))); // // float fAngle = 2.0f * asinf(fRadius / fDist); // // D3DXVECTOR3 zero(0.f, 0.f, 0.f); D3DXMATRIX view, proj; // shadowbuffer's viewing matrices, from *screen* space. D3DXVECTOR4 lightDir(m_lightDir.x, m_lightDir.y, m_lightDir.z, 0); // Push lightPos through the transformation // pipeline to generate a screen-space position // for the light. lightPos = TransformWorldToScreen(lightDir); if (lightPos.z == 0) { // Light behind near clip -- the correct thing // to do here is project it to its inverse // position on the infinity plane, and then // reverse the sense of the shadowbuffer depth // test. m_shadowDepthTestInverted = true; lightDir = -lightDir; lightPos = TransformWorldToScreen(lightDir); if (lightPos.z == 0) { // Light is pointed (almost) exactly perpendicular to // the camera direction. Generate a light pos that // points roughly the right direction, and is very far // from the screen box. D3DXVECTOR4 pos; D3DXVec4Transform(&pos, &lightDir, &m_matViewProjPrime); pos.z = 0; pos.w = 0; D3DXVec4Normalize(&pos, &pos); lightPos.x = pos.x * 1000.f; lightPos.y = pos.y * 1000.f; lightPos.z = infinityZ; } } else { m_shadowDepthTestInverted = false; } D3DXVECTOR3 center(0.f, 0.f, 0.5f); D3DXVECTOR3 zaxis(0.f, 0.f, 1.f); float fRadius = 1.0f; float fDist = D3DXVec3Length(&(lightPos - center)); // Look at the screen-box, from the screen-space light position. D3DXMatrixLookAtLH(&view, &lightPos, ¢er, &zaxis); float fAngle = 2.0f * asinf(fRadius / fDist); float n = fDist - fRadius * 2.f; float f = fDist + fRadius * 2.f; if (n < 0.001f) { n = 0.001f; } D3DXMatrixPerspectiveFovLH(&proj, fAngle, 1.0f, n, f); // Compose matrices into m_matShadowBuffer. D3DXMATRIX matTemp, matTemp2; D3DXMatrixMultiply(&matTemp, &m_matViewPrime, &m_matProjPrime); D3DXMatrixMultiply(&matTemp2, &view, &proj); D3DXMatrixMultiply(&m_matShadowBuffer, &matTemp, &matTemp2); // D3DXMatrixMultiply(&m_matShadowBuffer, &view, &proj); } // Finally, we scale and offset by SHADOWBUFFERWIDTH/2, SHADOWBUFFERHEIGHT/2 // to move from [-1,+1] space to [0:0, SHADOWBUFFERWIDTH:SHADOWBUFFERHEIGHT] // texture space we also need to scale z by the zbuffer range. An additional // half texel offset is necessary because of the differences between texture // addressing and pixel addressing. D3DXMatrixIdentity( &mat ); // Scale mat._11 = SHADOWBUFFERWIDTH * 0.5f; mat._22 = -SHADOWBUFFERHEIGHT * 0.5f; mat._33 = g_fShadowBufferZRange[m_dwShadowBufferType]; // Offset mat._41 = SHADOWBUFFERWIDTH*0.5f + 0.5f; mat._42 = SHADOWBUFFERHEIGHT*0.5f + 0.5f; D3DXMatrixMultiply( &m_matTexture, &m_matShadowBuffer, &mat ); // m_matTexture now holds the appropriate transformation matrix // for shadowmapping on the XBox GPU. return S_OK; } //----------------------------------------------------------------------------- // Name: Render() // Desc: Called once per frame, the call is the entry point for 3d // rendering. This function sets up render states, clears the // viewport, and renders the scene. //----------------------------------------------------------------------------- HRESULT CXBoxSample::Render() { D3DXMATRIX m; LPDIRECT3DSURFACE8 pSurface; // Restore state that text clobbers m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP ); // // Render the scene into the shadow buffer from the viewpoint of the light // // Set shadowbuffer as render target z buffer & clear it m_pShadowBufferDepth->GetSurfaceLevel( 0, &pSurface ); m_pd3dDevice->SetRenderTarget( m_pShadowBufferTarget, pSurface ); D3DVIEWPORT8 viewport = { 0, 0, SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT, 0.0f, 1.0f }; m_pd3dDevice->SetViewport( &viewport ); // m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER, 0, 1.0f, 0 ); if (m_shadowDepthTestInverted == false) { // Regular depth test. m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_TARGET, 0, 1.0f, 0 ); } else { // Inverted depth test. m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_GREATEREQUAL ); m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_TARGET, 0, 0.0f, 0 ); } // Enable color writes, for debugging. Otherwise we'd disable, and save the RAM. // // Disable color writes // m_pd3dDevice->SetRenderState( D3DRS_COLORWRITEENABLE, 0 ); // The only real reason not to define this is to display shadows of // non-closed geometry. //#define BACKFACE_SHADOWS_ONLY #ifdef BACKFACE_SHADOWS_ONLY // Draw only backfaces into the shadow buffer. This gives some // extra leeway to the shadow depth test, and prevents jagged // self-shadowing on sillouettes of objects on the boundary where // shadowing kicks in. Better to implement that boundary with // ordinary normal-based lighting. if (m_shadowDepthTestInverted == false) { m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW ); } else { m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); } #else // not BACKFACE_SHADOWS_ONLY // Disable culling so all triangles cause shadows m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); #endif // not BACKFACE_SHADOWS_ONLY // Turn on z-offset. if (m_shadowDepthTestInverted == false) { m_pd3dDevice->SetRenderState( D3DRS_SOLIDOFFSETENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_POLYGONOFFSETZOFFSET, FtoDW(m_fZOffset) ); m_pd3dDevice->SetRenderState( D3DRS_POLYGONOFFSETZSLOPESCALE, FtoDW(m_fZSlopeScale) ); } else { // Inverted light; offset the opposite direction... m_pd3dDevice->SetRenderState( D3DRS_SOLIDOFFSETENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_POLYGONOFFSETZOFFSET, FtoDW(-m_fZOffset) ); m_pd3dDevice->SetRenderState( D3DRS_POLYGONOFFSETZSLOPESCALE, FtoDW(-m_fZSlopeScale) ); } // Render our scene into the shadowbuffer RenderForShadowBuffer(&m_FloorObj); {for (int i = 0; i < m_objectCount; i++) { RenderForShadowBuffer(m_Objects[i]); }} // { // // Show camera frustum. // m_pd3dDevice->SetVertexShader(m_dwShadowGenVS); // m_pd3dDevice->DrawPrimitiveUP(D3DPT_LINELIST, 4, g_vCameraFrustumLines, sizeof(g_vFrustumLines[0])); // m_pd3dDevice->DrawVerticesUP( D3DPT_LINELOOP, 4, g_vCameraFrustumLines, sizeof(g_vFrustumLines[0])*2 ); // m_pd3dDevice->DrawVerticesUP( D3DPT_LINELOOP, 4, g_vCameraFrustumLines+1, sizeof(g_vFrustumLines[0])*2 ); // } // Restore important state m_pd3dDevice->SetRenderTarget( m_pRenderTarget, m_pZBuffer ); m_pd3dDevice->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALL ); m_pd3dDevice->SetRenderState( D3DRS_SOLIDOFFSETENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); pSurface->Release(); // Now render the scene from the point of view of the camera // with shadow compare functionality enabled // Clear the main view m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, 0xff400000, 1.0f, 0L ); // Transformation matrices for regular rendering. m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProj ); // Set shadowbuffer state if (m_shadowDepthTestInverted == false) { // Regular shadow-buffer test. m_pd3dDevice->SetRenderState( D3DRS_SHADOWFUNC, D3DCMP_GREATER ); } else { // Inverted shadow-buffer test. m_pd3dDevice->SetRenderState(D3DRS_SHADOWFUNC, D3DCMP_LESS); } // Set shadowbuffer texture m_pd3dDevice->SetTexture( 1, m_pShadowBufferDepth ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ADDRESSU, D3DTADDRESS_BORDER ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ADDRESSV, D3DTADDRESS_BORDER ); if (m_shadowDepthTestInverted == false) { m_pd3dDevice->SetTextureStageState( 1, D3DTSS_BORDERCOLOR, 0xffffffff ); } else { m_pd3dDevice->SetTextureStageState( 1, D3DTSS_BORDERCOLOR, 0x00000000 ); } m_pd3dDevice->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); // Set the shadowbuffer pixel shader m_pd3dDevice->SetPixelShader( m_dwShadowBufPS ); // Render the objects in the scene m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); DisplaySBObject( &m_FloorObj ); {for (int i = 0; i < m_objectCount; i++) { DisplaySBObject(m_Objects[i]); }} // \todo more objects here... // Reset shadowbuffer state m_pd3dDevice->SetPixelShader( NULL ); m_pd3dDevice->SetTexture( 1, NULL ); m_pd3dDevice->SetRenderState(D3DRS_SHADOWFUNC, D3DCMP_NEVER ); // Draw the light object m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); m_LightObj.Render( OBJ_NOMCALCS ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); // Draw light frustrum if( m_bDrawFrustum ) { m_pd3dDevice->SetTexture( 1, NULL ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); D3DXMATRIX matIdentity; D3DXMatrixIdentity( &matIdentity ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matIdentity ); m_pd3dDevice->SetVertexShader( D3DFVF_XYZ|D3DFVF_DIFFUSE ); m_pd3dDevice->DrawPrimitiveUP( D3DPT_LINELIST, 4, g_vFrustumLines, sizeof(g_vFrustumLines[0]) ); m_pd3dDevice->DrawVerticesUP( D3DPT_LINELOOP, 4, g_vFrustumLines, sizeof(g_vFrustumLines[0])*2 ); m_pd3dDevice->DrawVerticesUP( D3DPT_LINELOOP, 4, g_vFrustumLines+1, sizeof(g_vFrustumLines[0])*2 ); } // Show the shadow map. ShowTexture(m_pShadowBufferColor, m_bigLightView ? 400.0f : 128.0f); // Show title, frame rate, and help if( m_bDrawHelp ) { m_Help.Render( &m_Font, g_HelpCallouts, NUM_HELP_CALLOUTS ); } else { m_Font.Begin(); m_Font.DrawText( 64, 50, 0xffffffff, L"ShadowBuffer" ); m_Font.DrawText( 640-64, 50, 0xffffff00, m_strFrameRate, XBFONT_RIGHT ); // Show buffer description WCHAR strBuffer[80]; swprintf( strBuffer, L"Type: %s, ZOffset: %.01f, ZOffset Slope Scale: %.01f %s", g_fShadowBufferDesc[m_dwShadowBufferType], m_fZOffset, m_fZSlopeScale, m_shadowDepthTestInverted ? "I" : " " ); m_Font.DrawText( 64, 70, 0xffffffff, strBuffer ); m_Font.End(); } // Present the scene m_pd3dDevice->Present( NULL, NULL, NULL, NULL ); return S_OK; } //----------------------------------------------------------------------------- // Name: DisplaySBObject // Desc: Displays a shadowbuffered object. // The two matrices set up here are the World*View*Projection matrix // that transforms the objects points on to the screen, and the // World*Texture matrix that transforms the objects points into // shadowbuffer space. //----------------------------------------------------------------------------- HRESULT CXBoxSample::DisplaySBObject( CXObject* pObject ) { D3DXMATRIX m, wvpmat, wtmat, wvpprime; pObject->CrunchMatrix(); // Get object orientation matrix // WVP matrix D3DXMatrixMultiply( &m, &pObject->m_matOrientation, &m_matView ); D3DXMatrixMultiply( &wvpmat, &m, &m_matProj ); D3DXMatrixTranspose( &wvpmat, &wvpmat ); m_pd3dDevice->SetVertexShaderConstant( 0, &wvpmat, 4 ); // shadowbuffer matrix D3DXMatrixMultiply( &wtmat, &pObject->m_matOrientation, &m_matTexture ); D3DXMatrixTranspose( &wtmat, &wtmat); m_pd3dDevice->SetVertexShaderConstant( 4, &wtmat, 4 ); // Light position D3DXVECTOR4 v4LocalLightPos; D3DXMatrixInverse( &m, NULL, &pObject->m_matOrientation ); D3DXVec3Transform( &v4LocalLightPos, &m_LightObj.m_vPosition, &m ); m_pd3dDevice->SetVertexShaderConstant( 8, &v4LocalLightPos, 1 ); // Ambient color float fAmbient[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; m_pd3dDevice->SetPixelShaderConstant( 0, fAmbient, 1 ); // Send the object if( pObject->m_Model ) { // pObject->m_Model->SetVertexShader( m_dwShadowBufVS ); // Set shadow buffer vshader pObject->m_Model->SetVertexShader(m_dwShadowDrawVS); pObject->m_Model->Render(); pObject->m_Model->SetVertexShader( 0 ); // Restore to fvf vshader } else // Object has an XBG model { // g_pd3dDevice->SetVertexShader( m_dwShadowBufVS ); g_pd3dDevice->SetVertexShader(m_dwShadowDrawVS); pObject->m_pXBMesh->Render( m_pd3dDevice, XBMESH_NOFVF ); } return S_OK; } /** Draw the given object into the shadowbuffer. */ void CXBoxSample::RenderForShadowBuffer( CXObject* pObject ) { D3DXMATRIX m; pObject->CrunchMatrix(); // Get object orientation matrix // Cull any objects that might poke through the z=0 plane. D3DXMatrixMultiply(&m, &pObject->m_matOrientation, &m_matViewPrime); if (m._43 - pObject->GetRadius() < 0.001f) { return; } // Full transformation matrix, world-to-shadowbuffer. D3DXMatrixMultiply(&m, &pObject->m_matOrientation, &m_matShadowBuffer); D3DXMatrixTranspose(&m, &m); m_pd3dDevice->SetVertexShaderConstant( 0, &m, 4 ); // Diffuse color. D3DXVECTOR4 color(1, 1, 1, 1); m_pd3dDevice->SetVertexShaderConstant( 8, &color, 1 ); // Send the object if( pObject->m_Model ) { pObject->m_Model->SetVertexShader(m_dwShadowGenVS); pObject->m_Model->Render(); pObject->m_Model->SetVertexShader( 0 ); // Restore to fvf vshader } else // Object has an XBG model { g_pd3dDevice->SetVertexShader(m_dwShadowGenVS); pObject->m_pXBMesh->Render( m_pd3dDevice, XBMESH_NOFVF ); } } /** Transform to normalized post-projection screen-space. */ D3DXVECTOR3 CXBoxSample::TransformWorldToScreen(const D3DXVECTOR4& vec) { D3DXVECTOR4 result(0, 0, 0, 1); D3DXVec4Transform(&result, &vec, &m_matViewProjPrime); if (result.w < 0.001f) { // Hm. return D3DXVECTOR3(0, 0, 0); } float recip = 1.0f / result.w; result.x *= recip; result.y *= recip; result.z *= recip; result.w = 1; return D3DXVECTOR3(result.x, result.y, result.z); } //----------------------------------------------------------------------------- // Name: ShowTexture() // \param sizePixels specifies how big to display the image on-screen. VOID CXBoxSample::ShowTexture( LPDIRECT3DTEXTURE8 pTexture, float sizePixels ) { D3DSURFACE_DESC d3dsd; pTexture->GetLevelDesc( 0, &d3dsd ); FLOAT x1 = 50.0f, x2 = x1 + sizePixels; FLOAT y1 = 50.0f, y2 = x1 + sizePixels; struct SPRITEVERTEX { FLOAT sx, sy, sz, rhw; FLOAT tu, tv; }; SPRITEVERTEX vSprite[4] = { { x1-0.5f, y1-0.5f, 0.99f, 1.0f, 0.0f, 0.0f }, { x2-0.5f, y1-0.5f, 0.99f, 1.0f, SHADOWBUFFERWIDTH, 0.0f }, { x2-0.5f, y2-0.5f, 0.99f, 1.0f, SHADOWBUFFERWIDTH, SHADOWBUFFERHEIGHT }, { x1-0.5f, y2-0.5f, 0.99f, 1.0f, 0.0f, SHADOWBUFFERHEIGHT }, }; // Set state m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); // Display the sprite m_pd3dDevice->SetTexture( 0, pTexture ); m_pd3dDevice->SetVertexShader( D3DFVF_XYZRHW|D3DFVF_TEX1 ); m_pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, vSprite, sizeof(SPRITEVERTEX) ); } //! for manual instant saving : void SaveBuffer(const char* filename, IDirect3DSurface8* buffer) { D3DSURFACE_DESC desc; buffer->GetDesc(&desc); XGWriteSurfaceToFile(buffer, filename); OutputDebugString("Screenshot saved\n"); }