为DirectX游戏计算帧率

来源:互联网 发布:彩虹秒赞最新源码8.0 编辑:程序博客网 时间:2024/05/29 07:26

为DirectX游戏计算帧率
 最近我写了自己的一个DirectX程序,使用的是自己的框架,以后我就会在我的DirectX程序框架上进行开发了。
 首先我们需要探讨的是一个效率问题。在ACM程序中,是很讲究时空比的,也就是说一个程序要达到规定的时间和规定的内存容量这才算成功。那么游戏又何尝不是如此呢?相比一般的数值计算程序,游戏更讲究时效性,也就是说游戏开发人员可能为了达到渲染速度,可能会丢失一些渲染精度。这对程序员来说是一种很困难的抉择:是否选择现有的算法,还是放弃采取低精度的算法,亦或是开发出更有效的算法?为了衡量游戏的渲染效率,我们提出了FPS这个概念。FPS就是frame per second,简单来说就是一秒画多少帧。程序员花了那么多的时间,就是为了精益求精,但是这在游戏开发中是值得的。试想,如果一个游戏的帧率是15或者更低,那么谁会玩这么卡的游戏呢?基于这样的考量,游戏开发人员必须要想尽一切办法提高FPS。

 以下是我电脑中部分游戏的截屏。(请注意右下角的FPS示意)


 那么FPS是怎样测定的呢?有两种方法:一种是使用软件进行测量。我使用的是fraps,这样比较从客观的角度来衡量渲染的效率。另外也可以在已有的游戏代码中嵌入一些计算FPS的代码,这样做的好处是便于携带,如果想在别的机器上测定游戏的运行的效率,只需要将游戏复制到目的机器上就行了。事实上,使用软件测定的FPS会偏低,因为别的软件由于监视游戏的运行,会占用一定的CPU,而使用自己游戏的代码来查看虽然也占用CPU,但是渲染的部分是耗在GPU上的,给CPU的负担微乎其微。所以我建议大家在编辑DirectX游戏的时候,还是开发出一套计算帧率的代码,因为好处明摆着呢,况且实现也非常简单。
 首先大家先想想,我们应该达到什么样的目的?我想大多数游戏给了我们答案。我们不想让自己游戏的FPS跳动太快,否则我们也看不清FPS到底是多少,但是我们也希望FPS能够随着时间的推移或者是用户的操作而产生变化。所以你应该猜得出每一秒FPS产生一次变化应当是理想的选择。那么FPS是怎样刚好在一秒内变化呢?这里我们应当想起了使用windows调用系统时间的API函数了。此外,我们也能够想到,计算出的FPS应当能够显示,因此我们可以使用D3DX字体,如果要求高一些,我们也可以使用纹理图案来显示。在这里,我使用了前者,因为它的操作最简单。下面我们就开始着手准备了。

Code:
  1. LPD3DXFONT g_FPSFont = NULL;// 指向FPS字体的指针   
  2. RECT g_FPSFontPos = { WINDOW_WIDTH - 100, WINDOW_HEIGHT - 15, WINDOW_WIDTH, WINDOW_HEIGHT};// FPS所在的矩形框   
  3. INT g_FrameCount = 0;// 帧的计数器   
  4. INT g_lastTime = 0;// 记录上一秒的时间   
  5. INT g_currentTime = 0;// 记录当前的时间   
  6. CHAR g_FPSstr[25] = { 0 };// 记录当前帧率的字符串  

 

 在上面的代码中,每一句我都添加了注释,在这里我就不复述了。随后我们在初始化游戏的时候添加创建字体的代码:

Code:
  1. // 创建字体   
  2.  if ( FAILED( D3DXCreateFont( g_JDevice, 15, 0, 1, 1, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 
  3. DEFAULT_PITCH | FF_DONTCARE, "Impact", &g_FPSFont ) ) ) return FALSE;  

 

 创建字体的代码有些复杂,为了避免陷入具体的代码细节中,在这里我就略去了,这里有个链接,大家可以点击查看更多关于创建字体的内容。http://msdn.microsoft.com/en-us/library/bb172773(VS.85).aspx

 然后就是我们的计数器代码了,虽然说计数器是我们这篇文章最有技术含量的内容,但它也非常简单。首先调用时间API函数,然后进行和前一秒记录的时间进行比对,如果超过了一秒,则更新当前的FPS,并且让前一秒的数值成为当前的数值,并且将帧计数器清零,否则帧计数器进行自加运算,并不更新FPS。下面就是我写的代码:

Code:
  1. // 计数器开始计数(以毫秒计)   
  2.  g_currentTime = GetTickCount();   
  3.  if ( g_currentTime - g_lastTime > 1000 )   
  4.  {   
  5.   sprintf_s( g_FPSstr, 25, "当前FPS:%d", g_FrameCount );   
  6.   g_lastTime = g_currentTime;   
  7.   g_FrameCount = 0;   
  8.  }   
  9.  else g_FrameCount++;  

 

 最后就是在渲染之中添加一个显示字体的代码了,代码如下面所示:

Code:
  1. g_FPSFont->DrawText( NULL, g_FPSstr, -1, &g_FPSFontPos, DT_CENTER,   
  2.   D3DCOLOR_XRGB( 255, 255, 255 ) );// 显示字体  

 

 程序的显示效果如下图所示。为了符合一般游戏的规律,我们将FPS显示的内容尽量小些,并且放在右下角,以免影响玩家进行游戏。


 下面我就这个程序做一点说明:
 如果游戏是在窗口形式运行的话,如果我没有猜错的话,大家的FPS一定会小于等于60。因为桌面的刷新频率默认是60Hz。这点可以从“右键-属性-设置标签页-高级-监视器标签页”中可以看到。但是如果是全屏的游戏,则没有这个限制。一般来说,在D3DPRESENT_PARAMETERS::FullScreen_RefreshRateInHz这里进行修改,要让程序来识别可以使用的显示器模式,可以使用EnumAdapterModes()函数进行测试。

 以下是我程序的全部代码:
 

Code:
  1. /*---------------------------------------------------------------------------  
  2. 蒋轶民制作 E-mail:jiangcaiyang123@163.com  
  3. 文件名:MainFrame.cpp  
  4. 作用:计算当前帧率(FPS)  
  5. ----------------------------------------------------------------------------*/  
  6. /*--------------------------------------------------------------------------*/  
  7.   
  8. // 头文件   
  9. #include<windows.h>   
  10. #include<d3d9.h>   
  11. #include<d3dx9.h>   
  12. #include<stdio.h>   
  13.   
  14. // 库文件   
  15. #pragma comment( lib, "d3d9.lib")   
  16. #pragma comment( lib, "d3dx9.lib")   
  17.   
  18. // 定义的宏   
  19. #define JCLASSNAME  "优化的程序"   
  20. #define JCAPTION  "程序演示"   
  21. #define WINDOW_WIDTH 320   
  22. #define WINDOW_HEIGHT 320   
  23. #define SAVE_RELEASE( p ) if ( p ) p->Release(); p = NULL;   
  24.   
  25. // 调整编译器设置   
  26. #pragma warning( disable: 4100 )   
  27.   
  28. /*----------------------------------------------------------------------------*/  
  29.   
  30. // 全局变量   
  31. LPDIRECT3D9 g_JD3D = NULL;// D3D结构体   
  32. LPDIRECT3DDEVICE9 g_JDevice = NULL;// D3D装置结构体   
  33. LPD3DXFONT g_FPSFont = NULL;// 指向FPS字体的指针   
  34. RECT g_FPSFontPos = { WINDOW_WIDTH - 100, WINDOW_HEIGHT - 15,    
  35. WINDOW_WIDTH, WINDOW_HEIGHT};// FPS所在的矩形框   
  36. INT g_FrameCount = 0;// 帧的计数器   
  37. INT g_lastTime = 0;// 记录上一秒的时间   
  38. INT g_currentTime = 0;// 记录当前的时间   
  39. CHAR g_FPSstr[25] = { 0 };// 记录当前帧率的字符串   
  40.   
  41. /*----------------------------------------------------------------------------*/  
  42. // 函数的声明   
  43. BOOL InitializeD3D( HWND hWnd )   
  44. {   
  45.  D3DDISPLAYMODE displayMode;   
  46.   
  47.  //创建D3D对象   
  48.  if ( ( g_JD3D = Direct3DCreate9( D3D_SDK_VERSION ) )== NULL )   
  49.   return FALSE;   
  50.   
  51.  // 获取显示器的模式   
  52.  if ( FAILED( g_JD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &displayMode ) ) )   
  53.   return FALSE;   
  54.   
  55.  // D3D显示的参数   
  56.  D3DPRESENT_PARAMETERS jd3dpp;   
  57.  ZeroMemory( &jd3dpp, sizeof( jd3dpp ) );   
  58.   
  59.  // 初始化D3DPRESENT_PARAMETERS   
  60.  jd3dpp.Windowed               = TRUE;   
  61.  jd3dpp.BackBufferWidth    = WINDOW_WIDTH;   
  62.  jd3dpp.BackBufferHeight    = WINDOW_HEIGHT;   
  63.  jd3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;   
  64.  jd3dpp.BackBufferFormat       = displayMode.Format;   
  65.  jd3dpp.EnableAutoDepthStencil = TRUE;   
  66.  jd3dpp.AutoDepthStencilFormat = D3DFMT_D16;   
  67.   
  68.  // 创建设备   
  69.  if(FAILED(g_JD3D->CreateDevice( D3DADAPTER_DEFAULT,    
  70.   D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,    
  71.   &jd3dpp, &g_JDevice ) ) )   
  72.   return FALSE;   
  73.   
  74.  // 设置渲染状态   
  75.  g_JDevice->SetRenderState( D3DRS_LIGHTING, FALSE );   
  76.  g_JDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );   
  77.   
  78.  // 创建字体   
  79.  if ( FAILED( D3DXCreateFont( g_JDevice, 15, 0, 1, 1, 0, DEFAULT_CHARSET,   
  80.   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,   
  81.   "Impact", &g_FPSFont ) ) ) return FALSE;   
  82.   
  83.  return TRUE;   
  84. }   
  85. /*----------------------------------------------------------------------------*/  
  86. // 释放所有资源   
  87. void ReleaseMemory( void )   
  88. {   
  89.  SAVE_RELEASE( g_JD3D );   
  90.  SAVE_RELEASE( g_JDevice );   
  91.  SAVE_RELEASE( g_FPSFont );   
  92. }   
  93. /*----------------------------------------------------------------------------*/  
  94. // 渲染屏幕   
  95. void RenderScene( void )   
  96. {   
  97.  // 计数器开始计数(以毫秒计)   
  98.  g_currentTime = GetTickCount();   
  99.  if ( g_currentTime - g_lastTime > 1000 )   
  100.  {   
  101.   sprintf_s( g_FPSstr, 25, "当前FPS:%d", g_FrameCount );   
  102.   g_lastTime = g_currentTime;   
  103.   g_FrameCount = 0;   
  104.  }   
  105.  else g_FrameCount++;   
  106.     
  107.  g_JDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,    
  108.   D3DCOLOR_XRGB( 0, 0, 0), 1.0f, 0);// 清除屏幕   
  109.     
  110.  // 开始渲染   
  111.  g_JDevice->BeginScene();   
  112.   
  113.  g_FPSFont->DrawText( NULL, g_FPSstr, -1, &g_FPSFontPos, DT_CENTER,   
  114.   D3DCOLOR_XRGB( 255, 255, 255 ) );// 显示字体   
  115.     
  116.  // 结束渲染   
  117.  g_JDevice->EndScene();   
  118.   
  119.  g_JDevice->Present( NULL, NULL, NULL, NULL );   
  120. }   
  121. /*----------------------------------------------------------------------------*/  
  122. // 回调函数,用来处理消息   
  123. HRESULT CALLBACK MyAppProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )   
  124. {   
  125.  switch ( msg )   
  126.  {   
  127.  case WM_DESTROY:   
  128.   PostQuitMessage( 0 );   
  129.   return 0;   
  130.  case WM_KEYDOWN:   
  131.   if ( wParam == VK_ESCAPE ) PostQuitMessage( 0 );   
  132.   return 0;   
  133.  }   
  134.  return (HRESULT)DefWindowProc( hWnd, msg, wParam, lParam );   
  135. };   
  136. /*----------------------------------------------------------------------------*/  
  137. // 程序的入口,主函数   
  138. INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmd, INT show )   
  139. {   
  140.  // 设置Window Class结构并且注册它   
  141.  WNDCLASSEX jWndCls = { sizeof( jWndCls ), CS_CLASSDC, MyAppProc, 0, 0, hInst,    
  142.   NULL, LoadCursor( NULL, IDC_ARROW) , 0, NULL, JCLASSNAME, NULL };   
  143.  RegisterClassEx( &jWndCls );   
  144.   
  145.  // 设置窗口并且显示窗口   
  146.  HWND hWnd = CreateWindow( JCLASSNAME, JCAPTION, WS_CAPTION | WS_SYSMENU, 100, 40,   
  147.   WINDOW_WIDTH, WINDOW_HEIGHT, GetDesktopWindow(), NULL, jWndCls.hInstance, NULL);   
  148.  if ( hWnd == NULL )   
  149.   return FALSE;   
  150.  ShowWindow( hWnd, SW_SHOWDEFAULT );   
  151.  UpdateWindow( hWnd );   
  152.   
  153.  // 初始化设置   
  154.  if ( InitializeD3D( hWnd ) == FALSE )   
  155.   return FALSE;   
  156.   
  157.  // 进入消息循环   
  158.  MSG msg;   
  159.  ZeroMemory( &msg, sizeof( msg ) );   
  160.  while( msg.message != WM_QUIT )   
  161.  {   
  162.   if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )   
  163.   {   
  164.    TranslateMessage( &msg );   
  165.    DispatchMessage( &msg );   
  166.   }   
  167.   else  
  168.   {   
  169.    // 渲染屏幕   
  170.    RenderScene();   
  171.   }   
  172.  }   
  173.   
  174.  // 程序结束,释放内存所有资源   
  175.  ReleaseMemory();   
  176.   
  177.  // 解除窗口注册   
  178.  UnregisterClass( JCLASSNAME, jWndCls.hInstance );   
  179.  return ( INT ) msg.wParam;   
  180. }  

 我更多的DirectX程序,请看:

为自己创作一个好的DirectX程序风

设计DirectX游戏开头动画效

设计DirectX游戏开头画面淡淡出的效

我的游戏:走迷宫(DirectX版) 

 好了,今天就说到这里了,祝愿所有喜欢游戏开发的同学们能够实现自己开发游戏的梦想!我又得回去学习我的数据结构了,顺便说一声了,我看到了关键路径来了,而我们的老师讲课的速度也太慢了吧。图都还没有讲呢