本文为 Introduction to 3D Game Programming with DirectX 11 读书笔记
本篇博客介绍Direct3D的基础,按照书上章节顺序介绍
Direct3D是底层图形API,它使得在渲染3D世界的时候自动使用3D硬件加速。
DirectX使用COM(Component Object Model)编程,使用COM组件编程的好处是内存管理会方便很多。当我们需要指向实现COM接口的对象指针的时候,可以通过特殊函数或者其他COM接口的方法;当不需要对象的时候只要调用Release方法就可以了。Release方法定义在IUnknown COM接口中,所有COM对象都继承该类。
DirectX使用的存储纹理信息的格式是DXGI_FORMAT枚举类型,当然可以用于做其他操作,不一定只用于纹理。一些常用的格式的枚举类型如下:
- DXGI_FORMAT_R32G32B32_FLOAT: Each element has three 32-bit floating-point components.
- DXGI_FORMAT_R16G16B16A16_UNORM: Each element has four 16-bit components mapped to the [0, 1] range.
- DXGI_FORMAT_R32G32_UINT: Each element has two 32-bit unsigned integer components.
- DXGI_FORMAT_R8G8B8A8_UNORM: Each element has four 8-bit unsigned components mapped to the [0, 1] range.
- DXGI_FORMAT_R8G8B8A8_SNORM: Each element has four 8-bit signed components mapped to the [-1, 1] range.
- DXGI_FORMAT_R8G8B8A8_SINT: Each element has four 8-bit signed integer components mapped to the [-128, 127] range.
- DXGI_FORMAT_R8G8B8A8_UINT: Each element has four 8-bit unsigned integer components mapped to the [0, 255] range.
一般的渲染系统都至少会使用两个buffer用于显示,back buffer和front buffer,front buffer用于输出显示,back buffer用于填充当前渲染的输出,然后在下一帧的时候交换两个buffer。这样可以防止只有一个buffer式的画面撕裂
深度图记录的是最终要输出的buffer中每个像素点的在camera坐标空间中深度信息。深度值在0~1之间,0表示距离camera最近的点(在视椎体的near面上),1表示距离camera最远的点(在视椎体的far面上)。depth buffer用于做深度测试,可以保证在前面的物体才会被渲染出来。
depth buffer也是一个纹理,比必须用特定的文件格式创建。用于创建depth buffer额格式如下:
- DXGI_FORMAT_D32_FLOAT_S8X24_UINT: Specifies a 32-bit floating-point depth buffer, with 8-bits (unsigned integer) reserved for the stencil buffer mapped to the [0, 255] range and 24-bits not used for padding.
- DXGI_FORMAT_D32_FLOAT: Specifies a 32-bit floating-point depth buffer.
- DXGI_FORMAT_D24_UNORM_S8_UINT: Specifies an unsigned 24-bit depth buffer mapped to the [0, 1] range with 8-bits (unsigned integer) reserved for the stencil buffer mapped to the [0, 255] range.
- DXGI_FORMAT_D16_UNORM: Specifies an unsigned 16-bit depth buffer mapped to the [0, 1] range.
纹理可用于渲染管线中的不同阶段;常见的应用是将纹理作为render target(如,DIrect3D最终将渲染输出到纹理中)和作为shader resource(如,纹理将会被shader采样)。这两个不同目的的texture resource在创建的时候要被绑定不同的flag:
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE
当然resource是不能直接绑定到管线上的,必须要通过相应纹理的resource view才行。比如,要使用texture作为render target和shader resource,那么我们就必须要创建两个view: ID3D11RenderTargetView 和 ID3D11ShaderResourceView。
Resource view做两件事。首先告诉Direct3D怎么使用resource,然后如果resource的格式被定义为typeless,那么就必须在创建view的时候声明类型。
多重采样可以抗锯齿
可以由上图看出锯齿的视觉效果,第二条线就是应用了抗锯齿的基础。这里介绍一个最简单的看锯齿技术supersampling,超采样通过把back buffer和depth buffer变为屏幕分辨率的4倍,然后将3D场景渲染到这个大的back buffer,最后在要输出back buffer到屏幕的时候,将back buffer下采样,比如选四个像素,然后做颜色值的平均,最终得到输出像素颜色值。
但是超采样需要相当于原先4倍的内存,而且对每个子像素颜色值的计算是最耗性能的,所以非常的不高效。
Direct3D支持一个稍微做了让步的抗锯齿技术MSAA (MultiSampling Anti-Aliasing),上图说明了MSAA的技术细节。
相比于超采样,MSAA使用了颜色值共享,每个像素的颜色值只需要计算一次,但是这是一个妥协,所以超采样的准确度更高。
typedef struct DXGI_SAMPLE_DESC {
UINT Count; //每个像素要采样的数量
UINT Quality; //指定quality level
} DXGI_SAMPLE_DESC, *LPDXGI_SAMPLE_DESC;
//不同的纹理格式和Sample数量决定了quality的等级,这就是查询quality level的方法
HRESULT ID3D11Device::CheckMultisampleQualityLevels(
DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels);
//D3D11支持的最大sampling数量
#define D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT ( 32 )
back buffer和depth buffer要使用相同的DXGI_SAMPLE_DESC结构
typedef enum D3D_FEATURE_LEVEL
{
D3D_FEATURE_LEVEL_9_1 = 0x9100,
D3D_FEATURE_LEVEL_9_2 = 0x9200,
D3D_FEATURE_LEVEL_9_3 = 0x9300,
D3D_FEATURE_LEVEL_10_0 = 0xa000,
D3D_FEATURE_LEVEL_10_1 = 0xa100,
D3D_FEATURE_LEVEL_11_0 = 0xb000,
} D3D_FEATURE_LEVEL;
//把下面的结构体用于初始化Direct3D,会逐个检查是否支持
D3D_FEATURE_LEVEL featureLevels[4] =
{
D3D_FEATURE_LEVEL_11_0, // First check D3D 11 support
D3D_FEATURE_LEVEL_10_1, // Second check D3D 10.1 support
D3D_FEATURE_LEVEL_10_0, // Next, check D3D 10 support
D3D_FEATURE_LEVEL_9_3 // Finally, check D3D 9.3 support
};
书上介绍的过程已经很好了
- Create the ID3D11Device and ID3D11DeviceContext interfaces using the D3D11CreateDevice function.
- Check 4X MSAA quality level support using the ID3D11Device::CheckMultisampleQualityLevels method.
- Describe the characteristics of the swap chain we are going to create by filling out an instance of the DXGI_SWAP_CHAIN_DESC structure.
- Query the IDXGIFactory instance that was used to create the device, and create an IDXGISwapChain instance.
- Create a render target view to the swap chain’s back buffer.
- Create the depth/stencil buffer and its associated depth/stencil view.
- Bind the render target view and depth/stencil view to the output merger stage of the rendering pipeline so that they can be used by Direct3D.
- Set the viewport.
// 创建Device和DeviceContext的方法
HRESULT D3D11CreateDevice(
IDXGIAdapter *pAdapter, //指定我们创建的device所代表的display adapter
D3D_DRIVER_TYPE DriverType, //我们将会总是指定D3D_DRIVER_TYPE_HARDWARE来使用3D硬件加速
HMODULE Software, //提供软件驱动
UINT Flags, //可选的设备创建flag,有D3D11_CREATE_DEVICE_DEBUG,D3D11_CREATE_DEVICE_SINGLETHREADED
CONST D3D_FEATURE_LEVEL *pFeatureLevels, //一个D3D_FEATURE_LEVEL数组,用来检查feature level support
UINT FeatureLevels, //上面数组的长度
UINT SDKVersion, //总是指定D3D11_SDK_VERSION.
ID3D11Device **ppDevice, //返回的创建的device
D3D_FEATURE_LEVEL *pFeatureLevel, //返回pFeatureLevels中第一个支持的feature level
ID3D11DeviceContext **ppImmediateContext//返回创建的device context
);
例子:
UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
ID3D11Device* md3dDevice;
ID3D11DeviceContext* md3dImmediateContext;
HRESULT hr = D3D11CreateDevice(
0, // default adapter
D3D_DRIVER_TYPE_HARDWARE,
0, // no software device
createDeviceFlags,
0, 0, // default feature level array
D3D11_SDK_VERSION,
& md3dDevice,
& featureLevel,
& md3dImmediateContext);
if(FAILED(hr))
{
MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
return false;
}
if(featureLevel != D3D_FEATURE_LEVEL_11_0)
{
MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
return false;
}
UINT m4xMsaaQuality;
HR(md3dDevice->CheckMultisampleQualityLevels(
DXGI_FORMAT_R8G8B8A8_UNORM, 4, & m4xMsaaQuality));
assert(m4xMsaaQuality > 0 );
// the characteristics of the swap chain
typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc; //我们想创建的back buffer的属性
DXGI_SAMPLE_DESC SampleDesc; //multisamples and quality level的数量
DXGI_USAGE BufferUsage; //指定为DXGI_USAGE_RENDER_TARGET_OUTPUT 因为我们要渲染到back buffer
UINT BufferCount;//swap chain中使用的back buffer的数量
HWND OutputWindow;//A handle to the window we are rendering into. win32编程的句柄
BOOL Windowed;//选择是窗口化显示还是全屏显示
DXGI_SWAP_EFFECT SwapEffect;//指定DXGI_SWAP_EFFECT_DISCARD让显卡驱动选择最搞笑的表现方式
UINT Flags;//可选flag
} DXGI_SWAP_CHAIN_DESC;
//对于flag。如果设置为DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH,那
//么在切换到全屏模式的时候,将会选择匹配back buffer设置的最佳display mode;
//如果不指定,那么在切换到全屏模式的时候,还是使用当前的display mode
// The DXGI_MODE_DESC type is another structure define
typedef struct DXGI_MODE_DESC
{
UINT Width; // desired back buffer width
UINT Height; // desired back buffer height
DXGI_RATIONAL RefreshRate; // display mode refresh rate
DXGI_FORMAT Format; // back buffer pixel format
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // display scanline mode
DXGI_MODE_SCALING Scaling; // display scaling mode
} DXGI_MODE_DESC;
例子:
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth; // use window's client area dims
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// Use 4X MSAA?
if(mEnable4xMsaa)
{
sd.SampleDesc.Count = 4;
// m4xMsaaQuality is returned via CheckMultisampleQualityLevels().
sd.SampleDesc.Quality = m4xMsaaQuality-1;
}
// No MSAA
else
{
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
}
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice, // Pointer to ID3D11Device.
DXGI_SWAP_CHAIN_DESC *pDesc, // Pointer to swap chain description.
IDXGISwapChain **ppSwapChain); // Returns created swap chain interface.
例子:
IDXGIDevice* dxgiDevice = 0;
HR(md3dDevice->QueryInterface(__uuidof(IDXGIDevice),
(void**)&dxgiDevice));
IDXGIAdapter* dxgiAdapter = 0;
HR(dxgiDevice->GetParent(__uuidof(IDXGIAdapter),
(void**))&dxgiAdapter));
// Finally got the IDXGIFactory interface.
IDXGIFactory* dxgiFactory = 0;
HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory),
(void**))&dxgiFactory));
// Now, create the swap chain.
IDXGISwapChain* mSwapChain;
HR(dxgiFactory->CreateSwapChain(md3dDevice, &sd, &mSwapChain));
// Release our acquired COM interfaces (because we are done with them).
ReleaseCOM(dxgiDevice);
ReleaseCOM(dxgiAdapter);
ReleaseCOM(dxgiFactory);
DXGI (DirectX Graphics Infrastructure)是与Direct3D分离的API,用于处理图形相关的事情,如 the swap chain, enumerating graphics hardware, and switching between windowed and full-screen mode.
ID3D11RenderTargetView* mRenderTargetView;
ID3D11Texture2D* backBuffer;
mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer));
md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView);
ReleaseCOM(backBuffer);
typedef struct D3D11_TEXTURE2D_DESC {
UINT Width; // The width of the texture in texels.
UINT Height; // The height of the texture in texels.
UINT MipLevels; // The number of mipmap levels.
UINT ArraySize; // The number of textures in a texture array
DXGI_FORMAT Format; // A member of the DXGI_FORMAT enumerated type specifying the format of the texels.
DXGI_SAMPLE_DESC SampleDesc; // The number of multisamples and quality level
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
} D3D11_TEXTURE2D_DESC;
作者建议避免使用D3D11_USAGE_DYNAMIC和D3D11_USAGE_STAGING,因为会降低性能
For maximum speed, graphics hardware works best when we create all of our resources and upload the data to the GPU, and the resources stay on the GPU where only the GPU reads and writes to the resources
只有在有的操作都由GPU完成的时候,性能才最好
例子:
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// Use 4X MSAA? --must match swap chain MSAA values.
if( mEnable4xMsaa )
{
depthStencilDesc.SampleDesc.Count = 4;
depthStencilDesc.SampleDesc.Quality = m4xMsaaQuality-1;
}
// No MSAA
else
{
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
}
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ID3D11Texture2D* mDepthStencilBuffer;
ID3D11DepthStencilView* mDepthStencilView;
HR(md3dDevice->CreateTexture2D(
&depthStencilDesc, // Description of texture to create.
0,
&mDepthStencilBuffer)); // Return pointer to depth/stencil buffer.
HR(md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer, // Resource we want to create a view to.
0,
&mDepthStencilView)); // Return depth/stencil view
md3dImmediateContext->OMSetRenderTargets(
1, &mRenderTargetView, mDepthStencilView);
// 指定视椎体的结构
typedef struct D3D11_VIEWPORT {
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D11_VIEWPORT;
例子:
D3D11_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = static_cast<float>(mClientWidth);
vp.Height = static_cast<float>(mClientHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
md3dImmediateContext->RSSetViewports(1, &vp);
看书的源码就行,我个人上传了一份,做了少量的修改,在win10上可以直接运行
https://gitee.com/alienity/d3d11