从零开始手敲次世代游戏引擎(十六)

上一篇我们将我们的图形接口从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轴旋转的向量组合而成的。因此我们用一个双重循环来生成它。

Torus – Wikipedia

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当中)

参考引用

  1. Torus – Wikipedia
  2. Microsoft/DirectX-Graphics-Samples

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

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s

%d 博主赞过: