BRE Architecture Series Part 7 – Geometry Pass

In this opportunity, we are going to talk about the most complex pass in BRE (at the time of writing this article). It is the Geometry Pass. It is the first pass to be executed at the beginning of each frame and its next pass is the EnvironmentLightPass.

Remember that BRE implements deferred shading. Thus, we have several geometry buffers that store material and geometry information. In the following image, we can see the different geometry buffers we have in BRE

untitled

It is important to mention that we do not create an additional buffer for the depth but we reuse the depth buffer.

As we already explained in BRE Architecture Series Part 2- Managers, there is a class named ModelManager that is responsible for loading models. But we did not explain what is the output of that class. Once ModelManager loads a model from disk, it creates an instance of Model class. That class has a list of meshes represented with the class Mesh. This class has the vertex and index buffers of that particular mesh. Its implementation is the following

Model.h

#pragma once

#include <vector>
#include <wrl.h>

#include <GeometryGenerator/GeometryGenerator.h>
#include <ModelManager/Mesh.h>

namespace BRE {
///
/// @brief Represents a model that can be loaded from a file
///
class Model {
public:
    ~Model() = default;
    Model(const Model&) = delete;
    const Model& operator=(const Model&) = delete;
    Model(Model&&) = delete;
    Model& operator=(Model&&) = delete;

    ///
    /// @brief Model constructor
    /// @param modelFilename Model filename. Must not be nullptr.
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Model(const char* modelFilename,
                   ID3D12GraphicsCommandList& commandList,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Model constructor
    /// @param meshData Mesh data.
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Model(const GeometryGenerator::MeshData& meshData,
                   ID3D12GraphicsCommandList& commandList,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Checks if there are meshes or not
    /// @return True if there are meshes. Otherwise, false.
    ///
    __forceinline bool HasMeshes() const noexcept
    {
        return (mMeshes.size() > 0UL);
    }

    ///
    /// @brief Get meshes
    /// @return List of meshes
    ///
    __forceinline const std::vector<Mesh>& GetMeshes() const noexcept
    {
        return mMeshes;
    }

private:
    std::vector<Mesh> mMeshes;
};
}

Model.cpp

#include "Model.h"

#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>

#include <Utils/DebugUtils.h>

namespace BRE {
Model::Model(const char* modelFilename,
             ID3D12GraphicsCommandList& commandList,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    BRE_ASSERT(modelFilename != nullptr);
    const std::string filePath(modelFilename);

    Assimp::Importer importer;
    const std::uint32_t flags{ aiProcessPreset_TargetRealtime_Fast | aiProcess_ConvertToLeftHanded };
    const aiScene* scene{ importer.ReadFile(filePath.c_str(), flags) };
    BRE_ASSERT_MSG(scene != nullptr, StringUtils::AnsiToWideString(importer.GetErrorString()).c_str());

    BRE_ASSERT(scene->HasMeshes());

    for (std::uint32_t i = 0U; i < scene->mNumMeshes; ++i) {
        aiMesh* mesh{ scene->mMeshes[i] };
        BRE_ASSERT(mesh != nullptr);
        mMeshes.push_back(Mesh(*mesh,
                               commandList,
                               uploadVertexBuffer,
                               uploadIndexBuffer));
    }
}

Model::Model(const GeometryGenerator::MeshData& meshData,
             ID3D12GraphicsCommandList& commandList,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    mMeshes.push_back(Mesh(meshData,
                           commandList,
                           uploadVertexBuffer,
                           uploadIndexBuffer));
}
}

Mesh.h

#pragma once

#include <cstdint>

#include <GeometryGenerator/GeometryGenerator.h>
#include <ResourceManager\VertexAndIndexBufferCreator.h>
#include <Utils/DebugUtils.h>

struct aiMesh;

namespace BRE {
class Model;

///
/// @brief Stores model's mesh vertex and index data.
///
class Mesh {
    friend class Model;

public:
    ~Mesh() = default;
    Mesh(const Mesh&) = delete;
    const Mesh& operator=(const Mesh&) = delete;
    Mesh(Mesh&&) = default;
    Mesh& operator=(Mesh&&) = delete;

    ///
    /// @brief Get vertex buffer data
    ///
    /// Data must be valid
    ///
    /// @return Vertex buffer data
    ///
    __forceinline const VertexAndIndexBufferCreator::VertexBufferData& GetVertexBufferData() const noexcept
    {
        BRE_ASSERT(mVertexBufferData.IsDataValid());
        return mVertexBufferData;
    }

    ///
    /// @brief Get index buffer data
    ///
    /// Data must be valid
    ///
    /// @return Index buffer data
    ///
    __forceinline const VertexAndIndexBufferCreator::IndexBufferData& GetIndexBufferData() const noexcept
    {
        BRE_ASSERT(mIndexBufferData.IsDataValid());
        return mIndexBufferData;
    }

private:
    ///
    /// @brief Mesh constructor
    /// @param mesh Assimp mesh
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Mesh(const aiMesh& mesh,
                  ID3D12GraphicsCommandList& commandList,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Mesh constructor
    /// @param meshData Mesh data where we extract vertex and indices
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Mesh(const GeometryGenerator::MeshData& meshData,
                  ID3D12GraphicsCommandList& commandList,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
    VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
};
}

Mesh.cpp

#include "Mesh.h"

#include <assimp/scene.h>

#include <Utils/DebugUtils.h>

using namespace DirectX;

namespace BRE {
namespace {
///
/// @brief Creates vertex and index buffer data
/// @param vertexBufferData Vertex buffer data
/// @param indexBufferData Index buffer data
/// @param meshData Mesh data to get vertices and indices
/// @param commandList Command list used to upload buffers content to GPU.
/// It must be executed after this function call to upload buffers content to GPU.
/// @param uploadVertexBuffer Upload buffer to create the buffer.
/// It has to be kept alive after the function call because
/// the command list has not been executed yet that performs the actual copy.
/// The caller can Release the uploadBuffer after it knows the copy has been executed.
/// @param uploadIndexBuffer Upload buffer to create the buffer.
/// It has to be kept alive after the function call because
/// the command list has not been executed yet that performs the actual copy.
/// The caller can Release the uploadBuffer after it knows the copy has been executed.
///
void CreateVertexAndIndexBufferData(VertexAndIndexBufferCreator::VertexBufferData& vertexBufferData,
                                    VertexAndIndexBufferCreator::IndexBufferData& indexBufferData,
                                    const GeometryGenerator::MeshData& meshData,
                                    ID3D12GraphicsCommandList& commandList,
                                    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                                    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    BRE_ASSERT(vertexBufferData.IsDataValid() == false);
    BRE_ASSERT(indexBufferData.IsDataValid() == false);

    // Create vertex buffer
    VertexAndIndexBufferCreator::BufferCreationData vertexBufferParams(meshData.mVertices.data(),
                                                                       static_cast<std::uint32_t>(meshData.mVertices.size()),
                                                                       sizeof(GeometryGenerator::Vertex));

    VertexAndIndexBufferCreator::CreateVertexBuffer(vertexBufferParams,
                                                    vertexBufferData,
                                                    commandList,
                                                    uploadVertexBuffer);

    // Create index buffer
    VertexAndIndexBufferCreator::BufferCreationData indexBufferParams(meshData.mIndices32.data(),
                                                                      static_cast<std::uint32_t>(meshData.mIndices32.size()),
                                                                      sizeof(std::uint32_t));

    VertexAndIndexBufferCreator::CreateIndexBuffer(indexBufferParams,
                                                   indexBufferData,
                                                   commandList,
                                                   uploadIndexBuffer);

    BRE_ASSERT(vertexBufferData.IsDataValid());
    BRE_ASSERT(indexBufferData.IsDataValid());
}
}

Mesh::Mesh(const aiMesh& mesh,
           ID3D12GraphicsCommandList& commandList,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    GeometryGenerator::MeshData meshData;

    // Positions and Normals
    const std::size_t numVertices{ mesh.mNumVertices };
    BRE_ASSERT(numVertices > 0U);
    BRE_ASSERT(mesh.HasNormals());
    meshData.mVertices.resize(numVertices);
    for (std::uint32_t i = 0U; i < numVertices; ++i) {
        meshData.mVertices[i].mPosition = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mVertices[i]));
        meshData.mVertices[i].mNormal = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mNormals[i]));
    }

