Direct3D 12 基础渲染器搭建

引言

偶尔翻到了很早前用DirectX12搭建的渲染器,很多细节都已经遗忘了,现在发上来作为一个参考,期末考完后应该会根据龙书继续学习DX12的细节。

本篇使用DX12,构建了一个加载了光照模型的基础渲染器

一 调用D3D12的基本步骤和准备工作

头文件和引用库文件

组件对象模型 COM

  • 令DirectX不受编程语言束缚,使之向后兼容

  • 通常视为接口,但考虑编程目的,目前当做C++类使用

  • 隐藏大量细节

  • 使用

    • 获取指向某COM接口的指针,需要借助特定函数或另一接口的方法,而不是C++的new
    • COM对象会统计其引用次数(引用计数为0,自行释放内存)
    • 使用完后,应该调用Release方法
  • 为了辅助管理,Window运行时库(Windows Runtime Library,WRL)提供了ComPtr类,可以当做是COM对象的智能指针

    • Get:返回一个指针

    • GetAddressOf:返回COM接口指针地址

    • Reset:设置为nullptr并释放与之相关的所有引用,与赋值nullptr作用相同

    • COM接口都以大写字母I开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料,不加载MFC时使用
#include <windows.h>
#include <tchar.h>
//添加WTL支持 方便使用COM
#include <wrl.h>
using namespace Microsoft;
using namespace Microsoft::WRL;
#include <dxgi1_6.h>
#include <DirectXMath.h>
using namespace DirectX;
//for d3d12
#include <d3d12.h>
#include <d3d12shader.h>
#include <d3dcompiler.h>
//linker链接库
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "d3dcompiler.lib")
//Debug
#if defined(_DEBUG)
#include <dxgidebug.h>
#endif
//将D3D12中的结构都派生(说扩展更合适)为简单的类,以便于使用,加入了构造函数
#include "d3dx12.h"


#define GRS_WND_CLASS_NAME _T("Game Window Class")
#define GRS_WND_TITLE _T("DirectX12 Trigger Sample")

//GRS_THROW_IF_FAILED这个宏其实它就是为了在调用COM接口时简化出错时处理时使用的一个宏
#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }

定义待渲染数据结构(三角形)

1
2
3
4
5
6
struct GRS_VERTEX
{
//<DirectXMath.h>
XMFLOAT3 position; //float[3]
XMFLOAT4 color; //float[4]
};

定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const UINT nFrameBackBufCount = 3u;//无符号整型

int iWidth = 1024;
int iHeight = 768; //窗口大小

UINT nFrameIndex = 0;
UINT nFrame = 0;

UINT nDXGIFactoryFlags = 0U;
UINT nRTVDescriptorSize = 0U;

HWND hWnd = nullptr; //窗口句柄
MSG msg = {}; //Message

float fAspectRatio = 3.0f; //分辨

D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {}; //顶点缓冲视图

UINT64 n64FenceValue = 0ui64; // ???

HANDLE hFenceEvent = nullptr; //???句柄

CD3DX12_VIEWPORT stViewPort(0.0f, 0.0f, static_cast<float>(iWidth), static_cast<float>(iHeight));
CD3DX12_RECT stScissorRect(0, 0, static_cast<LONG>(iWidth), static_cast<LONG>(iHeight));

//ComPtr<> WRL中的智能指针,模版类的析构函数帮助我们来管理这些COM组件实现的接口实例,而无需过多担心内存的泄漏。该智能指针的大小和一般的指针大小是一致的,没有额外的内存空间占用。
ComPtr<IDXGIFactory5> pIDXGIFactory5;
ComPtr<IDXGIAdapter1> pIAdapter;
ComPtr<ID3D12Device4> pID3DDevice;
ComPtr<ID3D12CommandQueue> pICommandQueue;
ComPtr<IDXGISwapChain1> pISwapChain1;
ComPtr<IDXGISwapChain3> pISwapChain3;
ComPtr<ID3D12DescriptorHeap> pIRTVHeap;
ComPtr<ID3D12Resource> pIARenderTargets[nFrameBackBufCount];
ComPtr<ID3D12CommandAllocator> pICommandAllocator;
ComPtr<ID3D12RootSignature> pIRootSignature;
ComPtr<ID3D12PipelineState> pIPipelineState;
ComPtr<ID3D12GraphicsCommandList> pICommandList;
ComPtr<ID3D12Resource> pIVertexBuffer;
ComPtr<ID3D12Fence> pIFence;
return 0;

二 创建窗口

WNDCLASSEX

设置窗口属性

1
2
3
4
5
6
7
WNDCLASSEX windowClass = { 0 };
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = DefWindowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = L"YSHRenderClass";

注册WNDCLASSEX结构体

1
RegisterClassEx(&windowClass);

Create Window

1
2
3
4
5
6
7
8
9
10
11
12
HWND hwnd = CreateWindow(
windowClass.lpszClassName,
L"YSHRender",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
nullptr,
nullptr,
hInstance,
nullptr);

Show Window

1
ShowWindow(hwnd, SW_SHOW);

添加While循环

  • 持续运行
1
2
3
while (true)
{
}

?教程一二完整代码

  • 因为未知的错误,所以运行不了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const UINT nFrameBackBufCount = 3u;//无符号整型

int iWidth = 1024;
int iHeight = 768; //窗口大小

UINT nFrameIndex = 0;
UINT nFrame = 0;

UINT nDXGIFactoryFlags = 0U;
UINT nRTVDescriptorSize = 0U;

HWND hWnd = nullptr; //窗口句柄
MSG msg = {}; //Message

float fAspectRatio = 3.0f; //分辨

D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {}; //顶点缓冲视图

UINT64 n64FenceValue = 0ui64; // ???

HANDLE hFenceEvent = nullptr; //???句柄

CD3DX12_VIEWPORT stViewPort(0.0f, 0.0f, static_cast<float>(iWidth), static_cast<float>(iHeight));
CD3DX12_RECT stScissorRect(0, 0, static_cast<LONG>(iWidth), static_cast<LONG>(iHeight));

//ComPtr<> 智能指针,帮助我们来管理这些COM组件实现的接口实例,而无需过多担心内存的泄漏。该智能指针的大小和一般的指针大小是一致的,没有额外的内存空间占用。
ComPtr<IDXGIFactory5> pIDXGIFactory5;
ComPtr<IDXGIAdapter1> pIAdapter;
ComPtr<ID3D12Device4> pID3DDevice;
ComPtr<ID3D12CommandQueue> pICommandQueue;
ComPtr<IDXGISwapChain1> pISwapChain1;
ComPtr<IDXGISwapChain3> pISwapChain3;
ComPtr<ID3D12DescriptorHeap> pIRTVHeap;
ComPtr<ID3D12Resource> pIARenderTargets[nFrameBackBufCount];
ComPtr<ID3D12CommandAllocator> pICommandAllocator;
ComPtr<ID3D12RootSignature> pIRootSignature;
ComPtr<ID3D12PipelineState> pIPipelineState;
ComPtr<ID3D12GraphicsCommandList> pICommandList;
ComPtr<ID3D12Resource> pIVertexBuffer;
ComPtr<ID3D12Fence> pIFence;

WNDCLASSEX windowClass = { 0 };
windowClass.cbSize = sizeof(WNDCLASSEX);
// windowClass.style = CS_HREDRAW | CS_VREDRAW; 不同教程
windowClass.style = CS_GLOBALCLASS; //去掉Redraw类型
windowClass.lpfnWndProc = DefWindowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = L"YSHRenderClass";
windowClass.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); //防止无聊的背景重绘,因为我们并不使用GDI的任何绘制,而是在使用D3D12绘制!

