Introduction to 3D Game Programming with DirectX 11学习笔记 4.4 演示程序框架

4.4.1 D3DApp
4.4.2 非框架方法
4.4.3 框架方法
4.4.4 帧的统计数值
4.4.5 消息处理函数
4.4.6 全屏模式
4.4.7 初始化Direct3D演示程序

本书中的演示程序均使用d3dUtil.h、d3dApp.h、d3dApp.cpp文件中的代码,这些文件可以从本书网站下载。由于本书的第Ⅱ部分和第Ⅲ部分的所有演示程序都会用到些常用文件,所以我们把些文件保存在了Common目录下,使些文件被所有的工程共享,避免多次复制文件。d3dUtil.h文件包含了一些有用的工具代码,d3dApp.h和d3dApp.cpp文件包含了Direct3D应用程序类的核心代码。我们希望读者在阅读本章之后,仔细研究一下些文件,因为我们不会涵盖些文件中的每一行代码(例如,我们不会讲解如何创建一个Windows窗口,因为基本的Win32编程是阅读本书的先决条件)。该框架的目标是隐藏窗口的创建代码和Direct3D的初始化代码;通过隐藏些代码,我们可以在设计演示程序时减少注意力的分散,把注意力集中在示例程序所要表达的特定细节上。


D3DApp

D3DApp是所有Direct3D应用程序类的基类,它提供了用于创建主应用程序窗口、运行应用程序消息循环、处理窗口消息和初始化Direct3D的函数。另外,这个类还定义了一些框架函数。**所有的Direct3D 应用程序类都继承于D3DApp类,重载它的virtual框架函数,并创建一个D3DApp派生类的单例对象。**D3DApp类的定义如下:

#ifndef D3DAPP_H
#define D3DAPP_H

#include "d3dUtil.h"
#include "GameTimer.h"
#include <string>

class D3DApp
{
public:
    D3DApp(HINSTANCE hInstance);
    virtual ~D3DApp();

    HINSTANCE AppInst()const;
    HWND      MainWnd()const;
    float     AspectRatio()const;

    int Run();

    // 框架方法。派生类需要重载这些方法实现所需的功能。

    virtual bool Init();
    virtual void OnResize(); 
    virtual void UpdateScene(float dt)=0;
    virtual void DrawScene()=0; 
    virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

    // 处理鼠标输入事件的便捷重载函数
    virtual void OnMouseDown(WPARAM btnState, int x, int y){ }
    virtual void OnMouseUp(WPARAM btnState, int x, int y)  { }
    virtual void OnMouseMove(WPARAM btnState, int x, int y){ }

protected:
    bool InitMainWindow();
    bool InitDirect3D();

    void CalculateFrameStats();

protected:

    HINSTANCE mhAppInst;     // 应用程序实例句柄
    HWND      mhMainWnd;     // 主窗口句柄
    bool      mAppPaused;    // 程序是否处在暂停状态
    bool      mMinimized;    // 程序是否最小化
    bool      mMaximized;    // 程序是否最大化
    bool      mResizing;     // 程序是否处在改变大小的状态
    UINT      m4xMsaaQuality;// 4X MSAA质量等级

    // 用于记录"delta-time"和游戏时间(§4.3)
    GameTimer mTimer;

    //  D3D11设备(§4.2.1),交换链(§4.2.4),用于深度/模板缓存的2D纹理(§4.2.6),
    //  渲染目标(§4.2.5)和深度/模板视图(§4.2.6),和视口(§4.2.8)。
    ID3D11Device* md3dDevice;
    ID3D11DeviceContext* md3dImmediateContext;
    IDXGISwapChain* mSwapChain;
    ID3D11Texture2D* mDepthStencilBuffer;
    ID3D11RenderTargetView* mRenderTargetView;
    ID3D11DepthStencilView* mDepthStencilView;
    D3D11_VIEWPORT mScreenViewport;

    //  下面的变量是在D3DApp构造函数中设置的。但是,你可以在派生类中重写这些值。

