【原创】把D3D画面渲染到桌面!不用创建任何窗口
其实这个很简单,只是创建设备的时候那个hwnd有点不一样! 可能有人就会说,用GetDesktopWindows()获得桌面的句柄不就行了?那就错了!这样会没有效果的!正确的代码如下:
其实这个很简单,只是创建设备的时候那个hwnd有点不一样! 可能有人就会说,用GetDesktopWindows()获得桌面的句柄不就行了?那就错了!这样会没有效果的!正确的代码如下:
引用
DirectX SDK 2006学习笔记1——框架
友情提醒:所谓的框架是指SDK目录下\Samples\C++\Common路径下的DXUT系列函数包装。学习框架的前提是必须有足够的Windows API,GUI编程经验,必须熟悉Windows的消息机制,回调机制,最好有万行左右的C/C++编程经验。MFC在这里没有任何用处。另外我觉得最好在看程序之前对于D3D的所有概念有点了解,什么是vertex,texture,matrix,lighting,mesh等等,以及相关的数学概念。这些都可以在网上找到中文翻译,帮助你快速入门。
使用dxut框架进行directx设计时,拷贝directx sdk 安装目录\Samples\C++\中的Common文件内容到你的程序目录下(如下图)。
main.cpp 源文件内容如下:
#include < dxstdafx.h >
// --------------------------------------------------------------------------------------
// 当Directx3D设备被创建后,这个回调函数马上被调用,因为D3DPOOL_MANAGED资源在设备被销毁后
// 需要重新装载,这里是最佳创建D3DPOOL_MANAGED资源的地方,创建的资源应在 OnDestroyDevice
// 函数中销毁 。
// --------------------------------------------------------------------------------------
HRESULT CALLBACK OnCreateDevice( IDirect3DDevice9 * pd3dDevice, const D3DSURFACE_DESC * pBackBufferSurfaceDesc, void * pUserContext )
{
return S_OK;
}
// --------------------------------------------------------------------------------------
// 当direct3d设备被复位后,这个函数立即被调用,这里最好放置D3DPOOL_DEFAULT 资源代码,因为这
// 这些资源在设备丢失后需要重新装载。在这里创建的资源应该在OnLostDevice 函数中释放
// --------------------------------------------------------------------------------------
HRESULT CALLBACK OnResetDevice( IDirect3DDevice9 * pd3dDevice, const D3DSURFACE_DESC * pBackBufferSurfaceDesc, void * pUserContext )
{
return S_OK;
}
// --------------------------------------------------------------------------------------
// 在Direct3D设备进入lost状态后在IDirect3DDevice9::Reset 调用之前调用此函数,在OnResetDevice
// 中创建的资源必须在这里释放,通常包括所有的D3DPOOL_DEFAULT 资源,
// --------------------------------------------------------------------------------------
void CALLBACK OnLostDevice( void * pUserContext )
{
}
// --------------------------------------------------------------------------------------
// 这个回调函数在每帧的开始被调用,这个在你的程序中用来处理场景更新最好的位置,但不能包含实际的
// 场景渲染调用,渲染工作应该放在OnFrameRender 回调函数中。常用于矩阵转换、摄像机等操作。
// --------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( IDirect3DDevice9 * pd3dDevice, double fTime, float fElapsedTime, void * pUserContext )
{
}
// --------------------------------------------------------------------------------------
// 此回调函数在每Frame最后被调用,在场景上执行所有的渲染调用,当窗口需要重绘(处理WM_PAINT消
// 息)时此函数也会被调用(此时不调用OnFrameMove),在此函数返回后,DXUT将调用
// IDirect3DDevice9::Present 来显示翻转链中下一个缓冲区内容。
// --------------------------------------------------------------------------------------
void CALLBACK OnFrameRender( IDirect3DDevice9 * pd3dDevice, double fTime, float fElapsedTime, void * pUserContext )
{
HRESULT hr;
V( pd3dDevice -> Clear( 0 , NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0 , 0 , 0 , 0 ), 1.0f , 0 ) );
if (SUCCEEDED(pd3dDevice -> BeginScene()))
{
// 更新图像
pd3dDevice -> EndScene();
}
}
// --------------------------------------------------------------------------------------
// 此回调函数在direct3d设备被销毁时调用,通常发生在程序终止,在OnCreateDevice 中创建的资源,要
// 在这里释放,通常包含所有的D3DPOOL_MANAGED资源
// IDirect3DDevice9::Present 来显示翻转链中下一个缓冲区内容。
// --------------------------------------------------------------------------------------
void CALLBACK OnDestroyDevice( void * pUserContext )
{
}
INT WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
{
// 设置回调函数,这些函数允许DXUT通知应用程序更换设备,用户输入和窗口消息。
// 回调函数是可选的,因此你要做的仅是设置你感兴趣的事件的回调函数。
DXUTSetCallbackDeviceCreated( OnCreateDevice );
DXUTSetCallbackDeviceReset( OnResetDevice );
DXUTSetCallbackDeviceLost( OnLostDevice );
DXUTSetCallbackDeviceDestroyed( OnDestroyDevice );
DXUTSetCallbackFrameRender( OnFrameRender );
DXUTSetCallbackFrameMove( OnFrameMove );
// 初始化DXUT并创建想要的Win32窗口和应用程序的Direct3D设备。调用这些
// 可选函数中的每一个,此外它们允许你设置几个选项来控制框架的行为。
DXUTInit( TRUE, TRUE, TRUE );
// directx 编码是unicode环境,所以字符串之前要加 L 。
DXUTCreateWindow( L " Welcome to topameng.spaces.live.com " );
DXUTCreateDevice( D3DADAPTER_DEFAULT, TRUE, 640 , 480 );
// 通过DXUT来处理消息循环并分派渲染调用。当在空闲时间和处理窗口消息的
// 时间间隔时,框架将调用OnFrameMove和OnFrameRender回调函数。
DXUTMainLoop();
return DXUTGetExitCode();
} 
这章主要介绍一下DXUT 里面的GUI元素。要在图形界面中添加GUI元素,首先要定义一个DialogResourceManager对象用来管理对话框资源。DialogResourceManager 管理渲染时状态、Sprite控制批量显示更新、对话框字体、纹理等等。CDXUTDialog 相当于MFC里面的对话框,作为各种控件资源的容器。CD3DSettingsDlg 是一个ms已经写好的对话框类,可以用来设置各种Direct3DDevice9 创建时的参数。点击该对话框的ok 按钮,D3D设备将会重建。
通过DXUT使用设备
DirectX设备的创建在DXUT中得到了改进。你可以让你的应用程序直接创建设备而其它有框架提供的特征仍然可用。
创建设备
选择最佳的设备设置
修改可用的设备设置
降为软件顶点处理
使用你自己的设备
创建设备
典型地,你将通过标准的Direct3D方法创建设备
HRESULT CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 **ppReturnedDeviceInterface
);
这个方法需要有效的适配器,设备类型(HAL or REF),窗口句柄,行为标志(software/hardware vertex processing 和其它驱动标志),以及呈现参数(presentation parameters).此外,D3DPRESENT_PARAMETER结构体还拥有大量的成员指定后备缓冲区,多重采样设定,交换效果,窗口模式,深度模版缓冲,刷新率,呈现间隔,以及呈现标志。
为所有这些参数选择有效的设定是具有挑战性的。框架通过DXUTCreateDevice函数简化了这一选择过程。
HRESULT DXUTCreateDevice(
UINT AdapterOrdinal = D3DADAPTER_DEFAULT,
BOOL bWindowed = TRUE,
INT nSuggestedWidth = 640,
INT nSuggestedHeight = 480,
LPDXUTCALLBACKISDEVICEACCEPTABLE pCallbackIsDeviceAcceptable = NULL,
LPDXUTCALLBACKMODIFYDEVICESETTINGS pCallbackModifyDeviceSettings = NULL
);
最基本的用法是全部使用缺省参数调用:
DXUTCreateDevice();
通过这样的调用框架使用缺省设置创建一个在大多数情况下可用的设备。缺省的设置如下:
| Direct3D Creation Flag | Description | Default Value from DXUTCreateDevice |
|---|---|---|
| AdapterFormat parameter of CheckDeviceFormat | Adapter surface format. | Desktop display mode, or D3DFMT_X8R8G8B8 if the desktop display mode is less than 32 bits. |
| Adapter parameter of IDirect3D9::CreateDevice | Display adapter ordinal. | D3DADAPTER_DEFAULT |
| D3DPRESENT_PARAMETERS. BackBufferCount | Number of back buffers. | 2, indicating triple buffering. |
| D3DPRESENT_PARAMETERS. BackBufferFormat | Back buffer format. | Desktop display mode, or D3DFMT_X8R8G8B8 if the desktop display mode is less than 32 bits. |
| D3DPRESENT_PARAMETERS. AutoDepthStencilFormat | Depth format of the automatic depth-stencil surface that the device will create. | D3DFMT_D16 if the backbuffer format is 16 bits or less, or D3DFMT_D32 otherwise. |
| The DeviceType parameter of IDirect3D9::CreateDevice | Enumerated type of the device. | D3DDEVTYPE_HAL if available, otherwise D3DDEVTYPE_REF or failure code if neither is available. |
| D3DPRESENT_PARAMETERS. MultiSampleQuality | Quality level. | MultiSampleQuality = 0, indicating multisampling is disabled. |
| D3DPRESENT_PARAMETERS. Flags | Presentation parameters flags. | D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL |
| D3DPRESENT_PARAMETERS. PresentationInterval | Presentation interval. | D3DPRESENT_INTERVAL_IMMEDIATE for windowed mode, or D3DPRESENT_INTERVAL_DEFAULT for full-screen mode. |
| D3DPRESENT_PARAMETERS. FullScreen_RefreshRateInHz | Rate at which the display adapter refreshes the screen. | 0, indicating windowed mode. |
| D3DPRESENT_PARAMETERS. BackBufferWidth and .BackBufferHeight | Display mode resolution. | 640 x 480 pixels for windowed mode, or the desktop resolution for full-screen mode. |
| D3DPRESENT_PARAMETERS. AutoDepthStencilFormat | Stencil format of the automatic depth-stencil surface that the device will create. | D3DFMT_D16 if the backbuffer format is 16 bits or less, or D3DFMT_D32 otherwise. |
| D3DPRESENT_PARAMETERS. SwapEffect | Swap effect. | D3DSWAPEFFECT_DISCARD |
| BehaviorFlags parameter of IDirect3D9::CreateDevice | Vertex processing flags. | D3DCREATE_HARDWARE_VERTEXPROCESSING if supported, otherwise D3DCREATE_SOFTWARE_VERTEXPROCESSING. |
| D3DPRESENT_PARAMETERS. Windowed | Windowed or full-screen mode. | true, indicating windowed mode. |
| hFocusWindow parameter of CreateDevice | Handle to the created window (see Using Application Windows with DXUT). | hWndFocus parameter of DXUTSetWindow |
| D3DPRESENT_PARAMETERS. hDeviceWindow | Handle to the device window. | hWndDeviceFullScreen or hWndDeviceWindowed parameters of DXUTSetWindow |
| D3DPRESENT_PARAMETERS. EnableAutoDepthStencil | Depth-stencil buffer creation flag. | true. |
选择最佳的设备设置
你可以使用IsDeviceAcceptable回调函数帮助框架为你的应用程序选择最佳的设备设置,就像下面的代码:
bool CALLBACK IsDeviceAcceptable(
D3DCAPS9* pCaps,
D3DFORMAT AdapterFormat,
D3DFORMAT BackBufferFormat,
bool bWindowed,
void* pUserContext )
{
// TODO: return true for acceptable settings and false otherwise.
return true;
}
这个回调函数的模型基于LPDXUTCALLBACKISDEVICEACCEPTABLE原型(This callback function is modeled on the prototype LPDXUTCALLBACKISDEVICEACCEPTABLE),框架为每个唯一的以下5个设置的有效组合调用这个函数一次:
D3DDEVTYPE DeviceType;
UINT AdapterOrdinal;
D3DFORMAT AdapterFormat;
D3DFORMAT BackBufferFormat;
bool Windowed;
注意适配器序号和设备类型没有直接的传入回调函数,而是分别作为D3DCAPS9结构体的成员。
通过这个回调函数,应用程序可以拒绝任何它不支持的或不想要的组合。例如,应用程序可以使用下面的代码拒绝16bits的后备缓冲区格式和所有至少不能支持像素着色器PS_2_0的设备:
bool CALLBACK IsDeviceAcceptable(
D3DCAPS9* pCaps,
D3DFORMAT AdapterFormat,
D3DFORMAT BackBufferFormat,
bool bWindowed )
{
if( pCaps->PixelShaderVersion < D3DPS_VERSION(2,0) )
return false;
if( BackBufferFormat == D3DFMT_X1R5G5B5 || BackBufferFormat == D3DFMT_R5G6B5 )
return false;
return true;
}
为每个唯一的组合调用回调函数后,框架排列剩下的可用组合,并选择它们当中最好的。排名较高的如下:
D3DDEVTYPE_HAL,获取硬件加速
如果应用程序以全屏模式显示,框架更趋向于使用匹配桌面格式的适配器格式,这样可以在全屏与窗口之间快速切换。例外的是,如果桌面显示模式小于32位,框架更趋向于D3DFMT_X8R8G8B8.
匹配适配器格式的后备缓冲区格式
在选择了这些排名高的组合后,要创建设备,行为标志和呈现参数仍然是需要的。对于这些设置,Direct3D使用上面表中的缺省值。
修改可用的设备设置
应用程序可以通过使用第二个可选的回调函数修改对框架可用的设置,这个函数是ModifyDeviceSettings:
bool CALLBACK ModifyDeviceSettings(
DXUTDeviceSettings* pDeviceSettings,
const D3DCAPS9* pCaps )
{
// TODO: Include device creation requirements here.
// 返回真创建设备返回False保持当前设置
return true;
}
这个函数是基于原型LPDXUTCALLBACKMODIFYDEVICESETTINGS的。DXUTDeviceSettings结构体被框架定义为:
struct DXUTDeviceSettings
{
UINT AdapterOrdinal;
D3DDEVTYPE DeviceType;
D3DFORMAT AdapterFormat;
DWORD BehaviorFlags;
D3DPRESENT_PARAMETERS pp;
};
这个结构体包含了创建设备所需要的所有东西,除了窗口句柄,它被假定为先前创建的窗口的句柄。框架用有效的数据填充这个结构体,然后允许应用程序通过ModifyDeviceSettings回调函数改变设备创建的选择。
在这个回调函数中,应用程序可以在DXUTDeviceSettings结构体中改变行为标志以及呈现参数,乃至结构体中任何其它的东西。如果应用程序在回调函数中什么都不改变,设备会成功的创建。然而,对设备创建设置的任何改变都需要被设备支持,否则可能会导致设备创建失败。
比如,如果应用程序需要一个D3DFMT_D24S8的深度模板缓冲区格式,就必须验证设备是否支持,就像下面的代码:
bool CALLBACK ModifyDeviceSettings(
DXUTDeviceSettings* pDeviceSettings,
const D3DCAPS9* pCaps )
{
IDirect3D9* pD3D = DXUTGetD3DObject();
if( SUCCEEDED( pD3D->CheckDeviceFormat(
pDeviceSettings->AdapterOrdinal,
pDeviceSettings->DeviceType,
pDeviceSettings->AdapterFormat,
D3DUSAGE_DEPTHSTENCIL,
D3DRTYPE_SURFACE,
D3DFMT_D24S8 ) ) )
{
if( SUCCEEDED( pD3D->CheckDepthStencilMatch(
pDeviceSettings->AdapterOrdinal,
pDeviceSettings->DeviceType,
pDeviceSettings->AdapterFormat,
pDeviceSettings->pp.BackBufferFormat,
D3DFMT_D24S8 ) ) )
{
pDeviceSettings->pp.AutoDepthStencilFormat = D3DFMT_D24S8;
}
}
return true;
}
候选的方案是,回调函数可以使用框架的CD3DEnumeration 对象验证D3DFMT_D24S8是否被支持:
bool CALLBACK ModifyDeviceSettings(
DXUTDeviceSettings* pDeviceSettings,
const D3DCAPS9* pCaps )
{
CD3DEnumeration *pEnum = DXUTGetEnumeration();
CD3DEnumDeviceSettingsCombo *pCombo;
pCombo = pEnum->GetDeviceSettingsCombo( pDeviceSettings );
if( pCombo->depthStencilFormatList.Contains( D3DFMT_D24S8 ) )
pDeviceSettings->pp.AutoDepthStencilFormat = D3DFMT_D24S8;
return true;
}
应用程序修改了设备的设置后,框架就会用新的设置创建设备。
DirectX April 2005 SDK Update中的更新,ModifyDeviceSettings 回调函数返回了一个bool值。如果应用程序返回true框架继续正常的创建设备。如果返回false框架不改变设备并且保持当前的设备,如果已经有一个存在的话。这允许应用程序能够拒绝框架将设备改变到程序不能使用的请求。例如,在多监视器的缺省配置下,在监视器之间拖动窗口会导致框架改变设备。然而,如果应用程序不能使用其它的设备的话,它应当可以拒绝改变,并继续使用当前的设备。
回降到软件顶点处理
如果你设置一个Direct3D设备到支持像素处理却不支持顶点处理的硬件,你会因此需要改变行为标志。为了确保正确地降到软件顶点处理,谨防你不能拒绝一个基于IsDeviceAcceptable回调函数中顶点着色器版本的设备,并确保行为标志在ModifyDeviceSettings 回调函数中被正确调整。这儿有一个例子演示怎样做这些事情。
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings,
const D3DCAPS9* pCaps )
{
// If device doesn't support HW T&L or doesn't support 1.1 vertex
// shaders in HW, then switch to SWVP.
if( (pCaps->DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == 0 ||
pCaps->VertexShaderVersion < D3DVS_VERSION(1,1) )
{
pDeviceSettings->BehaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
else
{
pDeviceSettings->BehaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
return true;
}
使用你自己的设备
你没有必要依赖于框架来创建Direct3D设备。应用程序自己可以创建设备并将他传递给框架使用。就像应用程序可以覆盖框架的window creation 设置。简单的使用你想要的设置创建一个设备,然后调用 DXUTSetDevice函数让框架在你的设备上渲染。
注意:如果应用程序创建了不依赖于框架的设备,那么应用程序也必须在主循环执行完以后亲自的通过cleanup 释放设备接口。
另请参阅
通过DXUT作更高级的设备选择
·内容
渲染到纹理是D3D中的一项高级技术。一方面,它很简单,另一方面它很强大并能产生很多特殊效果。 比如说发光效果,环境映射,阴影映射,都可以通过它来实现。渲染到纹理只是渲染到表面的一个延伸。我们只需再加些东西就可以了。首先,我们要创造一个纹理,并且做好一些防范措施。第二步我们就可以把适当的场景渲染到我们创建的纹理上了。然后,我们把这个纹理用在最后的渲染上。
·main.cpp
首先我们得声明所需要的对象。当然我们需要一张用来渲染的纹理。此外,我们还需要两个Surface对象。一个是用来存储后台缓冲区,一个用来当纹理的渲染对象。后面我再详细介绍它们。另外我们还需要两个矩阵,一个是用来当纹理的投影矩阵,另一个是存储原来的矩阵。
LPDIRECT3DTEXTURE9 pRenderTexture = NULL;
LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
D3DXMATRIX matProjection,matOldProjection;
现在我们来创建纹理。前两个参数是纹理的宽度和高度,第三个参数是纹理的多级渐进纹理序列参数,在这里是设为1,第四个参数非常重要而且必须设为D3DUSAGE_RENDERTARGET,表明我们所创建的纹理是用来渲染的。剩下的参数就是指纹理格式,顶点缓冲区的内存位置,和一个指向纹理的指针。当纹理是用来当渲染对象时,顶点缓冲区的内存位置必须设为D3D_DEFAILT。
g_App.GetDevice()->CreateTexture(256,256,1,D3DUSAGE_RENDERTARGET,D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);
为了访问纹理内存对象,我们需要一个Surface对象,因为D3D中的纹理是用这样的一个Surface来存储纹理数据的。为了得到纹理表面的Surface,我们需要调用方法GetSurfaceLevel() 。第一个参数我们设为0,第二个参数为一个指向surface对象的指针。
pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
下一步就是创建一个适合纹理维数的投影矩阵,因为纹理的横纵比和后台缓冲区的不一样。
D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI / 4.0f,1,1,100);
在我们的循环渲染之前,我们必须保存后台缓冲区和它的投影矩阵。
g_App.GetDevice()->GetTransform(D3DTS_PROJECTION,&matOldProjection);
g_App.GetDevice()->GetRenderTarget(0,&pBackBuffer);
渲染循环函数可以分为两个部分。第一部分是渲染到纹理的过程。因此,渲染对象必须设为纹理表面。然后我们就可以把东西渲染到这个对象上了。渲染到另一个表面上和正常地渲染到后台缓冲区差不多。只有一点不同,那就是先不调用Prensent()函数,因为纹理上的内容并不需要显示在屏幕上。象平时一样,我们先要重置表面颜色缓冲区,并且调用BeginSence()和EndSence()方法。为了能够适当的渲染,我们必须设置和纹理表面相符的投影矩阵。否则最后的图象可能被扭曲
//render-to-texture
g_App.GetDevice()->SetRenderTarget(0,pRenderSurface); //set new render target
g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100,100,100),1.0f,0); //clear texture
g_App.GetDevice()->BeginScene();
g_App.GetDevice()->SetTexture(0,pPyramideTexture);
D3DXMatrixRotationY(&matRotationY,fRotation);
D3DXMatrixTranslation(&matTranslation,0.0f,0.0f,5.0f);
g_App.GetDevice()->SetTransform(D3DTS_WORLD,&(matRotationY * matTranslation));
g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matProjection); //set projection matrix
g_App.GetDevice()->SetStreamSource(0,pTriangleVB,0,sizeof(D3DVERTEX));
g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);
g_App.GetDevice()->EndScene();
渲染循环的第二部分就是渲染最后场景的过程(也就是显示到屏幕上的过程)。渲染对象重新设为后台缓冲区,投影矩阵重新设为原来的投影矩阵。由于纹理已经准备好了,所以它和纹理层0相关联。
//render scene with texture
g_App.GetDevice()->SetRenderTarget(0,pBackBuffer); //set back buffer
g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
g_App.GetDevice()->BeginScene();
g_App.GetDevice()->SetTexture(0,pRenderTexture); //set rendered texture
g_App.GetDevice()->SetTransform(D3DTS_WORLD,&matTranslation);
g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matOldProjection); //restore projection matrix
g_App.GetDevice()->SetStreamSource(0,pQuadVB,0,sizeof(D3DVERTEX));
g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
g_App.GetDevice()->EndScene();
g_App.GetDevice()->Present(NULL,NULL,NULL,NULL);
最后我们通过调用Release()方法释放Surface对象。
pRenderSurface->Release();
pRenderSurface = NULL;
pBackBuffer->Release();
pBackBuffer = NULL;
渲染到纹理能让你做很多事情,但是你必须注意一些限制。首先深度缓冲区必须总是大于或等于渲染对象的大小。此外,渲染对象和深度缓冲区的格式必须一致。
引用
Direct3D10特性预览将我在GameResBlog的老文章重新贴回来,发现写的还是很有味道,嘿嘿~~刚拿到DirectX Dec2005 SDK,发现竟然有D3D10的文档和例子,速研究了一下,以下只是个人的读书笔记,仅作参考1.去掉了固定管线
文档里列出了用DX10特性模拟的一些固定管线的操作,MS那么大度把DX都开源了(^O^)。2.去掉了以前版本DX的设备能力检查(CAPS)
为DX10和Windows Vista提供的显示硬件必须满足DX10的所有硬件特性。这样对于开发者就比较可以放心的使用各种硬件特性了,很类似Console平台
的开发。3.状态对象(State Object)
"从100多个渲染状态中解脱出来吧!"
D3D10对渲染状态这个概念进行淡化,一方面使用全Shader化的架构使得状态的前后设置和互相影响对渲染成功率降低到最少
另外对API架构也更为简洁,另一方面对一些关键渲染状态进行封装和分类。主要分类有:
Input Layout Object 输入层对象
这个东西很类似D3D9里的顶点声明,也就是对用户输入数据进行整合和系统化
Rasterizer Object 光栅化对象
这部分主要控制光栅器的行为:填充模式(FILL_MODE),剔除模式(CULL_MODE),多采样,DepthBias等等
DepthStencil Object 深度缓冲对象
主要控制深度缓冲的行为,像Z-buffer Enable之类的
Blend Object 混合对象
设置象素混合的方法,类似AlphaBlend SrcAlpha ,DestAlpha等等
Sampler Object 采样器对象
设置纹理采样状态,包括过滤器和MipMap
4.新的资源访问模式和资源视图(View)概念
如果对比D3D9的Shader使用代码和D3D10的类似代码会发现一个不同。
D3D9 Shader需要对纹理进行操作时,需要将纹理设置到Shader就可以了,而D3D10里没有那么简单,设置前必须将
各种资源整合后为资源创建一个视图,再提交给Shader访问。这种操作在环境贴图里是很有好处的。对于CubeMap的6张纹理
就可以采用一个视图设置到设备,而自己要访问每张贴图只要轻松访问视图就可以了,也就是说把资源规整和集成化。5.新的可编程图形层(Stage)-几何Shader(Geometry Shader)
原来的VS和PS只是对逐个顶点或象素进行处理,而新的GS可以对每个顶点或象素的临近顶点设置Shader。也就是可以对批量几何进行处理
GS的用途有:
点精灵
动态粒子系统
皮毛系统
卷积阴影
单Pass渲染到球形贴图
逐多边形材质交换
逐多边形材质设置
6.设备的创建要求对ViewPort进行设置
D3D9里无需对ViewPort进行设置就可以进行渲染,而且默认的RenderTarget就是后备缓冲
而在D3D10里,这个过程变得更为自主化。取出后备缓冲的格式,将RenderTarget设置为屏幕
这让人感到D3D10设计更趋向于成熟的引擎设计。7.流输出层(Stream Output State)
这个层的功能是将VS和GS处理完成的数据输出给用户,由用户进行处理后再反馈给管线继续处理
8.多边形拓扑结构从绘制代码分离
现在可以单独设置拓扑结构
g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
和使用Draw函数绘制了,很类似OpenGL
9.严格的设备对象创建时间验证
这里的设备对象就是从设备创建出来的资源。为了减少CPU占用,D3D10重新设计了硬件资源调用调度,所有的设备对象都用面向对象的方法被设备
管理。这种设计方法避免了在渲染期的资源创建操作。
10.去掉BeginScene和EndScene
这个对于图形API确实多余
Dec2005 sdk里提供的文档让人很快想到这只是一个过渡版本
在DXUT的Mesh.Create函数中可以看到一个很有趣的现象:
创建D3D9对象,使用D3DX9里的载入X模型文件的函数载入X文件
将D3D9的模型数据转成D3D10的类型。这样做无非是在告诉我们一个
这样的信息,D3D10很有可能提供一种新的模型格式来作为研究使用
。Vista的图形系统Avalon从一些视频上分析,使用了大量的XML,所以
新的模型格式很有可能使用XML,并且X格式的解析接口确实不方便。而
XML的分析器可以由第三方提供。使得更多的研究人员能更方便的使用
这种新格式。顺便提供一些缩写对应的含义,这些在一些函数前缀会出现
IA-Input Assembler State
SO-Stream Output State
OM-Output Merger State
VS-Vertex Shader
PS-Pixel Shader
GS-Geometry ShaderPowered by Davy.xu
msn:[email protected]
提高3D图形程序的性能是个很大的课题。图形程序的优化大致可以分成两大任务,一是要有好的场景管理程序,能快速剔除不可见多边形,并根据对象距相机远近选择合适的细节(LOD);二是要有好的渲染程序,能快速渲染送入渲染管线的可见多边形。
我们知道,使用OpenGL或Direct3D渲染图形时,首先要设置渲染状态,渲染状态用于控制渲染器的渲染行为。应用程序可以通过改变渲染状态来控制OpenGL或Direct3D的渲染行为。比如设置Vertex/Fragment Program、绑定纹理、打开深度测试、设置雾效等。
改变渲染状态对于显卡而言是比较耗时的操作,而如果能合理管理渲染状态,避免多余的状态切换,将明显提升图形程序性能。这篇文章将讨论渲染状态的管理。
文档目录:
基本思想
实际问题
渲染脚本
文档内容:
基本思想
我们考虑一个典型的游戏场景,包含人、动物、植物、建筑、交通工具、武器等。稍微分析一下就会发现,实际上场景里很多对象的渲染状态是一样的,比如所有的人和动物的渲染状态一般都一样,所有的植物渲染状态也一样,同样建筑、交通工具、武器也是如此。我们可以把具有相同的渲染状态的对象归为一组,然后分组渲染,对每组对象只需要在渲染前设置一次渲染状态,并且还可以保存当前的渲染状态,设置渲染状态时只需改变和当前状态不一样的状态。这样可以大大减少多余的状态切换。下面的代码段演示了这种方法:
// 渲染状态组链表,由场景管理程序填充
RenderStateGroupList groupList;
// 当前渲染状态
RenderState curState;
……
// 遍历链表中的每个组
RenderStateGroup *group = groupList.GetFirst();
while ( group != NULL )
{
// 设置该组的渲染状态
RenderState *state = group->GetRenderState();
state->ApplyRenderState( curState );
// 该渲染状态组的对象链表
RenderableObjectList *objList = group->GetRenderableObjectList();
// 遍历对象链表的每个对象
RenderableObject *obj = objList->GetFirst();
while ( obj != NULL )
{
// 渲染对象
obj->Render();
obj = objList->GetNext();
}
group = groupList.GetNext();
}
//其中RenderState类的ApplyRenderState方法形如:
void RenderState::ApplyRenderState( RenderState &curState )
{
// 深度测试
if ( depthTest != curState.depthTest )
{
SetDepthTest( depthTest );
curState.depthTest = depthTest;
}
// Alpha测试
if ( alphaTest != curState.alphaTest )
{
SetAlphaTest( alphaTest );
curState.alphaTest = alphaTest;
}
// 其它渲染状态
……
}
这些分组的渲染状态一般被称为Material或Shader。这里Material不同于OpenGL和Direct3D里面用于光照的材质,Shader也不同于OpenGL里面的Vertex/Fragment Program和Direct3D里面的Vertex/Pixel Shader。而是指封装了的显卡渲染图形需要的状态(也包括了OpenGL和Direct3D原来的Material和Shader)。
从字面上看,Material(材质)更侧重于对象表面外观属性的描述,而Shader(这个词实在不好用中文表示)则有用程序控制对象表面外观的含义。由于显卡可编程管线的引入,渲染状态中包含了Vertex/Fragment Program,这些小程序可以控制物体的渲染,所以我觉得将封装的渲染状态称为Shader更合适。这篇文章也将称之为Shader。
上面的代码段只是简单的演示了渲染状态管理的基本思路,实际上渲染状态的管理需要考虑很多问题。
渲染状态管理的问题
消耗时间问题
改变渲染状态时,不同的状态消耗的时间并不一样,甚至在不同条件下改变渲染状态消耗的时间也不一样。比如绑定纹理是一个很耗时的操作,而当纹理已经在显卡的纹理缓存中时,速度就会非常快。而且随着硬件和软件的发展,一些很耗时的渲染状态的消耗时间可能会有减少。因此并没有一个准确的消耗时间的数据。
虽然消耗时间无法量化,情况不同消耗的时间也不一样,但一般来说下面这些状态切换是比较消耗时间的:
Vertex/Fragment Program模式和固定管线模式的切换(FF,Fixed Function Pipeline)
Vertex/Fragment Program本身程序的切换
改变Vertex/Fragment Program常量
纹理切换
顶点和索引缓存(Vertex & Index Buffers)切换
有时需要根据消耗时间的多少来做折衷,下面将会遇到这种情况。
渲染状态分类
实际场景中,往往会出现这样的情况,一类对象其它渲染状态都一样,只是纹理和顶点、索引数据不同。比如场景中的人,只是身材、长相、服装等不同,也就是说只有纹理、顶点、索引数据不同,而其它如Vertex/Fragment Program、深度测试等渲染状态都一样。相反,一般不会存在纹理和顶点、索引数据相同,而其他渲染状态不同的情况。我们可以把纹理、顶点、索引数据不归入到Shader中,这样场景中所有的人都可以用一个Shader来渲染,然后在这个Shader下对纹理进行分组排序,相同纹理的人放在一起渲染。
多道渲染(Multipass Rendering)
有些比较复杂的图形效果,在低档显卡上需要渲染多次,每次渲染一种效果,然后用GL_BLEND合成为最终效果。这种方法叫多道渲染Multipass Rendering,渲染一次就是一个pass。比如做逐像素凹凸光照,需要计算环境光、漫射光凹凸效果、高光凹凸效果,在NV20显卡上只需要1个pass,而在NV10显卡上则需要3个pass。Shader应该支持多道渲染,即一个Shader应该分别包含每个pass的渲染状态。
不同的pass往往渲染状态和纹理都不同,而顶点、索引数据是一样的。这带来一个问题:是以对象为单位渲染,一次渲染一个对象的所有pass,然后渲染下一个对象;还是以pass为单位渲染,第一次渲染所有对象的第一个pass,第二次渲染所有对象的第二个pass。下面的程序段演示了这两种方式:
以对象为单位渲染
// 渲染状态组链表,由场景管理程序填充
ShaderGroupList groupList;
……
// 遍历链表中的每个组
ShaderGroup *group = groupList.GetFirst();
while ( group != NULL )
{
Shader *shader = group->GetShader();
RenderableObjectList *objList = group->GetRenderableObjectList();
// 遍历相同Shader的每个对象
RenderableObject *obj = objList->GetFirst();
while ( obj != NULL )
{
// 获取shader的pass数
int iNumPasses = shader->GetPassNum();
for ( int i = 0; i < iNumPasses; i++ )
{
// 设置shader第i个pass的渲染状态
shader->ApplyPass( i );
// 渲染对象
obj->Render();
}
obj = objList->GetNext();
}
group = groupList->GetNext();
}
以pass为单位渲染
// 渲染状态组链表,由场景管理程序填充
ShaderGroupList groupList;
……
for ( int i = 0; i < MAX_PASSES_NUM; i++ )
{
// 遍历链表中的每个组
ShaderGroup *group = groupList.GetFirst();
while ( group != NULL )
{
Shader *shader = group->GetShader();
int iNumPasses = shader->GetPassNum();
// 如果shader的pass数小于循环次数,跳过此shader
if( i >= iNumPasses )
{
group = groupList->GetNext();
continue;
}
// 设置shader第i个pass的渲染状态
shader->ApplyPass( i );
RenderableObjectList *objList =
group->GetRenderableObjectList();
// 遍历相同Shader的每个对象
RenderableObject *obj = objList->GetFirst();
while ( obj != NULL )
{
obj->Render();
obj = objList->GetNext();
}
group = groupList->GetNext();
}
}
这两种方式各有什么优缺点呢?
以对象为单位渲染,渲染一个对象的第一个pass后,马上紧接着渲染这个对象的第二个pass,而每个pass的顶点和索引数据是相同的,因此第一个pass将顶点和索引数据送入显卡后,显卡Cache中已经有了这个对象顶点和索引数据,后续pass不必重新将顶点和索引数据拷到显卡,因此速度会非常快。而问题是每个pass的渲染状态都不同,这使得实际上每次渲染都要设置新的渲染状态,会产生大量的多余渲染状态切换。
以pass为单位渲染则正好相反,以Shader分组,相同Shader的对象一起渲染,可以只在这组开始时设置一次渲染状态,相比以对象为单位,大大减少了渲染状态切换。可是每次渲染的对象不同,因此每次都要将对象的顶点和索引数据拷贝到显卡,会消耗不少时间。
可见想减少渲染状态切换就要频繁拷贝顶点索引数据,而想减少拷贝顶点索引数据又不得不增加渲染状态切换。鱼与熊掌不可兼得 :-(
由于硬件条件和场景数据的情况比较复杂,具体哪种方法效率较高并没有定式,两种方法都有人使用,具体选用那种方法需要在实际环境测试后才能知道。
多光源问题
待续……
阴影问题
待续……
渲染脚本
现在很多图形程序都会自己定义一种脚本文件来描述Shader。
比如较早的OGRE(Object-oriented Graphics Rendering Engine,面向对象图形渲染引擎)的Material脚本,Quake3的Shader脚本,以及刚问世不久的Direct3D的Effect File,nVIDIA的CgFX脚本(文件格式与Direct3D Effect File兼容),ATI RenderMonkey使用的xml格式的脚本。OGRE Material和Quake3 Shader这两种脚本比较有历史了,不支持可编程渲染管线。而后面三种比较新的脚本都支持可编程渲染管线。
脚本 特性 范例
OGRE Material 封装各种渲染状态,不支持可编程渲染管线 >>>>
Quake3 Shader 封装渲染状态,支持一些特效,不支持可编程渲染管线 >>>>
Direct3D Effect File 封装渲染状态,支持multipass,支持可编程渲染管线 >>>>
nVIDIA CgFX脚本 封装渲染状态,支持multipass,支持可编程渲染管线 >>>>
ATI RenderMonkey脚本 封装渲染状态,支持multipass,支持可编程渲染管线 >>>>
使用脚本来控制渲染有很多好处:
可以非常方便的修改一个物体的外观而不需重新编写或编译程序
可以用外围工具以所见即所得的方式来创建、修改脚本文件(类似ATI RenderMonkey的工作方式),便于美工、关卡设计人员设定对象外观,建立外围工具与图形引擎的联系
可以在渲染时将相同外观属性及渲染状态的对象(也就是Shader相同的对象)归为一组,然后分组渲染,对每组对象只需要在渲染前设置一次渲染状态,大大减少了多余的状态切换
摘要
本文简要介绍了在DirectX 9 SDK中提供的Effect Framework支持,以及DirectX FX文件结构和Microsoft Hight Level Shading Language的基本知识。本文假定读者对DirectX Graphics有一定了解,并正在学习DirectX Effect Framework。希望能够与各位读者共同探讨、切磋。
声明:此文章翻译自DirectX 9.0C OCT 2006 SDK的Direct3D For C++帮助文档的Enabling Direct3D Debug Information主题,前面部分有些没有翻译的。