RegisterClassEx(&windowClass);

hWnd = CreateWindowW(GRS_WND_CLASS_NAME
, GRS_WND_TITLE
, WS_OVERLAPPED | WS_SYSMENU
, CW_USEDEFAULT
, 0
, iWidth
, iHeight
, nullptr
, nullptr
, hInstance
, nullptr);

ShowWindow(hWnd, SW_SHOW);

while (0) {

}

return 0;

添加事件处理

  • 把while改成这个,这下就可以正常关闭窗口了
1
2
3
4
5
6
7
8
9
MSG msg = {};
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
  • 但VS还没有终止程序,需要添加一个回调函数,之前默认的是DefWindowProc,现在改成自己的WindowProc函数
1
2
3
4
5
6
7
WNDCLASSEX windowClass = { 0 };
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WindowProc;//DefWindowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = L"YSHRenderClass";

WindowProc 回调函数

作用

  • 咱们有很多消息类型,通过Switch来选择处理不同类型的信息。
  • 比如WM_DESTROTY,就是在咱们点击关闭窗口按钮时会触发的,在case WM_DESTROY里面咱们调用了PostQuitMessage(0)方法,也就意味着退出了。
1
2
3
4
5
6
7
8
9
10
11
12
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

三 初始化Direct3D

基本知识

纹理格式

  • 尽管名称在字面上是颜色和Alpha值,但不一定存储的是颜色信息
    • 1 还可以储存任意3D向量
    • 也有无类型的纹理,仅用它来预留内存
    • image-20230721145950240

image-20230721145536444

深度缓冲

image-20230721150903460

资源与描述符

资源

  • GPU会对资源进行读和写操作
    • 读:例如从纹理或者存有3d场景中几何体位置信息的缓冲区读取数据
    • 写:例如向后台缓冲区或深度/模版缓冲区写入数据
  • 在发出绘制命令前,需要将本次draw call相关的Resource bind (or link)到render pipeline。
  • 部分资源可能在每次绘制时发生变化,每次需要重新绑定

描述符

  • 然而,GPU资源并非直接与render pipeline 绑定,需要**描述符(descriptor)**对象来对它进行间接引用,descriptor实际上即为一个中间层。
  • 为什么要额外使用这个中间层?
    • GPU Resource实际上都是一些普通的内存块
    • 由于Resource通用性,它们能被设置到render pipeline的不同阶段使用
      • 常见例子:先把纹理用作渲染目标(即D3d绘制到纹理技术),然后再将该纹理作为一个shader resource(经过采样作为shader的input data)
    • 有时,我们只想bind a part of resource data 到 render pipeline中
    • 创建一个resource 可能用的 无类型格式,这样GPU甚至不会知晓
  • 作用:
    • 指定资源数据
    • 为GPU解释资源(无类型,则创建descriptor时指定其type类型)
    • 告知D3D某个资源如何使用
    • 指定resouce的局部数据
descriptor==viewimage-20230721153444722

Descriptor具体类型

image-20230721153645480

描述符堆 Descriptor Heap

  • 存有一系列描述符,可看做描述符数组

  • 本质上是存放用户程序中某种特定类型描述符的一块内存

  • 我们需要为每一种描述符都创建出单独的描述符堆。另外,也可以为同一种描述符类型多个描述符堆

  • 我们可以用多个描述符引用同一个资源

    • 先把纹理用作渲染目标(即D3d绘制到纹理技术),然后再将该纹理作为一个shader resource(经过采样作为shader的input data),就要分别创建两个描述符:RTV描述符和SRV描述符

    • 如果以无类型格式创建一个资源,我们也可以创建两个Descriptor分别根据需求当做浮点值和整数值使用

创建Descriptor最佳时机在初始化期间,此过程中需要执行一些类型的检测和验证工作

全局变量

1
2
3
4
const UINT FrameCount = 2;
UINT width = 800;
UINT height = 600;
HWND hwnd;

管线对象

1
2
3
4
5
6
7
8
9
10
//管线对象
ComPtr<IDXGISwapChain3> swapChain;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12Resource> renderTargets[FrameCount];
ComPtr<ID3D12CommandAllocator> commandAllocator;
ComPtr<ID3D12CommandQueue> commandQueue;
ComPtr<ID3D12DescriptorHeap> rtvHeap;
ComPtr<ID3D12PipelineState> pipelineState;
ComPtr<ID3D12GraphicsCommandList> commandList;
UINT rtvDescriptorSize;

同步对象

1
2
3
4
UINT frameIndex;
HANDLE fenceEvent;
ComPtr<ID3D12Fence> fence;
UINT64 fenceValue;

加载管线对象

  • 新建一个函数叫LoadPipeline(),下面在这个函数里面加载咱们需要的管线对象了

辅助方法(官方抄来,可以自己写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::string HrToString(HRESULT hr)
{
char s_str[64] = {};
sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));
return std::string(s_str);
}

class HrException : public std::runtime_error
{
public:
HrException(HRESULT hr) : std::runtime_error(HrToString(hr)), m_hr(hr) {}
HRESULT Error() const { return m_hr; }
private:
const HRESULT m_hr;
};

void ThrowIfFailed(HRESULT hr)
{
if (FAILED(hr))
{
throw HrException(hr);
}
}

ID3D12Debug 调试层

1
2
3
4
5
6
7
8
9
#if defined(_DEBUG)
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
}
}
#endif

IDXGIFactory4 枚举显示适配器,创建交换链

1
ComPtr<IDXGIFactory4> mDxgiFactory;				ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(mDxgiFactory.GetAddressOf())));

IDXGIAdapter1

  • 不只1,还有2、3、4
  • 现在可以通过上面创建的工厂来枚举咱们的适配器了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
IDXGIAdapter1* GetSupportedAdapter(ComPtr<IDXGIFactory4>& dxgiFactory,const D3D_FEATURE_LEVEL featureLevel)
{
IDXGIAdapter1* adapter = nullptr;
for (std::uint32_t adapterIndex = 0U; ; ++adapterIndex)
{
IDXGIAdapter1* currentAdapter = nullptr;
if (DXGI_ERROR_NOT_FOUND == dxgiFactory->EnumAdapters1(adapterIndex, &currentAdapter))
{
break;
}

const HRESULT hres = D3D12CreateDevice(currentAdapter,featureLevel,_uuidof(ID3D12Device),nullptr);
if (SUCCEEDED(hres))
{
adapter = currentAdapter;
break;
}

currentAdapter->Release();
}

return adapter;
}
  • 找到咱们支持的适配器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};

IDXGIAdapter1* adapter = nullptr;
for (std::uint32_t i = 0U; i < _countof(featureLevels); ++i)
{
adapter = GetSupportedAdapter(mDxgiFactory, featureLevels[i]);
if (adapter != nullptr)
{
break;
}
}

ID3D12Device 包含对象创建方法

  • ID3D12Device接口作为最基础的接口之一,在整个d3d12编程中有很重要的作用。包含了很多重要对象的创建方法。
1
2
3
4
if (adapter != nullptr)
{
D3D12CreateDevice(adapter,D3D_FEATURE_LEVEL_12_1,IID_PPV_ARGS(device.GetAddressOf()));
}

ID3D12CommandQueue 命令队列

  • 在Direct3D12中,命令队列由接口ID3D12CommandQueue来表示。它是通过填充D3D12_COMMAND_QUEUE_DESC结构来描述队列,然后调用ID3D12Device::CreateCommandQueue来创建的。
1
2
3
4
5
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

ThrowIfFailed(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)));

IDXGISwapChain3 交换链

  • 正如你看到的,它带了个数字3,也就意味着还有。。。

交换链和页面翻转

为避免动画中画面闪烁,最好将动画帧绘制在一中称为后台缓冲区的离屏(off-screen)纹理内。

  • 后台缓冲区绘制完后,和前台缓冲区互换的操作,称为呈现,只需交换两个指针即可
  • 后台缓冲区和前台缓冲区构成了交换链(swap chain)
  • 使用两个缓冲区称为双缓冲(double buffering)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;

ComPtr<IDXGISwapChain1> swapChain1;
ThrowIfFailed(mDxgiFactory->CreateSwapChainForHwnd(
commandQueue.Get(),
hwnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain1
));

ThrowIfFailed(swapChain1.As(&swapChain));
frameIndex = swapChain->GetCurrentBackBufferIndex();

ID3D12DescriptorHeap 描述符堆

  • 描述符堆是什么?看名字就知道是存放东西的地方,每错,它就是用来存放描述符(内存那么多数据,我怎么知道这块内存是什么?就靠描述符来对它描述)或者说是资源视图的。
1
2
3
4
5
6
7
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FrameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap)));

rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

ID3D12Resource 资源

  • 首先通过GetBuffer方法获取交换链缓存区资源,然后为此资源创建视图。
1
2
3
4
5
6
for (UINT n = 0; n < FrameCount; n++)
{
ThrowIfFailed(swapChain->GetBuffer(n, IID_PPV_ARGS(&renderTargets[n])));
device->CreateRenderTargetView(renderTargets[n].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, rtvDescriptorSize);
}

ID3D12CommandAllocator 命令分配器

  • 创建命令分配器来存放命令。
1
ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)));

LoadPipeline方法完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
void LoadPipeline()
{

#if defined(_DEBUG)
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
}
}
#endif

ComPtr<IDXGIFactory4> mDxgiFactory;
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(mDxgiFactory.GetAddressOf())));

D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};

IDXGIAdapter1* adapter = nullptr;
for (std::uint32_t i = 0U; i < _countof(featureLevels); ++i)
{
adapter = GetSupportedAdapter(mDxgiFactory, featureLevels[i]);
if (adapter != nullptr)
{
break;
}
}

if (adapter != nullptr)
{
D3D12CreateDevice(adapter,D3D_FEATURE_LEVEL_12_1,IID_PPV_ARGS(device.GetAddressOf()));
}

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

ThrowIfFailed(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)));

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;

ComPtr<IDXGISwapChain1> swapChain1;
ThrowIfFailed(mDxgiFactory->CreateSwapChainForHwnd(
commandQueue.Get(),
hwnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain1
));

ThrowIfFailed(swapChain1.As(&swapChain));
frameIndex = swapChain->GetCurrentBackBufferIndex();

{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FrameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap)));

rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
}

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart());

for (UINT n = 0; n < FrameCount; n++)
{
ThrowIfFailed(swapChain->GetBuffer(n, IID_PPV_ARGS(&renderTargets[n])));
device->CreateRenderTargetView(renderTargets[n].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, rtvDescriptorSize);
}

ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)));
}

加载资源对象

  • 新建LoadAsset(),下面在这个函数中加载咱们需要的资源

  • 因为咱们现在只是初始化DX而已,并不需要什么资源,所以此方法内容比较简单。

  • 首先前面创建的命令列表在记录命令前得先关闭,然后创建了CPU和GPU同步的Fence。

  • ```cpp
    void LoadAsset()
    {
    ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList)));

    ThrowIfFailed(commandList->Close());//关闭命令队列

    {
    ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
    fenceValue = 1;

    fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (fenceEvent == nullptr)
    {
    ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    ## 添加命令

    * 创建一个新的方法`PopulateCommandList()`来记录命令,这里执行了一个最简单操作`ClearRenderTargetView`。

    ```cpp
    void PopulateCommandList()
    {
    ThrowIfFailed(commandAllocator->Reset());
    ThrowIfFailed(commandList->Reset(commandAllocator.Get(), pipelineState.Get()));

    D3D12_RESOURCE_BARRIER resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
    commandList->ResourceBarrier(1, &resBarrier);

    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart(), frameIndex, rtvDescriptorSize);

    const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
    commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

    resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
    commandList->ResourceBarrier(1, &resBarrier);

    ThrowIfFailed(commandList->Close());
    }

同步

  • 创建一个新的方法WaitForPreviousFrame()来进行同步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void WaitForPreviousFrame()
{
const UINT64 tempFenceValue = fenceValue;
ThrowIfFailed(commandQueue->Signal(fence.Get(), tempFenceValue));
fenceValue++;

if (fence->GetCompletedValue() < tempFenceValue)
{
ThrowIfFailed(fence->SetEventOnCompletion(tempFenceValue, fenceEvent));
WaitForSingleObject(fenceEvent, INFINITE);
}

frameIndex = swapChain->GetCurrentBackBufferIndex();
}

渲染

  • 创建一个新的方法OnRender()来渲染。

  • ```cpp
    void OnRender()
    {
    PopulateCommandList();

    ID3D12CommandList* ppCommandLists[] = { commandList.Get() };
    commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

    ThrowIfFailed(swapChain->Present(1, 0));

    WaitForPreviousFrame();
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    ## 清理

    * 创建一个新的方法OnDestroy()来清理。

    ```cpp
    void OnDestroy()
    {
    WaitForPreviousFrame();

    CloseHandle(fenceEvent);
    }

渲染

1
2
3
4
5
6
7
8
9
10
11
void OnRender()
{
PopulateCommandList();

ID3D12CommandList* ppCommandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

ThrowIfFailed(swapChain->Present(1, 0));

WaitForPreviousFrame();
}

WindowProc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
OnRender();
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

Main函数也要修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
WNDCLASSEX windowClass = { 0 };
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WindowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = L"YSHRenderClass";

RegisterClassEx(&windowClass);

hwnd = CreateWindow(
windowClass.lpszClassName,
L"YSHRender",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
nullptr,
nullptr,
hInstance,
nullptr);

LoadPipeline();
LoadAsset();

ShowWindow(hwnd, SW_SHOW);

MSG msg = {};
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

OnDestroy();

return 0;
}

四 背景颜色更新

全局变量:RGB颜色

1
2
3
4
5
//RGB颜色
float color[4];
bool isRAdd = true;
bool isGAdd = true;
bool isBAdd = true;

清屏颜色设置

  • 我们需要将添加命令函数PopulateCommandList()里的clearColor修改为全局变量

    1
    const float clearColor[] = { color[0], color[1], color[2], color[3] };

添加Update函数

  • 该功能实现窗口颜色的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void OnUpdate()
{

if (color[0] <= 1.0f && isRAdd)
{
color[0] += 0.001f;
isRAdd = true;
}
else
{
color[0] -= 0.002f;
color[0] <= 0 ? isRAdd = true : isRAdd = false;

}

if (color[1] <= 1.0f && isGAdd)
{
color[1] += 0.002f;
isGAdd = true;
}
else
{
color[1] -= 0.001f;
color[1] <= 0 ? isGAdd = true : isGAdd = false;

}

if (color[2] <= 1.0f && isBAdd)
{
color[2] += 0.001f;
isBAdd = true;
}
else
{
color[2] -= 0.001f;
color[2] <= 0 ? isBAdd = true : isBAdd = false;

}

color[3] = 1.0f;

}

窗口

  • 我们需要执行我们刚才新增的OnUpdate()函数

最后,我们就可以得到一个窗口不断变化背景颜色的程序了

五 绘制三角形

修改LoadAsset方法

  • 只需要修改一下LoadAsset方法

根签名 ID3D12RootSignature

什么是根签名?

根签名由应用配置,并将命令列表链接到着色器所需的资源。 图形命令列表同时具有图形和计算根签名。 计算命令列表只具有一个计算根签名。 这些根签名彼此独立。

详细请看根签名 - Win32 apps | Microsoft Learn

1
2
3
4
5
6
7
8
9
{
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));
}

第一个着色器

引入新的头文件(d3dcompiler.h)和库(d3dcompiler.lib)。

第一个shader

  • 新建一个Assets文件夹存放着色器文件,然后新建一个**shaders.hlsl**文件。
1
2
3
4
5
6
7
8
9
10
11
12
     ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;

#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif

ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr));
ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr));

shaders.hlsl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;

result.position = position;
result.color = color;

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}

创建管线状态对象 ID3D12PipelineState

是什么

  • 表示当前设置的所有着色器和某些固定函数状态对象的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ThrowIfFailed(device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)));

顶点缓存和顶点缓存视图

  • 咱们需要定义一个顶点结构体,这个结构体包含每个顶点的各种属性,暂时就包含位置和颜色2个属性。这里需要把DX的数学库(DirectXMath.h)包含一下

    1
    2
    3
    4
    5
    struct Vertex
    {
    XMFLOAT3 position;
    XMFLOAT4 color;
    };
  • 然后创建顶点缓存和顶点缓存视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Vertex triangleVertices[] =
{
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};

const UINT vertexBufferSize = sizeof(triangleVertices);


CD3DX12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC resourceDes = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&resourceDes,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer)));

UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0);
ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
vertexBuffer->Unmap(0, nullptr);

vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(Vertex);
vertexBufferView.SizeInBytes = vertexBufferSize;

LoadAsset方法完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
void LoadAsset()
{
{
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));
}

{
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;

#if defined(_DEBUG)
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif

ThrowIfFailed(D3DCompileFromFile(std::wstring(L"Assets/shaders.hlsl").c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr));
ThrowIfFailed(D3DCompileFromFile(std::wstring(L"Assets/shaders.hlsl").c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr));

D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ThrowIfFailed(device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)));
}

ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), pipelineState.Get(), IID_PPV_ARGS(&commandList)));

ThrowIfFailed(commandList->Close());

{
Vertex triangleVertices[] =
{
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};

const UINT vertexBufferSize = sizeof(triangleVertices);


CD3DX12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC resourceDes = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&resourceDes,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer)));

UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0);
ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
vertexBuffer->Unmap(0, nullptr);

vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(Vertex);
vertexBufferView.SizeInBytes = vertexBufferSize;
}

{
ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
fenceValue = 1;

fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (fenceEvent == nullptr)
{
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
}
}
}

修改PopulateCommandList方法

  • 使用commandList记录新的命令去绘制三角形。
  • 补充两个东西,一个视口viewport,还有一个裁剪矩形scissorRect
1
2
CD3DX12_VIEWPORT viewport(0.0f,0.0f, width,height);
CD3DX12_RECT scissorRect(0, 0, width, height);
  • 然后在PopulateCommandList方法里面添加几个新的命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void PopulateCommandList()
{
ThrowIfFailed(commandAllocator->Reset());
ThrowIfFailed(commandList->Reset(commandAllocator.Get(), pipelineState.Get()));

commandList->SetGraphicsRootSignature(rootSignature.Get());
commandList->RSSetViewports(1, &viewport);
commandList->RSSetScissorRects(1, &scissorRect);

D3D12_RESOURCE_BARRIER resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
commandList->ResourceBarrier(1, &resBarrier);

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart(), frameIndex, rtvDescriptorSize);

commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

const float clearColor[] = { color[0], color[1], color[2], 1.0f };
commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &vertexBufferView);
commandList->DrawInstanced(3, 1, 0, 0);

resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
commandList->ResourceBarrier(1, &resBarrier);

ThrowIfFailed(commandList->Close());
}

效果图

image-20230719150935918

六 绘制四边形

索引缓冲区

  • 索引缓冲区是索引列表。每个索引代表一个顶点。每 3 个索引代表一个三角形。

顶点数组

咱们只需要4个顶点就可以绘制一个四边形,更新以后的顶点结构体数组。

1
2
3
4
5
6
7
Vertex triangleVertices[] =
{
{ { -0.5f, 0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } }
};

索引数组

1
2
3
4
5
DWORD triangleIndexs[]
{
0,1,2,
0,3,1
};

顶点缓存和索引缓存

见代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
/*三角形
Vertex triangleVertices[] =
{
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};
*/
//顶点数组
Vertex triangleVertices[] =
{
{ { -0.5f, 0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } }
};
//索引数组
DWORD triangleIndexs[]
{
0,1,2,
0,3,1
};

const UINT vertexBufferSize = sizeof(triangleVertices);
const UINT indexBufferSize = sizeof(triangleIndexs);//新增:索引缓冲

CD3DX12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC resourceDes = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);//统一资源
//CD3DX12_RESOURCE_DESC indexResourceDes = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);//索引缓冲资源

ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&resourceDes,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer)));

//新增:创建提交的资源
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
//&indexResourceDes,
&resourceDes,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&indexBuffer)));

//UINT8* pVertexDataBegin;
UINT8* pDataBegin;//修改:pVertexDatabegin
CD3DX12_RANGE readRange(0, 0);

//ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
//memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pDataBegin)));
memcpy(pDataBegin, triangleVertices, sizeof(triangleVertices));//修改:pDataBegin
vertexBuffer->Unmap(0, nullptr);

//新增:索引缓存
ThrowIfFailed(indexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pDataBegin)));
memcpy(pDataBegin, triangleIndexs, sizeof(triangleIndexs));
indexBuffer->Unmap(0, nullptr);

vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(Vertex);
vertexBufferView.SizeInBytes = vertexBufferSize;

//新增:索引缓存
indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();//获得GPU虚拟地址
indexBufferView.Format = DXGI_FORMAT_R32_UINT;//
indexBufferView.SizeInBytes = indexBufferSize;
}

七 深度测试

上次的基础上绘制两个四边形那就很简单了,直接在顶点数组里面加点数据就可以了。

  • z轴越小,离摄像机越近
1
2
3
4
5
6
7
8
9
10
11
12
13
Vertex triangleVertices[] =
{
//第一个绿色四边形
{ { -0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
//第二个蓝色四边形
{ { -0.75f, 0.75f, 0.7f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { 0.0f, 0.0f, 0.7f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { -0.75f, 0.0f, 0.7f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
{ { 0.0f, 0.75f, 0.7f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};

绘制第二个四边形命令

1
2
commandList->DrawIndexedInstanced(6, 1, 0, 0,0);//第一个绿色四边形
commandList->DrawIndexedInstanced(6, 1, 0, 4, 0);//第二个蓝色四边形

结果:

蓝色却依旧覆盖了绿色,这是因为先绘制了绿色的四边形,然后绘制的蓝色四边形,把绿色覆盖了

image-20230719202449145

深度测试

深度模板堆

1
2
3
4
5
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&dsvHeap)));

修改管线状态对象,启用深度模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);//深度模板状态
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ThrowIfFailed(device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)));

深度缓存

创建深度模板缓存,这里得采用默认堆类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {};
depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;

D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0;

CD3DX12_HEAP_PROPERTIES heapProperties2 = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
CD3DX12_RESOURCE_DESC tex2D = CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, width, height, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL);

ThrowIfFailed(device->CreateCommittedResource(
&heapProperties2,
D3D12_HEAP_FLAG_NONE,
&tex2D,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue,
IID_PPV_ARGS(&depthStencilBuffer)));

device->CreateDepthStencilView(depthStencilBuffer.Get(), &depthStencilDesc, dsvHeap->GetCPUDescriptorHandleForHeapStart());

添加命令

然后在准备渲染之前得清除深度模板视图和清除渲染目标视图一样。

1
2
3
4
5
6
7
8
9
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart(), frameIndex, rtvDescriptorSize);

CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(dsvHeap->GetCPUDescriptorHandleForHeapStart());

commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);

const float clearColor[] = { color[0], color[1], color[2], 1.0f };
commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
commandList->ClearDepthStencilView(dsvHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

结果:

image-20230719211144048

八 常量缓存

常量缓存区结构体

定义咱们需要传递给着色器的结构体,这个结构体大小必须是256字节对齐

1
2
3
4
struct SceneConstantBuffer
{
XMFLOAT4 offset;
};
  • 添加一个256字节对齐的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
constexpr UINT CalcConstantBufferByteSize()
{
// Constant buffers must be a multiple of the minimum hardware
// allocation size (usually 256 bytes). So round up to nearest
// multiple of 256. We do this by adding 255 and then masking off
// the lower 2 bytes which store all bits < 256.
// Example: Suppose byteSize = 300.
// (300 + 255) & ~255
// 555 & ~255
// 0x022B & ~0x00ff
// 0x022B & 0xff00
// 0x0200
// 512
UINT byteSize = sizeof(T);
return (byteSize + 255) & ~255;
}

常量缓存堆

1
2
3
4
5
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc = {};
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&cbvHeap)));

常量缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const UINT constantBufferSize = CalcConstantBufferByteSize<SceneConstantBuffer>();
CD3DX12_RESOURCE_DESC constantResourceDes = CD3DX12_RESOURCE_DESC::Buffer(constantBufferSize);
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&constantResourceDes,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&constantBuffer)));

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = constantBuffer->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = constantBufferSize;
device->CreateConstantBufferView(&cbvDesc, cbvHeap->GetCPUDescriptorHandleForHeapStart());

ThrowIfFailed(constantBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pCbvDataBegin)));
memcpy(pCbvDataBegin, &constantBufferData, sizeof(constantBufferData));

根签名

根签名由根参数组成,根参数可以是根常量、根描述符和描述符表,在这里创建一个描述符表,存放了咱们的常量缓存视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
{
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}

CD3DX12_DESCRIPTOR_RANGE1 ranges[1];
CD3DX12_ROOT_PARAMETER1 rootParameters[1];

ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_VERTEX);

D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags =
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS;

CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 0, nullptr, rootSignatureFlags);

ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, featureData.HighestVersion, &signature, &error));
ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));

命令列表

添加一些代码去设置咱们的常量缓存堆。

1
2
3
4
ID3D12DescriptorHeap* ppHeaps[] = { cbvHeap.Get() };
commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

commandList->SetGraphicsRootDescriptorTable(0, cbvHeap->GetGPUDescriptorHandleForHeapStart());

OnUpdate方法

在OnUpdate方法里面对常量缓存里面的数据进行更新

1
2
3
4
5
6
7
8
9
const float translationSpeed = 0.005f;
const float offsetBounds = 1.25f;

constantBufferData.offset.x += translationSpeed;
if (constantBufferData.offset.x > offsetBounds)
{
constantBufferData.offset.x = -offsetBounds;
}
memcpy(pCbvDataBegin, &constantBufferData, sizeof(constantBufferData));

shaders.hlsl

在着色器代码里面添加相应的结构体,拿来接收咱们传递过来的数据,如何加到咱们的顶点位置上面去,这样咱们的四边形就能动起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cbuffer SceneConstantBuffer : register(b0)
{
float4 offset;
}

struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;

result.position = position + offset;
result.color = color;

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}

解决一个bug

  • 运行时一直在输出信息

image-20230720142427250

创建管线状态对象描述时加上下面最后一句就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT;//加上这玩意

九 绘制彩色立方体

顶点数组

1
2
3
4
5
6
7
8
9
10
11
Vertex triangleVertices[] =
{
{ { -1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f, 0.0f, 1.0f } },
{ { +1.0f, +1.0f, -1.0f }, { 1.0f, 0.0f, 1.0f, 1.0f } },
{ { +1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 0.5f, 0.5f, 1.0f } },
{ { -1.0f, +1.0f, +1.0f }, { 0.5f, 0.0f, 0.0f, 1.0f } },
{ { +1.0f, +1.0f, +1.0f }, { 0.5f, 0.5f, 0.0f, 1.0f } },
{ { +1.0f, -1.0f, +1.0f }, { 0.5f, 0.5f, 0.5f, 1.0f } }
};

索引数组

一个立方体就六个面,也就是12个三角形,也就是36个索引了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DWORD triangleIndexs[]
{
// front face
0, 1, 2,
0, 2, 3,

// back face
4, 6, 5,
4, 7, 6,

// left face
4, 5, 1,
4, 1, 0,

// right face
3, 2, 6,
3, 6, 7,

// top face
1, 5, 6,
1, 6, 2,

// bottom face
4, 0, 3,
4, 3, 7
};

常量缓存

咱们需要修改一下常量缓存,把上次的offset换成MVP矩阵

1
2
3
4
struct SceneConstantBuffer
{
XMFLOAT4X4 MVP;
};

OnUpdate

咱们就在更新函数里面构造MVP矩阵,你可以在这里面不断更新数据,让摄像机动起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
XMVECTOR pos = XMVectorSet(0.0f, 5.0f, -5.0f, 1.0f);
XMVECTOR target = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX v = XMMatrixLookAtLH(pos, target, up);

XMMATRIX m = XMMatrixIdentity();
XMMATRIX p = XMMatrixPerspectiveFovLH(XM_PIDIV4,width/height,1.0f,1000.0f);
XMMATRIX MVP = m * v * p;

SceneConstantBuffer objConstants;
XMStoreFloat4x4(&objConstants.MVP, XMMatrixTranspose(MVP));
memcpy(pCbvDataBegin, &objConstants, sizeof(objConstants));

命令列表

修改一下咱们的绘制调用

1
commandList->DrawIndexedInstanced(36, 1, 0, 0, 0);

着色器

修改一下着色器,在顶点着色器里面,咱们给每一个顶点的位置都乘以MVP矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cbuffer SceneConstantBuffer : register(b0)
{
float4x4 WorldViewProj;
}

struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;

result.position = mul(position,WorldViewProj);
result.color = color;

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}

十 纹理

加载纹理的方式有很多种,咱们采用Windows Imaging Component (WIC)。

加载纹理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
DXGI_FORMAT GetDXGIFormatFromWICFormat(WICPixelFormatGUID& wicFormatGUID)
{
if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFloat) return DXGI_FORMAT_R32G32B32A32_FLOAT;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAHalf) return DXGI_FORMAT_R16G16B16A16_FLOAT;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBA) return DXGI_FORMAT_R16G16B16A16_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA) return DXGI_FORMAT_R8G8B8A8_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat32bppBGRA) return DXGI_FORMAT_B8G8R8A8_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR) return DXGI_FORMAT_B8G8R8X8_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102XR) return DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM;