    //  窗口标题。D3DApp的默认标题是"D3D11 Application"。
    std::wstring mMainWndCaption;

    //  Hardware device还是reference device?D3DApp默认使用D3D_DRIVER_TYPE_HARDWARE。
    D3D_DRIVER_TYPE md3dDriverType;
    // 窗口的初始大小。D3DApp默认为800x600。注意,当窗口大小在运行阶段改变时,这些值也会随之改变。
    int mClientWidth;
    int mClientHeight;
    //  设置为true则使用4XMSAA(§4.1.8),默认为false。
    bool mEnable4xMsaa;
};
#endif // D3DAPP_H

非框架方法

1.D3DApp:构造函数,将数据成员简单地初始化为默认值。

2.~D3DApp:析构函数,释放D3DApp获取的COM接口

3.AppInst:简单的取值函数,返回应用程序实例句柄的一个副本。

4.MainWnd:简单的取值函数,返回主窗口句柄的一个副本。

5.AspectRatio:后台缓存区的长宽比,这个比值会在下一章中用到,可以通过下面的代码获得:

float D3DApp::AspectRatio() const
{
    return static_cast<float>(mClientWidth)/mClientHeight;
}

6.Run:该方法封装了应用程序消息循环。它使用Win32 PeekMessage函数,当没有消息时,它让应用程序处理我们的游戏逻辑。该函数的实现代码请参见4.3.3节。

7.InitMainWindow:初始化主应用程序窗口;我们假定读者已经具备了基本的Win32编程知识,知道如何初始化一个Windows窗口。

8.InitDirect3D:通过4.2节描述的各个步骤初始化Direct3D。

9.CalculateFrameStats:计算每秒的平均帧数和每帧的平均时间(单位为毫秒),这个方法的实现在4.4.4节中介绍。


框架方法

在本书的每个演示程序中,我们都会重载D3DApp的5个virtual函数。这5个函数用于实现特定示例中的代码细节。D3DApp类实现的这种结构可以将所有的初始化代码、消息处理代码和其他代码安排得井井有条,使派生类专注于实现演示程序的特定代码。下面是对这些框架方法的描述:

1.Init:该方法包含应用程序的初始化代码,比如分配资源、初始化对象和设置灯光。该方法在D3DApp的实现中包含InitMainWindow和InitDirect3D方法的调用语句;所以,当在派生类中重载该方法时,应首先调用该方法的D3DApp版本,就像下面这样:

void TestApp::Init() 
{
    if(!D3DApp::Init())
        return false;
    /* 剩下的初始化代码从这里开始 */
}

为你的后续初始化代码提供一个可用的ID3D11Device设备对象。(通常在获取 Direct3D资源时都要传递一个有效的ID3D11Device设备对象。)

2.OnResize:该方法在D3DApp::MsgProc收到WM_SIZE消息时调用。当窗口的尺寸改变时,一些与客户区大小相关的 Direct3D属性也需要改变。尤其是需要重新创建后台缓冲区和深度/模板缓冲区,使它们与窗口客户区的大小一致。后台缓冲区的大小可以通过调用IDXGISwapChain::ResizeBuffers方法来进行调整。而深度/模板缓冲区必须被销毁,然后根据新的大小重新创建。另外,渲染目标视图和深度/模板视图也必须重新创建。OnResize方法在D3DApp的实现中包含了调整后台缓冲区和深度/模板缓冲区的代码;详情请直接参见源代码。除缓冲区外,依赖于客户区大小的其他属性(例如,投影矩阵)也必须重新创建。我们把该方法作为框架的一部分是因为当窗口大小改变时,客户代码可能需要执行一些它自己的逻辑。

3.UpdateScene:该抽象方法每帧都会调用,用于随着时间更新3D应用程序(例如,实现动画和碰撞检测、检查用户输入、计算每秒帧数等等)。

4.DrawScene:该抽象方法每帧都会调用,用于将3D场景的当前帧绘制到后台缓冲区。当绘制当前帧时,我们调用了IDXGISwapChain::Present方法将后台缓冲区的内容呈现在屏幕上。

5.MsgProc:该方法是主应用程序窗口的消息处理函数。通常,当你只需重载该方法,就可以处理未由D3DApp::MsgProc处理(或者没按照你所希望的方式处理)的消息。该方法的D3DApp实现版本会在4.4.5节中讲解。如果你重载了这个方法,那么那些你没有处理的消息都会送到D3DApp::MsgProc中进行处理。

注意:除了上述的五个框架方法之外,为了使用起来更方便,我们还提供了三个虚函数,用于处理鼠标点击、释放和移动的事件。

virtual void OnMouseDown(WPARAM btnState, int x, int y){ }
virtual void OnMouseUp(WPARAM btnState, int x, int y)  { }
virtual void OnMouseMove(WPARAM btnState, int x, int y){ }

你可以重载这些方法处理鼠标事件,而用不着重载MsgProc方法。这些方法的第一个参数WPARAM都是相同的,保存了鼠标按键的状态(例如,哪个鼠标按键被按下),第二、三个参数是光标在客户区域的(x,y)坐标。


帧的统计数值

通常游戏和绘图应用程序都要测量每秒的渲染帧数(FPS)。要实现这一工作,我们只需计算在某一特定时间段t中处理的总帧数(并存储在中变量n中)。然后得到时间段t中的平均FPS为fpsavg=n/t。如果我们将t设为1,那么fpsavg=n/1=n。在我们的代码中,我们将t设为1,这样可以减少一次除法操作,而且,以1秒为限可以得到一个最恰当的平均值——个时间间隔既不长也不短。计算FPS的代码由D3Dapp::CalculateFrameStats方法实现:

void D3DApp::CalculateFrameStats()
{
    // 计算每秒平均帧数的代码,还计算了绘制一帧的平均时间。 
    // 这些统计信息会显示在窗口标题栏中。
    static int frameCnt = 0;
    static float timeElapsed = 0.0f;

    frameCnt++;

    // 计算一秒时间内的平均值
    if( (mTimer.TotalTime() - timeElapsed) >= 1.0f )
    {
        float fps = (float)frameCnt; // fps = frameCnt / 1
        float mspf = 1000.0f / fps;

        std::wostringstream outs;   
        outs.precision(6);
        outs << mMainWndCaption << L"    "
             << L"FPS: " << fps << L"    "
             << L"Frame Time: " << mspf << L" (ms)";
        SetWindowText(mhMainWnd, outs.str().c_str());

        // 为了计算下一个平均值重置一些值。
        frameCnt = 0;
        timeElapsed += 1.0f;
    }
}

为了统计帧数,我们在每帧中都会调用该方法。除了计算FPS外,上面的代码还计算了处理一帧所花费的平均时间,单位为毫秒:

float mspf = 1000.0f / fps;
//注意:帧时间与FPS是倒数关系,通过乘以1000ms/ 1s可以将秒转换为毫秒(1秒等于1000毫秒)。

这条语句的含义是:以毫秒为单位计算渲染一帧所花费的时间;是一个与FPS不同的值(虽然个值源于FPS)。实际上,计算帧时间比计算FPS更有用,因为它可以更直观地反映出由于修改场景而产生的渲染时间变化(增加或减少)。另一方面,FPS无法反映出这一变化。而且,[Dunlop03]在他的文章《FPS versus Frame Time》中指出:由于FPS曲线是非线性的,所以使用FPS可能会得到误导性的结果。例如,考虑情景一:假设我们的应用程序以1000FPS的速率运行,每1ms(毫秒)渲染一帧。当帧速率下降到250FPS时,每4ms渲染一帧。现在,再考虑情景二:假设我们的应用程序以的100FPS的速率运行,每10ms渲染一帧。当帧速率下落到大约76.9 FPS时,大约为每13ms渲染一帧。在两个情景中,帧时间都是增加了3毫秒,增加的渲染时间完全相同。但是FPS的读数不够直观。从表面看上,似乎从1000FPS下降到250FPS,要比从100FPS下降到76.9FPS更严重一些。然而,正如我们之前所说,它们实际表示的渲染时间的增长量是相同的。


