Direct3D 12 尝鲜(四): 旋转的彩色立方体

(转载请注明出处)

有点时间没更新了,原因是找了一个多星期的bug结果发现是少了一次加法运算。。。。
Direct3D 12 尝鲜(四): 旋转的彩色立方体_第1张图片
不过反正没人看也就没影响啦!_(:3」∠)_


这次的目的是做一个旋转的彩色立方体:
Direct3D 12 尝鲜(四): 旋转的彩色立方体_第2张图片

这是自己在学D3D11的时候做过的一个例子,现在搬到D3D12上, 这次增加的内容有:

  • 常量缓存
  • 深度缓存
  • 顶点/索引缓存

    1. 常量缓存, 常量缓存可以放一些GPU只读的数据, 这里就是存放的是转换矩阵,D3D12中,常量缓存是以256字节对齐的(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT), 所以请注意.
      这样可以创建一个常量缓存并创建CBV绑定到一个DESCRIPTOR上
    // 创建常量缓存
    if (SUCCEEDED(hr)) {
        hr = m_pd3dDevice->CreateCommittedResource(
            &CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_MISC_NONE,
            &CD3D12_RESOURCE_DESC::Buffer(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT),
            D3D12_RESOURCE_USAGE_GENERIC_READ,
            nullptr,
            IID_ID3D12Resource,
            reinterpret_cast<void**>(&m_pCBufferMatrix)
            );

    }
    // 绑定到常量缓存视图
    if (SUCCEEDED(hr)) {
        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
        cbvDesc.BufferLocation = m_pCBufferMatrix->GetGPUVirtualAddress();
        cbvDesc.SizeInBytes = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT;
        m_pd3dDevice->CreateConstantBufferView(
            &cbvDesc,
            m_aCpuHandleCSU[CSU_MatrixCBuffer]
            );
    }

这部分代码中, 使用D3D12_HEAP_TYPE_UPLOAD是因为我们每帧都要改写,这样比较方便, 相应的, 效率可能就会损失点.
还有就是之前提到的把所有相同的DESCRIPTOR集中放到一起, 所以写了个简单的框架, 使用m_aCpuHandleXXX数组存放的.
我们在这里存放3个矩阵: 世界、视角、透视转换矩阵
2. 深度缓存, 深度缓存的作用就不用说了, 灵活使用可以获取到不错的效果, 创建DSV绑定到DESCRIPTOR. 这里不使用模板, 仅仅使用深度:

    // 创建深度缓存
    if (SUCCEEDED(hr)) {
        D3D12_RESOURCE_DESC resourceDesc = CD3D12_RESOURCE_DESC::Tex2D(
            DXGI_FORMAT_R32_TYPELESS, m_uBufferWidth, m_uBufferHeight,
            1, 1, 1, 0, 
            D3D12_RESOURCE_MISC_ALLOW_DEPTH_STENCIL,
            D3D12_TEXTURE_LAYOUT_UNKNOWN, 0
            );
        D3D12_CLEAR_VALUE dsvClearValue;
        dsvClearValue.Format = DXGI_FORMAT_D32_FLOAT;
        dsvClearValue.DepthStencil.Depth = 1.0f;
        dsvClearValue.DepthStencil.Stencil = 0;
        hr = m_pd3dDevice->CreateCommittedResource(
            &CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
            D3D12_HEAP_MISC_NONE,
            &resourceDesc,
            D3D12_RESOURCE_USAGE_DEPTH,
            &dsvClearValue,
            IID_ID3D12Resource,
            reinterpret_cast<void**>(&m_pDepthBuffer)
            );
    }
    // 绑定深度缓存到DSV
    if (SUCCEEDED(hr)) {
        D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
        dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
        dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
        dsvDesc.Texture2D.MipSlice = 0;
        dsvDesc.Flags = D3D12_DSV_NONE;
        /*no return*/m_pd3dDevice->CreateDepthStencilView(
            m_pDepthBuffer, &dsvDesc,
            m_aCpuHandleDSV[DSV_MainDSV]
            );
    }

感觉没什么可以说的, D3D12提供的Helper很方便, 默认值就行,详细的可以查看官方文档.
3. 顶点/索引缓存, 渲染一个立方体所需要的东西, 目前我们需要: 顶点颜色与顶点坐标, 索引就是三角索引了