    // Texture Coordinates (if any)
    if (mesh.HasTextureCoords(0U)) {
        BRE_ASSERT(mesh.GetNumUVChannels() == 1U);
        const aiVector3D* aiTextureCoordinates{ mesh.mTextureCoords[0U] };
        BRE_ASSERT(aiTextureCoordinates != nullptr);
        for (std::uint32_t i = 0U; i < numVertices; i++) {
            meshData.mVertices[i].mUV = XMFLOAT2(reinterpret_cast<const float*>(&aiTextureCoordinates[i]));
        }
    }

    // Indices
    BRE_ASSERT(mesh.HasFaces());
    const std::uint32_t numFaces{ mesh.mNumFaces };
    for (std::uint32_t i = 0U; i < numFaces; ++i) {         const aiFace* face = &mesh.mFaces[i];         BRE_ASSERT(face != nullptr);         // We only allow triangles         BRE_ASSERT(face->mNumIndices == 3U);

        meshData.mIndices32.push_back(face->mIndices[0U]);
        meshData.mIndices32.push_back(face->mIndices[1U]);
        meshData.mIndices32.push_back(face->mIndices[2U]);
    }

    // Tangents
    if (mesh.HasTangentsAndBitangents()) {
        for (std::uint32_t i = 0U; i < numVertices; ++i) {
            meshData.mVertices[i].mTangent = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mTangents[i]));
        }
    }

    CreateVertexAndIndexBufferData(mVertexBufferData,
                                   mIndexBufferData,
                                   meshData,
                                   commandList,
                                   uploadVertexBuffer,
                                   uploadIndexBuffer);

    BRE_ASSERT(mVertexBufferData.IsDataValid());
    BRE_ASSERT(mIndexBufferData.IsDataValid());
}

Mesh::Mesh(const GeometryGenerator::MeshData& meshData,
           ID3D12GraphicsCommandList& commandList,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    CreateVertexAndIndexBufferData(mVertexBufferData,
                                   mIndexBufferData,
                                   meshData,
                                   commandList,
                                   uploadVertexBuffer,
                                   uploadIndexBuffer);

    BRE_ASSERT(mVertexBufferData.IsDataValid());
    BRE_ASSERT(mIndexBufferData.IsDataValid());
}
}

At the time of writing this article, BRE supports several techniques like color mapping, texture mapping, normal mapping, height mapping, color normal mapping, and color height mapping. To be able to implement these techniques, BRE has a base class called GeometryCommandListRecorder that has a common interface for all the techniques. For example, all of them need to write the geometry buffers, that are going to be used by the next passes. Its implementation is the following

GeometryCommandListRecorder.h

#pragma once

#include <d3d12.h>
#include <DirectXMath.h>
#include <memory>
#include <vector>

#include <CommandManager\CommandListPerFrame.h>
#include <ResourceManager\FrameUploadCBufferPerFrame.h>
#include <ResourceManager/VertexAndIndexBufferCreator.h>

namespace BRE {
struct FrameCBuffer;

///
/// @brief Responsible to record command lists for deferred shading geometry pass
///
/// Steps:
/// - Inherit from it and reimplement RecordAndPushCommandLists() method
/// - Call RecordAndPushCommandLists() to create command lists to execute in the GPU
///
class GeometryCommandListRecorder {
public:
    struct GeometryData {
        GeometryData() = default;

        VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
        VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
        std::vector<DirectX::XMFLOAT4X4> mWorldMatrices;
        std::vector<DirectX::XMFLOAT4X4> mInverseTransposeWorldMatrices;
    };

    GeometryCommandListRecorder() = default;
    virtual ~GeometryCommandListRecorder()
    {}

    GeometryCommandListRecorder(const GeometryCommandListRecorder&) = delete;
    const GeometryCommandListRecorder& operator=(const GeometryCommandListRecorder&) = delete;
    GeometryCommandListRecorder(GeometryCommandListRecorder&&) = default;
    GeometryCommandListRecorder& operator=(GeometryCommandListRecorder&&) = default;

    ///
    /// @brief Initializes the command list recorder
    /// @param geometryBufferRenderTargetViews Geometry buffers render target views. Must not be nullptr
    /// @param geometryBufferRenderTargetViewCount Geometry buffer render target views count. Must be greater than zero
    /// @param depthBufferView Depth buffer view
    ///
    void Init(const D3D12_CPU_DESCRIPTOR_HANDLE* geometryBufferRenderTargetViews,
              const std::uint32_t geometryBufferRenderTargetViewCount,
              const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept;

    ///
    /// @brief Records and pushes command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    virtual void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept = 0;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    virtual bool IsDataValid() const noexcept;

protected:
    CommandListPerFrame mCommandListPerFrame;

    // Base command data. Once you inherits from this class, you should add
    // more class members that represent the extra information you need (like resources, for example)

    std::vector<GeometryData> mGeometryDataVec;

    FrameUploadCBufferPerFrame mFrameUploadCBufferPerFrame;

    UploadBuffer* mObjectUploadCBuffers{ nullptr };
    D3D12_GPU_DESCRIPTOR_HANDLE mStartObjectCBufferView;

    D3D12_GPU_DESCRIPTOR_HANDLE mStartMaterialCBufferView;
    UploadBuffer* mMaterialUploadCBuffers{ nullptr };

    const D3D12_CPU_DESCRIPTOR_HANDLE* mGeometryBufferRenderTargetViews{ nullptr };
    std::uint32_t mGeometryBufferRenderTargetViewCount{ 0U };

    D3D12_CPU_DESCRIPTOR_HANDLE mDepthBufferView{ 0UL };
};

using GeometryCommandListRecorders = std::vector<std::unique_ptr<GeometryCommandListRecorder>>;
}

GeometryCommandListRecorder.cpp

#include "GeometryCommandListRecorder.h"

#include <Utils/DebugUtils.h>

namespace BRE {
bool
GeometryCommandListRecorder::IsDataValid() const noexcept
{
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ mGeometryDataVec[i].mWorldMatrices.size() };
        if (numMatrices == 0UL) {
            return false;
        }
    }

    return
        mObjectUploadCBuffers != nullptr &&
        mStartObjectCBufferView.ptr != 0UL &&
        geometryDataCount != 0UL &&
        mMaterialUploadCBuffers != nullptr &&
        mStartMaterialCBufferView.ptr != 0UL;
}

void
GeometryCommandListRecorder::Init(const D3D12_CPU_DESCRIPTOR_HANDLE* geometryBufferRenderTargetViews,
                                  const std::uint32_t geometryBufferRenderTargetViewCount,
                                  const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept
{
    BRE_ASSERT(geometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(geometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(depthBufferView.ptr != 0UL);

    mGeometryBufferRenderTargetViews = geometryBufferRenderTargetViews;
    mGeometryBufferRenderTargetViewCount = geometryBufferRenderTargetViewCount;
    mDepthBufferView = depthBufferView;
}
}

As we can see, this class has an internal class named GeometryData which contains the vertex buffer, index buffer, and world matrices per geometry (in this case, per mesh). The source code is the following

GeometryData

    struct GeometryData {
        GeometryData() = default;

        VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
        VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
        std::vector<DirectX::XMFLOAT4X4> mWorldMatrices;
        std::vector<DirectX::XMFLOAT4X4> mInverseTransposeWorldMatrices;
    };

We are not going to explain the implementation of each technique, like normal mapping, because there are a lot of resources to learn that, but in the next sections, we will explain all the command list recorders that implement them.

How do we implement color mapping?

In BRE, we have a type of command list recorder named ColorMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements color mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

ColorMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement color mapping
///
class ColorMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    ColorMappingCommandListRecorder() = default;
    ~ColorMappingCommandListRecorder() = default;
    ColorMappingCommandListRecorder(const ColorMappingCommandListRecorder&) = delete;
    const ColorMappingCommandListRecorder& operator=(const ColorMappingCommandListRecorder&) = delete;
    ColorMappingCommandListRecorder(ColorMappingCommandListRecorder&&) = default;
    ColorMappingCommandListRecorder& operator=(ColorMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties) noexcept;
};
}

ColorMappingCommandListRecorder.cpp

#include "ColorMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Frame CBuffer

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
ColorMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                               const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/ColorMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
ColorMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                      const std::vector<MaterialProperties>& materialProperties) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(materialProperties.empty() == false);

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties);

    BRE_ASSERT(IsDataValid());
}

void
ColorMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);

    commandList.SetGraphicsRootSignature(sRootSignature);
    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(2U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();
    CommandListExecutor::Get().AddCommandList(commandList);
}

void
ColorMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j],
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j,
                                            &objCBuffer,
                                            sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create materials cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);

    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i),
                                          &materialProperties[i],
                                          sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;

    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normal = normalize(input.mNormalViewSpace);
    output.mNormal_Smoothness.xy = Encode(normal);

    // Metal mask
    output.mBaseColor_MetalMask = gMaterialPropertiesCBuffer.mBaseColor_MetalMask;

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

color_mapping

How do we implement texture mapping?

In BRE, we have a type of command list recorder named TextureMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements texture mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials and diffuse textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

TextureMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement texture mapping
///
class TextureMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    TextureMappingCommandListRecorder() = default;
    ~TextureMappingCommandListRecorder() = default;
    TextureMappingCommandListRecorder(const TextureMappingCommandListRecorder&) = delete;
    const TextureMappingCommandListRecorder& operator=(const TextureMappingCommandListRecorder&) = delete;
    TextureMappingCommandListRecorder(TextureMappingCommandListRecorder&&) = default;
    TextureMappingCommandListRecorder& operator=(TextureMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties,
              const std::vector<ID3D12Resource*>& diffuseTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                             const std::vector<ID3D12Resource*>& diffuseTextures) noexcept;

    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorBufferGpuDescriptorsBegin;
};
}

TextureMappingCommandListRecorder.cpp

#include "TextureMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root Signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 4 -> Diffuse Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
TextureMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                 const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/TextureMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/TextureMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/TextureMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
TextureMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                        const std::vector<MaterialProperties>& materialProperties,
                                        const std::vector<ID3D12Resource*>& diffuseTextures) noexcept
{
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(IsDataValid() == false);

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties, diffuseTextures);

    BRE_ASSERT(IsDataValid());
}

void
TextureMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE texturesBufferGpuDesc(mBaseColorBufferGpuDescriptorsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(2U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(4U, texturesBufferGpuDesc);
            texturesBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();

    CommandListExecutor::Get().AddCommandList(commandList);
}

bool
TextureMappingCommandListRecorder::IsDataValid() const noexcept
{
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ mGeometryDataVec[i].mWorldMatrices.size() };
        if (numMatrices == 0UL) {
            return false;
        }
    }

    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorBufferGpuDescriptorsBegin.ptr != 0UL;

    return result;
}

void
TextureMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                                                       const std::vector<ID3D12Resource*>& diffuseTextures) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j], 
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create material properties cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);

    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> resVec;
    resVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> srvDescVec;
    srvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        resVec.push_back(diffuseTextures[i]);

        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = resVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = resVec.back()->GetDesc().MipLevels;
        srvDescVec.push_back(srvDesc);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i), &materialProperties[i], sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
    mBaseColorBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(resVec.data(),
                                                              srvDescVec.data(),
                                                              static_cast<std::uint32_t>(srvDescVec.size()));
}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;
    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    output.mUV = gObjCBuffer.mTexTransform * input.mUV;

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D DiffuseTexture : register (t0);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normalViewSpace = normalize(input.mNormalViewSpace);
    output.mNormal_Smoothness.xy = Encode(normalViewSpace);

    // Base color and metal mask
    const float3 diffuseColor = DiffuseTexture.Sample(TextureSampler,
                                                      input.mUV).rgb;
    output.mBaseColor_MetalMask = float4(gMaterialPropertiesCBuffer.mBaseColor_MetalMask.xyz * diffuseColor,
                                         gMaterialPropertiesCBuffer.mBaseColor_MetalMask.w);

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

texture_mapping

How do we implement normal mapping?

In BRE, we have a type of command list recorder named NormalMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements normal mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials, diffuse textures, and normal textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

NormalMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement normal mapping
///
class NormalMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    NormalMappingCommandListRecorder() = default;
    ~NormalMappingCommandListRecorder() = default;
    NormalMappingCommandListRecorder(const NormalMappingCommandListRecorder&) = delete;
    const NormalMappingCommandListRecorder& operator=(const NormalMappingCommandListRecorder&) = delete;
    NormalMappingCommandListRecorder(NormalMappingCommandListRecorder&&) = default;
    NormalMappingCommandListRecorder& operator=(NormalMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties,
              const std::vector<ID3D12Resource*>& diffuseTextures,
              const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                             const std::vector<ID3D12Resource*>& diffuseTextures,
                             const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorBufferGpuDescriptorsBegin;
    D3D12_GPU_DESCRIPTOR_HANDLE mNormalBufferGpuDescriptorsBegin;
};
}

NormalMappingCommandListRecorder.cpp

#include "NormalMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root Signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffers
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 4 -> Diffuse Texture
// "DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL), " \ 5 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
NormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();
    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/NormalMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/NormalMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/NormalMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
NormalMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                       const std::vector<MaterialProperties>& materialProperties,
                                       const std::vector<ID3D12Resource*>& diffuseTextures,
                                       const std::vector<ID3D12Resource*>& normalTextures) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(diffuseTextures.size() == normalTextures.size());

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties,
                        diffuseTextures,
                        normalTextures);

    BRE_ASSERT(IsDataValid());
}

void
NormalMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE texturesBufferGpuDesc(mBaseColorBufferGpuDescriptorsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE normalsBufferGpuDesc(mNormalBufferGpuDescriptorsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(2U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(4U, texturesBufferGpuDesc);
            texturesBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, normalsBufferGpuDesc);
            normalsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();

    CommandListExecutor::Get().AddCommandList(commandList);
}

bool
NormalMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorBufferGpuDescriptorsBegin.ptr != 0UL &&
        mNormalBufferGpuDescriptorsBegin.ptr != 0UL;

    return result;
}

void
NormalMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                                                      const std::vector<ID3D12Resource*>& diffuseTextures,
                                                      const std::vector<ID3D12Resource*>& normalTextures) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(diffuseTextures.size() == normalTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j], 
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create materials cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);

    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> textureResVec;
    textureResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> textureSrvDescVec;
    textureSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        textureResVec.push_back(diffuseTextures[i]);

        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = textureResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = textureResVec.back()->GetDesc().MipLevels;
        textureSrvDescVec.push_back(srvDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = normalResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDesc);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i), &materialProperties[i], sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
    mBaseColorBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(textureResVec.data(),
                                                              textureSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(textureSrvDescVec.size()));
    mNormalBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));
}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;

    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normal = normalize(input.mNormalViewSpace);
    output.mNormal_Smoothness.xy = Encode(normal);

    // Metal mask
    output.mBaseColor_MetalMask = gMaterialPropertiesCBuffer.mBaseColor_MetalMask;

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

normal_mapping

How do we implement height mapping?

In BRE, we have a type of command list recorder named HeightMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements normal mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials, diffuse textures, normal textures, and height textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

HeightMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>
#include <ResourceManager\UploadBuffer.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement height mapping
///
class HeightMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    HeightMappingCommandListRecorder() = default;
    ~HeightMappingCommandListRecorder() = default;
    HeightMappingCommandListRecorder(const HeightMappingCommandListRecorder&) = delete;
    const HeightMappingCommandListRecorder& operator=(const HeightMappingCommandListRecorder&) = delete;
    HeightMappingCommandListRecorder(HeightMappingCommandListRecorder&&) = default;
    HeightMappingCommandListRecorder& operator=(HeightMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties,
              const std::vector<ID3D12Resource*>& diffuseTextures,
              const std::vector<ID3D12Resource*>& normalTextures,
              const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param diffuseTextures List of diffuse textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                             const std::vector<ID3D12Resource*>& diffuseTextures,
                             const std::vector<ID3D12Resource*>& normalTextures,
                             const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorBufferGpuDescriptorsBegin;
    D3D12_GPU_DESCRIPTOR_HANDLE mNormalBufferGpuDescriptorsBegin;
    D3D12_GPU_DESCRIPTOR_HANDLE mHeightBufferGpuDescriptorsBegin;

    UploadBuffer* mHeightMappingUploadCBuffer{ nullptr };
};
}

HeightMappingCommandListRecorder.cpp

#include "HeightMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <GeometryPass\GeometrySettings.h>
#include <GeometryPass\Shaders\HeightMappingCBuffer.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "CBV(b2, visibility = SHADER_VISIBILITY_VERTEX), " \ 2 -> Height Mapping CBuffer
// "CBV(b0, visibility = SHADER_VISIBILITY_DOMAIN), " \ 3 -> Frame CBuffer
// "CBV(b1, visibility = SHADER_VISIBILITY_DOMAIN), " \ 4 -> Height Mapping CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_DOMAIN), " \ 5 -> Height Texture
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 6 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 7 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 8 -> Diffuse Texture
// "DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL), " \ 9 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
HeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mDomainShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/DS.cso");
    psoData.mHullShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/HS.cso");
    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/HeightMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mPrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
HeightMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                       const std::vector<MaterialProperties>& materialProperties,
                                       const std::vector<ID3D12Resource*>& diffuseTextures,
                                       const std::vector<ID3D12Resource*>& normalTextures,
                                       const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(diffuseTextures.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties,
                        diffuseTextures,
                        normalTextures,
                        heightTextures);

    BRE_ASSERT(IsDataValid());
}

void
HeightMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE texturesBufferGpuDesc(mBaseColorBufferGpuDescriptorsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE normalsBufferGpuDesc(mNormalBufferGpuDescriptorsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE heightsBufferGpuDesc(mHeightBufferGpuDescriptorsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(
        uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    const D3D12_GPU_VIRTUAL_ADDRESS heightMappingCBufferGpuVAddress(
        mHeightMappingUploadCBuffer->GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(2U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(4U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(7U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, heightsBufferGpuDesc);
            heightsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(6U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(8U, texturesBufferGpuDesc);
            texturesBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(9U, normalsBufferGpuDesc);
            normalsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();

    CommandListExecutor::Get().AddCommandList(commandList);
}

bool
HeightMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorBufferGpuDescriptorsBegin.ptr != 0UL &&
        mNormalBufferGpuDescriptorsBegin.ptr != 0UL &&
        mHeightBufferGpuDescriptorsBegin.ptr != 0UL &&
        mHeightMappingUploadCBuffer != nullptr;

    return result;
}

void
HeightMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                                                      const std::vector<ID3D12Resource*>& diffuseTextures,
                                                      const std::vector<ID3D12Resource*>& normalTextures,
                                                      const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == diffuseTextures.size());
    BRE_ASSERT(diffuseTextures.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j], 
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create materials cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);

    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> textureResVec;
    textureResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> textureSrvDescVec;
    textureSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> heightResVec;
    heightResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> heightSrvDescVec;
    heightSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        textureResVec.push_back(diffuseTextures[i]);
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = textureResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = textureResVec.back()->GetDesc().MipLevels;
        textureSrvDescVec.push_back(srvDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);
        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = normalResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDesc);

        // Height descriptor
        heightResVec.push_back(heightTextures[i]);
        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = heightResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = heightResVec.back()->GetDesc().MipLevels;
        heightSrvDescVec.push_back(srvDesc);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i), &materialProperties[i], sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
    mBaseColorBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(textureResVec.data(),
                                                              textureSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(textureSrvDescVec.size()));
    mNormalBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));
    mHeightBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(heightResVec.data(),
                                                              heightSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(heightSrvDescVec.size()));

    // Height mapping constant buffer
    const std::size_t heightMappingUploadCBufferElemSize =
        UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(HeightMappingCBuffer));

    mHeightMappingUploadCBuffer = &UploadBufferManager::CreateUploadBuffer(heightMappingUploadCBufferElemSize,
                                                                           1U);
    HeightMappingCBuffer heightMappingCBuffer(GeometrySettings::sMinTessellationDistance,
                                              GeometrySettings::sMaxTessellationDistance,
                                              GeometrySettings::sMinTessellationFactor,
                                              GeometrySettings::sMaxTessellationFactor,
                                              GeometrySettings::sHeightScale);

    mHeightMappingUploadCBuffer->CopyData(0U, &heightMappingCBuffer, sizeof(HeightMappingCBuffer));
}
}

VertexShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b2);

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;

    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;

    output.mTangentWorldSpace = mul(float4(input.mTangentObjectSpace, 0.0f),
                                    gObjCBuffer.mWorldMatrix).xyz;

    output.mUV = gObjCBuffer.mTexTransform * input.mUV;

    // Normalized tessellation factor. 
    // The tessellation is 
    //   0 if d >= min tessellation distance and
    //   1 if d <= max tessellation distance.  
    const float distance = length(output.mPositionWorldSpace - gFrameCBuffer.mEyePositionWorldSpace.xyz);
    const float tessellationFactor = saturate((gHeightMappingCBuffer.mMinTessellationDistance - distance) 
                                              / (gHeightMappingCBuffer.mMinTessellationDistance 
                                                 - gHeightMappingCBuffer.mMaxTessellationDistance));

    // Rescale [0,1] --> [min tessellation factor, max tessellation factor].
    output.mTessellationFactor = gHeightMappingCBuffer.mMinTessellationFactor 
        + tessellationFactor * (gHeightMappingCBuffer.mMaxTessellationFactor - gHeightMappingCBuffer.mMinTessellationFactor);

    return output;
}

HullShader

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

HullShaderConstantOutput constant_hull_shader(const InputPatch<Input, NUM_PATCH_POINTS> patch,
                                              const uint patchID : SV_PrimitiveID)
{
    // Average tess factors along edges, and pick an edge tess factor for 
    // the interior tessellation. It is important to do the tess factor
    // calculation based on the edge properties so that edges shared by 
    // more than one triangle will have the same tessellation factor.  
    // Otherwise, gaps can appear.
    HullShaderConstantOutput output = (HullShaderConstantOutput)0;
    output.mEdgeFactors[0] = 0.5f * (patch[1].mTessellationFactor + patch[2].mTessellationFactor);
    output.mEdgeFactors[1] = 0.5f * (patch[2].mTessellationFactor + patch[0].mTessellationFactor);
    output.mEdgeFactors[2] = 0.5f * (patch[0].mTessellationFactor + patch[1].mTessellationFactor);
    output.mInsideFactors = output.mEdgeFactors[0];

    return output;
}

[RootSignature(RS)]
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(NUM_PATCH_POINTS)]
[patchconstantfunc("constant_hull_shader")]
Output main(const InputPatch <Input, NUM_PATCH_POINTS> patch,
            const uint controlPointID : SV_OutputControlPointID,
            const uint patchId : SV_PrimitiveID)
{
    Output output = (Output)0;
    output.mPositionWorldSpace = patch[controlPointID].mPositionWorldSpace;
    output.mNormalWorldSpace = patch[controlPointID].mNormalWorldSpace;
    output.mTangentWorldSpace = patch[controlPointID].mTangentWorldSpace;
    output.mUV = patch[controlPointID].mUV;

    return output;
}

DomainShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D HeightTexture : register (t0);

struct Output {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

[RootSignature(RS)]
[domain("tri")]
Output main(const HullShaderConstantOutput HSConstantOutput,
            const float3 uvw : SV_DomainLocation,
            const OutputPatch <Input, NUM_PATCH_POINTS> patch)
{
    Output output = (Output)0;

    // Get texture coordinates
    output.mUV = uvw.x * patch[0].mUV + uvw.y * patch[1].mUV + uvw.z * patch[2].mUV;

    // Get normal
    const float3 normalWorldSpace =
        normalize(uvw.x * patch[0].mNormalWorldSpace + uvw.y * patch[1].mNormalWorldSpace + uvw.z * patch[2].mNormalWorldSpace);
    output.mNormalWorldSpace = normalize(normalWorldSpace);
    output.mNormalViewSpace = normalize(mul(float4(output.mNormalWorldSpace, 0.0f),
                                            gFrameCBuffer.mViewMatrix).xyz);

    // Get position
    float3 positionWorldSpace =
        uvw.x * patch[0].mPositionWorldSpace + uvw.y * patch[1].mPositionWorldSpace + uvw.z * patch[2].mPositionWorldSpace;
    float3 positionViewSpace = mul(float4(positionWorldSpace, 1.0f),
                                   gFrameCBuffer.mViewMatrix).xyz;

    const float height = HeightTexture.SampleLevel(TextureSampler,
                                                   output.mUV,
                                                   0).x;
    const float displacement = (gHeightMappingCBuffer.mHeightScale * (height - 1));

    // Offset vertex along normal
    positionWorldSpace += output.mNormalWorldSpace * displacement;
    positionViewSpace += output.mNormalViewSpace * displacement;

    // Get tangent
    output.mTangentWorldSpace =
        normalize(uvw.x * patch[0].mTangentWorldSpace + uvw.y * patch[1].mTangentWorldSpace + uvw.z * patch[2].mTangentWorldSpace);
    output.mTangentViewSpace = normalize(mul(float4(output.mTangentWorldSpace, 0.0f),
                                             gFrameCBuffer.mViewMatrix)).xyz;

    // Get binormal
    output.mBinormalWorldSpace = normalize(cross(output.mNormalWorldSpace,
                                                 output.mTangentWorldSpace));
    output.mBinormalViewSpace = normalize(cross(output.mNormalViewSpace,
                                                output.mTangentViewSpace));

    output.mPositionWorldSpace = positionWorldSpace;
    output.mPositionViewSpace = positionViewSpace;
    output.mPositionClipSpace = mul(float4(positionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D DiffuseTexture : register (t0);
Texture2D NormalTexture : register (t1);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space) 
    const float3 normalObjectSpace = normalize(NormalTexture.Sample(TextureSampler, 
                                                                    input.mUV).xyz * 2.0f - 1.0f);
    const float3x3 tbnWorldSpace = float3x3(normalize(input.mTangentWorldSpace),
                                            normalize(input.mBinormalWorldSpace),
                                            normalize(input.mNormalWorldSpace));
    const float3 normalWorldSpace = mul(normalObjectSpace, tbnWorldSpace);
    const float3x3 tbnViewSpace = float3x3(normalize(input.mTangentViewSpace),
                                           normalize(input.mBinormalViewSpace),
                                           normalize(input.mNormalViewSpace));
    output.mNormal_Smoothness.xy = Encode(normalize(mul(normalObjectSpace, tbnViewSpace)));

    // Base color and metal mask
    const float3 diffuseColor = DiffuseTexture.Sample(TextureSampler,
                                                      input.mUV).rgb;
    output.mBaseColor_MetalMask = float4(gMaterialPropertiesCBuffer.mBaseColor_MetalMask.xyz * diffuseColor,
                                         gMaterialPropertiesCBuffer.mBaseColor_MetalMask.w);

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

height_mapping

How do we implement color normal mapping?

In BRE, we have a type of command list recorder named ColorNormalMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements color normal mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials, and normal textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

ColorNormalMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement color normal mapping
///
class ColorNormalMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    ColorNormalMappingCommandListRecorder() = default;
    ~ColorNormalMappingCommandListRecorder() = default;
    ColorNormalMappingCommandListRecorder(const ColorNormalMappingCommandListRecorder&) = delete;
    const ColorNormalMappingCommandListRecorder& operator=(const ColorNormalMappingCommandListRecorder&) = delete;
    ColorNormalMappingCommandListRecorder(ColorNormalMappingCommandListRecorder&&) = default;
    ColorNormalMappingCommandListRecorder& operator=(ColorNormalMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties,
              const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                             const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    D3D12_GPU_DESCRIPTOR_HANDLE mNormalBufferGpuDescriptorsBegin;
};
}

ColorNormalMappingCommandListRecorder.cpp

#include "ColorNormalMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager/DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root Signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffers
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 4 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
ColorNormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                     const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorNormalMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorNormalMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/ColorNormalMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats,
           geometryBufferFormats,
           sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
ColorNormalMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                            const std::vector<MaterialProperties>& materialProperties,
                                            const std::vector<ID3D12Resource*>& normalTexturess) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == normalTexturess.size());

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties, normalTexturess);

    BRE_ASSERT(IsDataValid());
}

void
ColorNormalMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE normalsBufferGpuDesc(mNormalBufferGpuDescriptorsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(2U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(4U, normalsBufferGpuDesc);
            normalsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();

    CommandListExecutor::Get().AddCommandList(commandList);
}

bool
ColorNormalMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mNormalBufferGpuDescriptorsBegin.ptr != 0UL;

    return result;
}

void
ColorNormalMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                                                           const std::vector<ID3D12Resource*>& normalTextures) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == normalTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j], 
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create materials cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);
    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);

        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = normalResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDesc);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i),
                                          &materialProperties[i],
                                          sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
    mNormalBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));
}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

Output main(in const Input input)
{
    Output output;
    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;
    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    output.mUV = gObjCBuffer.mTexTransform * input.mUV;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mTangentWorldSpace = mul(float4(input.mTangentObjectSpace, 0.0f),
                                    gObjCBuffer.mWorldMatrix).xyz;
    output.mTangentViewSpace = mul(float4(output.mTangentWorldSpace, 0.0f),
                                   gFrameCBuffer.mViewMatrix).xyz;

    output.mBinormalWorldSpace = normalize(cross(output.mNormalWorldSpace,
                                                 output.mTangentWorldSpace));
    output.mBinormalViewSpace = normalize(cross(output.mNormalViewSpace,
                                                output.mTangentViewSpace));

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D NormalTexture : register (t0);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normalObjectSpace = normalize(NormalTexture.Sample(TextureSampler, 
                                                                    input.mUV).xyz * 2.0f - 1.0f);
    const float3x3 tbnWorldSpace = float3x3(normalize(input.mTangentWorldSpace),
                                            normalize(input.mBinormalWorldSpace),
                                            normalize(input.mNormalWorldSpace));
    const float3 normalWorldSpace = normalize(mul(normalObjectSpace, tbnWorldSpace));
    const float3x3 tbnViewSpace = float3x3(normalize(input.mTangentViewSpace),
                                           normalize(input.mBinormalViewSpace),
                                           normalize(input.mNormalViewSpace));
    output.mNormal_Smoothness.xy = Encode(normalize(mul(normalObjectSpace, tbnViewSpace)));

    // Base color and metal mask
    output.mBaseColor_MetalMask = gMaterialPropertiesCBuffer.mBaseColor_MetalMask;

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

color normal mapping

How do we implement color height mapping?

In BRE, we have a type of command list recorder named ColorHeightMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements color height mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of materials, normal textures, and height textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

ColorHeightMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>
#include <ResourceManager\UploadBuffer.h>

namespace BRE {
class MaterialProperties;

///
/// @brief Responsible to record command lists that implement color height mapping
///
class ColorHeightMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    ColorHeightMappingCommandListRecorder() = default;
    ~ColorHeightMappingCommandListRecorder() = default;
    ColorHeightMappingCommandListRecorder(const ColorHeightMappingCommandListRecorder&) = delete;
    const ColorHeightMappingCommandListRecorder& operator=(const ColorHeightMappingCommandListRecorder&) = delete;
    ColorHeightMappingCommandListRecorder(ColorHeightMappingCommandListRecorder&&) = default;
    ColorHeightMappingCommandListRecorder& operator=(ColorHeightMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<MaterialProperties>& materialProperties,
              const std::vector<ID3D12Resource*>& normalTextures,
              const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers
    /// @param materialProperties List of material properties. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                             const std::vector<ID3D12Resource*>& normalTextures,
                             const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    D3D12_GPU_DESCRIPTOR_HANDLE mNormalBufferGpuDescriptorsBegin;
    D3D12_GPU_DESCRIPTOR_HANDLE mHeightBufferGpuDescriptorsBegin;

    UploadBuffer* mHeightMappingUploadCBuffer{ nullptr };
};
}

ColorHeightMappingCommandListRecorder.cpp

#include "ColorHeightMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <GeometryPass\GeometrySettings.h>
#include <GeometryPass\Shaders\HeightMappingCBuffer.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <ShaderUtils\MaterialProperties.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "CBV(b2, visibility = SHADER_VISIBILITY_VERTEX), " \ 2 -> Height Mapping CBuffer
// "CBV(b0, visibility = SHADER_VISIBILITY_DOMAIN), " \ 3 -> Frame CBuffer
// "CBV(b1, visibility = SHADER_VISIBILITY_DOMAIN), " \ 4 -> Height Mapping CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_DOMAIN), " \ 5 -> Height Texture
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_PIXEL), " \ 6 -> Material CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_PIXEL), " \ 7 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 8 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
ColorHeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                     const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mDomainShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorHeightMapping/DS.cso");
    psoData.mHullShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorHeightMapping/HS.cso");
    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorHeightMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/ColorHeightMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/ColorHeightMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mPrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
ColorHeightMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                            const std::vector<MaterialProperties>& materialProperties,
                                            const std::vector<ID3D12Resource*>& normalTextures,
                                            const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());

    const std::size_t numResources = materialProperties.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitConstantBuffers(materialProperties,
                        normalTextures,
                        heightTextures);

    BRE_ASSERT(IsDataValid());
}

void
ColorHeightMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferGpuDesc(mStartObjectCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE materialsCBufferGpuDesc(mStartMaterialCBufferView);
    D3D12_GPU_DESCRIPTOR_HANDLE normalsBufferGpuDesc(mNormalBufferGpuDescriptorsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE heightsBufferGpuDesc(mHeightBufferGpuDescriptorsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);

    // Set frame constants root parameters
    const D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(
        uploadFrameCBuffer.GetResource()->GetGPUVirtualAddress());
    const D3D12_GPU_VIRTUAL_ADDRESS heightMappingCBufferGpuVAddress(
        mHeightMappingUploadCBuffer->GetResource()->GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(2U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(4U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(7U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferGpuDesc);
            objectCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, heightsBufferGpuDesc);
            heightsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(6U, materialsCBufferGpuDesc);
            materialsCBufferGpuDesc.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(8U, normalsBufferGpuDesc);
            normalsBufferGpuDesc.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();

    CommandListExecutor::Get().AddCommandList(commandList);
}

bool
ColorHeightMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        mNormalBufferGpuDescriptorsBegin.ptr != 0UL &&
        mHeightBufferGpuDescriptorsBegin.ptr != 0UL && 
        mHeightMappingUploadCBuffer != nullptr;

    return result;
}

void
ColorHeightMappingCommandListRecorder::InitConstantBuffers(const std::vector<MaterialProperties>& materialProperties,
                                                           const std::vector<ID3D12Resource*>& normalTextures,
                                                           const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(materialProperties.empty() == false);
    BRE_ASSERT(materialProperties.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);
    BRE_ASSERT(mMaterialUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(materialProperties.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {
            MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j], 
                                            objCBuffer.mWorldMatrix);
            MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],
                                            objCBuffer.mInverseTransposeWorldMatrix);
            mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    // Create materials cbuffer		
    const std::size_t matCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(MaterialProperties)) };
    mMaterialUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(matCBufferElemSize, numResources);

    D3D12_GPU_VIRTUAL_ADDRESS materialsGpuAddress{ mMaterialUploadCBuffers->GetResource()->GetGPUVirtualAddress() };
    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource()->GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> heightResVec;
    heightResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> heightSrvDescVec;
    heightSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Material cbuffer desc
        cBufferDesc.BufferLocation = materialsGpuAddress + i * matCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(matCBufferElemSize);
        materialCbufferViewDescVec.push_back(cBufferDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDescriptor{};
        srvDescriptor.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDescriptor.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDescriptor.Texture2D.MostDetailedMip = 0;
        srvDescriptor.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDescriptor.Format = normalResVec.back()->GetDesc().Format;
        srvDescriptor.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDescriptor);

        // Height descriptor
        heightResVec.push_back(heightTextures[i]);
        srvDescriptor = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDescriptor.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDescriptor.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDescriptor.Texture2D.MostDetailedMip = 0;
        srvDescriptor.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDescriptor.Format = heightResVec.back()->GetDesc().Format;
        srvDescriptor.Texture2D.MipLevels = heightResVec.back()->GetDesc().MipLevels;
        heightSrvDescVec.push_back(srvDescriptor);

        mMaterialUploadCBuffers->CopyData(static_cast<std::uint32_t>(i), &materialProperties[i], sizeof(MaterialProperties));
    }
    mStartObjectCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));
    mStartMaterialCBufferView =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(materialCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(materialCbufferViewDescVec.size()));
    mNormalBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));
    mHeightBufferGpuDescriptorsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(heightResVec.data(),
                                                              heightSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(heightSrvDescVec.size()));

    // Height mapping constant buffer
    const std::size_t heightMappingUploadCBufferElemSize = 
        UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(HeightMappingCBuffer));

    mHeightMappingUploadCBuffer = &UploadBufferManager::CreateUploadBuffer(heightMappingUploadCBufferElemSize,
                                                                           1U);
    HeightMappingCBuffer heightMappingCBuffer(GeometrySettings::sMinTessellationDistance,
                                              GeometrySettings::sMaxTessellationDistance,
                                              GeometrySettings::sMinTessellationFactor,
                                              GeometrySettings::sMaxTessellationFactor,
                                              GeometrySettings::sHeightScale);

    mHeightMappingUploadCBuffer->CopyData(0U, &heightMappingCBuffer, sizeof(HeightMappingCBuffer));
}
}

VertexShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b2);

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;

    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;

    output.mTangentWorldSpace = mul(float4(input.mTangentObjectSpace, 0.0f),
                                    gObjCBuffer.mWorldMatrix).xyz;

    output.mUV = gObjCBuffer.mTexTransform * input.mUV;
    
    // Normalized tessellation factor. 
    // The tessellation is 
    //   0 if d >= min tessellation distance and
    //   1 if d <= max tessellation distance.  
    const float distance = length(output.mPositionWorldSpace - gFrameCBuffer.mEyePositionWorldSpace.xyz);
    const float tessellationFactor = saturate((gHeightMappingCBuffer.mMinTessellationDistance - distance)
                                              / (gHeightMappingCBuffer.mMinTessellationDistance
                                                 - gHeightMappingCBuffer.mMaxTessellationDistance));

    // Rescale [0,1] --> [min tessellation factor, max tessellation factor].
    output.mTessellationFactor = gHeightMappingCBuffer.mMinTessellationFactor
        + tessellationFactor * (gHeightMappingCBuffer.mMaxTessellationFactor - gHeightMappingCBuffer.mMinTessellationFactor);

    return output;
}

HullShader

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

HullShaderConstantOutput constant_hull_shader(const InputPatch<Input, NUM_PATCH_POINTS> patch,
                                              const uint patchID : SV_PrimitiveID)
{
    // Average tess factors along edges, and pick an edge tess factor for 
    // the interior tessellation. It is important to do the tess factor
    // calculation based on the edge properties so that edges shared by 
    // more than one triangle will have the same tessellation factor.  
    // Otherwise, gaps can appear.
    HullShaderConstantOutput output = (HullShaderConstantOutput)0;
    output.mEdgeFactors[0] = 0.5f * (patch[1].mTessellationFactor + patch[2].mTessellationFactor);
    output.mEdgeFactors[1] = 0.5f * (patch[2].mTessellationFactor + patch[0].mTessellationFactor);
    output.mEdgeFactors[2] = 0.5f * (patch[0].mTessellationFactor + patch[1].mTessellationFactor);
    output.mInsideFactors = output.mEdgeFactors[0];

    return output;
}

[RootSignature(RS)]
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(NUM_PATCH_POINTS)]
[patchconstantfunc("constant_hull_shader")]
Output main(const InputPatch <Input, NUM_PATCH_POINTS> patch,
            const uint controlPointID : SV_OutputControlPointID,
            const uint patchId : SV_PrimitiveID)
{
    Output output = (Output)0;
    output.mPositionWorldSpace = patch[controlPointID].mPositionWorldSpace;
    output.mNormalWorldSpace = patch[controlPointID].mNormalWorldSpace;
    output.mTangentWorldSpace = patch[controlPointID].mTangentWorldSpace;
    output.mUV = patch[controlPointID].mUV;

    return output;
}

DomainShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D HeightTexture : register (t0);

struct Output {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

[RootSignature(RS)]
[domain("tri")]
Output main(const HullShaderConstantOutput HSConstantOutput,
            const float3 uvw : SV_DomainLocation,
            const OutputPatch <Input, NUM_PATCH_POINTS> patch)
{
    Output output = (Output)0;

    // Get texture coordinates
    output.mUV = uvw.x * patch[0].mUV + uvw.y * patch[1].mUV + uvw.z * patch[2].mUV;

    // Get normal
    const float3 normalWorldSpace =
        normalize(uvw.x * patch[0].mNormalWorldSpace + uvw.y * patch[1].mNormalWorldSpace + uvw.z * patch[2].mNormalWorldSpace);
    output.mNormalWorldSpace = normalize(normalWorldSpace);
    output.mNormalViewSpace = normalize(mul(float4(output.mNormalWorldSpace, 0.0f),
                                            gFrameCBuffer.mViewMatrix).xyz);

    // Get position
    float3 positionWorldSpace =
        uvw.x * patch[0].mPositionWorldSpace + uvw.y * patch[1].mPositionWorldSpace + uvw.z * patch[2].mPositionWorldSpace;
    float3 positionViewSpace = mul(float4(positionWorldSpace, 1.0f),
                                   gFrameCBuffer.mViewMatrix).xyz;

    const float height = HeightTexture.SampleLevel(TextureSampler,
                                                   output.mUV,
                                                   0).x;
    const float displacement = (gHeightMappingCBuffer.mHeightScale * (height - 1));

    // Offset vertex along normal
    positionWorldSpace += output.mNormalWorldSpace * displacement;
    positionViewSpace += output.mNormalViewSpace * displacement;

    // Get tangent
    output.mTangentWorldSpace =
        normalize(uvw.x * patch[0].mTangentWorldSpace + uvw.y * patch[1].mTangentWorldSpace + uvw.z * patch[2].mTangentWorldSpace);
    output.mTangentViewSpace =
        normalize(mul(float4(output.mTangentWorldSpace, 0.0f), gFrameCBuffer.mViewMatrix)).xyz;

    // Get binormal
    output.mBinormalWorldSpace = normalize(cross(output.mNormalWorldSpace, output.mTangentWorldSpace));
    output.mBinormalViewSpace = normalize(cross(output.mNormalViewSpace, output.mTangentViewSpace));

    output.mPositionWorldSpace = positionWorldSpace;
    output.mPositionViewSpace = positionViewSpace;
    output.mPositionClipSpace = mul(float4(positionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/MaterialProperties.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<MaterialProperties> gMaterialPropertiesCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D NormalTexture : register (t0);

struct Output {
    float4 mNormal_Smoothness : SV_Target0;
    float4 mBaseColor_MetalMask : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)     ;
    const float3 normalObjectSpace = normalize(NormalTexture.Sample(TextureSampler, 
                                                                    input.mUV).xyz * 2.0f - 1.0f);
    const float3x3 tbnWorldSpace = float3x3(normalize(input.mTangentWorldSpace),
                                            normalize(input.mBinormalWorldSpace),
                                            normalize(input.mNormalWorldSpace));
    const float3 normalWorldSpace = mul(normalObjectSpace, tbnWorldSpace);
    const float3x3 tbnViewSpace = float3x3(normalize(input.mTangentViewSpace),
                                           normalize(input.mBinormalViewSpace),
                                           normalize(input.mNormalViewSpace));
    output.mNormal_Smoothness.xy = Encode(normalize(mul(normalObjectSpace,
                                                        tbnViewSpace)));

    // Base color and metal mask
    output.mBaseColor_MetalMask = gMaterialPropertiesCBuffer.mBaseColor_MetalMask;

    // Smoothness
    output.mNormal_Smoothness.z = gMaterialPropertiesCBuffer.mSmoothness;

    return output;
}

You can see a screenshot from BRE in the following image

color height mapping

What is the pass that executes all these command list recorders?

As we described in BRE Architecture Series Part 5 – Scene Generation, the SceneLoader is responsible for reading the scene file and generate the GeometryCommandListRecorders. Also, we described that they are stored in an instance of the class Scene.
The pass responsible for executing all the GeometryCommandListRecorders is GeometryPass, and its implementation is the following

GeometryPass.h

#pragma once

#include <memory>
#include <vector>

#include <CommandManager\CommandListPerFrame.h>
#include <GeometryPass\GeometryCommandListRecorder.h>

namespace BRE {
struct FrameCBuffer;

///
/// @brief Responsible to execute command list recorders related with deferred shading geometry pass
///
class GeometryPass {
public:
    // Geometry buffers
    enum Buffers {
        NORMAL_SMOOTHNESS = 0U, // 2 encoded normals based on octahedron encoding + 1 smoothness
        BASECOLOR_METALMASK, // 3 base color + 1 metal mask
        BUFFERS_COUNT
    };

    GeometryPass(GeometryCommandListRecorders& geometryPassCommandListRecorders);
    ~GeometryPass() = default;
    GeometryPass(const GeometryPass&) = delete;
    const GeometryPass& operator=(const GeometryPass&) = delete;
    GeometryPass(GeometryPass&&) = delete;
    GeometryPass& operator=(GeometryPass&&) = delete;

    ///
    /// @brief Initializes geometry pass
    /// @param depthBufferView Dpeth buffer view
    ///
    void Init(const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept;

    ///
    /// @brief Get geometry buffers
    /// @return List of geometry buffers
    ///
    __forceinline Microsoft::WRL::ComPtr<ID3D12Resource>* GetGeometryBuffers() noexcept
    {
        return mGeometryBuffers;
    }

    ///
    /// @brief Executes the geometry pass
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    ///
    void Execute(const FrameCBuffer& frameCBuffer) noexcept;

private:
    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept;

    ///
    /// @brief Executes begin task for geometry pass
    ///
    void ExecuteBeginTask() noexcept;

    CommandListPerFrame mCommandListPerFrame;

    // Geometry buffers data
    Microsoft::WRL::ComPtr<ID3D12Resource> mGeometryBuffers[BUFFERS_COUNT];
    D3D12_CPU_DESCRIPTOR_HANDLE mGeometryBufferRenderTargetViews[BUFFERS_COUNT];

    GeometryCommandListRecorders& mGeometryCommandListRecorders;
};
}

GeometryPass.cpp

#include "GeometryPass.h"

#include <d3d12.h>
#include <DirectXColors.h>
#include <tbb/parallel_for.h>

#include <CommandListExecutor/CommandListExecutor.h>
#include <DescriptorManager\RenderTargetDescriptorManager.h>
#include <DXUtils/d3dx12.h>
#include <GeometryPass\Recorders\ColorMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\ColorHeightMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\ColorNormalMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\HeightMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\NormalMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\TextureMappingCommandListRecorder.h>
#include <ResourceManager\ResourceManager.h>
#include <ResourceStateManager\ResourceStateManager.h>
#include <ShaderUtils\CBuffers.h>
#include <Utils\DebugUtils.h>

using namespace DirectX;

namespace BRE {
namespace {
// Geometry buffer formats
const DXGI_FORMAT sGeometryBufferFormats[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]{
    DXGI_FORMAT_R16G16B16A16_FLOAT,
    DXGI_FORMAT_R8G8B8A8_UNORM,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN
};

///
/// @brief Create geometry buffers and render target views
/// @param buffers Output list of geometry buffers
/// @param bufferRenderTargetViews Output geometry buffers render target views
///
void
CreateGeometryBuffersAndRenderTargetViews(Microsoft::WRL::ComPtr<ID3D12Resource> buffers[GeometryPass::BUFFERS_COUNT],
                                          D3D12_CPU_DESCRIPTOR_HANDLE bufferRenderTargetViews[GeometryPass::BUFFERS_COUNT]) noexcept
{
    // Set shared buffers properties
    D3D12_RESOURCE_DESC resourceDescriptor = {};
    resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    resourceDescriptor.Alignment = 0U;
    resourceDescriptor.Width = ApplicationSettings::sWindowWidth;
    resourceDescriptor.Height = ApplicationSettings::sWindowHeight;
    resourceDescriptor.DepthOrArraySize = 1U;
    resourceDescriptor.MipLevels = 0U;
    resourceDescriptor.SampleDesc.Count = 1U;
    resourceDescriptor.SampleDesc.Quality = 0U;
    resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
    resourceDescriptor.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

    D3D12_CLEAR_VALUE clearValue[]
    {
        { DXGI_FORMAT_UNKNOWN, 0.0f, 0.0f, 0.0f, 1.0f },
        { DXGI_FORMAT_UNKNOWN, 0.0f, 0.0f, 0.0f, 0.0f },
    };
    BRE_ASSERT(_countof(clearValue) == GeometryPass::BUFFERS_COUNT);

    buffers[GeometryPass::NORMAL_SMOOTHNESS].Reset();
    buffers[GeometryPass::BASECOLOR_METALMASK].Reset();

    CD3DX12_HEAP_PROPERTIES heapProps{ D3D12_HEAP_TYPE_DEFAULT };

    // Create and store render target views
    const wchar_t* resourceNames[GeometryPass::BUFFERS_COUNT] =
    {
        L"Normal_SmoothnessTexture Buffer",
        L"BaseColor_MetalMaskTexture Buffer"
    };
    for (std::uint32_t i = 0U; i < GeometryPass::BUFFERS_COUNT; ++i) {
        resourceDescriptor.Format = sGeometryBufferFormats[i];

        clearValue[i].Format = resourceDescriptor.Format;

        D3D12_RENDER_TARGET_VIEW_DESC rtvDescriptor{};
        rtvDescriptor.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
        rtvDescriptor.Format = resourceDescriptor.Format;
        resourceDescriptor.MipLevels = 1U;
        ID3D12Resource* resource = &ResourceManager::CreateCommittedResource(heapProps,
                                                                             D3D12_HEAP_FLAG_NONE,
                                                                             resourceDescriptor,
                                                                             D3D12_RESOURCE_STATE_RENDER_TARGET,
                                                                             &clearValue[i],
                                                                             resourceNames[i]);

        buffers[i] = Microsoft::WRL::ComPtr<ID3D12Resource>(resource);
        RenderTargetDescriptorManager::CreateRenderTargetView(*buffers[i].Get(),
                                                              rtvDescriptor,
                                                              &bufferRenderTargetViews[i]);
    }
}
}

GeometryPass::GeometryPass(GeometryCommandListRecorders& geometryPassCommandListRecorders)
    : mGeometryCommandListRecorders(geometryPassCommandListRecorders)
{}

void
GeometryPass::Init(const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept
{
    BRE_ASSERT(IsDataValid() == false);

    BRE_ASSERT(mGeometryCommandListRecorders.empty() == false);

    CreateGeometryBuffersAndRenderTargetViews(mGeometryBuffers, mGeometryBufferRenderTargetViews);

    ColorMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);
    ColorHeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);
    ColorNormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);
    HeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);
    NormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);
    TextureMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);

    // Init geometry command list recorders
    for (GeometryCommandListRecorders::value_type& recorder : mGeometryCommandListRecorders) {
        BRE_ASSERT(recorder.get() != nullptr);
        recorder->Init(mGeometryBufferRenderTargetViews, BUFFERS_COUNT, depthBufferView);
    }

    BRE_ASSERT(IsDataValid());
}

void
GeometryPass::Execute(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());

    ExecuteBeginTask();

    const std::uint32_t taskCount{ static_cast<std::uint32_t>(mGeometryCommandListRecorders.size()) };
    CommandListExecutor::Get().ResetExecutedCommandListCount();

    // Execute tasks
    std::uint32_t grainSize{ max(1U, (taskCount) / ApplicationSettings::sCpuProcessorCount) };
    tbb::parallel_for(tbb::blocked_range<std::size_t>(0, taskCount, grainSize),
                      [&](const tbb::blocked_range<size_t>& r) {
        for (size_t i = r.begin(); i != r.end(); ++i)
            mGeometryCommandListRecorders[i]->RecordAndPushCommandLists(frameCBuffer);
    }
    );

    // Wait until all previous tasks command lists are executed
    while (CommandListExecutor::Get().GetExecutedCommandListCount() < taskCount) {
        Sleep(0U);
    }
}

bool
GeometryPass::IsDataValid() const noexcept
{
    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        if (mGeometryBuffers[i].Get() == nullptr) {
            return false;
        }
    }

    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        if (mGeometryBufferRenderTargetViews[i].ptr == 0UL) {
            return false;
        }
    }

    return mGeometryCommandListRecorders.empty() == false;
}

void
GeometryPass::ExecuteBeginTask() noexcept
{
    BRE_ASSERT(IsDataValid());

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(nullptr);

    CD3DX12_RESOURCE_BARRIER barriers[BUFFERS_COUNT];
    std::uint32_t barrierCount = 0UL;
    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        if (ResourceStateManager::GetResourceState(*mGeometryBuffers[i].Get()) != D3D12_RESOURCE_STATE_RENDER_TARGET) {
            barriers[barrierCount] = ResourceStateManager::ChangeResourceStateAndGetBarrier(*mGeometryBuffers[i].Get(),
                                                                                            D3D12_RESOURCE_STATE_RENDER_TARGET);
            ++barrierCount;
        }
    }

    if (barrierCount > 0UL) {
        commandList.ResourceBarrier(barrierCount, barriers);
    }

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);

    float zero[4U] = { 0.0f, 0.0f, 0.0f, 0.0f };
    commandList.ClearRenderTargetView(mGeometryBufferRenderTargetViews[NORMAL_SMOOTHNESS], Colors::Black, 0U, nullptr);
    commandList.ClearRenderTargetView(mGeometryBufferRenderTargetViews[BASECOLOR_METALMASK], zero, 0U, nullptr);

    BRE_CHECK_HR(commandList.Close());
    CommandListExecutor::Get().ExecuteCommandListAndWaitForCompletion(commandList);
}
}

As we can see, this pass executes all the command list recorders in parallel using Intel TBB. This is one of the strong points in BRE (NVIDIA recommendation), that you can parallelize command list recording and execution (because command lists are pushed to CommandListExecutor which runs in its own thread). Also, as we already mentioned, NVIDIA recommends minimizing draw calls and PSO changes.

Future Work

  • Currently, we have a single command list recorder per technique, and all the geometry that uses that technique is in the same command list recorder. We should benchmark different scenarios where the number of geometry and techniques is giant.
  • We should support new techniques.
  • We should add more parameters light height scale, etc., to height mapping technique
Advertisements

2 thoughts on “BRE Architecture Series Part 7 – Geometry Pass

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s