消息处理函数

我们在消息处理函数中实现的代码与整个应用程序框架相比微不足道。通常,我们不会用到许多Win32消息。其实,我们的应用程序的核心代码会在处理器空闲执行(即,当没有窗口消息执行)。不过,有一些重要的消息我们必须处理。因为考虑到篇幅问题,我们不可能在这里列出所有的代码; 我们只能对本例使用的几个消息做以讲解。我们希望读者下载源代码文件,花一些时间熟悉应用程序框架代码,因为它是本书每个示例的基础。

我们处理的第1个消息是WM_ACTIVATE。当应用程序获得焦点或失去焦点时,该消息被发送。我们这样来处理它:

// 当窗口被激活或非激活时会发送WM_ACTIVATE消息。  
// 当非激活时我们会暂停游戏,当激活时则重新开启游戏。 
case WM_ACTIVATE:
    if( LOWORD(wParam) == WA_INACTIVE )
    {
        mAppPaused = true;
        mTimer.Stop();
    }
    else
    {
        mAppPaused = false;
        mTimer.Start();
    }
    return 0;

可以看到,当应用程序失去焦点时,我们将数据成员mAppPaused设为true,当应用程序获得焦点时,我们将数据成员mAppPaused设为false。另外,当应用程序暂停时,计时器停止运行,当应用程序再次激活时,计时器恢复运行。如果回顾4.3.3节中D3DApp::Run方法,我们会发现当应用程序暂停时,我们并没有执行应用程序中的更新3D场景的代码,而是将空闲的CPU周期返回给了操作系统;通过这一方式,应用程序不会在处于非活动状态时独占CPU周期。

我们处理的第二个消息是WM_SIZE。该消息在改变窗口大小时发生。我们处理该消息的主要原因是希望后台缓冲区和深度/模板缓冲区的大小与窗口客户区的大小相同(为了不出现图像拉伸)。所以,每次改变窗口大小时,我们希望同改变缓冲区的大小。这一任务由D3DApp::OnResize方法实现。如前所述,后台缓冲区的大小可以通过调用IDXGISwapChain::ResizeBuffers方法来进行调整。而深度/模板缓冲区必须被销毁,然后根据新的大小重新创建。另外,渲染目标视图和深度/模板视图也必须重新创建。当用户拖动窗口边框时,我们必须格外小心,因为此时会有接连不断的WM_SIZE消息发出,我们不希望连续地调整缓冲区大小。所以,当用户拖动窗口边框时,我们(除了暂停应用程序外)不应该执行任何代码,等到用户的拖动操作结束之后我们再调整缓冲区的大小。我们通过处理WM_EXITSIZEMOVE消息来完成一工作。该消息在用户释放窗口边框时发送。

// 当用户拖动窗口边框时会发送WM_EXITSIZEMOVE消息。
case WM_ENTERSIZEMOVE:
    mAppPaused = true;
    mResizing  = true;
    mTimer.Stop();
    return 0;

// 当用户是否窗口边框时会发送WM_EXITSIZEMOVE消息。
// 然后我们会基于新的窗口大小重置所有图形变量
case WM_EXITSIZEMOVE:
    mAppPaused = false;
    mResizing  = false;
    mTimer.Start();
    OnResize();
    return 0;

最后处理的3个消息的实现过程非常简单,所以我们直接来看代码:

// 窗口被销毁时发送WM_DESTROY消息
case WM_DESTROY:
    PostQuitMessage(0);
    return 0;

// 如果使用者按下Alt和一个与菜单项不匹配的字符时,或者在显示弹出式菜单而
// 使用者按下一个与弹出式菜单里的项目不匹配的字符键时。 
case WM_MENUCHAR:
    // 按下alt-enter切换全屏时不发出声响
    return MAKELRESULT(0, MNC_CLOSE);

// 防止窗口变得过小。
case WM_GETMINMAXINFO:
    ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
    ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200; 
    return 0;

