本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
http://blog.csdn.net/zhmxy555/article/details/7707628
作者:毛星云 邮箱: happylifemxy@qq.com
本篇文章将讲解如何通过我们在之前的文章里面已掌握的DirectX 11的知识,来一步一步创建一个基于Direct3D11的Blank Windows Demo,而我们在这节里面完成的这个demo,将在后面的文章里面作为一个模板,用于演示之后的各种新奇DirectX11的功能。
首先呢,为了代码的重用性着想,我们会写一个 DirectX11DemoBase,并借鉴在笔记二十六中的Win32风格的Blank Win32 Window Demo中的代码,然后通过派生的方式,以及重载一些必要的虚函数,进行整合,来创建我们的demo。
一、 关于代码书写风格的讨论
首先,我们提出一个问题,采用自问自答的形式来讨论:
在这个demo的设计过程中,我们是采用C语言式的全局变量与全局函数的搭档模式来完成,还是采用C++式的面向对象风格的类Class来编写呢?
答案是后者,采用面向对象的思想来完成。至于这个问题的解释,答案就仁者见仁智者见智了。
浅墨之前看过一本C++界的名著《C++沉思录》,作者在文章的开篇举了一个例子,然后通过例子对比出来的效果,折射出了对C与C++的一个中肯的评价,是这样的一段话:
“C++鼓励采用类来表示类似于输出流的事物,而类就提供了一个理想的位置来存放状态信息。而C语言倾向于不存储状态信息,除非事先已经规划妥当。因此C程序员趋向于假设有这样一个“环境”:存在一个位置集合,他们可以在其中找到系统的当前状态。如果只有一个环境和一个系统,这样考虑毫无问题,但是,系统在不断增长的过程中往往需要引入某些独一无二的东西,并且创建更多这类东西。”
对这段话的解释,浅墨还是用自己的话来叙述吧:
通常我们采用一般的变量作为传递数据的容器,但是随着程序的复杂会导致数据量的加大,有太多的数据需要被传递,而且这些数据基本上都是需要传递到近乎是每一个函数当中的,这样我们就要创建很多的全局变量作为“容器”,如此下去我们的设计的程序只会越来越臃肿,越来越乱。别怕别怕,有绝招呢——我们可以创建一个类或者结构体来收容这些对象,使之显得不是那么乱,取而代之的是井井有条。
绕了这么大一圈子,一言以蔽之,就是运用全局变量是不太好的编程习惯,我们应当少用甚至不用,转而使用“类”来完成这些任务。
二、 Dx11DemoBase类的设计
作为目前来说,我们要求本节的demo做到以下几点功能:
▲初始化D3D
▲释放在启动过程中创建的Direct3D对象
▲为我们的D3D对象存储成员变量
▲提供一个装载demo的具体内容的方式
▲提供一个卸载demo的具体内容的方式
▲能够显示demo每帧的更新的具体内容
▲demo渲染内容的具体代码
由我们上面的清单来看,创建一个公共的初始化和卸载函数,用于装载和卸载内容功能的虚函数,以及渲染和更新游戏循环步骤的虚函数的基类是很有必要的。通过将这些函数设为虚函数,由基类派生出来的demo类能够实现他们自定义的逻辑和行为。
根据上面的这些叙述,我们可以写出下面的这段为Dx11DemoBase量身打造的代码:
代码段一 Dx11DemoBases类的头文件
#ifndef _DEMO_BASE_H_
#define _DEMO_BASE_H_
#include<d3d11.h>
#include<d3dx11.h>
#include<DxErr.h>
class Dx11DemoBase
{
public:
Dx11DemoBase();
virtual ~Dx11DemoBase();
bool Initialize( HINSTANCE hInstance, HWND hwnd );
void Shutdown( );
virtual bool LoadContent( );
virtual void UnloadContent( );
virtual void Update( float dt ) = 0;
virtual void Render( ) = 0;
protected:
HINSTANCE hInstance_;
HWND hwnd_;
D3D_DRIVER_TYPE driverType_;
D3D_FEATURE_LEVEL featureLevel_;
ID3D11Device* d3dDevice_;
ID3D11DeviceContext* d3dContext_;
IDXGISwapChain* swapChain_;
ID3D11RenderTargetView* backBufferTarget_;
};
#endif
上面这段代码中我们可以看到最精简的D3D对象,以protected类成员的形式存在于类之中。在类体外初始化变量是比较好的编程习惯,而且效率比让先调用拷贝构造函数,再调用默认构造函数要高得多。
Dx11DemoBase类构造函数,析构函数,装载内容,卸载内容,shutdown函数定义如下:
代码段二 一些Dx11DemoBase 组成代码
#include"Dx11DemoBase.h"
Dx11DemoBase::Dx11DemoBase( ) : driverType_( D3D_DRIVER_TYPE_NULL),
featureLevel_( D3D_FEATURE_LEVEL_11_0 ), d3dDevice_( 0 ),d3dContext_( 0 ),
swapChain_( 0 ), backBufferTarget_( 0 )
{
}
void Dx11DemoBase::UnloadContent( )
{
//可以在此处进行重载,加入代码实现相关功能
void Dx11DemoBase::Shutdown( )
{
UnloadContent( );
if( backBufferTarget_ ) backBufferTarget_->Release( );
if( swapChain_ ) swapChain_->Release( );
if( d3dContext_ ) d3dContext_->Release( );
if( d3dDevice_ ) d3dDevice_->Release( );
d3dDevice_ = 0;
d3dContext_ = 0;
swapChain_ = 0;
backBufferTarget_ = 0;
}
Dx11DemoBase类中的最后一个函数是Initialize函数。Initialize函数执行我们在这章中讲到的D3D初始化工作。这个函数开始声明我们的硬件,WARP或者软件的驱动类型,和我们的D3D11.0,10.1或者10.0的特征等级。代码的设定即尝试在D3D 11中创建一个硬件设备。如果创建失败,我们会尝试其他的驱动类型和特征等级直到我们找到一个合适的类型。这也意味着如果我们采用D3D10硬件我们可以也可以在硬件中运行这个demo,因为我们可以选择10.1或者10.0的特征等级。
下一步便是创建交换链的描述,以及使用这些信息来试着找到支持的设备类型和特征等级。如果成功的搜索到了我们需要的这些信息。接下来就是行云流水地创建渲染目标,创建视口,以及调用LoadContent方法加载特定的内容了。需要指出的是,LoadContent方法最好留着最后进行调用,以免出现不必要的错误。
下面便是DirectX11初始化的全过程:
代码段三Dx11DemoBase类的初始化函数
bool Dx11DemoBase::Initialize( HINSTANCE hInstance, HWND hwnd )
{
hInstance_ =hInstance;
hwnd_ = hwnd;
RECT dimensions;
GetClientRect( hwnd,&dimensions );
unsigned int width =dimensions.right - dimensions.left;
unsigned int height =dimensions.bottom - dimensions.top;
D3D_DRIVER_TYPEdriverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE
};
unsigned inttotalDriverTypes = ARRAYSIZE( driverTypes );
D3D_FEATURE_LEVELfeatureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
};
unsigned inttotalFeatureLevels = ARRAYSIZE( featureLevels );
DXGI_SWAP_CHAIN_DESCswapChainDesc;
ZeroMemory(&swapChainDesc, sizeof( swapChainDesc ) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.Windowed= true;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality= 0;
unsigned intcreationFlags = 0;
#ifdef _DEBUG
creationFlags |=D3D11_CREATE_DEVICE_DEBUG;
#endif
HRESULT result;
unsigned int driver =0;
for( driver = 0;driver < totalDriverTypes; ++driver )
{
result =D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver], 0, creationFlags,
featureLevels, totalFeatureLevels,
D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
&d3dDevice_, &featureLevel_, &d3dContext_ );
if( SUCCEEDED(result ) )
{
driverType_ =driverTypes[driver];
break;
}
}
if( FAILED( result ) )
{
DXTRACE_MSG("创建 Direct3D 设备失败!" );
return false;
}
ID3D11Texture2D*backBufferTexture;
result =swapChain_->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID*)&backBufferTexture );
if( FAILED( result ) )
{
DXTRACE_MSG("获取交换链后台缓存失败!" );
return false;
}
result =d3dDevice_->CreateRenderTargetView( backBufferTexture, 0,&backBufferTarget_ );
if( backBufferTexture)
backBufferTexture->Release( );
if( FAILED( result ) )
{
DXTRACE_MSG("创建渲染目标视图失败!" );
return false;
}
d3dContext_->OMSetRenderTargets( 1, &backBufferTarget_, 0 );
D3D11_VIEWPORTviewport;
viewport.Width =static_cast<float>(width);
viewport.Height =static_cast<float>(height);
viewport.MinDepth =0.0f;
viewport.MaxDepth =1.0f;
viewport.TopLeftX =0.0f;
viewport.TopLeftY =0.0f;
d3dContext_->RSSetViewports( 1, &viewport );
return LoadContent( );
}
bool Dx11DemoBase::LoadContent( )
{
//可以进行重载来丰富相关功能
return true;
}
void Dx11DemoBase::UnloadContent( )
{
//可以进行重载来丰富相关功能
}
void Dx11DemoBase::Shutdown( )
{
UnloadContent( );
if( backBufferTarget_) backBufferTarget_->Release( );
if( swapChain_ )swapChain_->Release( );
if( d3dContext_ )d3dContext_->Release( );
if( d3dDevice_ )d3dDevice_->Release( );
backBufferTarget_ = 0;
swapChain_ = 0;
d3dContext_ = 0;
d3dDevice_ = 0;
}
三、BlankDx11Demo类的设计
万事具备,只欠东风。
下面我们便从上面写的Dx11DemoBase类里派生出一个叫BlankDx11Demo的类。
以下就是BlankDx11Demo类头文件的代码:
代码段四BlankDx11Demo 类的头文件
#ifndef _BLANK_DEMO_H_
#define _BLANK_DEMO_H_
#include"Dx11DemoBase.h"
class BlankDx11Demo : public Dx11DemoBase
{
public:
BlankDx11Demo( );
virtual ~BlankDx11Demo( );
bool LoadContent( );
void UnloadContent( );
void Update( float dt );
void Render( );
};
#endif
这段代码中可以看到。叫做Update的函数中取了一个叫做dt的变量,后面将更详细地剖析这个变量,目前我们按这样理解就好了:在游戏程序中我们经常需要进行实时的游戏逻辑更新,而dt用于代表最后一帧的时间到当前时间的时间差,这个时间差记录我们用dt记录了下来,便于我们的基于时间的更新操作。
由于这个只是一个骨架式的空DirectXDemo,以尽量精简易懂作为此Demo的宗旨,以便于大家更容易地理解一个DirectX 11 Demo的筋骨脉络,所以在这里只是只进行了一个清屏的操作,且所有的函数重载都是空的。Render函数中我们也就调用了两个Direct3D的函数:ClearRenderTargetView函数用于清除屏幕上指定的颜色,Present函数用于显示新渲染的场景。
代码段五BlankDx11Demo类的源文件
#include"BlankDx11Demo.h"
BlankDx11Demo::BlankDx11Demo( )
{
}
BlankDx11Demo::~BlankDx11Demo( )
{
}
bool BlankDx11Demo::LoadContent( )
{
return true;
}
void BlankDx11Demo::UnloadContent( )
{
}
void BlankDx11Demo::Update( float dt )
{
}
void BlankDx11Demo::Render( )
{
if( d3dContext_ == 0 )
return;
float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
d3dContext_->ClearRenderTargetView( backBufferTarget_,clearColor );
swapChain_->Present( 0, 0 );
}
四、 赋予程序生命——wWinMain函数的书写
之前我们创建的这些类都只是一个躯壳,并没有生命,而现在我们会将今天我们创建的这个主角赋予生命。最后一步就是在工程中修改并添加我们在笔记二十六中提出的Blank Win32 Window demo中的wWinMain函数以及余下的功能函数,使我们今天设计出的这个demo浑然一体。以下就是最后需要的源码:
代码段六main.cpp的完整源代码
#include<Windows.h>
#include<memory>
#include"BlankDx11demo.h"
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAMwParam, LPARAM lParam );
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,LPWSTR cmdLine, int cmdShow )
{
UNREFERENCED_PARAMETER( prevInstance );
UNREFERENCED_PARAMETER( cmdLine );
WNDCLASSEX wndClass ={ 0 };
wndClass.cbSize =sizeof( WNDCLASSEX ) ;
wndClass.style =CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc =WndProc;
wndClass.hInstance =hInstance;
wndClass.hCursor =LoadCursor( NULL, IDC_ARROW );
wndClass.hbrBackground= ( HBRUSH )( COLOR_WINDOW + 1 );
wndClass.lpszMenuName= NULL;
wndClass.lpszClassName= "DX11BookWindowClass";
if( !RegisterClassEx(&wndClass ) )
return -1;
RECT rc = { 0, 0, 640,480 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE );
HWND hwnd =CreateWindowA( "DX11BookWindowClass", "Blank Direct3D 11 Window演示程序", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
NULL, NULL, hInstance, NULL );
if( !hwnd )
return -1;
ShowWindow( hwnd,cmdShow );
std::auto_ptr<Dx11DemoBase>demo( new BlankDemo( ) ); //使用智能指针
// Demo初始化工作
bool result =demo.Initialize( hInstance, hwnd );
if( result == false )
return -1;
MSG msg = { 0 };
while( msg.message !=WM_QUIT )
{
if( PeekMessage(&msg, 0, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// 更新以及绘制图形
demo.Update( 0.0f);
demo.Render( );
}
// Demo开始卸载
demo.Shutdown( );
returnstatic_cast<int>( msg.wParam );
}
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAMwParam, LPARAM lParam )
{
PAINTSTRUCTpaintStruct;
HDC hDC;
switch( message )
{
case WM_PAINT:
hDC =BeginPaint( hwnd, &paintStruct );
EndPaint( hwnd,&paintStruct );
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
default:
returnDefWindowProc( hwnd, message, wParam, lParam );
}
return 0;
}
笔记二十六里的demo的基础上,我们在wWinMain函数中加了7行代码。首先我们运用了C++中的标准智能指针auto_ptr<>。
auto_ptr<>智能指针会在指向的内容结束或者此指针的作用域指向其他的智能指针时自动释放内存。这个作用域可以是,一个if语句,一个内循环,或者在一对大括号里面随意摆放来创建一个新的作用域。
这样做的好处是非常舒服的——我们并不需要手动删除分配的数据,而且使用auto_ptr<>是非常安全环保的。即使出现了异常或者bug,应用程序停止运行了,auto_ptr<>在堆栈展开过程中依然会释放其数据。这样的话,即使运用程序崩溃了,依然会做到没有内存的泄露。若我们手动删除这个指针,且执行没有停止的话,就会留下泄露的内存。采用类似auto_ptr<>的内存对象有很多好处。
不是很熟悉这些内容的朋友,最好是阅读一些智能指针和其他新潮的C++编程语言的书籍进行了解和提高,掌握最新标准的C++(C++0x)。
在wWinMain函数中的最后一件事情是要注意,我们正在返回MSG对象的wParam成员,来返回应用程序的退出代码。由于wWinMain函数返回一个整型,我们把整个对象用C++标准运算符static_cast<>进行强制类型转换,转换为整型。
Blank Direct3D Window的截图可以在下面看到。采用深蓝色来清屏。

之后的demo的创建,我们只需要从Dx11DemoBsae里面派生出新类然后重载LoadContent,UnloadContent, Update, 以及Render,进行特殊的逻辑实现即可。
本节的知识就介绍到这里。
本篇文章配套的源代码请点击这里下载:【Visual C++】Note_Code_28
在这里公告一下,【Visual C++】游戏开发笔记系列文章现在是定在每周的周一进行更新,但是由于浅墨近期得办理回国相关
手续及收拾行李坐飞机回国,事情比较多比较杂,笔记系列估计得“停播”一周。觉得更新速度不够快,看得不过瘾的读者们
可以参看下我在笔记一中提到一些游戏开发相关的书籍。下下个周一,我们不见不散~~
感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们。
【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~
但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?
浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。
你们的支持是我写下去的动力~
精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。
大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~
如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。
最后,谢谢你们一直的支持~~~
——————————浅墨于2012年7月8日
分享到:
相关推荐
【Visual C++】游戏开发笔记二十八:最精简的Direct3D11 Demo筋骨脉络全攻略 本文档将指导你如何利用已有的Direct3D 11知识,构建一个基础的Windows桌面应用程序,即一个空白的Direct3D 11 Demo。此Demo将作为后续...
【Direct3D11】开发基础与Demo构建详解 Direct3D11是Microsoft开发的图形应用程序接口(API)...这个精简的Demo将作为后续学习Direct3D11高级特性和技巧的基础模板,帮助开发者逐步掌握Direct3D11在游戏开发中的应用。
博文的配套源码 《 【Visual C++】游戏开发笔记二十八 最精简的Direct3D11 Demo筋骨脉络全攻略》 ,文章地址为http://blog.csdn.net/zhmxy555/article/details/7727753, 点击debug下的exe文件可以直接看到运行...
《Visual C++游戏开发经典案例详解》这本书是针对使用Visual C++进行游戏开发的专业指南,旨在帮助读者通过实例学习和掌握C++编程语言在游戏开发中的应用。书中的内容覆盖了从基础到高级的游戏开发技术,包括图形...
根据给定的信息,我们可以归纳出一系列与Visual C++游戏开发相关的知识点。这些知识点主要围绕DirectX 11的使用、2D纹理映射、物理建模、游戏算法、游戏AI等多个方面展开。下面将逐一进行详细说明: ### DirectX 11...
《Visual C++ 游戏开发与设计实例》一书聚焦于使用Microsoft的Visual C++这一强大的编程工具进行游戏开发的实践教程。Visual C++作为一款集成开发环境,它提供了丰富的库支持,包括MFC(Microsoft Foundation ...
《Visual C++ 游戏开发案例实战》是一本专注于利用Microsoft的Visual C++环境进行游戏开发的实践教程。这本书由王浩编著,由清华大学出版社出版,提供了高清且带有书签的阅读体验,方便读者查阅和学习。在本书中,...
在《Visual C++ 游戏编程基础》这本书中,作者深入浅出地介绍了使用Visual C++进行游戏开发的基础知识和技巧。本部分压缩包包含了书中的部分源代码和相关资源,便于读者实践和理解。以下是关于这个主题的一些关键...
《Visual C++ DirectX 3D游戏开发实例教程》是一本专为希望利用Microsoft的Visual C++和DirectX库进行3D游戏开发的学习者所编写的教程。教程涵盖了从基础概念到高级技术,旨在帮助读者掌握3D游戏编程的核心技能。 ...
在本篇【Visual C++】游戏编程学习笔记中,我们将探讨如何使用Microsoft的Visual C++进行2D游戏的开发。2D游戏小demo是初学者理解游戏编程基础的一个绝佳起点,它涵盖了游戏循环、图形绘制、事件处理以及基础的游戏...
《Visual C++游戏开发技术与实例》是一本深入探讨使用Microsoft Visual C++进行游戏开发的专业教程。本书结合实例,详细讲解了如何利用Visual C++的强大功能来构建游戏应用程序。Visual C++作为一款强大的编程环境,...
《Visual C++游戏开发笔记:DirectX11 2D纹理映射知识全攻略》 在游戏开发中,2D纹理映射是提升视觉效果的关键技术之一。DirectX11作为一个强大的图形库,提供了丰富的功能来支持这一过程。本文将深入探讨DirectX11...
源码配套博文 《【Visual C++】游戏开发笔记三十四 浅墨DirectX提高班之三 起承转合的艺术:Direct3D渲染五步曲 》 ,文章地址为http://blog.csdn.net/zhmxy555/article/details/8223965, 点击debug下的exe文件...
,基于Visual C++ 2012开发Grid App、Split App、Metro DLL、Metro Component、Metro Direct2D App、Metro Direct3D App,以及用Visual C++ 2012在Windows 8下开发Metro经典案例 ,如Metro多点触控范例 、Metro笔迹...
《Visual C++ DirectX9 3D游戏开发引导》是一本专为想要学习使用Microsoft的Visual C++编程环境和DirectX9 API进行3D游戏开发的读者准备的书籍。该书的光盘源代码包含了丰富的实例和项目,旨在帮助开发者深入理解和...
《Visual C++游戏开发技术与实例》这本书是针对游戏开发初学者和爱好者的一本实用教程,它涵盖了使用Microsoft Visual C++进行游戏编程的基础知识和技术。通过本书的学习,读者可以了解到如何利用C++语言和Visual ...
在“Visual C++游戏编程基础11”这个主题中,我们将会探讨使用Microsoft的Visual C++集成开发环境(IDE)来构建游戏的基础知识。Visual C++不仅是一个强大的编程工具,而且由于其对DirectX库的支持,它在游戏开发领域...
在IT行业中,游戏开发是一项复杂而充满挑战的任务,尤其是在Visual C++环境下进行。Visual C++是一种功能强大的集成开发环境(IDE),它支持C++语言,为游戏开发者提供了丰富的工具和库,以创建高性能的游戏应用程序...
【Visual C++】游戏开发笔记27主要涵盖了Direct3D 11的入门知识,旨在帮助初学者理解和构建Direct3D应用。在这个教程中,作者首先回顾了上一节的Win32基础,然后逐步引入Direct3D的核心概念。本笔记的重点在于介绍...
《Visual C++游戏开发经典案例详解》是一本深入探讨使用Microsoft Visual C++进行游戏开发的专业书籍,源代码的提供使得读者能够亲手实践书中的每个案例,加深对游戏编程原理和技巧的理解。以下将详细解析该资源包含...