本文翻译自<Beginning DirectX 11 Game Programming> \ 第二章 \ 第三节 Time for Direct3D
该节内容和线数一样,都是入门必须懂的,如果不以翻译的形式,我又没耐心逐行看完,于是粗翻了一下..
线数部分可以阅读<Introduction to 3D Game Programming with DirectX 9.0>的Part I Mathematical Prerequisites
PS: <Introduction to 3D Game Programming with DirectX 9.0>是入门经典,中文名叫<DirectX 9 3D游戏程序设计入门>
初始化Direct3D
要载入Direct3D,要完成以下4步:
1.定义设备类型和特征级别.
2.创建Direct3D设备,渲染上下文和交换链
3.创建渲染目标
4.设置视口(viewport)
驱动类型和特征级别
在Direct3D 11中可以选择硬件设备,WARP(Windows高级光栅平台)设备,软件驱动设备或引用设备.
硬件设备是运行在图形硬件上的Direct3D设备,速度最快,前提是你的图形硬件支持.
引用设备是硬件不支持,利用CPU做渲染.引用设备就是完全以软件在CPU上模拟硬件渲染.速度非常慢,非常低效,只用于找不到更好选择时的开发.这个选项在DirectX发布新版本而市场上的硬件又未支持的时候有用.
软件驱动设备允许开发者自行开发渲染驱动并配合Direct3D使用.也称为可插式软件驱动.该选项不推荐用于性能要求高的程序,硬件或WARP会是更好的选择.
WARP设备是高效的CPU渲染设备,它模拟了Direct3D全部的特征集.WARP利用Vista/Win7的Windows图形运行时,使用高度优化的指令和代码,该选择优于引用模式.该选项用于低端机器上,如果想看看一个游戏在有限性能下的表现时有用.对于硬件不支持的情况,相比引用设备对于实时程序的过慢,WARP是一个不错的选择.WARP始终是模拟的,理所当然不及硬件设备快.
Direct3D的特征级别允许我们声明指定的特征集,用于忽略DirectX 9.0和更低的设备性能需求.贯穿全书我们的讨论对象只有三个版本的设备:Direct3D 11.0/10.1/10.0.如果你的硬件不支持DirectX 11,书上Demo代码可以用Direct3D 10.1或者10.0代替而不需要加任何代码.如果没有我们想要的提供给特征级别的硬件选项,我们会尝试从WARP或引用模式中获取支持.
以下代码展示驱动类型和特征级别的定义.创建了包含各个类型的数组,我们可以用来尝试创建大部分需求的设备.Win32宏ARRAYSIZE是用来获取数组的大,GetClientRect(Win32函数)的作用是计算程序的客户端区域(x,y),然后用来设置Direct3D设备的渲染宽高.注意Win32程序有客户端区域和无客户端区域一样,但我们只能渲染客户端区域.
RECT dimensions; GetClientRect(hwnd, &dimensions); unsigned int width = dimensions.right - dimensions.left; unsigned int height = dimensions.bottom - dimensions.top; D3D_DRIVER_TYPE driverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_SOFTWARE }; unsigned int totalDriverTypes = ARRAYSIZE(driverTypes); D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0 }; unsigned int totalFeatureLevels = ARRAYSIZE(featureLevels);
设备和交换链创建
下一步就是交换链的创建.交换链在Direct3D是一个给设备使用的渲染单元格集合.每个设备至少有一个交换链,而多个设备可以创建多个交换链.渲染单元格可以是用于渲染和显示到屏幕的颜色缓存,也可以是色深缓存,也可以是模版缓存等.
通常在游戏中我们会设置两个颜色缓存用于渲染,称之为主缓存和次缓存,也可以叫前端缓存和后端缓存.主缓存(前段缓存)是当前显示到屏幕的缓存,次缓存(后端缓存)是用于为下一帧绘图的缓存.
渲染发生得很快,屏幕的各个部分很可能会超过上一帧结果被更新到显示器前被绘制出来,这样会导致渲染出不自然的效果和不是想要的结果.在缓存间切换可以使得一个被写入的同时另一个被显示,以此避免不自然的效果.该项技术在计算机图形学中叫作双缓冲.一个交换链可以拥有一个或多个这样的缓存,Direct3D可以控制这些缓存的切换.
以下代码展示如何创建交换链描述,一个交换链描述用于定义我们希望交换链如何创建.它的成员有:
■ 缓存数(用于翻页处理的主/次缓存)
■ 缓存的宽和高
■ 缓存格式
■ 刷新速率,用于指定显示器的刷新频率,单位是赫兹
■ window句柄(用CreateWindow创建的相同窗口)
■ "Windowed"布尔标记,用以指定是以窗口还是全屏模式显示Direct3D
■ 采样数和采样描述的质量
DXGI_SWAP_CHAIN_DESC swapChainDesc; 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;
采样描述定义了Direct3D的多重采样属性.多重采样是一项技术,用于采样和平均渲染像素,用于在锐利颜色转换中创建平滑转移.我们试图利用多重采样来减少不自然效果的手段叫锯齿边缘,也被称为楼梯效果.
交换链的缓存使用和描述包含了大部分成员设置,全部都是简单易懂的设置.缓存使用设置为DXGI_USAGE_RENDER_TARGET_OUTPUT,这样交换链可以用于输出,或者说可以用于被渲染.
已有交换链描述,下一步是创建渲染上下文,设备和交换链.Direct3D设备就是设备本身,它和硬件通讯.Direct3D上下文是用于通知设备如何绘图的渲染上下文.它还包括渲染状态和其它绘图信息.我们之前讨论的交换链是用于渲染设备和上下文即将绘制到的单元格.
以下代码展示设备,渲染上下文和交换链的创建.Direct3D设备用类型ID3D11Device,渲染上下文用类型ID3D11Context,交换链用类型IDXGISwapChain.
ID3D11Device device_; ID3D11Context d3dContext_; IDXGISwapChain swapChain_; unsigned int creationFlags = 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("Failed to create the Direct3D device!"); return false; }
交换链,设备和渲染上下文可以由单个Direct3D函数调用或者指定对象Direct3D调用(例如CreateSwapChain创建一个交换链).这个函数叫D3D11CreateDeviceAndSwapChain.在上面代码中,我们迭代各个驱动类型,尝试创建一个硬件设备,一个WARP设备或一个引用设备.如果全部失败,我们就不能初始化Direct3D. D3D11CreateDeviceAndSwapChain函数也依赖特征级别,所以至少那些特征级别的其中之一存在,还有设备类型存在,这个函数才会成功执行.
D3D11CreateDeviceAndSwapChain作为参数有以下值:
■ 用来创建设备的视频适配器指针.传入null值会导致Direct3D使用默认设备.当机器装有多个设备的时候有用.
■ 我们想要创建的驱动类型(硬件,WARP,软件,或者引用).
■ 实现软件渲染设备的dll的句柄.如果我们的驱动类型是软件(D3D_DRIVER_TYPE_SOFTWARE),该参数不能为null.
■ 创建标记.在Direct3D中,0标记用于释放我们的游戏资源, D3D11_CREATE_DEVICE_DEBUG用于创建一个可调试的设备,便于开发.
■ 我们指定的特征级别,按需求从高到低排序.本书主要是Direct3D 11或者Direct3D 10,但我们也可以通过D3D_FEATURE_
LEVEL_9_3,D3D_FEATURE_LEVEL_9_2,或者D3D_FEATURE_LEVEL_9_1指定到Direct3D 9.
■ 特征级别数组的元素数
■ SDK版本,用DirectX 11 SDK的话一直都是D3D11_SDK_VERSION.
■ 交换链描述对象.
■ 设备对象的地址(设备类型是ID3D11Device).
■ 选定的特征级别的地址.早期我们提供一份预留的特征列表,但只选其中一个保存到该地址中.
■ 渲染上下文的地址(上下文类型是ID3D11Context).
渲染目标视图创建
渲染目标视图是Direct3D资源,用输出合并器写入.在输出合并器渲染交换链的后端缓存(次缓存)的同时,创建它的渲染目标视图.
在第三章我们会讨论纹理的细节,不过现在先知道纹理一般是一张图片.交换链的主次渲染缓存是颜色纹理,可以调用交换链函数GetBuffer来获取它的指针.GetBuffer的参数:
■ 缓存索引
■ 操作的接口类型.2D纹理的类型是ID3D11Texture2D.
■ 获取的缓存地址.必须转换类型到LPVOID
利用一个缓存指针,调用Direct3D设备对象函数CreateRenderTargetView来创建渲染目标视图.渲染目标视图有类型ID3D11RenderTargetView和CreateRenderTargetView函数,需要的参数有,我们为2D纹理创建的视图,渲染目标描述和我们创建的ID3D11RenderTarget视图对象.设置渲染目标描述参数为null可以返回一个0级别mip的整体表面.Mip-map级别的更多细节将会在第三章讨论.
一旦我们完成创建渲染目标视图,我们可以释放交换链的后端缓存的指针.如果我们通过COM对象获得了引用,我们必须调用COM的Release函数来递减引用数量.这个必须做以避免内存泄漏,因为我们不希望操作系统在程序关闭之后仍然持有它.
每次我们想渲染一个指定的目标,我们必须在绘图调用之前设置它.这一步会由OMSetRenderTarget函数完成,该函数输出合并器的一部分(因此OM在OMSetRenderTarget).OMSetRenderTarget函数需要参数是,我们调用函数绑定的视图数,渲染目标视图列表和深度/模版视图.我们会在第三章讨论深度和模版.
以下代码展示创建和绑定渲染目标视图:
ID3D11RenderTargetView* backBufferTarget_; ID3D11Texture2D* backBufferTexture; HRESULT result = swapChain_->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferTexture); if(FAILED(result)) { DXTRACE_MSG("Failed to get the swap chain back buffer!"); return false; } result = d3dDevice_->CreateRenderTargetView(backBufferTexture, 0, &backBufferTarget_); if(backBufferTexture) { backBufferTexture->Release(); } if(FAILED(result)) { DXTRACE_MSG("Failed to create the render target view!"); return false; } d3dContext_->OMSetRenderTargets(1, &backBufferTarget_, 0);
代码中用到的宏DXTRACE_MSG是用于调试目的,后面会讨论到,单独和DirectX Error Handling Library一起讨论.
视口
Direct3D 11的最后一块拼图是视口的创建和设置.视口定义用于渲染的屏幕区域.单玩家或非分屏多玩家游戏中通常是全屏操作,所以我们把视口的宽高设置成跟Direct3D交换链的宽高一样.对于分屏游戏则可以创建两个视口,一个定义高位部分一个定义低位部分.为了渲染分屏视图,我们必须分别渲染一次玩家1和一次玩家2的场景.虽然多玩家游戏超出了本书范围,但你可以在http://www.ultimategameprogramming.com/网站上找到创建分屏的demo.
视口的创建是利用D3D11_VIEWPORT对象,调用RSSetViewports函数将该对象设置到渲染上下文.RSSetViewports需要参数是我们设置的视口数量和视口对象列表.以下代码展示如何创建和设置全屏视口,其中XY变量用来标记屏幕的左上位置,而视口的色深是0到1之间的值.
D3D11_VIEWPORT viewport; 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);
清空和显示屏幕
渲染到屏幕需要几个不同的步骤.第一步通常是清空准备渲染的目标界面.大部分游戏都包含缓存,如色深缓存,我们会在第三章讨论.在下面展示的空Direct3D窗口demo中,我们清空渲染目标视图的颜色缓存来指定颜色.通过调用Direct3D内容的ClearRenderTargetView函数实现. ClearRenderTargetView的原型如下:
void ClearRenderTargetView( ID3D11RenderTargetView* pRenderTargetView,
const FLOAT ColorRGBA[4] );
ClearRenderTargetView函数需要的参数是需要清空的渲染目标视图和清空后的初始值.为了清空屏幕,我们需要指定一个颜色作为背景阴影.该颜色是由红,绿,蓝和alpha数组组成,值的范围从0.0到1.0.其中0.0表示无亮度,1.0表示全亮度.在字节范畴,1.0等值于255.如果颜色红,绿和蓝组件都是1.0,我们就得到纯白色.颜色会在第三章讨论.
下一步是绘制场景的几何图形.本章我们不绘制任何几何图形,第三章会交待更多细节.
最后一步是显示渲染缓存到屏幕,通过调用交换链的Present函数.Present的原型如下:
HRESULT IDXGISwapChain::Present( UINT SyncInterval, UINT Flags );
Present函数的参数是同步间隔和展现标记.同步间隔可以是0代表直接显示,或者1,2,3,4..n表示在第n个垂直空白.一个垂直空白是介乎于当前帧的最后一条线和和下一帧的第一条线之间的时间差.设备例如计算机屏幕会垂直的一行一行的更新像素.
Present函数标记可以是0表示输出每个缓存, DXGI_PRESENT_ TEST表示输出任意测试用途的值,DXGI_PRESENT_DO_ NOT_SEQUENCE表示呈现输出而不测序,利用垂直空白同步.我们可以值传入0,0给Present函数来显示我们的渲染结果.
下面代码展示了清空屏幕和展现到视图.在第三章我们会深入更多渲染缓存的特性,关于颜色,色深,用于平滑动画的双缓冲等等.本章只关注学习Direct3D 11的安装.下面代码会显示蓝黑的背景颜色.
float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f }; d3dContext_->ClearRenderTargetView(backBufferTarget_, clearColor); swapChain_->Present(0, 0);
清除
Direct3D程序最后要做的事就是清除和释放你创建的对象.例如,在程序的开始,你至少创建一个Direct3D设备,一个Direct3D渲染上下文,一个交换链和一个渲染目标视图.当程序关闭的时候,你需要释放这些对象,这样资源才会返回给操作系统重用.
COM对象会保留一个引用计数器来告诉操作什么时候可以安全从内存中移除对象.当计数器归0时,操作系统会收回这些资源.
下面代码展示如何释放Direct3D对象.其中if语句首先判断和确认对象非null,然后调用Release函数.以创建顺序的相反顺序释放对象是一个好的程序思维.
if(backBufferTarget_) backBufferTarget_->Release(); if(swapChain_) swapChain_->Release(); if(d3dContext_) d3dContext_->Release(); if(d3dDevice_) d3dDevice_->Release();
格式
有时候你会需要指定一个DXGI格式.格式是用来描述一张图片的层,用到的颜色的比特数或者顶点缓存的顶点的层.最普遍的是DXGI格式用来描述交换链中的缓存层. DXGI格式不是任何数据类型的声明,它只是一个格式.
例如,DXGI格式, DXGI_FORMAT_R8G8B8A8_UNORM声明数据会使用8个比特来保存RGBA组件.当定义顶点时,像DXGI_FORMAT_R32G32B32_FLOAT这样的格式用于32比特有效的RGB组件.即使一个格式指定RGB,它也只是描述数据如何摆放,而不是数据用来干什么.
有时候你会看到格式指定相同数值的比特给各个组件,但是却有不通的扩展.例如, DXGI_
FORMAT_R32G32B32A32_FLOAT和DXGI_FORMAT_R32G32B32A32_UINT都保留相同的比特数给各个组件,但是也指定包含这些比特的数据类型.这些被认为是完全类型的格式.
那些不会声明类型的格式称为无类型格式.他们保留相同的比特数给各个组件,但不关心包含的数据类型,类似的有DXGI_FORMAT_R32G32B32A32_TYPELESS.下表列出了通用的格式.
Format |
Description |
DXGI_FORMAT_R32G32B32A32_TYPELESS |
128-bit format consisting of four typeless RGBA components. |
DXGI_FORMAT_R32G32B32A32_FLOAT |
128-bit format consisting of four float RGBA components. |
DXGI_FORMAT_R32G32B32A32_UINT |
128-bit format consisting of four unsigned integer RGBA components. |
DXGI_FORMAT_R32G32B32A32_SINT |
128-bit format consisting of four signed integer RGBA components. |
DXGI_FORMAT_R8G8B8A8_TYPELESS |
32-bit format consisting of four typeless RGBA components. |
DXGI_FORMAT_R8G8B8A8_UINT |
32-bit format consisting of four unsigned integer RGBA components. |
DXGI_FORMAT_R8G8B8A8_SINT |
32-bit format consisting of four signed integer RGBA components. |