• Direct3D轮回:文字显示及FPS效率统计


    组成一个完整游戏的元素大体来说其实只有两种:图形和字体。

    前几节涉及的内容大都为图形元素,本节我们来看如何在Direct3D中实现简单的字体绘制~

    Direct3D中的文字绘制主要依赖于ID3DXFont接口对象。我们事先构造一个D3DXFONT_DESC结构体,详细描述所要生成字体的信息,而后调用D3DXCreateFontIndirect函数即可获得ID3DXFont接口对象。

    那么接下来依然是以人性化接口为目的的二次封装~

    /*-------------------------------------

    代码清单:D3DFont.h
    来自:
    http://www.cnblogs.com/kenkao

    -------------------------------------
    */

    #include 
    "D3DInit.h"

    #pragma once

    class CD3DFont
    {
    public:
        CD3DFont(IDirect3DDevice9 
    *pDevice, D3DPRESENT_PARAMETERS* pD3DPP);
        
    ~CD3DFont(void);
    public:
        
    bool LoadFont(char *fontName, UINT fontSize);    // 加载字体
        void Release();                                  // 释放字体
    public:
        ID3DXFont
    * GetFontHandle(){return m_pFont;}      // 获得字体句柄
        RECT       GetFontArea(){return m_FontArea;}     // 获得字体默认有效区域(屏幕区域)
    private:
        IDirect3DDevice9
    * m_pDevice;                     // 3D设备
        ID3DXFont* m_pFont;                              // 字体对象
        RECT m_FontArea;                                 // 字体默认有效区域
    };
    /*-------------------------------------

    代码清单:D3DFont.cpp
    来自:
    http://www.cnblogs.com/kenkao

    -------------------------------------
    */

    #include 
    "StdAfx.h"
    #include 
    "D3DFont.h"

    CD3DFont::CD3DFont(IDirect3DDevice9 
    *pDevice, D3DPRESENT_PARAMETERS* pD3DPP) : m_pDevice(pDevice), m_pFont(NULL)
    {
        m_FontArea.left 
    = 0;
        m_FontArea.top 
    = 0;
        m_FontArea.right 
    = pD3DPP->BackBufferWidth;
        m_FontArea.bottom 
    = pD3DPP->BackBufferHeight;
    }

    CD3DFont::
    ~CD3DFont(void)
    {
    }

    bool CD3DFont::LoadFont(char *fontName, UINT fontSize)
    {
        
    // 生成D3DXFONT_DESC结构体并初始化
        D3DXFONT_DESC d3dxFont;
        ZeroMemory(
    &d3dxFont,sizeof(d3dxFont));
        _tcscpy(d3dxFont.FaceName,fontName);
        d3dxFont.Width 
    = fontSize;  
        d3dxFont.Height 
    = fontSize * 2
        d3dxFont.Weight 
    = 100;
        d3dxFont.Italic 
    = false;
        d3dxFont.CharSet 
    = DEFAULT_CHARSET;
        
    // 创建字体对象
        if(FAILED(D3DXCreateFontIndirect(m_pDevice,&d3dxFont,&m_pFont))){
            
    return false;    
        }
        
    return true;
    }

    void CD3DFont::Release()
    {
        ReleaseCOM(m_pFont);
    }

    接下来,为我们先前构造的CD3DSprite添加几个重载函数,赋予其绘制字体的能力:

    D3DSprite.h
    public:
        
    void DrawText(
            CD3DFont
    * pFont,                     // 字体对象
            char *szString,                      // 文字内容
            RECT &DesRect,                       // 目标区域
            DWORD AlignFormat,                   // 对齐格式
            D3DCOLOR Color);                     // 字体颜色
        void DrawText(
            CD3DFont
    * pFont, 
            
    char *szString, 
            RECT 
    &DesRect, 
            D3DCOLOR Color 
    = D3DXCOLOR_WHITE);
        
    void DrawText(
            CD3DFont 
    *pFont, 
            
    char *szString, 
            D3DXVECTOR2 
    &Pos,                    // 字体位置
            D3DCOLOR Color = D3DXCOLOR_WHITE);
    D3DSprite.cpp
    void CD3DSprite::DrawText(CD3DFont *pFont, char *szString, RECT &DesRect, D3DCOLOR Color)
    {
        DrawText(pFont, szString, DesRect, DT_TOP
    |DT_LEFT, Color);
    }

    void CD3DSprite::DrawText(CD3DFont *pFont, char *szString, D3DXVECTOR2 &Pos, D3DCOLOR Color)
    {
        RECT DesRect;
        DesRect.left 
    = Pos.x;
        DesRect.top 
    = Pos.y;
        DesRect.right 
    = pFont->GetFontArea().right;
        DesRect.bottom 
    = pFont->GetFontArea().bottom;
        DrawText(pFont, szString, DesRect, Color);
    }

    void CD3DSprite::DrawText(CD3DFont* pFont, char *szString, RECT &DesRect, DWORD AlignFormat, D3DCOLOR Color)
    {
        pFont
    ->GetFontHandle()->DrawText(m_pSprite, szString, -1&DesRect, AlignFormat, Color);
    }

    看到这里,大家可能会觉得奇怪。由第三个重载函数我们不难看出:最终的DrawText调用方依然是ID3DXFont接口对象,那么为什么我们要将字体绘制的功能封装到CD3DSprite对象中呢?

    由DirectX说明文档可知,ID3DXFont.DrawText函数的第一个参数是一个宿主ID3DXSprite接口对象,尽管允许其为空,但要想实现更高效率的字体绘制,则我们需要指定一个专属的ID3DXSprite接口对象。

    从Xna的SpriteBatch对象封装的手法上,我们可以略微看出端倪,尽管巧合的几率更大些,但各种因素促使我们有理由这么做~

    实现字体绘制功能之后,我们再来实现一个可以统计游戏运行效率的计时装置。

    FPS 即 Frames Per Second(每秒传输帧数),是我们用于测算游戏运行效率的通用标准。如果游戏在执行密集型计算或大批量渲染时,FPS值依然能达到一个正常标准(通常为60帧/s),则说明我们的游戏运行效率良好。

    下面就来看这个游戏计时器的实现及使用方法:

    /*-------------------------------------

    代码清单:GameTime.h
    来自:
    http://www.cnblogs.com/kenkao

    -------------------------------------
    */

    #include 
    <stdio.h>
    #include 
    "mmsystem.h"

    #pragma once

    class CGameTime
    {
    public:
        CGameTime(
    void);
        
    ~CGameTime(void);
    public:
        
    void Start();     // 游戏计时器启动
        void Tick();      // 游戏计时器心跳(每次Update时调用一次)
        void Stop();      // 游戏计时器停止
    public:
        
    float GetTotalTicks()     {return m_totalTicks;}          // 获得系统启动时间
        float GetTotalGameTime()  {return m_totalGameTime;}       // 获得游戏运作时间
        float GetElapsedGameTime(){return m_elapsedGameTime;}     // 获得单帧时间差
    public:
        
    void  CalcFPS();                                          // 计算FPS(每次Update时调用一次)(需显式调用,默认不执行)
        char* ShowFPS()           {return m_strFPS;}              // 显示FPS(字符串)
        float GetFPS()            {return m_FPS;}                 // 获得FPS(float数值)
    private:
        
    float m_totalTicks;          // 系统启动时间
        float m_totalGameTime;       // 游戏运作时间
        float m_elapsedGameTime;     // 单帧运作时间
        float m_previousTicks;       // 前次时间戳
        float m_startTicks;          // 启动时间戳
        DWORD m_FrameCnt;            // 帧总数(计算FPS辅助变量)
        float m_TimeElapsed;         // 消耗时间(计算FPS辅助变量)
        float m_FPS;                 // FPS值
        char  m_strFPS[13];          // FPS串
    };
    GameTime.cpp
    /*-------------------------------------

    代码清单:GameTime.cpp
    来自:
    http://www.cnblogs.com/kenkao

    -------------------------------------
    */

    #include 
    "StdAfx.h"
    #include 
    "GameTime.h"

    #pragma comment(lib, "winmm.lib")

    CGameTime::CGameTime(
    void) : m_totalTicks(0.0f),
                                 m_totalGameTime(
    0.0f),
                                 m_elapsedGameTime(
    0.0f),
                                 m_previousTicks(
    0.0f),
                                 m_startTicks(
    0.0f),
                                 m_FrameCnt(
    0),
                                 m_TimeElapsed(
    0.0f),
                                 m_FPS(
    0.0f)
    {
        
    // 初始FPS串
        char* strFPS = "FPS:0000000\0";
        memcpy(m_strFPS,strFPS,
    13);
    }

    CGameTime::
    ~CGameTime(void)
    {
    }

    void CGameTime::Start()
    {
        
    // 计时器启动时先捕捉起始时间戳及前次时间戳
        m_startTicks = (float)timeGetTime();
        m_previousTicks 
    = m_startTicks;
    }

    void CGameTime::Tick()
    {
        
    // 每次心跳时更新系统时间戳
        m_totalTicks = (float)timeGetTime();
        
    // 单帧时间差 = 系统时间戳 - 前次时间戳
        m_elapsedGameTime = m_totalTicks - m_previousTicks;
        
    // 游戏运作时间 = 系统时间戳 - 起始时间戳
        m_totalGameTime = m_totalTicks - m_startTicks;
        
    // 更新前次时间戳
        m_previousTicks = m_totalTicks;
    }

    void CGameTime::Stop()
    {
        
    // 计时器停止时全部变量归0
        m_totalTicks = 0.0f;
        m_totalGameTime 
    = 0.0f;
        m_elapsedGameTime 
    = 0.0f;
        m_previousTicks 
    = 0.0f;
        m_startTicks 
    = 0.0f;
        m_FrameCnt 
    = 0;
        m_TimeElapsed 
    = 0.0f;
        m_FPS 
    = 0.0f;
    }

    void CGameTime::CalcFPS()
    {
        
    // 帧数递增
        m_FrameCnt++;
        
    // 累计时间递增
        m_TimeElapsed += m_elapsedGameTime;
        
    // 当累计时间超过1秒时
        if(m_TimeElapsed >= 1000.0f)
        {
            
    // FPS测算
            m_FPS = (float)m_FrameCnt * 1000.0f / m_TimeElapsed;
            sprintf(
    &m_strFPS[4], "%f", m_FPS);
            m_strFPS[
    12= '\0';
            
    // 处理累计时间及帧数
            m_TimeElapsed -= 1000.0f;
            m_FrameCnt    
    = 0;
        }
    }

    很显然,CGameTime的Start和Stop是超越了游戏主循环的,而这部分对CD3DGame而言是透明的,我们需要将CGameTime部分相关代码放置到Win32App的默认代码中:

    int APIENTRY _tWinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPTSTR    lpCmdLine,
                         
    int       nCmdShow)
    {
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);

         
    // TODO: 在此放置代码。
        MSG msg;
        HACCEL hAccelTable;

        
    // 初始化全局字符串
        LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadString(hInstance, IDC_KEN3DGAME, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);

        
    // 执行应用程序初始化:
        if (!InitInstance (hInstance, nCmdShow))
        {
            
    return FALSE;
        }

        hAccelTable 
    = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_KEN3DGAME));

        LoadContent();
        ZeroMemory(
    &msg, sizeof(MSG));

        
    // 声明游戏计时器对象,该对象随入口方法的结束而自动释放(生命周期终止)
        CGameTime GameTime;
        
    // 计时器随主循环的启动而启动
        GameTime.Start();
        
    while(msg.message != WM_QUIT) {
            
    if(PeekMessage(&msg, NULL, 00, PM_REMOVE)) {
                TranslateMessage(
    &msg);
                DispatchMessage(
    &msg);
            }
            
    // 计时器心跳
            GameTime.Tick();
            Update(
    &GameTime);
            Draw(
    &GameTime);
        }
        
    // 计时器随主循环的停止而停止
        GameTime.Stop();
        UnloadContent();
        Dispose();

        
    return (int) msg.wParam;
    }

    以上为变更之后的Win32入口函数。我们可以看到CD3DGame的Update和Draw函数参数不再是单纯的float值,而是功能完整的游戏计数器指针。

    接下来,我们来看CD3DGame主体代码:

    D3DGame.cpp
    /*-------------------------------------

    代码清单:D3DGame.cpp
    来自:
    http://www.cnblogs.com/kenkao

    -------------------------------------
    */

    #include 
    "StdAfx.h"
    #include 
    "D3DGame.h"
    #include 
    "D3DSprite.h"
    #include 
    "SpriteBatch.h"
    #include 
    "D3DFont.h"
    #include 
    "D3DCamera.h"
    #include 
    "BaseTerrain.h"
    #include 
    <stdio.h>
    #include 
    <time.h>

    //---通用全局变量

    HINSTANCE  g_hInst;
    HWND       g_hWnd;
    D3DXMATRIX g_matProjection;
    D3DPRESENT_PARAMETERS g_D3DPP;

    //---D3D全局变量

    IDirect3D9       
    *g_pD3D           = NULL;
    IDirect3DDevice9 
    *g_pD3DDevice     = NULL;
    CMouseInput      
    *g_pMouseInput    = NULL;
    CKeyboardInput   
    *g_pKeyboardInput = NULL;
    CD3DSprite       
    *g_pSprite        = NULL;
    CD3DCamera       
    *g_pD3DCamera     = NULL;
    CBaseTerrain     
    *g_pBaseTerrain   = NULL;
    CSpriteBatch     
    *g_pSpriteBatch   = NULL;
    CD3DFont         
    *g_pFont          = NULL;
    CD3DFont         
    *g_pFont2         = NULL;

    void Initialize(HINSTANCE hInst, HWND hWnd)
    {
        g_hInst 
    = hInst;
        g_hWnd  
    = hWnd;
        InitD3D(
    &g_pD3D, &g_pD3DDevice, g_D3DPP, g_matProjection, hWnd);
        g_pMouseInput 
    = new CMouseInput;
        g_pMouseInput
    ->Initialize(hInst,hWnd);
        g_pKeyboardInput 
    = new CKeyboardInput;
        g_pKeyboardInput
    ->Initialize(hInst,hWnd);
        srand(time(
    0));
    }

    void LoadContent()
    {
        g_pD3DCamera 
    = new CD3DCamera;
        g_pSprite 
    = new CD3DSprite(g_pD3DDevice);
        
    // 声明并加载两种不同的字体
        g_pFont = new CD3DFont(g_pD3DDevice,&g_D3DPP);
        g_pFont
    ->LoadFont("宋体",8);
        g_pFont2 
    = new CD3DFont(g_pD3DDevice,&g_D3DPP);
        g_pFont2
    ->LoadFont("隶书",16);
    }

    void Update(CGameTime* gameTime)
    {
        
    // 统计FPS
        gameTime->CalcFPS();
        g_pMouseInput
    ->GetState();
        g_pKeyboardInput
    ->GetState();
        g_pD3DCamera
    ->Update();
        g_pBoundingFrustum
    ->Update(g_pD3DCamera->GetViewMatrix() * g_matProjection);
    }

    void Draw(CGameTime* gameTime)
    {
        g_pD3DDevice
    ->SetTransform(D3DTS_VIEW,&g_pD3DCamera->GetViewMatrix());
        g_pD3DDevice
    ->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f0);
        
    if(SUCCEEDED(g_pD3DDevice->BeginScene())) 
        {
            
    // 开启绘制
            g_pSprite->Begin(D3DXSPRITE_ALPHABLEND);
            
    // 显示FPS
            g_pSprite->DrawText(g_pFont, gameTime->ShowFPS(), D3DXVECTOR2(100,100), D3DXCOLOR_WHITE);
            
    // 绘制具体文字
            g_pSprite->DrawText(g_pFont2, "花瓣散落了满地的记忆,拾起来放入口中\r\n细细咀嚼,再也找不回原来的味道…", D3DXVECTOR2(100,200), D3DCOLOR_ARGB(128,0,0,255));
            
    // 结束绘制
            g_pSprite->End();
            g_pD3DDevice
    ->EndScene();
        }
        g_pD3DDevice
    ->Present(NULL, NULL, NULL, NULL);
    }

    void UnloadContent()
    {
        ReleaseCOM(g_pFont2);
        ReleaseCOM(g_pFont);
        ReleaseCOM(g_pSprite);
    }

    void Dispose()
    {
        ReleaseCOM(g_pKeyboardInput);
        ReleaseCOM(g_pMouseInput);
        ReleaseCOM(g_pD3DDevice);
        ReleaseCOM(g_pD3D);
    }

    如下效果图:

    可能大家会有疑问:只绘制了缪缪几行文字,简单统计一下数据,为什么FPS只有60帧左右呢?

    DirectX9.0及之后版本在原有基础上做了改进,我们可以通过D3DPRESENT_PARAMETERS.PresentationInterval认为的控制3D设备的刷新行为。

    其中,D3DPRESENT_INTERVAL_IMMEDIATE代表即时刷新;D3DPRESENT_INTERVAL_ONE代表屏幕刷新率标准;D3DPRESENT_INTERVAL_DEFAULT为默认方式,与D3DPRESENT_INTERVAL_ONE接近但FPS稍低。

    如我们所知,超出显示器刷新频率的刷新标准没有实际意义,而且会空耗D3D设备性能。因此,如果不是效率极端测试,不建议用D3DPRESENT_INTERVAL_IMMEDIATE,只需用D3DPRESENT_INTERVAL_ONE即可。

    除去以上三种标准,剩余的还有D3DPRESENT_INTERVAL_TWO、D3DPRESENT_INTERVAL_THREE、D3DPRESENT_INTERVAL_FOUR,它们不太常用,而且使用方式也不像前三种那样单纯,大家感兴趣可自行翻看DX帮助文档,在此不再赘述。

    由于ID3DXFont底层是基于Gdi的,因此虽然功能强大,但却在效率上不被看好。不过由于游戏大都以图形元素为主要表现形式,而文字大都为辅助作用,因此在一般情况下上述方法应该是够用的。

    以上,谢谢 ^ ^





  • 相关阅读:
    Reflector 插件
    Tips for ILMerge
    WaitAll for multiple handles on a STA thread is not supported 解决方案
    MSI: UAC return 0x800704C7
    SET与SETX的区别
    年在Copyright中的含义
    gacutil : 添加.NET 4.0 assembly 到GAC失败
    LicenseContext.GetSavedLicenseKey 需要 FileIOPermission
    Linq学习之linq基础知识
    SQL Server 2008如何导出带数据的脚本文件
  • 原文地址:https://www.cnblogs.com/kenkao/p/2175528.html
Copyright © 2020-2023  润新知