else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102) return DXGI_FORMAT_R10G10B10A2_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat16bppBGRA5551) return DXGI_FORMAT_B5G5R5A1_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR565) return DXGI_FORMAT_B5G6R5_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFloat) return DXGI_FORMAT_R32_FLOAT;
else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayHalf) return DXGI_FORMAT_R16_FLOAT;
else if (wicFormatGUID == GUID_WICPixelFormat16bppGray) return DXGI_FORMAT_R16_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat8bppGray) return DXGI_FORMAT_R8_UNORM;
else if (wicFormatGUID == GUID_WICPixelFormat8bppAlpha) return DXGI_FORMAT_A8_UNORM;

else return DXGI_FORMAT_UNKNOWN;
}

WICPixelFormatGUID GetConvertToWICFormat(WICPixelFormatGUID& wicFormatGUID)
{
if (wicFormatGUID == GUID_WICPixelFormatBlackWhite) return GUID_WICPixelFormat8bppGray;
else if (wicFormatGUID == GUID_WICPixelFormat1bppIndexed) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat2bppIndexed) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat4bppIndexed) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat8bppIndexed) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat2bppGray) return GUID_WICPixelFormat8bppGray;
else if (wicFormatGUID == GUID_WICPixelFormat4bppGray) return GUID_WICPixelFormat8bppGray;
else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayFixedPoint) return GUID_WICPixelFormat16bppGrayHalf;
else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFixedPoint) return GUID_WICPixelFormat32bppGrayFloat;
else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR555) return GUID_WICPixelFormat16bppBGRA5551;
else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR101010) return GUID_WICPixelFormat32bppRGBA1010102;
else if (wicFormatGUID == GUID_WICPixelFormat24bppBGR) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat24bppRGB) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat32bppPBGRA) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat32bppPRGBA) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat48bppRGB) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat48bppBGR) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRA) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBA) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppPBGRA) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat48bppBGRFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf;
else if (wicFormatGUID == GUID_WICPixelFormat128bppPRGBAFloat) return GUID_WICPixelFormat128bppRGBAFloat;
else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFloat) return GUID_WICPixelFormat128bppRGBAFloat;
else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat;
else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat;
else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBE) return GUID_WICPixelFormat128bppRGBAFloat;
else if (wicFormatGUID == GUID_WICPixelFormat32bppCMYK) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppCMYK) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat40bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat80bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA;

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
else if (wicFormatGUID == GUID_WICPixelFormat32bppRGB) return GUID_WICPixelFormat32bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppRGB) return GUID_WICPixelFormat64bppRGBA;
else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBAHalf) return GUID_WICPixelFormat64bppRGBAHalf;
#endif

else return GUID_WICPixelFormatDontCare;
}

int GetDXGIFormatBitsPerPixel(DXGI_FORMAT& dxgiFormat)
{
if (dxgiFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) return 128;
else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_FLOAT) return 64;
else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_UNORM) return 64;
else if (dxgiFormat == DXGI_FORMAT_R8G8B8A8_UNORM) return 32;
else if (dxgiFormat == DXGI_FORMAT_B8G8R8A8_UNORM) return 32;
else if (dxgiFormat == DXGI_FORMAT_B8G8R8X8_UNORM) return 32;
else if (dxgiFormat == DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM) return 32;

else if (dxgiFormat == DXGI_FORMAT_R10G10B10A2_UNORM) return 32;
else if (dxgiFormat == DXGI_FORMAT_B5G5R5A1_UNORM) return 16;
else if (dxgiFormat == DXGI_FORMAT_B5G6R5_UNORM) return 16;
else if (dxgiFormat == DXGI_FORMAT_R32_FLOAT) return 32;
else if (dxgiFormat == DXGI_FORMAT_R16_FLOAT) return 16;
else if (dxgiFormat == DXGI_FORMAT_R16_UNORM) return 16;
else if (dxgiFormat == DXGI_FORMAT_R8_UNORM) return 8;
else if (dxgiFormat == DXGI_FORMAT_A8_UNORM) return 8;
return 0;
}

int LoadImageDataFromFile(BYTE** imageData, D3D12_RESOURCE_DESC& resourceDescription, LPCWSTR filename, int& bytesPerRow)
{
HRESULT hr;

static IWICImagingFactory* wicFactory;

IWICBitmapDecoder* wicDecoder = NULL;
IWICBitmapFrameDecode* wicFrame = NULL;
IWICFormatConverter* wicConverter = NULL;

bool imageConverted = false;

if (wicFactory == NULL)
{
CoInitialize(NULL);

hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&wicFactory)
);
if (FAILED(hr)) return 0;

hr = wicFactory->CreateFormatConverter(&wicConverter);
if (FAILED(hr)) return 0;
}

hr = wicFactory->CreateDecoderFromFilename(
filename,
NULL,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&wicDecoder
);
if (FAILED(hr)) return 0;

hr = wicDecoder->GetFrame(0, &wicFrame);
if (FAILED(hr)) return 0;

WICPixelFormatGUID pixelFormat;
hr = wicFrame->GetPixelFormat(&pixelFormat);
if (FAILED(hr)) return 0;

UINT textureWidth, textureHeight;
hr = wicFrame->GetSize(&textureWidth, &textureHeight);
if (FAILED(hr)) return 0;

DXGI_FORMAT dxgiFormat = GetDXGIFormatFromWICFormat(pixelFormat);

if (dxgiFormat == DXGI_FORMAT_UNKNOWN)
{
WICPixelFormatGUID convertToPixelFormat = GetConvertToWICFormat(pixelFormat);

if (convertToPixelFormat == GUID_WICPixelFormatDontCare) return 0;

dxgiFormat = GetDXGIFormatFromWICFormat(convertToPixelFormat);

BOOL canConvert = FALSE;
hr = wicConverter->CanConvert(pixelFormat, convertToPixelFormat, &canConvert);
if (FAILED(hr) || !canConvert) return 0;

hr = wicConverter->Initialize(wicFrame, convertToPixelFormat, WICBitmapDitherTypeErrorDiffusion, 0, 0, WICBitmapPaletteTypeCustom);
if (FAILED(hr)) return 0;

imageConverted = true;
}

int bitsPerPixel = GetDXGIFormatBitsPerPixel(dxgiFormat);
bytesPerRow = (textureWidth * bitsPerPixel) / 8;
int imageSize = bytesPerRow * textureHeight;

*imageData = (BYTE*)malloc(imageSize);

if (imageConverted)
{
hr = wicConverter->CopyPixels(0, bytesPerRow, imageSize, *imageData);
if (FAILED(hr)) return 0;
}
else
{
hr = wicFrame->CopyPixels(0, bytesPerRow, imageSize, *imageData);
if (FAILED(hr)) return 0;
}

resourceDescription = {};
resourceDescription.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resourceDescription.Alignment = 0;
resourceDescription.Width = textureWidth;
resourceDescription.Height = textureHeight;
resourceDescription.DepthOrArraySize = 1;
resourceDescription.MipLevels = 1;
resourceDescription.Format = dxgiFormat;
resourceDescription.SampleDesc.Count = 1;
resourceDescription.SampleDesc.Quality = 0;
resourceDescription.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resourceDescription.Flags = D3D12_RESOURCE_FLAG_NONE;

return imageSize;
}

创建描述符堆