全屏模式

我们创建的IDXGISwapChain接口可以自动捕获Alt+Enter组合键消息,将应用程序切换到全屏模式(full-screen mode)。在全屏模式下,再次按下Alt+Enter组合键,可以返回到窗口模式。在这两种模式的切换中,应用程序的窗口大小会发生变化,会有一个WM_SIZE消息发送到应用程序的消息队列中;应用程序可以在此时调整后台缓冲区和深度/模板缓冲区的大小,使缓冲区与新的窗口大小匹配。另外,当切换到全屏模式时,窗口样式也会发生改变(即,窗口边框和标题栏会消失)。读者可以使用Visual Studio的Spy++工具查看一下在按下Alt+Enter组合键时由演示程序产生的Windows消息。
这里写图片描述
图4.10 第4章示例程序的屏幕截图。

注意:读者可以回顾一下4.2.3节描述的DXGI_SWAP_CHAIN_DESC::Flags标志值。


初始化Direct3D演示程序

现在,我们已经讨论了应用程序框架的所有内容,下面让我们来使用该框架生成一个小程序。基本上,我们用不着做任何实际工作就可以实现这个程序,因为基类D3DApp已经实现了它所需要的大部分功能。读者在这里应该关注是如何编写D3DApp的派生类以及实现框架方法,我们将要在这些框架方法中编写特定的示例代码。本书中的所有程序都使用这一模板。

#include "d3dApp.h"

class InitDirect3DApp : public D3DApp
{
public:
    InitDirect3DApp(HINSTANCE hInstance);
    ~InitDirect3DApp();

    bool Init();
    void OnResize();
    void UpdateScene(float dt);
    void DrawScene(); 
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
                   PSTR cmdLine, int showCmd)
{
    // Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    InitDirect3DApp theApp(hInstance);

    if( !theApp.Init() )
        return 0;

    return theApp.Run();
}

InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance) 
{
}

InitDirect3DApp::~InitDirect3DApp()
{
}

bool InitDirect3DApp::Init()
{
    if(!D3DApp::Init())
        return false;

    return true;
}

void InitDirect3DApp::OnResize()
{
    D3DApp::OnResize();
}

void InitDirect3DApp::UpdateScene(float dt)
{

}

void InitDirect3DApp::DrawScene()
{
    assert(md3dImmediateContext);
    assert(mSwapChain);

    md3dImmediateContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&Colors::Blue));
    md3dImmediateContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

    HR(mSwapChain->Present(0, 0));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
