上一篇我们将我们的图形接口从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 国际许可协议进行许可。