首先创建堆来存放咱们的常量缓冲视图和着色器资源视图。

1
2
3
4
5
6
D3D12_DESCRIPTOR_HEAP_DESC cbvsrvHeapDesc = {};
cbvsrvHeapDesc.NumDescriptors = 2;
cbvsrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvsrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(device->CreateDescriptorHeap(&cbvsrvHeapDesc, IID_PPV_ARGS(&cbvsrvHeap)));
cbvsrvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

根签名

根参数

创建一个描述符表存放常量缓冲视图和着色器资源视图,然后放入根参数索引为0的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
{
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}

CD3DX12_DESCRIPTOR_RANGE1 ranges[2];
CD3DX12_ROOT_PARAMETER1 rootParameters[1];

ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);

rootParameters[0].InitAsDescriptorTable(2, &ranges[0], D3D12_SHADER_VISIBILITY_ALL);

静态采样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
D3D12_STATIC_SAMPLER_DESC sampler = {};
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.MipLODBias = 0;
sampler.MaxAnisotropy = 0;
sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
sampler.MinLOD = 0.0f;
sampler.MaxLOD = D3D12_FLOAT32_MAX;
sampler.ShaderRegister = 0;
sampler.RegisterSpace = 0;
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

创建根签名

1
2
3
4
5
6
7
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 1, &sampler, rootSignatureFlags);

ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, featureData.HighestVersion, &signature, &error));
ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 定义一个用于根签名功能数据的结构
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

// 设置应用程序支持的根签名的最高版本(1.1)
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

// 检查设备是否支持指定的根签名版本
if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
{
// 如果不支持,则回退到版本1.0
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}

// 定义描述符范围数组和根参数数组
CD3DX12_DESCRIPTOR_RANGE1 ranges[2];
CD3DX12_ROOT_PARAMETER1 rootParameters[1];

// 初始化描述符范围
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);

// 将根参数初始化为带有两个范围(CBV 和 SRV)的描述符表
rootParameters[0].InitAsDescriptorTable(2, &ranges[0], D3D12_SHADER_VISIBILITY_ALL);

// 定义根签名标志
D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags =
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS;

// 定义静态采样器描述
D3D12_STATIC_SAMPLER_DESC sampler = {};
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.MipLODBias = 0;
sampler.MaxAnisotropy = 0;
sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
sampler.MinLOD = 0.0f;
sampler.MaxLOD = D3D12_FLOAT32_MAX;
sampler.ShaderRegister = 0;
sampler.RegisterSpace = 0;
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

// 初始化版本化的根签名描述
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 1, &sampler, rootSignatureFlags);

// 将根签名序列化为一个数据 blob
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, featureData.HighestVersion, &signature, &error));

// 从序列化的数据 blob 创建根签名
ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));

顶点结构体

去掉颜色,加上纹理C

1
2
3
4
5
struct Vertex
{
XMFLOAT3 position;
XMFLOAT2 texCoord;
};

输入布局

1
2
3
4
5
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

顶点数组

  • 去掉颜色,加上纹理(瞎写的纹理坐标哦)
1
2
3
4
5
6
7
8
9
10
11
Vertex triangleVertices[] =
{
{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f } },
{ { -1.0f, +1.0f, -1.0f }, { 1.0f, 1.0f } },
{ { +1.0f, +1.0f, -1.0f }, { 0.0f, 1.0f } },
{ { +1.0f, -1.0f, -1.0f }, { 1.0f, 0.0f } },
{ { -1.0f, -1.0f, +1.0f }, { 0.0f, 1.0f } },
{ { -1.0f, +1.0f, +1.0f }, { 1.0f, 0.0f } },
{ { +1.0f, +1.0f, +1.0f }, { 0.0f, 0.0f } },
{ { +1.0f, -1.0f, +1.0f }, { 1.0f, 1.0f } }
};

加载图像数据

1
2
3
              D3D12_RESOURCE_DESC textureDesc;
int imageBytesPerRow;
int imageSize = LoadImageDataFromFile(&imageData, textureDesc, L"Resources/wall.jpg", imageBytesPerRow);

创建纹理资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CD3DX12_HEAP_PROPERTIES heapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&textureBuffer)));

const UINT64 uploadBufferSize = GetRequiredIntermediateSize(textureBuffer.Get(), 0, 1);

CD3DX12_HEAP_PROPERTIES heapProperties2 = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC resourceDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
ThrowIfFailed(device->CreateCommittedResource(
&heapProperties2,
D3D12_HEAP_FLAG_NONE,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&textureBufferUploadHeap)));

把资源从上传堆(CPU和GPU都可以访问)拷贝到默认堆(只能GPU访问),然后对资源设置屏障(也就是这个资源干什么用的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义纹理数据结构,并初始化为零
D3D12_SUBRESOURCE_DATA textureData = {};

// 设置纹理数据指针,指向图像数据的第一个元素
textureData.pData = &imageData[0];

// 设置纹理数据的行间距,即每一行的字节数
textureData.RowPitch = imageBytesPerRow;

// 设置纹理数据的层间距,即整个纹理的字节数
textureData.SlicePitch = imageBytesPerRow * textureDesc.Height;

// 使用UpdateSubresources函数将纹理数据从上传堆复制到默认堆
UpdateSubresources(commandList.Get(), textureBuffer.Get(), textureBufferUploadHeap.Get(), 0, 0, 1, &textureData);

// 定义资源屏障对象,用于转换纹理缓冲区的状态
CD3DX12_RESOURCE_BARRIER resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);

// 将纹理缓冲区状态转换为非像素着色器资源状态和像素着色器资源状态
commandList->ResourceBarrier(1, &resBarrier);

创建着色器资源视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 定义着色器资源视图描述结构体,并初始化为零
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};

// 设置着色器资源视图描述中的成员
// 使用默认的着色器4分量映射
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;

// 设置纹理资源的格式与原始纹理一致
srvDesc.Format = textureDesc.Format;

// 设置视图维度为2D纹理
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;

// 设置纹理资源的Mip级别数量为1
srvDesc.Texture2D.MipLevels = 1;

// 获取着色器资源视图描述的CPU描述符句柄
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvsrvHandle(cbvsrvHeap->GetCPUDescriptorHandleForHeapStart());

// 将描述符句柄偏移至正确的位置(这里假设在堆中已经有一个CBV(常量缓冲区视图))
cbvsrvHandle.Offset(1, cbvsrvDescriptorSize);

// 使用设备创建着色器资源视图
device->CreateShaderResourceView(textureBuffer.Get(), &srvDesc, cbvsrvHandle);

// 关闭命令列表
ThrowIfFailed(commandList->Close());

// 提交命令列表以执行复制和状态转换操作
ID3D12CommandList* ppCommandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

设置根签名和描述符表命令

1
2
3
4
5
6
7
8
9
10
11
12
// 将命令列表与根签名关联,以便在绘制时使用正确的根签名
commandList->SetGraphicsRootSignature(rootSignature.Get());

// 将着色器资源堆(包含着色器资源视图和常量缓冲区视图)与命令列表关联,
// 这样在绘制时可以使用着色器资源和常量缓冲区
ID3D12DescriptorHeap* ppHeaps[] = { cbvsrvHeap.Get() };
commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

// 将着色器资源堆的第一个描述符表(索引为0)绑定到图形管道的根参数(索引为0)上
// 这样在着色器代码中就可以使用该描述符表中的着色器资源
commandList->SetGraphicsRootDescriptorTable(0, cbvsrvHeap->GetGPUDescriptorHandleForHeapStart());

