DirectX 渲染的主要步骤
- 创建窗口
- DirectX 初始化
- 创建设备和设备上下文
- 创建Factroy
- 填充交换链描述符
- 创建交换链
- 创建渲染目标视图,设置视口大小
- 创建着色器
- 填充缓冲区并绑定到渲染管线
- 定义顶点数据结构体
- 创建顶点缓冲区、填充并绑定
- 创建索引缓冲区、填充并绑定
- 创建顶点常量缓冲区、填充并绑定
- 创建像素常量缓冲区、填充并绑定
- 设置图元类型
- 创建并绑定输入布局(也可以在创建完顶点着色器后立马创建)
- 将着色器和相应阶段的常量缓冲区绑定到渲染管线。
- 渲染
- 主函数调用所有流程
- 着色器代码
- 创建窗口。
- 创建设备和上下文。
- 创建交换链和渲染视图。
- 创建顶点着色器。
- 创建并绑定输入布局。
- 创建片段着色器。
- 创建顶点、索引、常量缓冲区,并填充和绑定。
- 设置图元拓扑(图元类型)。
- 将着色器和常量缓冲区绑定到渲染管线。
- 清空渲染视图。
- 绘制
- 呈现
- 转到步骤10.
以下是每个步骤的代码
准备工作
需要包含的头文件
#include <windows.h>
#include <wrl/client.h> // Comptr智能指针
#include <d3d11_1.h>
#include <d3d11_2.h>
#include <string>
#include <DirectXMath.h>
#include <d3dcompiler.h>
// 添加DirectX11所有要引用的库
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "D3DCompiler.lib")
#pragma comment(lib, "winmm.lib")
整个流程所需要的变量和结构体
template <class T>
using ComPtr = Microsoft::WRL::ComPtr<T>; // 微软的智能指针
using namespace DirectX;
// 需要的所有变量
int winX = 0;
int winY = 0;
int winWidth = 1920;
int winHeight = 1080;
struct VertexPosColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT4 color;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};
const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = {
//-|-语义名字-|--|语义索引-|----|---------数据格式-----------|--|-输入槽索引[0-15]-|---|-字节对齐偏移量-|---|----输入槽类(顶点or索引)-----|---|-多实例渲染个数-|-
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DAT
struct VSConstantBuffer //顶点常量缓冲区
{
XMMATRIX world;
XMMATRIX view;
XMMATRIX proj;
};
struct PSConstantBuffer
{
XMFLOAT4 color;
XMMATRIX proj;
};
HWND ghMainWnd = 0; // 窗口句柄
ComPtr<ID3D11Device> pd3dDevice = nullptr; // D3D11设备
ComPtr<ID3D11DeviceContext> pd3dDeviceContext = nullptr; // D3D11设备上下文
DXGI_SWAP_CHAIN_DESC sd;
ComPtr<IDXGISwapChain> pSwapChain = nullptr; // D3D11交换链
ComPtr<IDXGIFactory1> dxgiFactory = nullptr;
//常用资源
ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer = nullptr; // 深度模板缓冲区
ComPtr<ID3D11RenderTargetView> m_pRenderTargetView = nullptr; // 渲染目标视图
ComPtr<ID3D11DepthStencilView> m_pDepthStencilView = nullptr; // 深度模板视图
D3D11_VIEWPORT m_ScreenViewport; // 视口
//着色器资源
ComPtr<ID3DBlob> vertexBlob; // 顶点着色器
ComPtr<ID3DBlob> pixBlob; //像素着色器
ComPtr<ID3D11InputLayout> pVertexLayout = nullptr; // 顶点输入布局
ComPtr<ID3D11Buffer> pVertexBuffer = nullptr; // 顶点缓冲区
ComPtr<ID3D11VertexShader> pVertexShader = nullptr; // 顶点着色器
ComPtr<ID3D11PixelShader> pPixelShader = nullptr; // 像素着色器
ComPtr<ID3D11Buffer> pIndexBuffer = nullptr; // 索引缓冲区
ComPtr<ID3D11Buffer> pVSConstBuffer = nullptr; // 顶点常量缓冲区
ComPtr<ID3D11Buffer> pPSConstBuffer = nullptr; // 顶点常量缓冲区
VSConstantBuffer VSCBuffer; // 用于修改顶点GPU常量缓冲区的变量
PSConstantBuffer PSCBuffer; // 用于修改顶点GPU常量缓冲区的变量
创建窗口
/*************************************************创建窗口*************************************************/
/* 注意
* 1. 必须设置窗口事件处理回调函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
* 2. 窗口的信息可以在 WNDCLASS 中设置,比如窗口的图标,标题等。
* 3. 调用CreateWindow创建窗口。
* 4. 创建完窗口,可以得到一个窗口句柄(HWND ghMainWnd),后面创建交换链会用到这个窗口句柄。
*/
// 窗口事件处理回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_LBUTTONDOWN:
MessageBox(0, L"Hello,world!", L"Hello", MB_OK);
return 0;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
DestroyWindow(ghMainWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
//创建窗口函数
bool InitWindowsApp(HINSTANCE instanceHandle, int show)
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW; // 水平或者垂直方向发生变化时,重新绘制 redraw
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = instanceHandle;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0,IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"BasicWndClass";
if (!RegisterClass(&wc))
{
MessageBox(0, L"RegisterClass FAILED", 0, 0);
return false;
}
ghMainWnd = CreateWindow(
L"BasicWndClass",
L"Win32Basic",
WS_OVERLAPPEDWINDOW,
winX,
winY,
winWidth,
winHeight,
0,
0,
instanceHandle,
0
);
if (ghMainWnd == 0)
{
MessageBox(0, L"CreateWindow FAILED", 0, 0);
return false;
}
ShowWindow(ghMainWnd,show);
UpdateWindow(ghMainWnd);
return true;
}
DirectX 初始化
- 创建设备和设备上下文
- 创建Factory
- 填充交换链描述符
- 创建交换链
- 创建渲染目标视图(窗口大小发生变化时)
- 设置视口变换
创建设备和设备上下文
// 创建设备和设备上下文
bool createDirectDevice(){
UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// 驱动类型数组
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE, //硬件驱动
//D3D_DRIVER_TYPE_WARP, //WARP驱动
D3D_DRIVER_TYPE_REFERENCE, //软件驱动
};
UINT numDriverTypes = ARRAYSIZE(driverTypes);
// 特性等级数组
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};
UINT numFeatureLevels = ARRAYSIZE(featureLevels);
D3D_FEATURE_LEVEL featureLevel;
D3D_DRIVER_TYPE d3dDriverType;
HRESULT hr;
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
d3dDriverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, pd3dDevice.GetAddressOf(), &featureLevel, pd3dDeviceContext.GetAddressOf());
// 如果你的系统不支持Direct3D 11.1的API,D3D11CreateDevice会立即停止特性数组的轮询并返回E_INVALIDARG
if (hr == E_INVALIDARG)
{
// Direct3D 11.0 的API不承认D3D_FEATURE_LEVEL_11_1,所以我们需要尝试特性等级11.0以及以下的版本
hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
D3D11_SDK_VERSION, pd3dDevice.GetAddressOf(), &featureLevel, pd3dDeviceContext.GetAddressOf());
}
if (SUCCEEDED(hr))
break;
if (FAILED(hr))
{
MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
return false;
}
// 检测是否支持特性等级11.0或11.1
if (featureLevel != D3D_FEATURE_LEVEL_11_0 && featureLevel != D3D_FEATURE_LEVEL_11_1)
{
MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
return false;
}
return hr == S_OK;
创建Factroy
// 创建Factroy
bool createFactroy()
{
//
HRESULT hr;
ComPtr<IDXGIDevice> dxgiDevice = nullptr;
ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
hr = pd3dDevice.As(&dxgiDevice);
dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf());
dxgiAdapter->GetParent(__uuidof(IDXGIFactory1),reinterpret_cast<void**>(dxgiFactory.GetAddressOf()));
return hr == S_OK;
}
填充交换链描述符
// 填充交换链描述符
bool initSwapChainDesc()
{
HRESULT hr;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = winWidth;
sd.BufferDesc.Height = winHeight;
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;
sd.SampleDe
创建交换链
// 创建交换链
bool createSwapChain()
{
HRESULT hr;
hr = dxgiFactory->CreateSwapChain(pd3dDevice.Get(),&sd,pSwapChain.GetAddressOf());
return hr == S_OK;
}
创建渲染目标视图,设置视口大小
// 创建渲染目标视图,设置视口大小(窗口大小发生变化时重新创建)
void resizeWin()
{
assert(pd3dDevice);
assert(pd3dDeviceContext);
assert(pSwapChain);
m_pRenderTargetView.Reset();
HRESULT hr;
ComPtr<ID3D11Texture2D> backBuffer;
hr = pSwapChain->ResizeBuffers(1,winWidth,winHeight,DXGI_FORMAT_R8G8B8A8_UNORM,0);
hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf()));
hr = pd3dDevice->CreateRenderTargetView(backBuffer.Get(),nullptr,m_pRenderTargetView.GetAddressOf());
backBuffer.Reset();
pd3dDeviceContext->OMSetRenderTargets(1,m_pRenderTargetView.GetAddressOf(),nullptr);
m_ScreenViewport.TopLeftX = 0.0f;
m_ScreenViewport.TopLeftY = 0.0f;
m_ScreenViewport.Width = static_cast<float>(winWidth);
m_ScreenViewport.Height = static_cast<float>(winHeight);
m_ScreenViewport.MinDepth = 0.0f;
m_ScreenViewport.MaxDepth = 1.0f;
pd3dDeviceContext->RSSetViewports(1,&m_ScreenViewport);
创建着色器
- 编译顶点着色器到Blob,同时写入到cso文件,着色器若未修改,下次直接读取,不用编译。
- 使用设备创建顶点着色器。
- 编译像素着色器到Blob,同时写入到cso文件,着色器若未修改,下次直接读取,不用编译。
- 使用设备创建像素着色器。
/*************************************************创建着色器*************************************************/
bool createShader()
{
HRESULT hr;
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
// 设置 D3DCOMPILE_DEBUG 标志用于获取着色器调试信息。该标志可以提升调试体验,
// 但仍然允许着色器进行优化操作
dwShaderFlags |= D3DCOMPILE_DEBUG;
// 在Debug环境下禁用优化以避免出现一些不合理的情况
dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
//创建顶点着色器
{
hr = D3DCompileFromFile(L"C:\\Users\\MultiMediaServer\\Desktop\\learn\\\D3D11Learn\\HLSL\\D3D11Learn_VS.hlsl",
nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "VS", "vs_5_0",
dwShaderFlags, 0, vertexBlob.ReleaseAndGetAddressOf(), nullptr);
// 若指定了输出文件名,则将着色器二进制信息输出
hr = D3DWriteBlobToFile(vertexBlob.Get(), L"HLSL\\D3D11Learn_VS.cso", FALSE);
hr = pd3dDevice->CreateVertexShader(vertexBlob->GetBufferPointer(), vertexBlob->GetBufferSize(), nullptr, pVertexShader.GetAddressOf());
}
{
hr = D3DCompileFromFile(L"C:\\Users\\MultiMediaServer\\Desktop\\learn\\\D3D11Learn\\HLSL\\D3D11Learn_PS.hlsl",
nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "PS", "ps_5_0",
dwShaderFlags, 0, pixBlob.ReleaseAndGetAddressOf(), nullptr);
// 若指定了输出文件名,则将着色器二进制信息输出
hr = D3DWriteBlobToFile(pixBlob.Get(), L"HLSL\\D3D11Learn_PS.cso", FALSE);
hr = pd3dDevice->CreatePixelShader(pixBlob->GetBufferPointer(),pixBlob->GetBufferSize(),nullptr,pPixelShader.GetAddressOf());
}
return hr == S_OK;
}
填充缓冲区并绑定到渲染管线
定义顶点数据结构体
创建顶点缓冲区、填充并绑定
创建索引缓冲区、填充并绑定
创建顶点常量缓冲区、填充并绑定
创建像素常量缓冲区、填充并绑定
设置图元类型
创建并绑定输入布局(也可以在创建完顶点着色器后立马创建)
将着色器和相应阶段的常量缓冲区绑定到渲染管线。
bool initResource()
{
HRESULT hr;
// 设置三角形顶点和颜色
VertexPosColor vertices[] =
{
{ XMFLOAT3(0.0f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
//设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd,sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(vertices);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 创建顶点缓冲区
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData, sizeof(initData));
initData.pSysMem = vertices;
hr = pd3dDevice->CreateBuffer(&vbd,&initData, pVertexBuffer.GetAddressOf());
//绑定顶点缓冲区
UINT stride = sizeof(VertexPosColor); // 跨越字节数
UINT offset = 0; // 起始偏移量
pd3dDeviceContext->IASetVertexBuffers(0, 1, pVertexBuffer.GetAddressOf(), &stride, &offset);
DWORD indices[] = {
0, 1, 2,0
};
//设置索引缓冲区描述
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(indices);
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
initData.pSysMem = indices; // D3D11_SUBRESOURCE_DATA
// 新建索引缓冲区
hr = pd3dDevice->CreateBuffer(&ibd, &initData, pIndexBuffer.GetAddressOf());
pd3dDeviceContext->IASetIndexBuffer(pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT,0);
/****************************常量缓冲区***********************************/
//设置顶点常量缓冲区描述
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.ByteWidth = sizeof(VSConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
//创建顶点常量缓冲区
hr = pd3dDevice->CreateBuffer(&cbd,nullptr,pVSConstBuffer.GetAddressOf());
VSCBuffer.world = XMMatrixTranspose(XMMatrixTranslation(0.4, 0.0, 0.0));
VSCBuffer.view = XMMatrixIdentity();
VSCBuffer.proj = XMMatrixIdentity();
//填充顶点常量缓冲区
VSCBuffer.world = XMMatrixTranspose(XMMatrixTranslation(0.4, 0.0, 0.0));
VSCBuffer.view = XMMatrixIdentity();
VSCBuffer.proj = XMMatrixIdentity();
D3D11_MAPPED_SUBRESOURCE mappedData;
hr = pd3dDeviceContext->Map(pVSConstBuffer.Get(),0, D3D11_MAP_WRITE_DISCARD,0,&mappedData);
memcpy_s(mappedData.pData, sizeof(VSCBuffer), &VSCBuffer, sizeof(VSCBuffer));
pd3dDeviceContext->Unmap(pVSConstBuffer.Get(), 0);
//设置像素常量缓冲区描述
D3D11_BUFFER_DESC cbd1;
ZeroMemory(&cbd1, sizeof(cbd1));
cbd1.Usage = D3D11_USAGE_DYNAMIC;
cbd1.ByteWidth = sizeof(PSConstantBuffer);
cbd1.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd1.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
//创建像素常量缓冲区
hr = pd3dDevice->CreateBuffer(&cbd,nullptr,pPSConstBuffer.GetAddressOf());
//填充像素常量缓冲区
PSCBuffer.color = XMFLOAT4(1.0, 0.0, 0.0, 1.0);
PSCBuffer.proj = XMMatrixIdentity();
D3D11_MAPPED_SUBRESOURCE mappedData1;
hr = pd3dDeviceContext->Map(pPSConstBuffer.Get(),0, D3D11_MAP_WRITE_DISCARD,0,&mappedData1);
memcpy_s(mappedData1.pData, sizeof(PSCBuffer), &PSCBuffer, sizeof(PSCBuffer));
pd3dDeviceContext->Unmap(pPSConstBuffer.Get(), 0);
//创建输入布局并绑定
pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout), vertexBlob->GetBufferPointer(), vertexBlob->GetBufferSize(), pVertexLayout.GetAddressOf());
pd3dDeviceContext->IASetInputLayout(pVertexLayout.Get());
//设置图元类型
pd3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// 将着色器绑定到渲染管线
pd3dDeviceContext->VSSetShader(pVertexShader.Get(), nullptr, 0);
pd3dDeviceContext->VSSetConstantBuffers(0, 1, pVSConstBuffer.GetAddressOf());
pd3dDeviceContext->PSSetShader(pPixelShader.Get(), nullptr, 0);
pd3dDeviceContext->PSSetConstantBuffers(1, 1, pPSConstBuffer.GetAddressOf());
return hr == S_OK;
}
渲染
int Run()
{
MSG msg = { 0 };
BOOL bRet = 1;
assert(pd3dDeviceContext);
assert(pSwapChain);
while ((bRet = GetMessage(&msg,0,0,0)) != 0)
{
static float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
pd3dDeviceContext->ClearRenderTargetView(m_pRenderTargetView.Get(), black);
pd3dDeviceContext->DrawIndexed(3, 0, 0);
pSwapChain->Present(1, 0);
}
return (int)msg.wParam;
}
主函数调用所有流程
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow)
{
if (!InitWindowsApp(hInstance, nCmdShow))
return 0;
bool b = createDirectDevice();
b = createFactroy();
b = initSwapChainDesc();
b = createSwapChain();
resizeWin();
b = createShader();
b = initResource();
return Run();
}
着色器代码
头文件
cbuffer VSConstantBuffer : register(b0)
{
matrix g_World;
matrix g_View;
matrix g_Proj;
}
cbuffer PSConstantBuffer : register(b1)
{
float4 g_color;
matrix temp;
}
struct VertexIn
{
float3 pos : POSITION;
};
struct VertexOut
{
float4 pos : SV_POSITION;
};
像素着色器
#include "D3D11Learn.hlsli"
float4 PS() : SV_Target
{
return g_color;
}
// 顶点着色器
#include "D3D11Learn.hlsli"
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
vOut.pos = mul(float4(vIn.pos, 1.0f), g_World);
vOut.pos = mul(vOut.pos, g_View);
vOut.pos = mul(vOut.pos, g_Proj);
//vOut.pos = float4(vIn.pos, 1.0f);
return vOut;
}