使用d3d绘图
上一篇使用d2d绘图,d3d有一点稍微不同的是会引入着色器,而且初始化比d2d会麻烦一些。
首先头文件引用以及库的链接。
注意这里将dx11改成了dx11.1,主要是考虑到11.1多了一些比11.0好很多的特特性。
考虑到D3D的复杂程度,引入了方便调试的库。
具体参考:
DirectX11--HR宏关于dxerr库的替代方案
如下:
#pragma once
#include <Windows.h>
//Comptr智能指针库
#include <wrl/client.h>
// Direct2D 头文件
#include <d2d1.h>
//Direct3D 头文件
#include <d3d11_1.h>
//Debug模式所用调试库
#include <assert.h>
#include"DXTrace.h"
//链接相关库
#pragma comment(lib,"d2d1.lib")
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib,"dxgi.lib")
1.D3D的初始化
龙书第四章有初始化D3D的具体方式、步骤,这里只大概记录一下:
1. 创建ID3D11Device和ID3D11DeviceContext
类声明中添加
~~~ c++
//D3D资源
ComPtr<ID3D11Device> m_pd3dDevice;
ComPtr<ID3D11DeviceContext> m_pd3dDeviceContext;
//D3D
bool InitDirect3D(); //Direct3D初始化
void FinalizeDirect3D();//Direct3D释放资源
~~~
C++方法:
~~~ c++
//1. 创建设备和上下文
/
创建设备
HRESULT WINAPI D3D11CreateDevice(
In_opt IDXGIAdapter pAdapter, //适配器类型,先选择主显卡,以后可能会增加功能,切换集显独显之类的
D3D_DRIVER_TYPE DriverType, //驱动类型,一般选择HARDWARE,软件模拟效率低还烫
HMODULE Software, //直接填NULL,因为不考虑软件模拟
UINT Flags, //调试时可以设置为Debug,发布时设为NULL或者Release
In_reads_opt( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels, //特征等级数组,用于测试顺序,一般由高到低.也可以不填
UINT FeatureLevels, //上一个数组的长度,为null的话这里填0
UINT SDKVersion, //始终为D3D11_SDK_VERSION
COM_Outptr_opt ID3D11Device** ppDevice, //返回设备对象的地址
Out_opt D3D_FEATURE_LEVEL* pFeatureLevel, //返回上述数组中的第一个可用元素。为null的话返回最高等级
COM_Outptr_opt ID3D11DeviceContext** ppImmediateContext ); //返回创建后的设备上下文
*/
//Flag
UINT createDeviceFlag = 0; //默认为release模式,如果调试模式启动,设为DEBUG
if defined( DEBUG ) || defined( _DEBUG )
createDeviceFlag = D3D11_CREATE_DEVICE_DEBUG;
endif
//特征类型数组
D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
UINT numFeatureLevels = ARRAYSIZE( featureLevels );
D3D_FEATURE_LEVEL featureLevel;
//创建设备和上下文
hr=D3D11CreateDevice( 0, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlag, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel,
m_pd3dDeviceContext.GetAddressOf() );
if (FAILED(hr)){
MessageBox(0,L"创建设备失败!" ,L"Error",0);
return false;
}
~~~
2. 使用D3D的方法检测4X多重采样等级
类声明:
~~~ c++
UINT m_4xMsaaQuality; // 检测4xMsaa质量支持
~~~
方法:
~~~ c++
//检测4XMSAA支持的质量等级
m_pd3dDevice->CheckMultisampleQualityLevels( DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality );
assert( m_4xMsaaQuality > 0 );
~~~
3. 创建交换链,交换链可以实现多缓冲
由于d3d11.0与11.1的SwapChain Desc结构有些许不同,这一块就比较复杂。
不过说复杂也不太准确,主要是创建交换链所需要的结构体属性太多,只看逻辑的话还是很简单的。
类声明:
//D3D11资源
ComPtr<ID3D11Device> m_pd3dDevice;
ComPtr<ID3D11DeviceContext> m_pd3dDeviceContext;
ComPtr<IDXGISwapChain> m_pSwapChain;
//11.1 资源,支持11.1的话直接将原指针作为11.1的指针用
ComPtr<ID3D11Device> m_pd3dDevice1;
ComPtr<ID3D11DeviceContext> m_pd3dDeviceContext1;
ComPtr<IDXGISwapChain1> m_pSwapChain1;
方法:
~~~ c++
//3. 创建交换链,因为dx11.1与dx1.0创建SwapChain方法不同,若在不支持11.1的机器上跑,会报错
//判断是否支持dx11.1 方法参考XJun所写方法
ComPtr<IDXGIDevice> dxgiDevice = nullptr;
ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
ComPtr<IDXGIFactory1> dxgiFactory0 = nullptr; //D3D11.0 包含DXGI1.1
ComPtr<IDXGIFactory2> dxgiFactory1 = nullptr; //D3D11.1 包含DXGI1.2 特有的接口类
//要使用IDXGIFACTORY创建DXGI交换链,需要dxgi.lib,IDXGIFACTORY由上面的m_pd3dDevice得来。
HR(m_pd3dDevice.As( &dxgiDevice ));
HR(dxgiDevice->GetAdapter( dxgiAdapter.GetAddressOf() ));
HR(dxgiAdapter->GetParent( __uuidof( IDXGIFactory1 ), reinterpret_cast<void **>( dxgiFactory0.GetAddressOf() ) ));
//查看该对象是否包含IDXGIFactory2的接口,如果指针地址变化,说明支持,这时候调用dx11.1的创建方法
hr = dxgiFactory0.As( &dxgiFactory1 );
//dx11.1创建交换链
if (dxgiFactory1!=nullptr){
//这里太长就省略不放了,结构体的声明
//...
//是否开启4xMsaa?
if (m_enable4xMsaa){
sd.SampleDesc.Count = 4;
sd.SampleDesc.Quality=m_4xMsaaQuality-1;
} else {
sd.SampleDesc.Count=1;
sd.SampleDesc.Quality=0;
}
//全屏下的描述,这里相当于Dx11.0 的一些属性
//...
//创建交换链
HR(dxgiFactory1->CreateSwapChainForHwnd(m_pd3dDevice.Get(),m_hMainWnd,&sd,&fd,nullptr,m_pSwapChain1.GetAddressOf()));
m_pSwapChain1.As(&m_pSwapChain);
}
//dx11.0创建交换链
else {
//dx11.0交换链描述
/*
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc; //后台缓冲区描述
DXGI_SAMPLE_DESC SampleDesc; //采样器描述
DXGI_USAGE BufferUsage; //设为 DXGI_USAGE_RENDER_TARGET_OUTPUT,因为要渲染到后台缓冲区
UINT BufferCount; //后台缓冲区Buffer数量,双缓存为1,三缓存为2
HWND OutputWindow; //渲染到的窗口的句柄
BOOL Windowed; //true时,程序以窗口模式运行,为false时,程序以全屏模式运行
DXGI_SWAP_EFFECT SwapEffect; //设为 DXGI_SWAP_EFFECT_DISCARD,最高效的显示模式
UINT Flags; //使用该标志后,设为.._ALLOW_MODE_SWITCH,切换到全屏后,D3D会选择最匹配的显示模式
} DXGI_SWAP_CHAIN_DESC;
*/
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory( &sd, sizeof sd );
/*
typedef struct DXGI_MODE_DESC
{
UINT Width; //缓冲区宽带
UINT Height; //缓冲区高度
DXGI_RATIONAL RefreshRate; //刷新率
DXGI_FORMAT Format; //像素格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; //显示扫描线顺序 ??
DXGI_MODE_SCALING Scaling; //显示扫描线??
} DXGI_MODE_DESC;
*/
//...结构体的声明
//是否开启4xMsaa?
if ( m_enable4xMsaa ) {
sd.SampleDesc.Count = 4;
sd.SampleDesc.Quality = m_4xMsaaQuality - 1;
} else {
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
}
//创建交换链
dxgiFactory0->CreateSwapChain(m_pd3dDevice.Get(),&sd,m_pSwapChain.GetAddressOf());
}
~~~
### 4. 创建渲染目标视图 Render Target View
Dx中(可能其他图形API也是?),资源自身不能直接被绑定到管线,必须创建相关的View才能绑定到管线中。要想把交换链中的后台缓冲区的内容绑定到Dx的管线中执行渲染时,就需要创建一个RenderTargetView。
类声明:
~~~ c++
//通用资源
ComPtr<ID3D11RenderTargetView> m_pRenderTargetView; //渲染目标视图
~~~
方法:
//4 .创建渲染目标视图
//后台缓冲区
ComPtr<ID3D11Texture2D> backBuffer;
//第1个参数为索引值,只用了1个所以为0.第2个为缓冲区接口类型,通常为ITexture2D
HR( m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast<void **>( backBuffer.GetAddressOf() ) ) );
//第2个参数在弱类型时才用指定
HR( m_pd3dDevice->CreateRenderTargetView( backBuffer.Get(),nullptr, m_pRenderTargetView.GetAddressOf() ) );
//创建视图后就不需要原来的指针了,可以释放掉
backBuffer.Reset();
5. 创建深度/模板缓冲区以及视图
与前面后台缓冲区声明类似,深度缓冲区同样是一个Texture2D的结构,不过这次要先声明缓冲区,再创建视图。模板信息与深度信息共享一个缓冲区,所以只用创建一个缓冲区、一个视图。
类声明:
~~~ c++
ComPtr<ID3D11DepthStencilView> m_pDepthStencilView; //深度模板缓冲区视图
ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer; //深度模板缓冲区(Texture2D的缓冲区i,不是Buffer)
~~~
方法(这里不复制赋值的语句了,直接看定义和注释就行):
~~~ c++
//5. 创建深度/模板缓冲区及其视图
//要创建纹理,首先声明描述
/*
typedef struct D3D11_TEXTURE2D_DESC
{
UINT Width; //纹理宽带
UINT Height; //纹理高度
UINT MipLevels; //mipmap level的数量 ,深度缓冲区1个就够了
UINT ArraySize; //纹理数组中纹理的数量,深度缓冲区1个就够了
DXGI_FORMAT Format; //纹理格式。必须是以下之一 : DXGI_FORMAT_D开头的4个
DXGI_SAMPLE_DESC SampleDesc; //多重采样描述,同之前类似
D3D11_USAGE Usage; //Usage。四个可选值:
//Default:CPU不能读写,由GPU全权管理,现在使用这个;
//IMMUTABLE 表示资源内容创建后不变,GPU只读,CPU不读不写
//DYNAMIC 表示CPU会频繁更新。GPU读取,CPU写。
//STAGING 表示CPU会读取副本(从显存复制到内存)
UINT BindFlags; //指定资源绑定到管线哪个阶段,对于深度/模板缓冲区,设为D3D11_BIND_DEPTH_STENCIL.
//其他选项
//D3D11_BIND_RENDER_TARGET 作为渲染目标绑定
//D3D11_BIND_SHADER_RESOURCE 作为着色器资源绑定
UINT CPUAccessFlags; //指定CPU访问权限,要结合Usage的值来选择
//0表示不读不写
//WRITE表示写
//READ表示读
UINT MiscFlags; //可选的标志值,与深度缓冲区无关,设为0
} D3D11_TEXTURE2D_DESC;
*/
//结构体复制
//....
////创建深度模板缓冲区
HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc,nullptr,m_pDepthStencilBuffer.GetAddressOf()));
//创建深度模板缓冲区视图
HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(),nullptr,m_pDepthStencilView.GetAddressOf()));
~~~
6. OM阶段以及设置最终输出视口
这里绑定渲染目标和深度缓冲视图到管线的输出合并阶段(Output Merger Stage,OM),DX管线的最后一个阶段。
设置视口是因为有时我们不会把整个3D场景渲染到震哥哥后台缓冲区,可能只需要绘制到一小块后台缓冲区上,例如双人游戏的分屏等等。这就可以通过设置Viewport来实现。
方法:
~~~c++
//将视图绑定到Output Merger Stage OM阶段,Dx管线的最后一个阶段
m_pd3dDeviceContext->OMSetRenderTargets(1,m_pRenderTargetView.GetAddressOf(),m_pDepthStencilView.Get());
//设置视口,绑定viewport到(Rasterize Stage,RS)光栅化阶段
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport,sizeof(viewport));
viewport.TopLeftX=0;
viewport.TopLeftY=0;
viewport.Width=static_cast<float>(m_ClientWidth);
viewport.Height=static_cast<float>(m_ClientHeight);
viewport.MaxDepth=1.0;
viewport.MinDepth=0.0;
UINT numViewports=1;
m_pd3dDeviceContext->RSGetViewports(&numViewports,&viewport);
return true;
~~~
以上就是InitDirect3D()方法的全部,最后在Init()中调用以初始化。
测试:
修改DrawScene()
如下:
~~~c++
//绘制3D
//***************************************************************************************
assert(m_pd3dDeviceContext);
assert(m_pSwapChain);
static float black[] = {0.0f,0.0f,0.0f,1.0f};
m_pd3dDeviceContext->ClearRenderTargetView(m_pRenderTargetView.Get(),black);
m_pd3dDeviceContext->ClearDepthStencilView(m_pDepthStencilView.Get(),D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.0f,0);
HR(m_pSwapChain->Present(0,0));
//***************************************************************************************
~~~
最后输出了一个黑色的屏幕~
以上就是D3D初始化的整体框架,当然还有很多不完善的地方,日后修改~