修改着色器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Texture2D t1 : register(t0);
SamplerState s1 : register(s0);

cbuffer SceneConstantBuffer : register(b0)
{
float4x4 WorldViewProj;
}

struct PSInput
{
float4 position : SV_POSITION;
float2 texCoord : TEXCOORD;
};

PSInput VSMain(float4 position : POSITION, float2 texCoord : TEXCOORD)
{
PSInput result;

result.position = mul(position,WorldViewProj);
result.texCoord = texCoord;

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
return t1.Sample(s1, input.texCoord);
}

成果

我是非常满意的

image-20230720223522198

十一 Phong和Blinn-Phong光照模型

Phone

顶点结构体

首先修改顶点结构体添加法线。

1
2
3
4
5
6
7
struct Vertex
{
Vertex(float x, float y, float z,float nx,float ny,float nz, float u, float v) : position(x, y, z), normal(nx,ny,nz), texCoord(u, v) {}
XMFLOAT3 position;
XMFLOAT3 normal;
XMFLOAT2 texCoord;
};

顶点结构体数组

因为每一个顶点都有自己的法线,所以咱们得使用36个顶点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Vertex triangleVertices[] =
{
// front face
{ -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f },
{ 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f },
{ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f },
{ 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f },

// right side face
{ 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f },
{ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f },
{ 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
{ 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f },

// left side face
{ -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
{ -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
{ -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f },
{ -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f },

// back face
{ 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f },
{ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f },
{ 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f },
{ -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f },

// top face
{ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f },
{ 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f },
{ 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f },
{ -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f },

// bottom face
{ 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f },
{ -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f },
{ 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f },
{ -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f },
};

常量缓存

1
2
3
4
5
6
7
8
9
10
11
struct SceneConstantBuffer
{
XMFLOAT4X4 Model;
XMFLOAT4X4 MVP;
XMFLOAT3 lightColor;
float padding1;
XMFLOAT3 lightPosition;
float padding2;
XMFLOAT3 viewPosition;
float padding3;
};

顶点索引数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DWORD triangleIndexs[]
{
// front face
0, 1, 2, // first triangle
0, 3, 1, // second triangle

// left face
4, 5, 6, // first triangle
4, 7, 5, // second triangle

// right face
8, 9, 10, // first triangle
8, 11, 9, // second triangle

// back face
12, 13, 14, // first triangle
12, 15, 13, // second triangle

// top face
16, 17, 18, // first triangle
16, 19, 17, // second triangle

// bottom face
20, 21, 22, // first triangle
20, 23, 21, // second triangle
};

OnUpdate

修改OnUpdate方法,去掉背景颜色更新的数据,加上光位置运动的数据,并且把Phone光照模型需要的数据通过常量缓冲传递过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void OnUpdate()
{

if (offset <= 3.0f && isOffset)
{
offset += 0.01f;
isOffset = true;
}
else
{
offset -= 0.01f;
offset <= -3 ? isOffset = true : isOffset = false;

}

XMVECTOR pos = XMVectorSet(0.0f, 5.0f, -5.0f, 1.0f);
XMVECTOR target = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX v = XMMatrixLookAtLH(pos, target, up);

XMMATRIX m = XMMatrixIdentity();
XMMATRIX p = XMMatrixPerspectiveFovLH(XM_PIDIV4,width/height,1.0f,1000.0f);
XMMATRIX MVP = m * v * p;

SceneConstantBuffer objConstants;
XMStoreFloat4x4(&objConstants.Model, XMMatrixTranspose(m));
XMStoreFloat4x4(&objConstants.MVP, XMMatrixTranspose(MVP));
objConstants.lightColor = { 1.0f,1.0f,1.0f };
objConstants.lightPosition = { 1.0f+offset, 1.0f, 2.0f };
objConstants.viewPosition = { 0.0f, 1.0f, -3.0f };
memcpy(pCbvDataBegin, &objConstants, sizeof(objConstants));

}

添加命令

颜色

//const float clearColor[] = { color[0], color[1], color[2], 1.0f };
const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };

管线对象

1
2
3
4
5
6
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }//记得变为24;
};

着色器

修改着色器,完成咱们的Phone着色模型,这里需要注意一个点,那就是法线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Texture2D t1 : register(t0);
SamplerState s1 : register(s0);

cbuffer SceneConstantBuffer : register(b0)
{
float4x4 Model;
float4x4 MVP;
float3 lightColor;
float3 lightPosition;
float3 viewPosition;
}

struct PSInput
{
float4 position : SV_POSITION;
float2 texCoord : TEXCOORD;
float3 normal : NORMAL;
float4 worldPosition : POSITION;
};

PSInput VSMain(float4 position : POSITION, float3 normal : NORMAL, float2 texCoord : TEXCOORD)
{
PSInput result;

result.worldPosition = mul(position, Model);
result.position = mul(position, MVP);
result.texCoord = texCoord;
result.normal = normal; //如果物体进行了非等比缩放,这里需要对法线进行模型矩阵左上角的逆矩阵的转置矩阵变换。

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
float4 color = t1.Sample(s1, input.texCoord);
//ambient
float ambientStrength = 0.1;
float3 ambient = mul(ambientStrength, lightColor);

// diffuse
float3 norm = normalize(input.normal);
float3 lightDir = normalize(lightPosition - viewPosition);
float diff = max(dot(norm, lightDir), 0.0);
float3 diffuse = mul(diff, lightColor);

// specular
float specularStrength = 0.5;
float3 viewDir = normalize(viewPosition - input.worldPosition.xyz);
float3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
float3 specular = mul(mul(specularStrength, spec), lightColor);

color = mul((ambient + diffuse + specular), color.xyz);

return color;
}

Blinn-Phong

其实Blinn-Phong和Phong效果差不多,就是提升了点性能。

其他东西都不变,着色器修改一下反射光部分就可以了。

着色器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Texture2D t1 : register(t0);
SamplerState s1 : register(s0);

cbuffer SceneConstantBuffer : register(b0)
{
float4x4 Model;
float4x4 MVP;
float3 lightColor;
float3 lightPosition;
float3 viewPosition;
}

struct PSInput
{
float4 position : SV_POSITION;
float2 texCoord : TEXCOORD;
float3 normal : NORMAL;
float4 worldPosition : POSITION;
};

PSInput VSMain(float4 position : POSITION, float3 normal : NORMAL, float2 texCoord : TEXCOORD)
{
PSInput result;

result.worldPosition = mul(position, Model);
result.position = mul(position, MVP);
result.texCoord = texCoord;
result.normal = normal; //如果物体进行了非等比缩放,这里需要对法线进行模型矩阵左上角的逆矩阵的转置矩阵变换。

return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
float4 color = t1.Sample(s1, input.texCoord);
//ambient
float ambientStrength = 0.1;
float3 ambient = mul(ambientStrength, lightColor);

// diffuse
float3 norm = normalize(input.normal);
float3 lightDir = normalize(lightPosition - viewPosition);
float diff = max(dot(norm, lightDir), 0.0);
float3 diffuse = mul(diff, lightColor);

// specular
float specularStrength = 0.5;
float3 viewDir = normalize(viewPosition - input.worldPosition.xyz);
//float3 reflectDir = reflect(-lightDir, norm);
float3 H = normalize(lightDir + viewDir);
float spec = pow(max(dot(H, norm), 0.0), 32);
float3 specular = mul(mul(specularStrength, spec), lightColor);

color = mul((ambient + diffuse + specular), color.xyz);

return color;
}

成果

image-20230722124204513