This updated bestseller provides an introduction to programming interactive computer graphics, with an emphasis on game development using DirectX 11. The book is divided into three main parts: basic mathematical tools, fundamental tasks in Direct3D, and techniques and special effects. It includes new Direct3D 11 features such as hardware tessellation and the compute shader, and covers advanced rendering techniques such as ambient occlusion, normal and displacement mapping, shadow rendering, particle systems, and character animation. Includes a companion DVD with code and figures. Brief Table of Contents: Part I Mathematical Prerequisites. Vector Algebra. Matrix Algebra. Transformations. Part II Direct3D Foundations. Direct3D Initialization. The Rendering Pipeline. Drawing in Direct3D. Lighting. Texturing. Blending. Stenciling. The Geometry Shader. The Compute Shader. The Tessellation Stages. Part III Direct3D Topics. Building a First Person Camera. Instancing and Frustum Culling. Picking. Cube Mapping. Normal and Displacement Mapping. Terrain Rendering. Particle Systems and Stream-Out. Shadow Mapping. Ambient Occlusion. Meshes. Quaternions. Character Animation. Appendices. Introduction to Windows Programming. High-Level Shading Language Reference. Some Analytic Geometry. Selected solutions. Features: +Provides an introduction to programming interactive computer graphics, with an emphasis on game development using DirectX 11 +Covers new Direct3D 11 features +Includes companion DVD with source code and 4-color graphics
### 回答1: 《DirectX 11 3D 游戏编程介绍》是一本探索使用DirectX 11进行3D游戏编程的入门指南。作者在书中详细介绍了使用DirectX 11构建高性能、交互性强的3D游戏的基本原理和技术。 这本书首先向读者介绍了DirectX 11的基本知识,包括如何安装和配置DirectX 11开发环境,以及如何使用DirectX 11的各种工具和函数编写3D游戏程序。接着,作者解释了3D计算机图形学的基础知识,如顶点和像素着色器、纹理贴图和辐射衰减等。通过这些基础知识的介绍,读者可以快速掌握构建3D游戏所需的关键技术。 在介绍了基本概念之后,书中详细介绍了使用DirectX 11来创建各种类型的3D游戏元素,如物体、相机、光照等。通过丰富的示例代码和图文并茂的解释,读者可以了解到如何创建和渲染一个真实感十足的3D场景。 除了基本的游戏元素之外,本书还涵盖了一些高级主题,如阴影、环境映射以及粒子系统等。这些高级技术的介绍,使读者能够在游戏中添加更多的特效和细节,提高游戏的表现力和吸引力。 总之,《DirectX 11 3D 游戏编程介绍》是一本全面而深入的指南,适合对DirectX 11编程感兴趣的读者。通过阅读本书,读者可以快速掌握使用DirectX 11构建3D游戏所需的基本知识和技术,为自己的游戏编程之路打下坚实的基础。 ### 回答2: 《DirectX 11 3D游戏编程入门》是一本介绍使用DirectX 11进行3D游戏编程的书籍。这本书深入探讨了如何使用DirectX 11框架来创建高质量的游戏图形和动画效果。 首先,书中介绍了DirectX 11的基本原理和概念,包括DirectX API、3D渲染管线以及基本的数学知识。读者将学习如何使用DirectX 11的各个组件来创建并控制游戏世界的各个方面,包括场景、模型、光照、纹理和动画等。 其次,书中还涵盖了一些高级主题,如几何处理、阴影技术和物理仿真。通过学习这些技术,读者将能够创建更加真实和逼真的游戏效果。 除了理论知识,这本书还提供了大量的实际编程示例和练习,帮助读者巩固所学的内容。读者将通过实践性的编程项目来运用所学知识,提高自己的编程能力和理解。书中的示例代码和项目都是使用C++语言编写的,读者需要基本的C++编程知识。 总而言之,《DirectX 11 3D游戏编程入门》是一本全面介绍使用DirectX 11进行游戏编程的教材。无论是对于初学者还是已有一定游戏开发经验的读者来说,都是一本不可或缺的学习资源。通过学习这本书,读者将能够掌握使用DirectX 11创建引人入胜的3D游戏的技能。 ### 回答3: 《DirectX 11 3D游戏编程入门》是一本介绍使用DirectX 11进行3D游戏编程的入门教材。DirectX 11是微软的一个图形编程接口,它提供了丰富的功能和工具,方便开发者实现高性能、逼真的图形效果。 本书首先介绍了计算机图形学的基础知识,如顶点、三角形、光照、材质等。然后详细介绍了DirectX 11的各个组件和功能,如绘制2D图形、创建3D模型、使用着色器编程、应用纹理和光照效果等。 书中通过一系列具体的示例代码和案例,手把手地教读者如何使用DirectX 11进行游戏编程。读者可以学会如何加载模型、设置相机视角、实现场景渲染等基本功能。同时,本书还介绍了一些高级的特效技术,如阴影和反射效果的实现。 在学习过程中,本书鼓励读者动手实践,通过编写自己的游戏项目来巩固所学知识。此外,本书还提供了一些调试技巧和最佳实践,帮助读者解决常见的问题,并编写出高效、稳定的游戏程序。 总之,《DirectX 11 3D游戏编程入门》是一本系统、全面介绍DirectX 11游戏编程的教材,适合有一定编程基础和对3D游戏开发感兴趣的读者阅读。通过学习本书,读者可以了解DirectX 11的核心概念和基本功能,掌握基于DirectX 113D游戏开发技巧,为进一步深入学习和开发打下坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值