DXUT进阶
概要
这个指南涵盖了更多DXUT的高级应用。这个指南里的大部分功能是可选的,为了以最小的代价来增强你的应用程序。DXUT提供了一个简单的基于GUI系统的精灵和一个设备设置对话框。另外,它还提供了一些摄像机类。
这个指南的结果是一个具有完善功能的GUI 用于更改设备和场景的设置。它将有按钮,滑块,和文本来示范这些功能。
导航
DXUT摄像机
DXUT对话框
资源管理程序初始化
3D设置对话框
文本渲染
DXUT摄像机
DXUT中的CModelViewerCamera 类可以简单的的管理视图变换和透视变换,就像GUI 的功能一样。
CModelViewerCamera g_Camera; // A model viewing camera
摄像机类提供的第一个功能是创建视图和透视矩阵。有了这个摄像机,没有必要担心这些矩阵。反而,你可以指定你在哪,你看着什么,还有窗口的大小。然后,把这些参数传递给摄像机对象,它会在后台创建这些矩阵。这里我们设置摄像机的一部分视图参数.我们指定我们在哪,和我们看着什么。
// Initialize the camera
D3DXVECTOR3 Eye(0.0f, 0.0f, -800.0f);
D3DXVECTOR3At(0.0f, 0.0f, 0.0f);
g_Camera.SetViewParams(&Eye,&At);
接下来,我们指定摄像机的投影参数。也就是说,我们需要提供观察角度,纵横比,和视图截锥的近、远裁剪面。这些信息跟之前的指南提供的一样,不同的是,你不必担心怎么去创建矩阵。
// Setup the camera's projection parameters
float fAspectRatio =pBackBufferSurfaceDesc->Width /(FLOAT)pBackBufferSurfaceDesc->Height;
g_Camera.SetProjParams(D3DX_PI/4, fAspectRatio, 0.1f,5000.0f);
g_Camera.SetWindow(pBackBufferSurfaceDesc->Width,p00BackBufferSurfaceDesc->Height);
另外,这个摄像机还封装了简单的鼠标反馈。在这里,我们指定三个鼠标按键给提供的鼠标操作:模型旋转,放缩,还有摄像机旋转。试着编译工程并用每个按键体会一下各个操作。
g_Camera.SetButtonMasks( MOUSE_LEFT_BUTTON, MOUSE_WHEEL,MOUSE_MIDDLE_BUTTON );
在这个按钮组的基础上,摄像机会监听这些输入并产生相应效果。为了响应用户输入,需要加入一个监听到MsgProc回调函数(DXUT消息处理函数)。
// Pass all remaining windows messages to camera so itcan respond to user input
g_Camera.HandleMessages(hWnd, uMsg, wParam,lParam);
最后,在把所有数据都输入摄像机后,就是取出真正的变换矩阵的时候了。这里我们用相关函数获取投影矩阵和视图矩阵,摄像机对象会自己计算这些矩阵。
g_pProjectionVariable->SetMatrix((float*)g_Camera.GetProjMatrix());
g_pViewVariable->SetMatrix((float*)g_Camera.GetViewMatrix());
DXUT 对话框
用户交互可以用CDXUTDialog来实现,它在一个包含控件的对话框里接受用户输入,并且通过程序句柄传递它们。首先,对话框类要实例化,然后每个单独的控件才能加入。
声明
在这个指南里,有两个对话框会被加入,一个叫 g_HUD跟D3D10示例共享相同的代码,另一个叫 g_SampleUI用于显示函数细节给这个指南。第二个对话框用来控制模型的"胖瘦",就是设置一个变量传递给shaders。
CDXUTDialogg_HUD; // manages the 3DUI
CDXUTDialog g_SampleUI; // dialog forsample specific controls
这些对话框被CDXUTDialogResourceManager控制着。这个管理程序会在对话框之间传递消息并处理共享资源.
CDXUTDialogResourceManagerg_DialogResourceManager; // managerfor shared resources of dialogs
最后,一些回调函数与GUI处理的实际消息相关联. 这个函数用于处理控件间的交互。
void CALLBACK OnGUIEvent(UINT nEvent, intnControlID, CDXUTControl* pControl,
void* pUserContext);
对话框初始化
既然已经介绍了许多有用的东西,那么需要被初始化,这个指南将这些模块的真正初始化转移到一个单独的函数, 叫InitApp()。
每个对话框的控件被初始化就是在这个函数里。每个对话框都需要调用它的初始化函数,并传递给资源管理者来指定把控件供给谁。同样,它们设置回调函数来处理GUI响应。既然这样,这个相关的回调函数是 OnGUIEvent。
g_HUD.Init(&g_DialogResourceManager);
g_SampleUI.Init(&g_DialogResourceManager);
g_HUD.SetCallback(OnGUIEvent);
g_SampleUI.SetCallback(OnGUIEvent);
在它们被初始化后,每个都对话框可以加入它们想要使用的控件。对话框 HUD加入了3个按钮用于基本功能:切换全屏,切换引用(软件) 渲染器,和改变设备.
要加入一个按钮,你要指定你想要使用的IDC标识符,一个用于显示的字符串,坐标,宽和高,还有可选的与它关联的热键。热键可以用键盘快速地开关这些按钮。
注意指定的坐标与对话框的定位相关。
int iY = 10;
g_HUD.AddButton(IDC_TOGGLEFULLSCREEN, L"Toggle full screen", 35,iY, 125, 22);
g_HUD.AddButton(IDC_TOGGLEREF, L"Toggle REF (F3)", 35, iY += 24,125, 22);
g_HUD.AddButton(IDC_CHANGEDEVICE, L"Change device (F2)", 35, iY +=24, 125, 22, VK_F2);
同样对于sampleUI,加入了这些控件,一个静态文本,一个滑块,还有一个复选按钮。
传递给静态文本的参数是IDC 标识符,字符串,坐标,和宽高。
滑块的参数是IDC 标识符,字符串,坐标,和宽高,还有滑块的最大最小值,最后是存储结果的变量。
复选按钮包括IDC标识符,一个字符串标签,wf坐标,宽高,还有存储结果的布尔值。
iY = 10;
WCHARsz[100];
iY +=24;
StringCchPrintf(sz, 100, L"Puffiness: %0.2f",g_fModelPuffiness);
g_SampleUI.AddStatic(IDC_PUFF_STATIC, sz, 35, iY += 24, 125,22);
g_SampleUI.AddSlider(IDC_PUFF_SCALE, 50, iY += 24, 100, 22, 0,2000,
(int)(g_fModelPuffiness*100.0f));
iY +=24;
g_SampleUI.AddCheckBox(IDC_TOGGLESPIN, L"Toggle Spinning",35,
iY += 24, 125, 22, g_bSpinning);
一旦初始化了,这个对话框要显示在屏幕上。这个由 OnD3D10ResizedSwapChain调用来完成既然屏幕坐标每次改变这个交换链会重建 (可能由于窗口大小的变化)。
g_HUD.SetLocation(pBackBufferSurfaceDesc->Width-170,0);
g_HUD.SetSize(170, 170);
g_SampleUI.SetLocation(pBackBufferSurfaceDesc->Width-170,
pBackBufferSurfaceDesc->Height-300 );
g_SampleUI.SetSize(170, 300);
对话框的最后一步在哪需要鉴别它们自己是 OnD3D10FrameRender函数。一个对话框如果你不绘制它并且用户看不到不太好吧?
// Render the UI
g_HUD.OnRender(fElapsedTime);
g_SampleUI.OnRender(fElapsedTime);
资源管理程序初始化
资源管理程序在每次回调初始化和销毁时需要被初始化。这是因为在每次设备被重建的时候GUI需被重建,或者交换链被重建的时候。CDXUTDialogResourceManager类包含符合每个这样的回调的函数,具有一模一样的名字。因此,它仅仅是在合适的地方插入代码来调用它们。
V_RETURN(g_DialogResourceManager.OnD3D10CreateDevice(pd3dDevice));
V_RETURN(g_DialogResourceManager.OnD3D10ResizedSwapChain(pd3dDevice,pBackBufferSurfaceDesc));
g_DialogResourceManager.OnD3D10ReleasingSwapChain();
g_DialogResourceManager.OnD3D10DestroyDevice();
响应GUI事件
当全部初始化完成后,我们最后可以开始写代码来处理GUI交互了。在我们初始化对话框的时候,我们设置这些对话框的回调函数做为OnGUIEvent。现在我们将要创建OnGUIEvent 函数,它会监听并处理GUI 相关事件(被框架调用的)。
这是一个简单的对每个IDC标识符包含一个case代码块的函数,它在对话框创建时被监听。在每个case块中会是处理代码,假设用户以某种方式控制。这里的处理控制的代码跟Win32 代码很像。
HUD相关的控制实际上调用DXUT内置的函数。有一个DXUT函数关系到切换全屏,绑定引用软件渲染器,并且更改设备设置(它会调用下面提到的3D设置对话框)。
SampleUI对话框包含自定义的代码操作跟滑块相关联的变量。它会收集数值,更新与它相关的文本,并把数值传递给滑块。
void CALLBACK OnGUIEvent(UINT nEvent, int nControlID,CDXUTControl* pControl,
void* pUserContext) {
switch(nControlID) {
case IDC_TOGGLEFULLSCREEN:
DXUTToggleFullScreen();
break;
case IDC_TOGGLEREF:
DXUTToggleREF();
break;
case IDC_CHANGEDEVICE:
g_D3DSettingsDlg.SetActive(!g_D3DSettingsDlg.IsActive());
break;
case IDC_TOGGLESPIN: {
g_bSpinning =g_SampleUI.GetCheckBox(IDC_TOGGLESPIN)->GetChecked();
break;
}
case IDC_PUFF_SCALE: {
g_fModelPuffiness=(float)(g_SampleUI.GetSlider(IDC_PUFF_SCALE)->Getvalue()*0.01f);
WCHAR sz[100];
StringCchPrintf(sz, 100, L"Puffiness: %0.2f",g_fModelPuffiness);
g_SampleUI.GetStatic(IDC_PUFF_STATIC)->SetText(sz);
g_pPuffiness->SetFloat(g_fModelPuffiness);
break;
}
}
}
更新消息处理
既然现在我们有对话框消息和用户交互, 那就会有传递给应用程序的消息需要被解析,如果适用的话,那些代码会在DXUT提供的 MsgProc回调函数中被处理。在之前的指南里,这段是空白因为没有消息需要被处理。但是现在,我们要确保发送给资源管理程序和对话框的消息被适当地发送。
不需要专门的消息处理代码,我们只需要对每个对话框调用MsgProcs来确保消息被处理了。这通过调用每个单独的类的相应的 MsgProc函数来完成。应该注意到这个函数提供了一个标记通报给框架,不需要更多的处理。因此,可以退出。
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAMwParam, LPARAM lParam,
bool* pbNoFurtherProcessing, void* pUserContext) {
// Always allow dialog resource manager calls to handleglobal messages
// so GUI state is updated correctly
*pbNoFurtherProcessing = g_DialogResourceManager.MsgProc(hWnd,uMsg, wParam, lParam);
if(*pbNoFurtherProcessing)
return 0;
if(g_D3DSettingsDlg.IsActive()) {
g_D3DSettingsDlg.MsgProc(hWnd,uMsg, wParam, lParam);
return 0;
}
// Give the dialogs a chance to handle themessage first
*pbNoFurtherProcessing = g_HUD.MsgProc(hWnd, uMsg, wParam,lParam);
if(*pbNoFurtherProcessing)
return 0;
*pbNoFurtherProcessing = g_SampleUI.MsgProc(hWnd,uMsg, wParam, lParam);
if(*pbNoFurtherProcessing)
return 0;
if(uMsg == WM_CHAR && wParam =='1')
DXUTToggleFullScreen();
return 0;
}
3D设置对话框
有一个专门的内置对话框用来控制D3DDevice的设置。DXUT提供的这个对话框就是CD3DSettingsDlg。它的功能像一个自定义对话框,但是它会提供给用户所有的他们需要修改的设置选项.
CD3DSettingsDlgg_D3DSettingsDlg; // Device settings dialog
初始化就像其它对话框一样,你可以调用初始化函数。然而,每次Direct3D改变它的交换链或者设备,这个对话框必须被更新。因此,它必须包含一个调用在OnD3D10CreateDevice和OnD3D10ResizedSwapChain里,相对应地命名,来反映这些改变。同样地,销毁对象的改变必须被通报。因而,需要在OnD3D10DestroyDevice里调用。
g_D3DSettingsDlg.Init(&g_DialogResourceManager);
V_RETURN(g_D3DSettingsDlg.OnD3D10CreateDevice(pd3dDevice));
V_RETURN(g_D3DSettingsDlg.OnD3D10ResizedSwapChain(pd3dDevice,pBackBufferSurfaceDesc));
g_D3DSettingsDlg.OnD3D10DestroyDevice();
在渲染的这一边,因为对话框的出现可以被改变,它通过一个叫IsActive()的标记来转换。如果这个标记被设成false,那么这个面板将不会被渲染。这个面板的改变是受上面说过的HUD对话框处理的。标记TheIDC_CHANGEDEVICE与HUD 控件相关,就像上面提到的那样。
if(g_D3DSettingsDlg.IsActive()) {
g_D3DSettingsDlg.MsgProc(hWnd, uMsg, wParam, lParam);
return 0;
}
一旦这些初始步骤都完成了,你就可以在你的程序中加入这个对话框。试着编译这个指南并与更改设置的面板进行交互来观察它的效果。真正的D3DDevice或交换链的重构在DXUT内部完成。
文本渲染
一个程序不会有趣如果用户不知道干什么,所以就DXUT包含了一个工具类用于在屏幕上绘制2D文本,用来给用户反馈。这个类,CDXUTD3D10TextHelper,允许你在屏幕的任意位置绘制一行行的文本,并有简单的字符串输入。开始之前,我们要实例化这个类,既然文本渲染与大多数初始化过程独立,在这个指南里我们让大部分代码在RenderText10里。
CDXUTD3D10TextHelper txtHelper(g_pFont,g_pSprite, 15);
初始化
我们传入的第一个参数是我们要绘制的字体。字体的类型是D3DX提供的ID3DXFont。要初始化这个字体,你调用D3DX10CreateFont,并且你要传入设备,高度,宽度,重量,mip层次(一般取 1),斜体,字符集,精度,质量,倾斜度和家族,字体名称,还有你的对象的指针。虽然它看起来像很多字符,只有开始的4个和最后的2个真正有意义。
V_RETURN(D3DX10CreateFont(pd3dDevice, 15, 0,FW_BOLD, 1, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,DEFAULT_PITCH | FF_DONTCARE,
L"Arial", &g_pFont));
第2个参数需要我们初始化一个ID3DXSprite 类。要做这个的话,我们可以调用 D3DX10CreateSprite(notice the trend here?)。这个函数只需要设备和对象的指针做为参数。
// Initialize the sprite
V_RETURN(D3DX10CreateSprite( pd3dDevice,&g_pSprite));
当然,像其它对象一样,字体和精灵在我们完成时必须被销毁。这可以用常见的 SAFE_RELEASE 宏完成。
SAFE_RELEASE(g_pFont);
SAFE_RELEASE(g_pSprite);
渲染
在这个示例里包含的文本里有渲染的统计信息。另外,还有一个区域显示怎样用鼠标操作模型。
渲染必须在OnD3D10FrameRender里调用,而且在这个指南里,它是在帧渲染调用里调用RenderText10来完成的.
第一个区域一直在绘制。因此,它最先完成。第一个文本渲染调用是Begin()。它通知引擎我要开始输出文本到屏幕。在那之后,我们设置好“指针”的位置, 文本的颜色, 我们就准备绘制了。
输出一个字符串文本要调用DrawTextLine来完成,你传入字符串它就会在当前位置输出。它在文本书写的同时增加指针。因此,如果q包含了一个\n,它会自动移动指针到下一行。
// Output statistics
txtHelper.Begin();
txtHelper.SetInsertionPos(2, 0);
txtHelper.SetForegroundColor(D3DXCOLOR( 1.0f, 1.0f, 0.0f,1.0f));
txtHelper.DrawTextLine(DXUTGetFrameStats());
txtHelper.DrawTextLine(DXUTGetDeviceStats());
还有另一个方法来输出文本,就好似通常使用的printf。你用特殊的字符来格式化字符串,然后插入变量到字符串里。这通过DrawFormattedTextLine来实现。
txtHelper.SetForegroundColor(D3DXCOLOR(1.0f, 1.0f, 1.0f,1.0f));
txtHelper.DrawFormattedTextLine(L"fTime: %0.1f sin(fTime): %0.4f", fTime, sin(fTime));
既然帮助绘制用一种相似的方式,所以不需要涵盖那部分代码了。一定要注意你能在任何时候通过调用SetInsertionPos来重新设置指针的位置。
最后,当你对文本输出满意后,调用 End() 来通知引擎你完成了。
转自:GameRes游戏开发资源网