上一篇我们将我们的图形接口从DX11升级到了DX12。但是依然只能画一个三角形。本篇我们继续完善我们的代码,让它能够绘制一个面包圈。
因为目前我们还没有文件读写的模块,为了让代码尽量简洁,我们采用实时生成的方式生成我们的面包圈顶点数据。这会需要一些线性空间的计算,所以我们重新加入之前画立方体用过的数学库。同时,我们定义了一个叫SimpleMesh的类,用来存储我们生成的模型数据。
#include "DirectXMath.h" #include "Mesh.h"
我们还需要申请更多的堆,用来存放我们的顶点数据。我们还将使用贴图,以及进行动画。因此我们也需要为贴图(包括采样器)和动画数据(常量)申请堆。我们首先增加保存这些配置信息的全局变量。
ComPtr<ID3D12DescriptorHeap> g_pDsvHeap; // an array of descriptors of GPU objects ComPtr<ID3D12DescriptorHeap> g_pCbvSrvHeap; // an array of descriptors of GPU objects ComPtr<ID3D12DescriptorHeap> g_pSamplerHeap; // an array of descriptors of GPU objects ComPtr<ID3D12Resource> g_pIndexBuffer; // the pointer to the vertex buffer D3D12_INDEX_BUFFER_VIEW g_IndexBufferView; // a view of the vertex buffer ComPtr<ID3D12Resource> g_pTextureBuffer; // the pointer to the texture buffer ComPtr<ID3D12Resource> g_pDepthStencilBuffer; // the pointer to the depth stencil buffer ComPtr<ID3D12Resource> g_pConstantUploadBuffer; // the pointer to the depth stencil buffer
创建3D模型数据
我们将引入光照,并根据物体表面法线进行局部光照计算。我们首先需要修改我们的顶点数据结构,使其包括法线和切线数据。同时我们要进行贴图,因此还需要位置保存贴图坐标(uv)
一个面包圈模型实际上是由一个绕Z轴旋转的向量和一个绕Y轴旋转的向量组合而成的。因此我们用一个双重循环来生成它。
struct SimpleMeshVertex
{
XMFLOAT3 m_position;
XMFLOAT3 m_normal;
XMFLOAT4 m_tangent;
XMFLOAT2 m_uv;
};
SimpleMesh torus;
void BuildTorusMesh(
float outerRadius, float innerRadius,
uint16_t outerQuads, uint16_t innerQuads,
float outerRepeats, float innerRepeats,
SimpleMesh* pDestMesh)
{
const uint32_t outerVertices = outerQuads + 1;
const uint32_t innerVertices = innerQuads + 1;
const uint32_t vertices = outerVertices * innerVertices;
const uint32_t numInnerQuadsFullStripes = 1;
const uint32_t innerQuadsLastStripe = 0;
const uint32_t triangles = 2 * outerQuads * innerQuads; // 2 triangles per quad
pDestMesh->m_vertexCount = vertices;
pDestMesh->m_vertexStride = sizeof(SimpleMeshVertex);
pDestMesh->m_vertexAttributeCount = kVertexElemCount;
pDestMesh->m_vertexBufferSize = pDestMesh->m_vertexCount * pDestMesh->m_vertexStride;
pDestMesh->m_indexCount = triangles * 3; // 3 vertices per triangle
pDestMesh->m_indexType = IndexSize::kIndexSize16; // whenever possible, use smaller index
// to save memory and enhance cache performance.
pDestMesh->m_primitiveType = PrimitiveType::kPrimitiveTypeTriList;
pDestMesh->m_indexBufferSize = pDestMesh->m_indexCount * sizeof(uint16_t);
// build vertices
pDestMesh->m_vertexBuffer = new uint8_t[pDestMesh->m_vertexBufferSize];
SimpleMeshVertex* outV = static_cast<SimpleMeshVertex*>(pDestMesh->m_vertexBuffer);
const XMFLOAT2 textureScale = XMFLOAT2(outerRepeats / (outerVertices - 1.0f), innerRepeats / (innerVertices - 1.0f));
for (uint32_t o = 0; o < outerVertices; ++o)
{
const float outerTheta = o * 2 * XM_PI / (outerVertices - 1);
const XMMATRIX outerToWorld = XMMatrixTranslation(outerRadius, 0, 0) * XMMatrixRotationZ(outerTheta);
for (uint32_t i = 0; i < innerVertices; ++i)
{
const float innerTheta = i * 2 * XM_PI / (innerVertices - 1);
const XMMATRIX innerToOuter = XMMatrixTranslation(innerRadius, 0, 0) * XMMatrixRotationY(innerTheta);
const XMMATRIX localToWorld = innerToOuter * outerToWorld;
XMVECTOR v = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
v = XMVector4Transform(v, localToWorld);
XMStoreFloat3(&outV->m_position, v);
v = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
v = XMVector4Transform(v, localToWorld);
XMStoreFloat3(&outV->m_normal, v);
v = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
v = XMVector4Transform(v, localToWorld);
XMStoreFloat4(&outV->m_tangent, v);
outV->m_uv.x = o * textureScale.x;
outV->m_uv.y = i * textureScale.y;
++outV;
}
}
// build indices
pDestMesh->m_indexBuffer = new uint8_t[pDestMesh->m_indexBufferSize];
uint16_t* outI = static_cast<uint16_t*>(pDestMesh->m_indexBuffer);
uint16_t const numInnerQuadsStripes = numInnerQuadsFullStripes + (innerQuadsLastStripe > 0 ? 1 : 0);
for (uint16_t iStripe = 0; iStripe < numInnerQuadsStripes; ++iStripe)
{
uint16_t const innerVertex0 = iStripe * innerQuads;
for (uint16_t o = 0; o < outerQuads; ++o)
{
for (uint16_t i = 0; i < innerQuads; ++i)
{
const uint16_t index[4] = {
static_cast<uint16_t>((o + 0) * innerVertices + innerVertex0 + (i + 0)),
static_cast<uint16_t>((o + 0) * innerVertices + innerVertex0 + (i + 1)),
static_cast<uint16_t>((o + 1) * innerVertices + innerVertex0 + (i + 0)),
static_cast<uint16_t>((o + 1) * innerVertices + innerVertex0 + (i + 1)),
};
outI[0] = index[0];
outI[1] = index[2];
outI[2] = index[1];
outI[3] = index[1];
outI[4] = index[2];
outI[5] = index[3];
outI += 6;
}
}
}
}
接下来,我们需要通知GPU我们全新的顶点数据结构。这是靠更新相关的DESCRIPTOR来实现的。
// create the input layout object
D3D12_INPUT_ELEMENT_DESC ied[] =
{
{"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},
{"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 40, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
};
我们还需要在我们申请的堆当中分出一块缓冲区,用来将顶点索引数据传给GPU。在DX12当中,这些缓冲区的创建过程都是极为类似的。
// create index buffer
{
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(torus.m_indexBufferSize),
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&g_pIndexBuffer)));
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(torus.m_indexBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&pIndexBufferUploadHeap)));
// Copy data to the intermediate upload heap and then schedule a copy
// from the upload heap to the vertex buffer.
D3D12_SUBRESOURCE_DATA indexData = {};
indexData.pData = torus.m_indexBuffer;
indexData.RowPitch = torus.m_indexType;
indexData.SlicePitch = indexData.RowPitch;
UpdateSubresources<1>(g_pCommandList.Get(), g_pIndexBuffer.Get(), pIndexBufferUploadHeap.Get(), 0, 0, 1, &indexData);
g_pCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(g_pIndexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER));
// initialize the vertex buffer view
g_IndexBufferView.BufferLocation = g_pIndexBuffer->GetGPUVirtualAddress();
g_IndexBufferView.Format = DXGI_FORMAT_R16_UINT;
g_IndexBufferView.SizeInBytes = torus.m_indexBufferSize;
}
最后,我们需要在录制绘图指令的时候,告诉GPU我们的顶点索引数据缓存区的地址。
g_pCommandList->IASetIndexBuffer(&g_IndexBufferView);
创建常量
为了让我们的模型动起来,我们需要创建常量,也就是MVP矩阵,以及光照数据。我们首先定义常量的结构。
struct SimpleConstants
{
XMFLOAT4X4 m_modelView;
XMFLOAT4X4 m_modelViewProjection;
XMFLOAT4 m_lightPosition;
XMFLOAT4 m_lightColor;
XMFLOAT4 m_ambientColor;
XMFLOAT4 m_lightAttenuation;
};
uint8_t* g_pCbvDataBegin = nullptr;
SimpleConstants g_ConstantBufferData;
然后我们定义一些全局变量,用来记住计算MVP所需要的几个重要的变换矩阵。并且写一个初始化函数,对其进行初始化。
XMMATRIX g_mWorldToViewMatrix;
XMMATRIX g_mViewToWorldMatrix;
XMMATRIX g_mLightToWorldMatrix;
XMMATRIX g_mProjectionMatrix;
XMMATRIX g_mViewProjectionMatrix;
void InitConstants() {
g_mViewToWorldMatrix = XMMatrixIdentity();
const XMVECTOR lightPositionX = XMVectorSet(-1.5f, 4.0f, 9.0f, 1.0f);
const XMVECTOR lightTargetX = XMVectorSet( 0.0f, 0.0f, 0.0f, 1.0f);
const XMVECTOR lightUpX = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f);
g_mLightToWorldMatrix = XMMatrixInverse(nullptr, XMMatrixLookAtRH(lightPositionX, lightTargetX, lightUpX));
const float g_depthNear = 1.0f;
const float g_depthFar = 100.0f;
const float aspect = static_cast<float>(nScreenWidth)/static_cast<float>(nScreenHeight);
g_mProjectionMatrix = XMMatrixPerspectiveOffCenterRH(-aspect, aspect, -1, 1, g_depthNear, g_depthFar);
const XMVECTOR eyePos = XMVectorSet( 0.0f, 0.0f, 2.5f, 1.0f);
const XMVECTOR lookAtPos = XMVectorSet( 0.0f, 0.0f, 0.0f, 1.0f);
const XMVECTOR upVec = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f);
g_mWorldToViewMatrix = XMMatrixLookAtRH(eyePos, lookAtPos, upVec);
g_mViewToWorldMatrix = XMMatrixInverse(nullptr, g_mWorldToViewMatrix);
g_mViewProjectionMatrix = g_mWorldToViewMatrix * g_mProjectionMatrix;
}
我们必须逐帧更新这些常量,也就是改变模型的位置,从而产生动画效果。
// this is the function used to update the constants
void Update()
{
const float rotationSpeed = XM_PI * 2.0 / 120;
static float rotationAngle = 0.0f;
rotationAngle += rotationSpeed;
if (rotationAngle >= XM_PI * 2.0) rotationAngle -= XM_PI * 2.0;
const XMMATRIX m = XMMatrixRotationRollPitchYaw(rotationAngle, rotationAngle, 0.0f);
XMStoreFloat4x4(&g_ConstantBufferData.m_modelView, XMMatrixTranspose(m * g_mWorldToViewMatrix));
XMStoreFloat4x4(&g_ConstantBufferData.m_modelViewProjection, XMMatrixTranspose(m * g_mViewProjectionMatrix));
XMVECTOR v = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
v = XMVector4Transform(v, g_mLightToWorldMatrix);
v = XMVector4Transform(v, g_mWorldToViewMatrix);
XMStoreFloat4(&g_ConstantBufferData.m_lightPosition, v);
g_ConstantBufferData.m_lightColor = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
g_ConstantBufferData.m_ambientColor = XMFLOAT4(0.0f, 0.0f, 0.7f, 1.0f);
g_ConstantBufferData.m_lightAttenuation = XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f);
memcpy(g_pCbvDataBegin, &g_ConstantBufferData, sizeof(g_ConstantBufferData));
}
同样的,我们需要创建一块CPU和GPU都能看到的缓冲区,用来向GPU传递这个常量。
// Create the constant buffer.
{
size_t sizeConstantBuffer = (sizeof(SimpleConstants) + 255) & ~255; // CB size is required to be 256-byte aligned.
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(sizeConstantBuffer),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&g_pConstantUploadBuffer)));
for (uint32_t i = 0; i < nFrameCount; i++)
{
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvHandle(g_pCbvSrvHeap->GetCPUDescriptorHandleForHeapStart(), i + 1, g_nCbvSrvDescriptorSize);
// Describe and create a constant buffer view.
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = g_pConstantUploadBuffer->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = sizeConstantBuffer;
g_pDev->CreateConstantBufferView(&cbvDesc, cbvHandle);
}
// Map and initialize the constant buffer. We don't unmap this until the
// app closes. Keeping things mapped for the lifetime of the resource is okay.
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
ThrowIfFailed(g_pConstantUploadBuffer->Map(0, &readRange, reinterpret_cast<void**>(&g_pCbvDataBegin)));
}
// 1 SRV + how many CBVs we have
uint32_t nFrameResourceDescriptorOffset = 1 + g_nFrameIndex;
CD3DX12_GPU_DESCRIPTOR_HANDLE cbvSrvHandle(g_pCbvSrvHeap->GetGPUDescriptorHandleForHeapStart(), nFrameResourceDescriptorOffset, g_nCbvSrvDescriptorSize);
g_pCommandList->SetGraphicsRootDescriptorTable(2, cbvSrvHandle);
创建贴图
接下来让我们创建贴图。我们这里创建一个512×512的国际象棋棋盘格的基本贴图。
const uint32_t nTextureWidth = 512;
const uint32_t nTextureHeight = 512;
const uint32_t nTexturePixelSize = 4; // R8G8B8A8
// Generate a simple black and white checkerboard texture.
uint8_t* GenerateTextureData()
{
const uint32_t nRowPitch = nTextureWidth * nTexturePixelSize;
const uint32_t nCellPitch = nRowPitch >> 3; // The width of a cell in the checkboard texture.
const uint32_t nCellHeight = nTextureWidth >> 3; // The height of a cell in the checkerboard texture.
const uint32_t nTextureSize = nRowPitch * nTextureHeight;
uint8_t* pData = new uint8_t[nTextureSize];
for (uint32_t n = 0; n < nTextureSize; n += nTexturePixelSize)
{
uint32_t x = n % nRowPitch;
uint32_t y = n / nRowPitch;
uint32_t i = x / nCellPitch;
uint32_t j = y / nCellHeight;
if (i % 2 == j % 2)
{
pData[n] = 0x00; // R
pData[n + 1] = 0x00; // G
pData[n + 2] = 0x00; // B
pData[n + 3] = 0xff; // A
}
else
{
pData[n] = 0xff; // R
pData[n + 1] = 0xff; // G
pData[n + 2] = 0xff; // B
pData[n + 3] = 0xff; // A
}
}
return pData;
}
然后依然是在堆上创建缓冲区,用来传递贴图给GPU。
// Generate the texture
uint8_t* pTextureData = GenerateTextureData();
// Create the texture and sampler.
{
// Describe and create a Texture2D.
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = nTextureWidth;
textureDesc.Height = nTextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&g_pTextureBuffer)));
const UINT subresourceCount = textureDesc.DepthOrArraySize * textureDesc.MipLevels;
const UINT64 uploadBufferSize = GetRequiredIntermediateSize(g_pTextureBuffer.Get(), 0, subresourceCount);
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&pTextureUploadHeap)));
// Copy data to the intermediate upload heap and then schedule a copy
// from the upload heap to the Texture2D.
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = pTextureData;
textureData.RowPitch = nTextureWidth * nTexturePixelSize;
textureData.SlicePitch = textureData.RowPitch * nTextureHeight;
UpdateSubresources(g_pCommandList.Get(), g_pTextureBuffer.Get(), pTextureUploadHeap.Get(), 0, 0, subresourceCount, &textureData);
g_pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(g_pTextureBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
// Describe and create a sampler.
D3D12_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
g_pDev->CreateSampler(&samplerDesc, g_pSamplerHeap->GetCPUDescriptorHandleForHeapStart());
// Describe and create a SRV for the texture.
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(g_pCbvSrvHeap->GetCPUDescriptorHandleForHeapStart());
g_pDev->CreateShaderResourceView(g_pTextureBuffer.Get(), &srvDesc, srvHandle);
}
更新Root Signature
Root Signature是DX12新增的概念。其实就好比我们代码头文件里面的函数申明,是向DX介绍我们资源的整体结构。因为我们增加了贴图和常量这两种类型的新资源,我们需要更新它。
CD3DX12_DESCRIPTOR_RANGE1 ranges[3];
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
ranges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 6, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
CD3DX12_ROOT_PARAMETER1 rootParameters[3];
rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[2].InitAsDescriptorTable(1, &ranges[2], D3D12_SHADER_VISIBILITY_ALL);
为新增资源类型创建Heap
所有的图形资源缓冲区都需要在堆里面创建。每种资源对于堆的管理(包括内存读写保护,可执行标志,以及访问它的GPU模块)的要求不同,我们需要分别为他们申请堆。(贴图和常量是可以共用一个堆的)
// Describe and create a depth stencil view (DSV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(g_pDev->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&g_pDsvHeap)));
// Describe and create a shader resource view (SRV) and constant
// buffer view (CBV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC cbvSrvHeapDesc = {};
cbvSrvHeapDesc.NumDescriptors =
nFrameCount // FrameCount Cbvs.
+ 1; // + 1 for the Srv(Texture).
cbvSrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvSrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(g_pDev->CreateDescriptorHeap(&cbvSrvHeapDesc, IID_PPV_ARGS(&g_pCbvSrvHeap)));
// Describe and create a sampler descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc = {};
samplerHeapDesc.NumDescriptors = 1;
samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(g_pDev->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&g_pSamplerHeap)));
g_nCbvSrvDescriptorSize = g_pDev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
ThrowIfFailed(g_pDev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_pCommandAllocator)));
创建深度/模板缓冲区
我们需要打开深度测试来完成图形的隐藏面消隐。
// Create the depth stencil view.
{
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;
ThrowIfFailed(g_pDev->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, nScreenWidth, nScreenHeight, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL),
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue,
IID_PPV_ARGS(&g_pDepthStencilBuffer)
));
g_pDev->CreateDepthStencilView(g_pDepthStencilBuffer.Get(), &depthStencilDesc, g_pDsvHeap->GetCPUDescriptorHandleForHeapStart());
}
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_pRtvHeap->GetCPUDescriptorHandleForHeapStart(), g_nFrameIndex, g_nRtvDescriptorSize);
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(g_pDsvHeap->GetCPUDescriptorHandleForHeapStart());
g_pCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
g_pCommandList->ClearDepthStencilView(g_pDsvHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
绘图
最后,更新我们的绘图指令,让其根据索引顺序进行绘制,而不是顶点缓冲区当中的顶点顺序。
g_pCommandList->DrawIndexedInstanced(torus.m_indexCount, 1, 0, 0, 0);
Shader
由于我们改变了顶点数据结构,并且引进了光照,我们需要修改我们的Shader。为了方便,我们可以为VS Shader和PS Shader定义不同的入口函数(而不是缺省的main),这样就可以把两个Shader的代码放在一个文件当中。
让我们新创建一个simple.hlsl的文件,输入如下代码:
#include "cbuffer2.h"
#include "vsoutput2.hs"
#include "illum.hs"
v2p VSMain(a2v input) {
v2p output;
output.Position = mul(float4(input.Position.xyz, 1), m_modelViewProjection);
float3 vN = normalize(mul(float4(input.Normal, 0), m_modelView).xyz);
float3 vT = normalize(mul(float4(input.Tangent.xyz, 0), m_modelView).xyz);
output.vPosInView = mul(float4(input.Position.xyz, 1), m_modelView).xyz;
output.vNorm = vN;
output.vTang = float4(vT, input.Tangent.w);
output.TextureUV = input.TextureUV;
return output;
}
SamplerState samp0 : register(s0);
Texture2D colorMap : register(t0);
float4 PSMain(v2p input) : SV_TARGET
{
float3 lightRgb = m_lightColor.xyz;
float4 lightAtten = m_lightAttenuation;
float3 ambientRgb = m_ambientColor.rgb;
float specPow = 30;
const float3 vN = input.vNorm;
const float3 vT = input.vTang.xyz;
const float3 vB = input.vTang.w * cross(vN, vT);
float3 vL = m_lightPosition.xyz - input.vPosInView;
const float3 vV = normalize(float3(0,0,0) - input.vPosInView);
float d = length(vL); vL = normalize(vL);
float attenuation = saturate(1.0f/(lightAtten.x + lightAtten.y * d + lightAtten.z * d * d) - lightAtten.w);
float4 normalGloss = { 1.0f, 0.2f, 0.2f, 0.0f };
normalGloss.xyz = normalGloss.xyz * 2.0f - 1.0f;
normalGloss.y = -normalGloss.y; // normal map has green channel inverted
float3 vBumpNorm = normalize(normalGloss.x * vT + normalGloss.y * vB + normalGloss.z * vN);
float3 vGeomNorm = normalize(vN);
float3 diff_col = colorMap.Sample(samp0, input.TextureUV.xy).xyz;
float3 spec_col = 0.4 * normalGloss.w + 0.1;
float3 vLightInts = attenuation * lightRgb * BRDF2_ts_nphong_nofr(vBumpNorm, vGeomNorm, vL, vV, diff_col, spec_col, specPow);
vLightInts += (diff_col * ambientRgb);
return float4(vLightInts, 1);
}
因为我们修改了入口函数,我们需要通知DX这个修改。具体是在我们代码当中动态Compile Shader的地方:
D3DCompileFromFile(
L"simple.hlsl",
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
"VSMain",
"vs_5_0",
compileFlags,
0,
&vertexShader,
&error);
if (error) { OutputDebugString((LPCTSTR)error->GetBufferPointer()); error->Release(); throw std::exception(); }
D3DCompileFromFile(
L"simple.hlsl",
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
"PSMain",
"ps_5_0",
compileFlags,
0,
&pixelShader,
&error);
if (error) { OutputDebugString((LPCTSTR)error->GetBufferPointer()); error->Release(); throw std::exception(); }
如果Shader的代码编译有问题,编译错误会通过OutputDebugString进行输出。这个输出在用Visual Studio调试的时候可以看到。
编译
编译命令如下:
调试版
D:wenliSourceReposGameEngineFromScratchPlatformWindows>cl /EHsc /Debug /Zi /D_DEBUG_SHADER -I./DirectXMath helloengine_d3d12.cpp user32.lib d3d12.lib d3dcompiler.lib dxgi.lib
执行版
D:wenliSourceReposGameEngineFromScratchPlatformWindows>cl /EHsc -I./DirectXMath helloengine_d3d12.cpp user32.lib d3d12.lib d3dcompiler.lib dxgi.lib
(本文完整代码在GitHub的branch article_16当中)
参考引用

本作品采用知识共享署名 4.0 国际许可协议进行许可。