#注意!
この記事の内容は古いものとなっています。新しいものを書いていますので、そちらにお願いします。
DirectX12で3D描画
DirectXを全くいじったことがない初心者が無謀にもDirectX12を勉強してみたのでその備忘録的なものとして...
参考資料や知識などが少なく(参考資料よりも知識が無いほうが痛い),DirectX11の本などを読みながらプログラムを作成したので,間違いなど多分に含んでいる恐れがあります.間違いや勘違いがあった場合(というか絶対あります)教えていただけるとありがたいです.(お手柔らかにお願いしますm(_ _)m)
環境
Windows10 Pro
Visual Studio 2015 Community
SDK 10.0.10586.0
1. D3D12Managerクラスの宣言
Direct3D12をまとめたクラスを作ります.ウインドウハンドルとウインドウのサイズは外からもらってきます.ウインドウは今までどおりWinAPIで作成してあります.
#ifndef DEF_D3D12MANAGER_H
#define DEF_D3D12MANAGER_H
#include <Windows.h>
#include <d3d12.h>
#include <d3d12shader.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#define RTV_BUFFER_NUM (2)
#define SAFE_RELEASE(p) {if(p){(p)->Release(); (p) = nullptr;}}
using namespace DirectX;
class D3D12Manager{
public:
typedef struct Vertex3D{
XMFLOAT3 Position;
XMFLOAT3 Color;
}Vertex3D;
typedef struct ConstantBufferData{
XMFLOAT4X4 WVP;
}ConstantBufferData;
public:
D3D12Manager(HWND hWnd, int WindowWidth, int WindowHeight);
~D3D12Manager();
int CreateDevice();
int CreateCommandQueue();
int CreateSwapChain();
int CreateRenderTarget();
int CreateDepthStencilBuffer();
int CreateCommandList();
int CreateConstantBuffer();
int CreateIndexBuffer();
int CreateVertexBuffer();
int CreateRootSignature();
int CompileShader();
int CreatePipelineStateObject();
int SetVertexData();
int WaitForPreviousFrame();
int SetResourceBarrier(D3D12_RESOURCE_STATES BeforeState, D3D12_RESOURCE_STATES AfterState);
int PopulateCommandList();
int Render();
int SetPlaneData();
private:
HWND m_hWnd;
int m_iWindowWidth;
int m_iWindowHeight;
UINT64 Frames;
UINT RTVIdx;
IDXGIFactory4 *pDXGIFactory;
IDXGIAdapter3 *pAdapter;
IDXGISwapChain3 *pSwapChain;
ID3D12Device *pDevice;
ID3D12CommandQueue *pComandQueue;
HANDLE handleFenceEvent;
ID3D12Fence *pQueueFence;
ID3D12DescriptorHeap *pDescriptorHeapRTV;
ID3D12Resource *pRenderTarget[RTV_BUFFER_NUM];
D3D12_CPU_DESCRIPTOR_HANDLE handleRTV[RTV_BUFFER_NUM];
ID3D12Resource *pDepthBuffer;
ID3D12DescriptorHeap *pDescriptorHeapDSB;
D3D12_CPU_DESCRIPTOR_HANDLE handleDSV;
D3D12_RECT RectScissor;
D3D12_VIEWPORT Viewport;
ID3D12GraphicsCommandList *pCommandList;
ID3D12CommandAllocator *pCmdAllocator;
ID3D12Resource *pConstantBuffer;
ID3D12DescriptorHeap *pDescriptorHeapCBV;
D3D12_CPU_DESCRIPTOR_HANDLE handleCBV;
ID3D12Resource *pIndexBuffer;
D3D12_INDEX_BUFFER_VIEW IndexView;
ID3D12Resource *pVertexBuffer;
D3D12_VERTEX_BUFFER_VIEW VertexView;
ID3D12PipelineState *pPipelineState;
ID3D12RootSignature *pRootSignature;
ID3DBlob *pRootSignatureBlob;
ID3DBlob *pErrorBlob;
ID3DBlob *VertexShader;
ID3DBlob *PixelShader;
};
#endif
コンストラクタではメソッドを順番に呼んで初期化し,ViewPortとシザー矩形の設定をします.
D3D12Manager::D3D12Manager(HWND hWnd, int WindowWidth, int WindowHeight):
m_hWnd(hWnd),
m_iWindowWidth(WindowWidth),
m_iWindowHeight(WindowHeight),
Frames(1),
RTVIdx(0),
pDXGIFactory(nullptr),
pAdapter(nullptr),
pSwapChain(nullptr),
pDevice(nullptr),
pComandQueue(nullptr),
handleFenceEvent(nullptr),
pQueueFence(nullptr),
pDescriptorHeapRTV(nullptr),
handleRTV{0},
pDepthBuffer(nullptr),
pDescriptorHeapDSB(nullptr),
handleDSV{0},
RectScissor{0},
Viewport{0},
pCommandList(nullptr),
pCmdAllocator(nullptr),
pConstantBuffer(nullptr),
pIndexBuffer(nullptr),
IndexView{0},
pVertexBuffer(nullptr),
VertexView{0},
pPipelineState(nullptr),
pRootSignature(nullptr),
pRootSignatureBlob(nullptr),
pErrorBlob(nullptr),
VertexShader(nullptr),
PixelShader(nullptr){
CreateDevice();
CreateCommandQueue();
CreateSwapChain();
CreateRenderTarget();
CreateDepthStencilBuffer();
CreateCommandList();
CreateBuffer();
CreateRootSignature();
CompileShader();
CreatePipelineStateObject();
SetVertexData();
Viewport.TopLeftX = 0.f;
Viewport.TopLeftY = 0.f;
Viewport.Width = (FLOAT)m_iWindowWidth;
Viewport.Height = (FLOAT)m_iWindowHeight;
Viewport.MinDepth = 0.f;
Viewport.MaxDepth = 1.f;
RectScissor.top = 0;
RectScissor.left = 0;
RectScissor.right = m_iWindowWidth;
RectScissor.bottom = m_iWindowHeight;
}
デストラクタではオブジェクトの開放をします.
D3D12Manager::~D3D12Manager(){
SAFE_RELEASE(VertexShader);
SAFE_RELEASE(PixelShader);
SAFE_RELEASE(pPipelineState);
SAFE_RELEASE(pErrorBlob);
SAFE_RELEASE(pRootSignatureBlob);
SAFE_RELEASE(pRootSignature);
SAFE_RELEASE(pVertexBuffer);
SAFE_RELEASE(pIndexBuffer);
SAFE_RELEASE(pConstantBuffer);
SAFE_RELEASE(pCmdAllocator);
SAFE_RELEASE(pCommandList);
SAFE_RELEASE(pDepthBuffer);
SAFE_RELEASE(pDescriptorHeapDSB);
for(int i = RTV_BUFFER_NUM - 1; i >= 0; --i){
SAFE_RELEASE(pRenderTarget[i]);
}
SAFE_RELEASE(pDescriptorHeapRTV);
SAFE_RELEASE(pQueueFence);
SAFE_RELEASE(pComandQueue);
SAFE_RELEASE(pDevice);
SAFE_RELEASE(pSwapChain);
SAFE_RELEASE(pAdapter);
SAFE_RELEASE(pDXGIFactory);
}
2. デバイスの準備
まずはデバイスの作成からです.ファクトリを生成し,アダプタを見つけ,デバイスを作成するという流れです.デバッグモードの場合,デバッグレイヤーを有効にする処理も入っています.デバッグモードでファクトリを作成する場合はフラグにDXGI_CREATE_FACTORY_DEBUG
を渡します.DirectX12ではデバイスを作成する際にD3D_FEATURE_LEVEL_11_0
以上を要求するようです.
int D3D12Manager::CreateDevice(){
HRESULT hr;
UINT flagsDXGI = 0;
#if defined(_DEBUG)
ID3D12Debug *dxDebug = nullptr;
hr = D3D12GetDebugInterface(IID_PPV_ARGS(&dxDebug));
if(FAILED(hr)){
return -1;
}
//デバッグレイヤーを有効にする
dxDebug->EnableDebugLayer();
dxDebug->Release();
dxDebug = nullptr;
flagsDXGI |= DXGI_CREATE_FACTORY_DEBUG;
#endif
//ファクトリの作成
hr = CreateDXGIFactory2(flagsDXGI, IID_PPV_ARGS(&pDXGIFactory));
if(FAILED(hr)){
return -1;
}
DXGI_ADAPTER_DESC AdapterDesc;
//最初に見つかったアダプターを使用する
hr = pDXGIFactory->EnumAdapters(0, (IDXGIAdapter**)&pAdapter);
if(SUCCEEDED(hr)){
pAdapter->GetDesc(&AdapterDesc);
hr = D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_1, IID_PPV_ARGS(&pDevice));
if(FAILED(hr)){
return -1;
}
}else{
return -1;
}
return 0;
}
エラーが返されたときの処理などは真面目に書いていません.また,アウトプットデバイスの取得も行っていません.アダプタも実際はforなどで列挙して適切なものを選ぶようにしたほうがいいかと思います.今回は使用していませんが,AdapterDesc.VendorId == 0x1414 && AdapterDesc.DeviceId == 0x8c
の場合,WARPデバイスだそうです(EnumWarpAdapter
関数を使ってもWARPデバイスは取得できます).私の環境だとVM環境ではWARPデバイスでしか実行できませんでした.またWARPデバイスではアウトプットの取得もできませんでした.
3. コマンドキューの作成
次にコマンドキューの作成をします.DirectX12からはコマンドがすべて遅延実行されるようになりました.そのコマンドを蓄え,実行するためのオブジェクトがコマンドキューです.次のように作成します.
int D3D12Manager::CreateCommandQueue(){
HRESULT hr;
// コマンドキューを生成.
D3D12_COMMAND_QUEUE_DESC descCommandQueue;
ZeroMemory(&descCommandQueue, sizeof(descCommandQueue));
descCommandQueue.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
descCommandQueue.Priority = 0;
descCommandQueue.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
descCommandQueue.NodeMask = 0;
hr = pDevice->CreateCommandQueue(&descCommandQueue, IID_PPV_ARGS(&pComandQueue));
if (FAILED(hr)) {
return -1;
}
//コマンドキュー用のフェンスの生成
handleFenceEvent = CreateEventEx(nullptr, FALSE, FALSE, EVENT_ALL_ACCESS);//CreateEvent(NULL, FALSE, FALSE, NULL);
if(handleFenceEvent == NULL ){
return -1;
}
hr = pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pQueueFence));
if (FAILED(hr)) {
return -1;
}
return 0;
}
DirectX12ではドローコールはこのCommandQueueに積み込みます.そしてこのCommandQueueはいつ実行が終わるかわかりません(遅延実行のため).なので積んだコマンドが終了しているか確認しなければなりません.ここで作成したフェンスはその実行の終了待ちをするためのものです.
4. スワップチェインの作成
続いて,スワップチェインの作成です.DirectX11とほとんど変わり無いようですが,BufferCountが裏表合わせた数指定しなければなりません.
int D3D12Manager::CreateSwapChain(){
HRESULT hr = S_OK;
IDXGISwapChain *pSwapChain = nullptr;
DXGI_SWAP_CHAIN_DESC DXGISwapChainDesc;
ZeroMemory(&DXGISwapChainDesc, sizeof(DXGISwapChainDesc));
DXGISwapChainDesc.BufferDesc.Width = m_iWindowWidth;
DXGISwapChainDesc.BufferDesc.Height = m_iWindowHeight;
DXGISwapChainDesc.OutputWindow = m_hWnd;
DXGISwapChainDesc.Windowed = TRUE;
DXGISwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
DXGISwapChainDesc.BufferCount = RTV_BUFFER_NUM;//#define RTV_BUFFER_NUM 2
DXGISwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
DXGISwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
DXGISwapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
DXGISwapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
DXGISwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
DXGISwapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
DXGISwapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
DXGISwapChainDesc.SampleDesc.Count = 1;
DXGISwapChainDesc.SampleDesc.Quality = 0;
//IDXGISwapChain 型でスワップチェインを作成
hr = pDXGIFactory->CreateSwapChain(pComandQueue, &DXGISwapChainDesc, &pSwapChain);
if(FAILED(hr)){return -1;}
//IDXGISwapChain3 で受け直す
hr = pSwapChain->QueryInterface(&pSwapChain);
if(FAILED(hr)){return -1;}
pSwapChain->Release();
pSwapChain = nullptr;
return 0;
}
構造体に値を設定して関数に投げているだけです.始めにIDXGISwapChain
型で作成してQueryInterface
関数を使用してIDXGISwapChain3
型として受け取っています.
5. レンダーターゲットの作成
レンダーターゲットの作成はまずDescriptorHeapの作成から始めます.デスクリプタはDirectX12で初出のものです.DirectX11でいうところのViewに相当するものです.メモリ上のデータを指し示すものです.DescriptorHeapを作成したらレンダーターゲットを作成してDescriptorHeapに登録します.
int D3D12Manager::CreateRenderTarget(){
HRESULT hr;
D3D12_DESCRIPTOR_HEAP_DESC HeapDesc;
//RTV用DescriptorHeapの作成
ZeroMemory(&HeapDesc, sizeof(HeapDesc));
HeapDesc.NumDescriptors = 2;
HeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
HeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
HeapDesc.NodeMask = 0;
hr = pDevice->CreateDescriptorHeap(&HeapDesc, IID_PPV_ARGS(&pDescriptorHeapRTV));
if (FAILED(hr)) {
return -1;
}
//レンダーターゲットを作成する処理
UINT DescriptorHandleIncrementSize = pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
for(UINT i = 0; i < RTV_BUFFER_NUM; ++i){
//スワップチェインからバッファを受け取る
hr = pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pRenderTarget[i]));
if(FAILED(hr)){
return -1;
}
//RenderTargetViewを作成してDescriptorHeapに登録
handleRTV[i] = pDescriptorHeapRTV->GetCPUDescriptorHandleForHeapStart();
handleRTV[i].ptr += DescriptorHandleIncrementSize * i;
pDevice->CreateRenderTargetView(pRenderTarget[i], nullptr, handleRTV[i]);
}
return 0;
}
6. DepthBufferの作成
DepthBufferの作成です.3Dオブジェクトの前後関係(深度)を記録するバッファです.
int D3D12Manager::CreateDepthStencilBuffer(){
HRESULT hr;
D3D12_DESCRIPTOR_HEAP_DESC descDescriptorHeapDSB;
ZeroMemory(&descDescriptorHeapDSB, sizeof(descDescriptorHeapDSB));
descDescriptorHeapDSB.NumDescriptors = 1;
descDescriptorHeapDSB.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
descDescriptorHeapDSB.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
descDescriptorHeapDSB.NodeMask = 0;
hr = pDevice->CreateDescriptorHeap(&descDescriptorHeapDSB, IID_PPV_ARGS(&pDescriptorHeapDSB));
if(FAILED(hr)){
return -1;
}
D3D12_RESOURCE_DESC DepthDesc;
D3D12_HEAP_PROPERTIES HeapProps;
D3D12_CLEAR_VALUE ClearValue;
ZeroMemory(&DepthDesc, sizeof(DepthDesc));
ZeroMemory(&HeapProps, sizeof(HeapProps));
ZeroMemory(&ClearValue, sizeof(ClearValue));
DepthDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
DepthDesc.Width = m_iWindowWidth;
DepthDesc.Height = m_iWindowHeight;
DepthDesc.DepthOrArraySize = 1;
DepthDesc.MipLevels = 0;
DepthDesc.Format = DXGI_FORMAT_R32_TYPELESS;
DepthDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
DepthDesc.SampleDesc.Count = 1;
DepthDesc.SampleDesc.Quality = 0;
DepthDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
HeapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
HeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
HeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
HeapProps.CreationNodeMask = 0;
HeapProps.VisibleNodeMask = 0;
ClearValue.Format = DXGI_FORMAT_D32_FLOAT;
ClearValue.DepthStencil.Depth = 1.0f;
ClearValue.DepthStencil.Stencil = 0;
hr = pDevice->CreateCommittedResource(&HeapProps, D3D12_HEAP_FLAG_NONE, &DepthDesc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &ClearValue, IID_PPV_ARGS(&pDepthBuffer));
if(FAILED(hr)){
return -1;
}
D3D12_DEPTH_STENCIL_VIEW_DESC DSVDesc;
ZeroMemory(&DSVDesc, sizeof(DSVDesc));
DSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
DSVDesc.Format = DXGI_FORMAT_D32_FLOAT;
DSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
DSVDesc.Texture2D.MipSlice = 0;
DSVDesc.Flags = D3D12_DSV_FLAG_NONE;
handleDSV = pDescriptorHeapDSB->GetCPUDescriptorHandleForHeapStart();
pDevice->CreateDepthStencilView(pDepthBuffer, &DSVDesc, handleDSV);
return 0;
}
7. コマンドリスト・コマンドアロケータ
続いてコマンドリストとコマンドアロケータです.コマンドリストは先程出てきたコマンドキューに渡すコマンドをまとめるオブジェクトです.DirectX12では頂点やインデックスの設定やレンダーターゲットの設定などのドローコールなどはすべてコマンドリストに設定するようになりました.コマンドリストに積まれたコマンドがハードウエアネイティブに変換されたものを蓄えるものがコマンドアロケータです.
int D3D12Manager::CreateCommandList(){
HRESULT hr;
//コマンドアロケータの作成
hr = pDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&pCmdAllocator));
if (FAILED(hr)) {
return -1;
}
//コマンドアロケータとバインドしてコマンドリストを作成する
hr = pDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, pCmdAllocator, nullptr, IID_PPV_ARGS(&pCommandList));
if (FAILED(hr)) {
return -1;
}
return 0;
}
ここで作成したコマンドリストに描画コマンドを設定し,先ほど作成したコマンドキューに流すことで描画が行われます.
8. 定数・頂点・インデックスバッファの作成
3Dオブジェクトの情報を書き込むためのバッファ類を作成します.今回は定数バッファも頂点バッファもインデックスバッファもすべて同じ設定なので,構造体を使いまわして初期化してしまいます.手抜きです.
int D3D12Manager::CreateBuffer(){
HRESULT hr;
D3D12_HEAP_PROPERTIES HeapPropeties;
D3D12_RESOURCE_DESC ResourceDesc;
ZeroMemory(&HeapPropeties, sizeof(HeapPropeties));
ZeroMemory(&ResourceDesc, sizeof(ResourceDesc));
HeapPropeties.Type = D3D12_HEAP_TYPE_UPLOAD;
HeapPropeties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
HeapPropeties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
HeapPropeties.CreationNodeMask = 0;
HeapPropeties.VisibleNodeMask = 0;
ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
ResourceDesc.Width = 256;
ResourceDesc.Height = 1;
ResourceDesc.DepthOrArraySize = 1;
ResourceDesc.MipLevels = 1;
ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ResourceDesc.SampleDesc.Count = 1;
ResourceDesc.SampleDesc.Quality = 0;
//定数バッファの作成
hr = pDevice->CreateCommittedResource(&HeapPropeties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pConstantBuffer));
if(FAILED(hr)){
return -1;
}
//インデックスバッファの作成
hr = pDevice->CreateCommittedResource(&HeapPropeties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pIndexBuffer));
if(FAILED(hr)){
return -1;
}
//頂点バッファの作成
hr = pDevice->CreateCommittedResource(&HeapPropeties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pVertexBuffer));
if(FAILED(hr)){
return -1;
}
return 0;
}
9. ルートシグネチャの作成
ルートシグネチャは作成したバッファとシェーダーのレジスタを結びつけるテーブルのようなものです.今回は定数バッファのみを設定しています.テクスチャやサンプラーも設定する場合はD3D12_DESCRIPTOR_RANGE
も設定してDescriptorTable
に登録しますが,定数バッファはルートパラメータに直接設定できるので今回はそうします.
int D3D12Manager::CreateRootSignature(){
HRESULT hr;
D3D12_ROOT_SIGNATURE_DESC RootSignatureDesc;
D3D12_ROOT_PARAMETER RootParameters[1];
ZeroMemory(&RootSignatureDesc, sizeof(RootSignatureDesc));
ZeroMemory(&RootParameters[0], sizeof(RootParameters[0]));
RootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
RootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
RootParameters[0].Descriptor.ShaderRegister = 0;
RootParameters[0].Descriptor.RegisterSpace = 0;
RootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
RootSignatureDesc.NumParameters = _countof(RootParameters);
RootSignatureDesc.pParameters = RootParameters;
hr = D3D12SerializeRootSignature(&RootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &pRootSignatureBlob, &pErrorBlob);
if(FAILED(hr)){
return -1;
}
hr = pDevice->CreateRootSignature(0, pRootSignatureBlob->GetBufferPointer(), pRootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&pRootSignature));
if(FAILED(hr)){
return -1;
}
return 0;
}
10. シェーダーの読み込み
シェーダーの読み込みです.DirectX11とくらべても特に変わったところは無いようです.D3DCompileFromFile
関数を使用し,ID3DBlob
へのポインタ型で受け取ります.
int D3D12Manager::CompileShader(){
HRESULT hr;
#if defined(_DEBUG)
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
hr = D3DCompileFromFile(L"Shaders.hlsl", nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &VertexShader, nullptr);
if(FAILED(hr)){
return -1;
}
hr = D3DCompileFromFile(L"Shaders.hlsl", nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &PixelShader, nullptr);
if(FAILED(hr)){
return -1;
}
return 0;
}
シェーダーの中身ですが極小です.特に何もしていません.変換行列の類は別々に受け取ってシェーダー内で計算してしまっています.
//定数場ファファの値
cbuffer cbBuffer : register(b0){
float4x4 WVP;
};
struct VS_INPUT{
float3 Position : POSITION;
float3 Color : COLOR;
};
struct PS_INPUT{//(VS_OUTPUT)
float4 Position : SV_POSITION;
float4 Color : COLOR;
};
PS_INPUT VSMain(VS_INPUT input){
PS_INPUT output;
float4 pos4 = float4(input.Position, 1.0);
output.Position = mul(pos4, WVP);
output.Color = float4(input.Color, 1.0);
return output;
}
float4 PSMain(PS_INPUT input) : SV_TARGET{
return input.Color;
}
11. PipelineStateObject (PSO)の作成
PipelineStateObject の作成です.DirectX11では描画するときにシェーダーやらラスタライザステートやらブレンドステートやらを設定していましたが,それをひとまとめにして管理してしまおうというオブジェクトです.なのでDirectX12ではシェーダーやラスタライザやブレンドステートなどは始めにここで設定して描画時にはこのオブジェクト一つをセットするだけです.このオブジェクトはすべてまとまってしまっているため,描画時に違うパラメータを設定したい場合はその分PipelineStateObject を作成しなければならないようです.
int D3D12Manager::CreatePipelineStateObject(){
HRESULT hr;
// 頂点レイアウト.
D3D12_INPUT_ELEMENT_DESC descInputElements[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC PiplineStateDesc;
ZeroMemory(&PiplineStateDesc, sizeof(PiplineStateDesc));
//シェーダーの設定
PiplineStateDesc.VS.pShaderBytecode = VertexShader->GetBufferPointer();
PiplineStateDesc.VS.BytecodeLength = VertexShader->GetBufferSize();
PiplineStateDesc.HS.pShaderBytecode = nullptr;
PiplineStateDesc.HS.BytecodeLength = 0;
PiplineStateDesc.DS.pShaderBytecode = nullptr;
PiplineStateDesc.DS.BytecodeLength = 0;
PiplineStateDesc.GS.pShaderBytecode = nullptr;
PiplineStateDesc.GS.BytecodeLength = 0;
PiplineStateDesc.PS.pShaderBytecode = PixelShader->GetBufferPointer();
PiplineStateDesc.PS.BytecodeLength = PixelShader->GetBufferSize();
//インプットレイアウトの設定
PiplineStateDesc.InputLayout.pInputElementDescs = descInputElements;
PiplineStateDesc.InputLayout.NumElements = _countof(descInputElements);
//サンプル系の設定
PiplineStateDesc.SampleDesc.Count = 1;
PiplineStateDesc.SampleDesc.Quality = 0;
PiplineStateDesc.SampleMask = UINT_MAX;
//レンダーターゲットの設定
PiplineStateDesc.NumRenderTargets = 1;
PiplineStateDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
//三角形に設定
PiplineStateDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
//ルートシグネチャ
PiplineStateDesc.pRootSignature = pRootSignature;
//ラスタライザステートの設定
PiplineStateDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
PiplineStateDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
PiplineStateDesc.RasterizerState.FrontCounterClockwise = FALSE;
PiplineStateDesc.RasterizerState.DepthBias = 0;
PiplineStateDesc.RasterizerState.DepthBiasClamp = 0;
PiplineStateDesc.RasterizerState.SlopeScaledDepthBias = 0;
PiplineStateDesc.RasterizerState.DepthClipEnable = TRUE;
PiplineStateDesc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
PiplineStateDesc.RasterizerState.AntialiasedLineEnable = FALSE;
PiplineStateDesc.RasterizerState.MultisampleEnable = FALSE;
//ブレンドステートの設定
for(int i = 0; i < _countof(PiplineStateDesc.BlendState.RenderTarget); ++i){
PiplineStateDesc.BlendState.RenderTarget[i].BlendEnable = FALSE;
PiplineStateDesc.BlendState.RenderTarget[i].SrcBlend = D3D12_BLEND_ONE;
PiplineStateDesc.BlendState.RenderTarget[i].DestBlend = D3D12_BLEND_ZERO;
PiplineStateDesc.BlendState.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
PiplineStateDesc.BlendState.RenderTarget[i].SrcBlendAlpha = D3D12_BLEND_ONE;
PiplineStateDesc.BlendState.RenderTarget[i].DestBlendAlpha = D3D12_BLEND_ZERO;
PiplineStateDesc.BlendState.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
PiplineStateDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
PiplineStateDesc.BlendState.RenderTarget[i].LogicOpEnable = FALSE;
PiplineStateDesc.BlendState.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_CLEAR;
}
PiplineStateDesc.BlendState.AlphaToCoverageEnable = FALSE;
PiplineStateDesc.BlendState.IndependentBlendEnable = FALSE;
//デプスステンシルステートの設定
PiplineStateDesc.DepthStencilState.DepthEnable = TRUE; //深度テストあり
PiplineStateDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
PiplineStateDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
PiplineStateDesc.DepthStencilState.StencilEnable = FALSE; //ステンシルテストなし
PiplineStateDesc.DepthStencilState.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK;
PiplineStateDesc.DepthStencilState.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK;
PiplineStateDesc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
PiplineStateDesc.DepthStencilState.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
PiplineStateDesc.DepthStencilState.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
PiplineStateDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT;
hr = pDevice->CreateGraphicsPipelineState(&PiplineStateDesc, IID_PPV_ARGS(&pPipelineState));
if(FAILED(hr)){
return -1;
}
return 0;
}
12. 頂点データの設定
作ったVertexBufferとIndexBufferに頂点データの設定をします.
int D3D12Manager::SetVertexData(){
HRESULT hr;
void *Mapped;
//頂点データをVertexBufferに書き込む
Vertex3D Vertices[] = {
{XMFLOAT4(-1.f, 1.f, 0.f, 1.f), XMFLOAT4(0.f, 0.f, 1.f, 0.f), XMFLOAT2(0.f, 0.f)},
{XMFLOAT4( 1.f, 1.f, 0.f, 1.f), XMFLOAT4(0.f, 0.f, 1.f, 0.f), XMFLOAT2(1.f, 0.f)},
{XMFLOAT4( 1.f, -1.f, 0.f, 1.f), XMFLOAT4(0.f, 0.f, 1.f, 0.f), XMFLOAT2(1.f, 1.f)},
{XMFLOAT4(-1.f, -1.f, 0.f, 1.f), XMFLOAT4(0.f, 0.f, 1.f, 0.f), XMFLOAT2(0.f, 1.f)},
};
hr = pVertexBuffer->Map(0, nullptr, &Mapped);
if (SUCCEEDED(hr)) {
CopyMemory(Mapped, Vertices, sizeof(Vertices));
pVertexBuffer->Unmap(0, nullptr);
Mapped = nullptr;
}else{
return -1;
}
VertexView.BufferLocation = pVertexBuffer->GetGPUVirtualAddress();
VertexView.StrideInBytes = sizeof(Vertex3D);
VertexView.SizeInBytes = sizeof(Vertices);
//インデックスデータをIndexBufferに書き込む
uint16_t Index[] = {0, 1, 3, 1, 2, 3};
hr = pIndexBuffer->Map(0, nullptr, &Mapped);
if (SUCCEEDED(hr)) {
CopyMemory(Mapped, Index, sizeof(Index));
pIndexBuffer->Unmap(0, nullptr);
Mapped = nullptr;
}else{
return -1;
}
IndexView.BufferLocation = pIndexBuffer->GetGPUVirtualAddress();
IndexView.SizeInBytes = sizeof(Index);
IndexView.Format = DXGI_FORMAT_R16_UINT;
return 0;
}
いや~長かった......
これでひとまずオブジェクトの生成は終了です.次から描画に入りたいと思います.
つづく
オブジェクトの作成だけで大変長くなってしまいました.今回はここで一旦終了したいと思います.次回は描画周りをやる予定です.そして何度も申し上げますが,DirectXをいじったことがない人が独学で調べたものなので,間違いを多く含む恐れがありますので,ご了承ください.
次回はこちら
だいぶ長い間記事を放置してしまいました……。
読み返してみると、自分自身全く理解していないまま書いたものなので色々説明足りなかったりコード間違っていたりしてますね^^;
時間が空いたらちゃんとしたものを書こうと思います。