通常在图形编程中,需要能够将场景渲染到纹理上,并且针对某些目的可以使用这些纹理,例如,也许想在游戏中有一个安全监视器,游戏中间有一个电视屏可以在不同的位置显示游戏的状态。这对游戏玩家而言,非常有用,并且可以在很大程度上提高程序的真实感。
同样,也可以使用幕外渲染技术创建映射、阴影映射纹理和大量如场地模糊、夜视、热视力、高精度动态范围渲染等许多不同的后处理效果。在Direct3D中实现幕外渲染需要创建幕外表面,并对其渲染而不是渲染到常用的后台缓存中。然后将渲染结果存储在贴在幕外表面上的纹理图像中。并且可以像所有常用的纹理图像一样使用。要增加幕外渲染功能,只要创建一个LPDIRECT3DSURFACE9对象,并在想要渲染该对象时,切换到该对象即可。Direct3D在内部可以处理这一切工作,所以尤其是和OpenGL中的幕外渲染相比,这里的工作量要少很多。
为了创建幕外表面,就必须创建一个想要保存渲染结果的LPDIRECT3DTEXTURE9对象。这通过调用D3DXCreateTexture()函数就可以实现。该函数的参数有pDevice、纹理的宽度(Width)和高度(Height)、纹理中Mipmap的个数(Miplevels)、指定纹理使用方式的标识符(Usage)、图像格式(Format)、描述放置图像的内存类(Pool),以及正在创建的纹理对象(ppTexture)。
有了创建好的纹理,就可以调用LPDIRECT3DTEXTURE9的成员函数GetSurfaceLevel()创建幕外表面。GetSurfaceLevel()函数是一个可以从纹理对象中获取表面对象的函数。该表面就是要渲染的表面,而纹理对象将场景存储为一幅图像,而不是将其显示在屏幕上。GetSurfaceLevel()函数原型如程序清单4.23所示。其中,Level是创建表面时的纹理资源级别。ppSurfaceLevel是正在创建的LPDIRECT3DSURFACE9对象。
程序清单4.23 GetSurfaceLevel()函数原型
HRESULT GetSurfaceLevel(
UINT Level, // 创建表面时的纹理资源级别
IDirect3DSurface9** ppSurfaceLevel // 正在创建的LPDIRECT3DSURFACE9对象
);
有了这两个函数,就需要一个渲染用的幕外表面和纹理。要牢记:为了避免内存泄漏,在使用完对象后,一定要释放它们。到目前为止,这对所创建和使用的每个Direct3D对象都是如此。
当要渲染表面时,有两件事要做。首先,需要一份后台缓存表面的副本,这样在处理完幕外表面时,可以返回正常的渲染。这通过使用Direct3D设备函数GetBackBuffer()就可以实现。GetBackBuffer()函数原型如程序清单4.24所示,其中iSwapChain是一个无符号整数,用于指定正在使用的交换链索引,BackBuffer是后台缓存索引,Type在Direct3D9.0中只能是D3DBACKBUFFER_TYPE_MODO,ppBackBuffer是保存后台缓存的LPDIRECT3DSURFACE9对象。
程序清单4.24 GetBackBuffer()函数原型
HRESULT GetBackBuffer(
UINT iSwapChain, // 指定正在使用的交换链索引
UINT BackBuffer, // 后台缓存索引
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface9 ** ppBackBuffer // 保存后台缓存的LPDIRECT3DSURFACE9对象
);
其次,当有了后台缓存后,就可以转换到想要用的所有渲染目标,这只需调用Direct3D设备函数SetRenderTarget()即可。SetRenderTarget()函数原型如程序清单4.25所示,其中RenderTargetIndex是渲染目标的索引,pRenderTarget代表要绘制的LPDIRECT3DSURFACE9对象。
HRESULT SetRenderTarget(
DWORD RenderTargetIndex, // 渲染目标的索引
IDirect3DSurface9 * pRenderTarget // 要绘制的LPDIRECT3DSURFACE9对象
);
至此,就得到该演示程序的全部内容。有了这4个函数,就包含了所要渲染的全部内容。既然已经了解到这一点,接下来就可以开发一个程序,实现幕外渲染功能。
#include<d3d9.h>
#include<d3dx9.h>
#define WINDOW_CLASS "UGPDX"
#define WINDOW_NAME "Off-Screen Rendering"
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
// Function Prototypes...
bool InitializeD3D(HWND hWnd, bool fullscreen);
bool InitializeObjects();
void RenderScene();
void Shutdown();
// Direct3D object and device.
LPDIRECT3D9 g_D3D = NULL;
LPDIRECT3DDEVICE9 g_D3DDevice = NULL;
// Matrices.
D3DXMATRIX g_projection;
D3DXMATRIX g_ViewMatrix;
// stD3DVertex buffer to hold the square's geometry.
LPDIRECT3DVERTEXBUFFER9 g_VertexBuffer = NULL;
// g_Teapot object and its material.
LPD3DXMESH g_Teapot = NULL;
D3DMATERIAL9 g_Material;
float g_RotationAngle = 0.0f;
// Light object.
D3DLIGHT9 g_Light;
// Back buffer, offscreen texture, offscreen surface.
LPDIRECT3DSURFACE9 g_BackSurface = NULL;
LPDIRECT3DTEXTURE9 g_SurfaceTexture = NULL;
LPDIRECT3DSURFACE9 g_OffScreenSurface = NULL;
// A structure for our custom vertex type
struct stD3DVertex
{
float x, y, z;
float tu, tv;
};
// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_VERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
case WM_KEYUP:
if(wParam == VK_ESCAPE) PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)
{
// Register the window class
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
WINDOW_CLASS, NULL };
RegisterClassEx(&wc);
// Create the application's window
HWND hWnd = CreateWindow(WINDOW_CLASS, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
100, 100, WINDOW_WIDTH, WINDOW_HEIGHT,
GetDesktopWindow(), NULL, wc.hInstance, NULL);
// Initialize Direct3D
if(InitializeD3D(hWnd, false))
{
// Show the window
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
// Enter the message loop
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
RenderScene();
}
}
// Release any and all resources.
Shutdown();
// Unregister our window.
UnregisterClass(WINDOW_CLASS, wc.hInstance);
return 0;
}
bool InitializeD3D(HWND hWnd, bool fullscreen)
{
D3DDISPLAYMODE displayMode;
// Create the D3D object.
g_D3D = Direct3DCreate9(D3D_SDK_VERSION);
if(g_D3D == NULL) return false;
// Get the desktop display mode.
if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode)))
return false;
// Set up the structure used to create the D3DDevice
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
if(fullscreen)
{
d3dpp.Windowed = FALSE;
d3dpp.BackBufferWidth = WINDOW_WIDTH;
d3dpp.BackBufferHeight = WINDOW_HEIGHT;
}
else
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = displayMode.Format;
d3dpp.BackBufferCount = 1;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// Create the D3DDevice
if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_D3DDevice)))
{
return false;
}
// Initialize any objects we will be displaying.
if(!InitializeObjects()) return false;
return true;
}
bool InitializeObjects()
{
// Setup the g_Light source.
g_Light.Type = D3DLIGHT_DIRECTIONAL;
g_Light.Direction = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
g_Light.Diffuse.r = 1;
g_Light.Diffuse.g = 1;
g_Light.Diffuse.b = 1;
g_Light.Diffuse.a = 1;
g_Light.Specular.r = 1;
g_Light.Specular.g = 1;
g_Light.Specular.b = 1;
g_Light.Specular.a = 1;
g_D3DDevice->SetLight(0, &g_Light);
g_D3DDevice->LightEnable(0, TRUE);
// Setup the material properties for the teapot.
ZeroMemory(&g_Material, sizeof(D3DMATERIAL9));
g_Material.Diffuse.r = g_Material.Ambient.r = 0.6f;
g_Material.Diffuse.g = g_Material.Ambient.g = 0.6f;
g_Material.Diffuse.b = g_Material.Ambient.b = 0.7f;
g_Material.Specular.r = 0.4f;
g_Material.Specular.g = 0.4f;
g_Material.Specular.b = 0.4f;
g_Material.Power = 8.0f;
// Create the teapot.
if(FAILED(D3DXCreateTeapot(g_D3DDevice, &g_Teapot, NULL)))
return false;
// Create the texture that will be rendered to.
if(FAILED(D3DXCreateTexture(g_D3DDevice, WINDOW_WIDTH,
WINDOW_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &g_SurfaceTexture))) return false;
// Set texture to offscreen surface. 将纹理与幕外表面关联起来
g_SurfaceTexture->GetSurfaceLevel(0, &g_OffScreenSurface);
// Square that will be textured with offscreen rendering data.
stD3DVertex square[] =
{
{-1, 1, -1, 0, 0}, {1, 1, -1, 1, 0},
{-1, -1, -1, 0, 1}, {1, -1, -1, 1, 1}
};
g_D3DDevice->CreateVertexBuffer(4 * sizeof(stD3DVertex), 0,
D3DFVF_VERTEX, D3DPOOL_DEFAULT,
&g_VertexBuffer, NULL);
void *pData = NULL;
g_VertexBuffer->Lock(0, sizeof(square), (void**)&pData, 0);
memcpy(pData, square, sizeof(square));
g_VertexBuffer->Unlock();
// Set the image states to get a good quality image.
g_D3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_D3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Set default rendering states.
g_D3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
//让逆时针的三角形显示出来
g_D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
//启用深度缓存
g_D3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
// Set the projection matrix.
D3DXMatrixPerspectiveFovLH(&g_projection, 45.0f,
WINDOW_WIDTH/WINDOW_HEIGHT, 0.1f, 1000.0f);
g_D3DDevice->SetTransform(D3DTS_PROJECTION, &g_projection);
// Define camera information.
D3DXVECTOR3 cameraPos(0.0f, 0.0f, -4.0f);
D3DXVECTOR3 lookAtPos(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 upDir(0.0f, 1.0f, 0.0f);
// Build view matrix.
D3DXMatrixLookAtLH(&g_ViewMatrix, &cameraPos,
&lookAtPos, &upDir);
return true;
}
void RenderScene()
{
// Get a copy of the back buffer.
g_D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &g_BackSurface);
// Prepare to draw to the offscreen surface.
g_D3DDevice->SetRenderTarget(0, g_OffScreenSurface);
// Clear the offscreen surface.
g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET/* | D3DCLEAR_ZBUFFER*/, D3DCOLOR_XRGB(200,200,200), 1.0f, 0);
// Begin the scene. Start rendering.
g_D3DDevice->BeginScene();
// Turn on lighting.
g_D3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
// Set projection.
g_D3DDevice->SetTransform(D3DTS_PROJECTION, &g_projection);
// Create rotation matrix to rotate teapot.
D3DXMATRIXA16 w;
D3DXMatrixRotationY(&w, g_RotationAngle);
g_D3DDevice->SetTransform(D3DTS_WORLD, &w);
// Add to the rotation.
g_RotationAngle += 0.02f;
if(g_RotationAngle >= 360) g_RotationAngle = 0.0f;
// Apply the view (camera).
g_D3DDevice->SetTransform(D3DTS_VIEW, &g_ViewMatrix);
// Set the material and draw the Teapot.
g_D3DDevice->SetMaterial(&g_Material);
g_D3DDevice->SetTexture(0, NULL);
g_Teapot->DrawSubset(0);
// End the scene. Stop rendering.
g_D3DDevice->EndScene();
// Switch back to our back buffer.
g_D3DDevice->SetRenderTarget(0, g_BackSurface);
// Clear the back buffer.
g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
// Begin the scene. Start rendering.
g_D3DDevice->BeginScene();
// Turn off lighting. Don't need it.
g_D3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
// Set projection.
g_D3DDevice->SetTransform(D3DTS_PROJECTION, &g_projection);
// Rotate just a little to see this is a flat surface.
D3DXMatrixRotationY(&w, 120);
g_D3DDevice->SetTransform(D3DTS_WORLD, &w);
// Apply the view (camera).
g_D3DDevice->SetTransform(D3DTS_VIEW, &g_ViewMatrix);
g_D3DDevice->SetTexture(0, g_SurfaceTexture);
g_D3DDevice->SetStreamSource(0, g_VertexBuffer, 0, sizeof(stD3DVertex));
g_D3DDevice->SetFVF(D3DFVF_VERTEX);
g_D3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
// End the scene. Stop rendering.
g_D3DDevice->EndScene();
// Display the scene.
g_D3DDevice->Present(NULL, NULL, NULL, NULL);
}
void Shutdown()
{
if(g_D3DDevice != NULL) g_D3DDevice->Release();
g_D3DDevice = NULL;
if(g_D3D != NULL) g_D3D->Release();
g_D3D = NULL;
if(g_VertexBuffer != NULL) g_VertexBuffer->Release();
g_VertexBuffer = NULL;
if(g_Teapot != NULL) g_Teapot->Release();
g_Teapot = NULL;
if(g_BackSurface != NULL) g_BackSurface->Release();
g_BackSurface = NULL;
if(g_SurfaceTexture != NULL) g_SurfaceTexture->Release();
g_SurfaceTexture = NULL;
if(g_OffScreenSurface != NULL) g_OffScreenSurface->Release();
g_OffScreenSurface = NULL;
}