// 创建带颜色的正方体
auto SceneRenderer::CreateColoredCube(
    ID3D12Resource*& vibuffer,
    D3D12_VERTEX_BUFFER_VIEW& vbuffer_view,
    D3D12_INDEX_BUFFER_VIEW& ibuffer_view) noexcept -> HRESULT {
    HRESULT hr = S_OK;
    ID3D12Resource* pVIBuffer = nullptr;
    // 立方体的8个顶点 与相应颜色
    VertexColor vertices[] = {
            { DirectX::XMFLOAT3(-1.f, -1.f, -1.f), DirectX::XMFLOAT4(0.f, 0.f, 0.f, 1.f) },
            { DirectX::XMFLOAT3(-1.f,  1.f, -1.f), DirectX::XMFLOAT4(1.f, 0.f, 0.f, 1.f) },
            { DirectX::XMFLOAT3(1.f,  1.f, -1.f), DirectX::XMFLOAT4(0.f, 1.f, 0.f, 1.f) },
            { DirectX::XMFLOAT3(1.f, -1.f, -1.f), DirectX::XMFLOAT4(0.f, 0.f, 1.f, 1.f) },
            { DirectX::XMFLOAT3(-1.f, -1.f,  1.f), DirectX::XMFLOAT4(0.f, 1.f, 1.f, 1.f) },
            { DirectX::XMFLOAT3(-1.f,  1.f,  1.f), DirectX::XMFLOAT4(1.f, 1.f, 0.f, 1.f) },
            { DirectX::XMFLOAT3(1.f,  1.f,  1.f), DirectX::XMFLOAT4(1.f, 0.f, 1.f, 1.f) },
            { DirectX::XMFLOAT3(1.f, -1.f,  1.f), DirectX::XMFLOAT4(1.f, 1.f, 1.f, 1.f) }
    };
    // 立方体 6个面 12个三角面 36个顶点
    uint16_t indices[] = {
        0, 1, 2, 0, 2, 3,
        4, 5, 1, 4, 1, 0,
        7, 6, 5, 7, 5, 4,
        3, 2, 6, 3, 6, 7,
        1, 5, 6, 1, 6, 2,
        4, 0, 3, 4, 3, 7
    };

    // 创建顶点缓存-索引缓存共用缓冲区
    if (SUCCEEDED(hr)) {
        hr = m_pd3dDevice->CreateCommittedResource(
            &CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_MISC_NONE,
            &CD3D12_RESOURCE_DESC::Buffer(sizeof(vertices)+sizeof(indices)),
            D3D12_RESOURCE_USAGE_GENERIC_READ,
            nullptr,
            IID_ID3D12Resource,
            reinterpret_cast<void**>(&pVIBuffer)
            );
    }
    // 映射
    void* buffer = nullptr;
    if (SUCCEEDED(hr)) {
        hr = pVIBuffer->Map(0, nullptr, &buffer);
    }
    // 复制-取消映射-设置
    if (SUCCEEDED(hr)) {
        ::memcpy(buffer, vertices, sizeof(vertices));
        ::memcpy(
            reinterpret_cast(buffer)+ sizeof(vertices), 
            indices, sizeof(indices)
            );
        pVIBuffer->Unmap(0, nullptr);
        // 设置
        vbuffer_view.BufferLocation = pVIBuffer->GetGPUVirtualAddress();
        vbuffer_view.StrideInBytes = sizeof(VertexColor);
        vbuffer_view.SizeInBytes = sizeof(vertices);
        ibuffer_view.BufferLocation = vbuffer_view.BufferLocation + sizeof(vertices);
        ibuffer_view.Format = DXGI_FORMAT_R16_UINT;
        ibuffer_view.SizeInBytes = sizeof(indices);
        vibuffer = ::SafeAcquire(pVIBuffer);
    }
    ::SafeRelease(pVIBuffer);
    return hr;
}

得益于D3D12/Win10的虚拟GPU地址, 我们可以将顶点缓存缓存与索引缓存一起申请, 提高效率. 对应的输入布局:

    // 输入布局
    D3D12_INPUT_ELEMENT_DESC inputLayout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, sizeof(DirectX::XMFLOAT3), D3D12_INPUT_PER_VERTEX_DATA, 0 },
    };

其实颜色也是可以用3个浮点数就行, 这样每个节点就能节约1个浮点了.对应的shader就是:

// C Buffer 0 : 储存转换矩阵
cbuffer MatrixBuffer : register (b0) {
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

// VS 输入
struct VertexInputType {
    float4 position     : POSITION;
    float4 color        : COLOR;
};

// VS 输出
struct PixelInputType {
    float4 position     : SV_POSITION;
    float4 color        : COLOR;
};

// 处理
PixelInputType ColorVertexShader(VertexInputType input) {
    PixelInputType output;
    // 坐标转换
    output.position = mul(float4(input.position.xyz, 1), worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    // 直接输出
    output.color = input.color;

    return output;
}

// 像素着色器处理
float4 ColorPixelShader(PixelInputType input) : SV_TARGET {
    return input.color;
}

请注意, 因为都算是“固定管线”,所以我们在着色器里面使用的一切都要说明:
这里使用了在b0寄存器上cbuffer.
毕竟输入小,管线占用资源就少, 序列化RootSignature时提供的参数D3D12_ROOT_SIGNATURE表明了这个:

        D3D12_ROOT_SIGNATURE rootSigDesc = D3D12_ROOT_SIGNATURE();
        D3D12_ROOT_PARAMETER params[1];
        D3D12_DESCRIPTOR_RANGE descRange[1];
        descRange[0].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 0);
        params[0].InitAsDescriptorTable(lengthof(descRange), descRange);
        // 初始化
        rootSigDesc.NumParameters = lengthof(params);
        rootSigDesc.pParameters = params;
        rootSigDesc.Flags = D3D12_ROOT_SIGNATURE_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;

目前仅需一个DESCRIPTOR表即可,并且只有一个常量缓存视图(CBV), 所以才是descRange[0].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 0);

这次清空命令多了个清空深度缓存:

    }
    // 执行清空命令
    if (SUCCEEDED(hr)) {
        this->SetResourceBarrier(m_pCmdClear, m_pTargetBuffer, D3D12_RESOURCE_USAGE_PRESENT, D3D12_RESOURCE_USAGE_RENDER_TARGET);
        m_pCmdClear->RSSetViewports(1, &view);
        float clearColor[4] = { 0.4f, 0.8f, 1.0f, 1.0f};
        m_pCmdClear->ClearRenderTargetView(
            m_aCpuHandleRTV[RTV_MainRTV],
            clearColor, nullptr, 0
            );
        m_pCmdClear->ClearDepthStencilView(
            m_aCpuHandleDSV[DSV_MainDSV], D3D12_CLEAR_DEPTH, 1.0f, 
            0, nullptr, 0
            );
        this->SetResourceBarrier(m_pCmdClear, m_pTargetBuffer, D3D12_RESOURCE_USAGE_RENDER_TARGET, D3D12_RESOURCE_USAGE_PRESENT);
        hr = m_pCmdClear->Close();
    }

刻画命令多了: 设置DESCRIPTOR表(对应管线), 与DESCRIPTOR堆(提供资源),以及设置顶点缓存和索引缓存:

    // 执行命令
    if (SUCCEEDED(hr)) {
        ID3D12DescriptorHeap* heaps[] = {
            m_pCSUDescriptors
        };
        D3D12_RECT scissor = {};
        scissor.right = m_uBufferWidth;
        scissor.bottom = m_uBufferHeight;
        //
        this->SetResourceBarrier(m_pCmdDraw, m_pTargetBuffer, D3D12_RESOURCE_USAGE_PRESENT, D3D12_RESOURCE_USAGE_RENDER_TARGET);
        m_pCmdDraw->RSSetViewports(1, &view);
        m_pCmdDraw->RSSetScissorRects(1, &scissor);
        m_pCmdDraw->SetRenderTargets(m_aCpuHandleRTV + RTV_MainRTV, true, 1, m_aCpuHandleDSV + DSV_MainDSV);
        m_pCmdDraw->SetGraphicsRootSignature(m_prsPipeline);
        m_pCmdDraw->SetDescriptorHeaps(heaps, lengthof(heaps));
        m_pCmdDraw->SetGraphicsRootDescriptorTable(0, m_pCSUDescriptors->GetGPUDescriptorHandleForHeapStart());
        m_pCmdDraw->SetPipelineState(m_pPipelineState);
        m_pCmdDraw->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
        m_pCmdDraw->SetVertexBuffers(0, &m_cubeVBV, 1);
        m_pCmdDraw->SetIndexBuffer(&m_cubeIBV);
        m_pCmdDraw->DrawIndexedInstanced(36, 1, 0, 0, 0);
        this->SetResourceBarrier(m_pCmdDraw, m_pTargetBuffer, D3D12_RESOURCE_USAGE_RENDER_TARGET, D3D12_RESOURCE_USAGE_PRESENT);
        hr = m_pCmdDraw->Close();

    }

更新常量缓存直接修改即可, 会自动上传的:
Direct3D 12 尝鲜(四): 旋转的彩色立方体_第3张图片


代码下载地址: 点击这里


你可能感兴趣的:(C++,Direct3D)