d3d12龍書閱讀----繪製幾何體(上)

dyccyber發表於2024-03-27

d3d12龍書閱讀----繪製幾何體(上)

本節主要介紹了構建一個簡單的彩色立方體所需流程與重要的api
下面主要結合立方體程式碼分析本節相關知識

頂點

輸入裝配器階段的輸入
首先,我們需要定義立方體的八個頂點
頂點結構體:

struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

當然,對於更復雜的情況,我們不僅要定義頂點的位置與顏色,還要包括法線向量、紋理x座標、紋理y座標等等
但在這裡情形比較簡單
之後,我們還需要定義一個頂點結構體描述子陣列,被稱為輸入佈局描述
陣列中的每個成員與頂點結構體的成員一一對應,同時也與頂點著色器中的引數對應:

std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

mInputLayout =
{
    { "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 }
};

//頂點著色器
struct VertexIn
{
	float3 PosL  : POSITION;
    float4 Color : COLOR;
};

D3D12_INPUT_ELEMENT_DESC的定義與引數說明可見:
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/ns-d3d12-d3d12_input_element_desc
接著,我們還需要為頂點建立頂點緩衝區,與第四章內容建立深度緩衝區的步驟相似,我們首先要填寫D3D12_RESOURCE_DESC結構體描述緩衝區資源,然後使用CreateCommittedResource 方法,建立資源與一個堆,並把資源上傳到堆中。

CreateCommittedResource 方法的引數說明可見:
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource
其中有三個引數在本節中很重要
一個是D3D12_HEAP_PROPERTIES *pHeapProperties
一個是D3D12_RESOURCE_DESC *pDesc
一個是D3D12_RESOURCE_STATES

D3D12_RESOURCE_STATES代表著資源狀態
在d3d的初始化中我們提到這樣可以防止資源冒險 比如在讀的狀態在寫資源等等
詳細的資源種類可見:
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/ne-d3d12-d3d12_resource_states
D3D12_HEAP_PROPERTIES是一個結構體:

img
其中D3D12_HEAP_TYPE的型別主要有以下幾種:

img

D3D12_RESOURCE_DESC 與 D3D12_HEAP_PROPERTIES的建立 這裡分別借用了CD3DX12_HEAP_PROPERTIES 與 CD3DX12_RESOURCE_DESC兩種變體方法來簡化緩衝區的建立過程:

ThrowIfFailed(device->CreateCommittedResource(
    //預設堆 
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
    D3D12_HEAP_FLAG_NONE,
    //bytesize 代表緩衝區所佔位元組數
    &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
    //common狀態
	D3D12_RESOURCE_STATE_COMMON,
    nullptr,
    IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

讓我們回到建立頂點緩衝區上來,當我們想要為樹木、地形等預設幾何體(每一幀都不會發生變化的結合體)來建立頂點緩衝區時,常常選擇預設堆來最佳化效能,當頂點緩衝區初始化完畢後,只有gpu需要從中讀取資料來繪製幾何體。但是在初始化緩衝區時,需要cpu向預設堆中的頂點緩衝區寫入資料,這是我們就需要一個上傳堆作為中介,為此本節編寫了CreateDefaultBuffer函式:

Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(
    ID3D12Device* device,
    ID3D12GraphicsCommandList* cmdList,
    const void* initData,
    UINT64 byteSize,
    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
    //建立緩衝區資源
    ComPtr<ID3D12Resource> defaultBuffer;
    ThrowIfFailed(device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
		D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

    //建立上傳堆 作為中介
    ThrowIfFailed(device->CreateCommittedResource(
        //上傳堆
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
		D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
        //上傳堆所需要的啟動狀態
		D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(uploadBuffer.GetAddressOf())));


    // 描述我們要傳入預設堆的資料
    D3D12_SUBRESOURCE_DATA subResourceData = {};
    subResourceData.pData = initData;
    subResourceData.RowPitch = byteSize;
    subResourceData.SlicePitch = subResourceData.RowPitch;

    //轉換資源狀態  將資料複製給上傳堆 上傳堆再複製到預設堆
	cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), 
		D3D12_RESOURCE_STATE_COMMON, 
        //資源處於複製目標狀態
        D3D12_RESOURCE_STATE_COPY_DEST));
    UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
	cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
		D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));
    return defaultBuffer;
}

整個建立頂點緩衝區的流程如下:
img

然後我們還需要為其建立檢視(無需為其建立描述符堆) 以及將其繫結到渲染流水線上的輸入槽,這樣就可以向輸入裝配器傳入頂點資料:

D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const
{
	D3D12_VERTEX_BUFFER_VIEW vbv;
    //虛擬地址 使用函式即可獲得
	vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
    //頂點緩衝區所佔位元組大小
	vbv.StrideInBytes = VertexByteStride;
    //每個頂點資料所佔位元組大小
	vbv.SizeInBytes = VertexBufferByteSize;

	return vbv;
}
//0 代表繫結第0個輸入槽 共有16個
//1 代表頂點緩衝區的數量為1
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());

最後繪製頂點:

定義圖元拓撲型別
 mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

img

索引

索引緩衝區的建立過程和頂點的過程很類似:

定義索引
std::array<std::uint16_t, 36> indices =
{
	// 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
};
//索引緩衝區大小
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
//定義預設堆 與 上傳堆
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
//初始化索引緩衝區
mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
	mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);
//建立檢視 繫結到渲染流水線
D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
{
	D3D12_INDEX_BUFFER_VIEW ibv;
	ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
	ibv.Format = IndexFormat;
	ibv.SizeInBytes = IndexBufferByteSize;

	return ibv;
}
mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
//繪製頂點
mCommandList->DrawIndexedInstanced(
		mBoxGeo->DrawArgs["box"].IndexCount, 
		1, 0, 0, 0);

注意在上述過程中我們採用索引來繪製頂點 而不是像上一部分那樣使用DrawInstanced 引數解釋如下:
img

頂點著色器

頂點著色器程式碼如下

//cbuffer 代表常量緩衝區 b0儲存資源的暫存器
cbuffer cbPerObject : register(b0)
{
    //從區域性空間轉換到齊次裁剪空間
	float4x4 gWorldViewProj; 
};

//頂點著色器輸入 
//冒號後面的是引數語義
//要和之前提到的輸入佈局描述對應 同時也要與頂點著色器的輸入引數對應
//冒號簽名的是自定義的資料成員的名稱 叫做輸入簽名
struct VertexIn
{
	float3 PosL  : POSITION;
    float4 Color : COLOR;
};
//頂點著色器輸出 語義作為下一步幾何著色器或者畫素著色器的輸入引數
struct VertexOut
{
	float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};

VertexOut VS(VertexIn vin)
{
	VertexOut vout;
	
	//轉換到齊次裁剪空間
    //mul 有向量矩陣 或者矩陣矩陣乘法的多個過載版本
    //透視除法步驟是交由硬體處理 人為無需編寫程式碼
	vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
	
	// 直接將輸入顏色傳遞給畫素著色器
    vout.Color = vin.Color;
    
    return vout;
}

不同暫存器儲存不同型別資源如下:
img
由於使用的著色器語言 HLSL沒有 引用或者指標 所以返回多條資料 可以使用結構體的形式 在HLSL中所有函式都是內聯的

注意上述程式碼的語義都是特定的 比如SV_POSITION就代表著儲存著齊次裁剪空間的頂點位置資訊 其餘語義說明可見:
https://learn.microsoft.com/zh-cn/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics

還有一個地方注意的是 頂點著色器中使用的資料必須要都在之前的頂點結構體中定義(當然還有輸入佈局描述)但是我們定義的頂點結構體資料可以更多 必須是一個包含關係

畫素著色器

對頂點著色器輸出的資料 進行插值 在不使用幾何著色器的情況下 插值的結果作為畫素著色器的輸入
這裡還強調了一下pixel fragment 與 pixel的區別 畫素著色器的輸入是畫素片段 而畫素是已經透過深度測試 模版測試等等 最終繪製到螢幕上去的畫素
d3d還提到 由於硬體最佳化的原因 有些畫素片段 進行early-z之後就已經被篩除 但是有可能畫素著色器中對畫素片段的深度值進行了改變 此時就不能進行early-z 因為畫素片段的最終深度值尚未確定

本節的畫素著色器的程式碼很簡單,直接輸出顏色:
函式引數列表之後的SV_Target語義表示 輸出的格式應該與渲染目標的格式相匹配

float4 PS(VertexOut pin) : SV_Target
{
    return pin.Color;
}

著色器編譯

ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
mvsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mpsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");

ComPtr<ID3DBlob> d3dUtil::CompileShader(
	const std::wstring& filename,
	const D3D_SHADER_MACRO* defines,
	const std::string& entrypoint,
	const std::string& target)
{
	UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)  
	compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

	HRESULT hr = S_OK;

	ComPtr<ID3DBlob> byteCode = nullptr;
	ComPtr<ID3DBlob> errors;
	hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
		entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);

	if(errors != nullptr)
		OutputDebugStringA((char*)errors->GetBufferPointer());

	ThrowIfFailed(hr);

	return byteCode;
}

其中比較重要的引數有
檔名 比如:L"Shaders\color.hlsl" 這裡的型別是wstring 因此要使用L
著色器的入口點 VS/PS
著色器版本 vs_5_0等等
img
這裡簡要介紹了一下ID3DBlob這個型別:
img
我在知乎看到一個回答介紹的更為詳細:
https://zhuanlan.zhihu.com/p/304352552
下面引用如下

Blob(binary large object),二進位制大物件。ID3DBlob則是DX12內建的一種存放較為龐大的二進位制物件。在GPU上面,我們對於大部分資源的描述一般都是用地址起點(address starting point)加上物件記憶體容量(object memory)來描述並且確定某一物件資源
因為其資源記憶體容量較為龐大的特點,這些資源大多數都不能直接上傳到GPU,而是首先在CPU預處理成Blob,然後再上傳繫結到GPU上面,才能供GPU使用
上傳的物件包括但不限於頂點資料(Vertex data),索引資料(Index data),材質(Texture)等,還包括我們著色器程式(shader)。即我們寫的HLSL(high level shader language)程式,需要在CPU端透過預處理和編譯才能上傳到GPU端供GPU讀取並且執行

常量緩衝區

常量緩衝區也是一種GPU資源(ID3D12Resource),但是常量緩衝區是CPU每幀都要更新一次,比如攝像機如果每幀都在移動,那麼常量緩衝區每幀都需要更新其中的檢視矩陣,所以我們需要將常量緩衝區建立到一個上傳堆而非預設堆,這樣我們就可以從cpu端更新常量。

下面讓我們來看看示例程式中是如何建立常量緩衝區的
首先,定義常量緩衝區結構體:

struct ObjectConstants
{
    XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};

我們可以看到目前裡面只定義了檢視矩陣

其次,定義了上傳緩衝區的輔助類UploadBuffer.h
注意該輔助類主要用於需要提交到上傳堆的gpu資源,而我們之前有一個用於建立預設堆的輔助函式:

template<typename T>
class UploadBuffer
{
public:
    //引數說明 
	//elementCount表示ObjectConstants的數量
	//isConstantBuffer表示是否為要建立常量緩衝區
    UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : 
        mIsConstantBuffer(isConstantBuffer)
    {
        mElementByteSize = sizeof(T);
        
		//如果為常量緩衝區,重新計算ObjectConstants結構體的大小
        if(isConstantBuffer)
            mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));
        //建立gpu資源(常量緩衝區) 與 一個上傳堆 並把資源提交到堆上
        ThrowIfFailed(device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),
			D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&mUploadBuffer)));
        
		//使用map方法,在cpu端分配一塊虛擬地址範圍,用來對映gpu的資源
        ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));

    }

    UploadBuffer(const UploadBuffer& rhs) = delete;
    UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
    ~UploadBuffer()
    {
		//呼叫unmap取消對gpu資源的對映
        if(mUploadBuffer != nullptr)
            mUploadBuffer->Unmap(0, nullptr);

        mMappedData = nullptr;
    }
    //獲取gpu資源
    ID3D12Resource* Resource()const
    {
        return mUploadBuffer.Get();
    }
    //從cpu端更新常量緩衝區中的內容
    void CopyData(int elementIndex, const T& data)
    {
        memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));
    }

private:
    Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
    BYTE* mMappedData = nullptr;
    UINT mElementByteSize = 0;
    bool mIsConstantBuffer = false;
};

建立常量緩衝區 我們可以使用如下程式碼:

std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
定義常量緩衝區儲存的是ObjectConstants型別資料 數量為1
mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

上述程式碼中 我們可以看到UploadBuffer這個類是使用了模版 這意味著該方法不僅可以建立常量緩衝區資源 也可以建立其它使用上傳堆的gpu資源

同時上述程式碼中在獲取ObjectConstants的大小時,我們可以看到使用了d3dUtil::CalcConstantBufferByteSize的方法,該方法程式碼如下:

static UINT CalcConstantBufferByteSize(UINT byteSize)
{
    // Example: Suppose byteSize = 300.
    // (300 + 255) & ~255
    // 555 & ~255
    // 0x022B & ~0x00ff
    // 0x022B & 0xff00
    // 0x0200
    // 512
    return (byteSize + 255) & ~255;
}

這是因為常量緩衝區的大小必須是硬體最小分配空間的整數倍(通常是256b) 這是因為硬體只能按照這樣的規格來檢視常量資料,所以要對常量緩衝區的陣列進行填充位元組

然後,我們還需要建立相應的描述符來將資源繫結到渲染流水線上,和之前頂點緩衝區描述符以及索引不同,我們要為常量緩衝區描述符建立描述堆,然後再建立描述符:

//建立cbv描述符堆
void BoxApp::BuildDescriptorHeaps()
{
    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;
	cbvHeapDesc.NodeMask = 0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
        IID_PPV_ARGS(&mCbvHeap)));
}

//計算第i個物體ObjectConstants的起始記憶體位置 與大小
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
int boxCBufIndex = 0;
cbAddress += boxCBufIndex*objCBByteSize;

//填寫描述符 建立檢視
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(
	&cbvDesc,
	mCbvHeap->GetCPUDescriptorHandleForHeapStart());

根簽名與描述符表

根簽名的作用是,定義繫結到渲染流水線上的資源,與對應的著色器的輸入暫存器的對映關係,從而可以被著色器程式訪問。
不同的繪製呼叫可能用到一組不同的著色器程式,這就意味著用到不同的根簽名。
在d3d中,根簽名使用ID3DRootSignature介面來表示,並且由一組描述繪製呼叫過程中著色器所需資源的根引數定義而成
根引數可以是根常量、根描述符或者描述符表。在本章中,我們只是簡要了解根簽名,詳細的介紹將在下一章中展開,本章只使用了描述符表,即描述符堆中存有描述符的一塊連續區域
下面根據程式碼簡要分析:

void BoxApp::BuildRootSignature()
{

	// 根引數
	CD3DX12_ROOT_PARAMETER slotRootParameter[1];

	// 建立一個cbv的描述符表
	CD3DX12_DESCRIPTOR_RANGE cbvTable;
	cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 
    1, //描述符數量
    0 //繫結到b0暫存器);
	slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);

	// 根簽名由一組根引數構成
	CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, 
		D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

	// 建立根簽名  必須要先將根簽名的描述佈局透過ID3DBlob序列化才能傳入建立根簽名的方法
	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
		serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

	if(errorBlob != nullptr)
	{
		::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(md3dDevice->CreateRootSignature(
		0,
		serializedRootSig->GetBufferPointer(),
		serializedRootSig->GetBufferSize(),
		IID_PPV_ARGS(&mRootSignature)));
}

然後還要透過命令列表設定cbv堆與根簽名,再透過設定描述符表繫結資源:

ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

一些關於根簽名的注意事項:
img

配置光柵器狀態與流水線狀態物件

大多數控制圖形流水線狀態物件被統稱為流水線狀態物件PSO,用介面ID3D12PipelineState表示
建立其的程式碼如下:

void BoxApp::BuildPSO()
{
    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
    ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
    //繫結輸入佈局
    psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
    //根簽名
    psoDesc.pRootSignature = mRootSignature.Get();
    //頂點著色器
    psoDesc.VS = 
	{ 
		reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()), 
		mvsByteCode->GetBufferSize() 
	};
    //畫素著色器
    psoDesc.PS = 
	{ 
		reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()), 
		mpsByteCode->GetBufferSize() 
	};
    //填寫光柵器狀態 這裡使用預設值建立
    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] = mBackBufferFormat;
    psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
    psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
    psoDesc.DSVFormat = mDepthStencilFormat;
    ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

描述符的詳細屬性可檢視微軟文件

相關文章