BRE Architecture Series Part 3 – Helpers

In this opportunity, we are going to talk about the commonly denominated “utilities” or “helpers” of BRE.

To explain in more detail, we are going to divide this part per library and file.

Utils Library

The Utils library has “helpers” for debugging purposes and string conversions. StringUtils has the following implementation and, as you can see, it contains functions related to conversions between strings and wide strings.

StringUtils.h

#pragma once

#include <string>

namespace BRE {
namespace StringUtils {
///
/// @brief Convert a string to wide string
/// @param source Source string
/// @param destination Destination wide string
///
 void AnsiToWideString(const std::string& source,
                       std::wstring& destination) noexcept;

///
/// @brief Converts ANSI string to wide string
/// @param str Source string
/// @return Wide string
///
std::wstring AnsiToWideString(const std::string& str) noexcept;
}
}

StringUtils.cpp

#include "StringUtils.h"

#include <codecvt>
#include <Windows.h>

namespace BRE {
namespace StringUtils {
void
AnsiToWideString(const std::string& source,
                 std::wstring& destination) noexcept
{
    destination.assign(source.begin(), source.end());
}

std::wstring
AnsiToWideString(const std::string& str) noexcept
{
    static const std::uint32_t bufferMaxSize = 512U;
    WCHAR buffer[bufferMaxSize];
    MultiByteToWideChar(CP_ACP, 0U, str.c_str(), -1, buffer, bufferMaxSize);
    return std::wstring(buffer);
}
}
}

I do not use wide strings but, for example, Windows MessageBox expects a wide string for its content. Also, DDSTextureLoader, from the DirectXTex library, expects wide strings too. That is why we need StringUtils.

DebugUtils maybe has the most used macros over all the application. Its implementation is the following

DebugUtils.h

#pragma once

#include <cassert>
#include <comdef.h>
#include <string>

#include <Utils\StringUtils.h>

#define BRE_ASSERT(condition) assert(condition)

#define BRE_CHECK_MSG(condition, msg) \
{ \
	if ((condition) == false) { \
		MessageBox(0, msg, 0, 0); \
		abort(); \
	} \
}

#ifndef BRE_CHECK_HR
#define BRE_CHECK_HR(x) \
{ \
    const HRESULT __hr__ = (x); \
	if (FAILED(__hr__)) { \
		_com_error err(__hr__); \
		const std::wstring errorMessage = err.ErrorMessage(); \
		MessageBox(0, errorMessage.c_str(), 0, 0); \
		abort(); \
	} \
}
#endif

BRE_ASSERT works directly as assert() in cassert library. BRE_CHECK_MSG checks if a condition is true or false, and iit is false it shows a MessageBox of Windows with a message about the error. This is very used when the scene is loaded or in similar situations, so the user has feedback about the error. By last, BRE_CHECK_HR checks HRESULT values returned by DirectX12 API and shows a MessageBox with the error message. This last function is not an assertion so it can be used in debug or release modes.

Math Utils Library

For the mathematical part, BRE uses DirectXMath library that has all the math stuff we need and it uses SIMD for operations with matrices and vectors. Despite this, its interface is not user-friendly and that is why we have MathUtils. It has methods to do lerp, clamp, min, max, etc., but it also has functions with a simpler interface (that under the hood uses DirectXMath functions) to perform vectors and matrices operations easily.

MathUtils.h

#pragma once

#include <Windows.h>
#include <DirectXMath.h>
#include <cstdint>

#include <Utils\DebugUtils.h>

namespace BRE {
///
/// @brief Responsible of mathematical utilities for vectors, matrices, etc
///
class MathUtils {
public:
    MathUtils() = delete;
    ~MathUtils() = delete;
    MathUtils(const MathUtils&) = delete;
    const MathUtils& operator=(const MathUtils&) = delete;
    MathUtils(MathUtils&&) = delete;
    MathUtils& operator=(MathUtils&&) = delete;

    ///
    /// @brief Computes a random float in an interval
    /// @param bottomValue Bottom value
    /// @param topValue Top Value
    /// @return Random value
    ///
    static float RandomFloatInInverval(const float bottomValue,
                                       const float topValue) noexcept
    {
        BRE_ASSERT(bottomValue < topValue);
        const float randomBetweenZeroAndOne = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
        return bottomValue + randomBetweenZeroAndOne * (topValue - bottomValue);
    }

    ///
    /// @brief Computes random integer in an interval
    /// @param a First integer in invertal
    /// @param b Second integer in interval
    /// @return Random integer
    ///
    static int RandomIntegerInInterval(const int32_t a,
                                       const int32_t b) noexcept
    {
        return a + rand() % ((b - a) + 1);
    }

    ///
    /// @brief Computes the minimum of 2 values
    /// @param a First value
    /// @param b Second value
    /// @return Minimum value
    ///
    template<typename T>
    static T Min(const T& a,
                 const T& b) noexcept
    {
        return a < b ? a : b;
    }

    ///
    /// @brief Computes the maximum of 2 values
    /// @param a First value
    /// @param b Second value
    /// @return Maximum value
    ///
    template<typename T>
    static T Max(const T& a,
                 const T& b) noexcept
    {
        return a > b ? a : b;
    }

    ///
    /// @brief Computes the linear interpolation
    /// @param a First value
    /// @param b Second value
    /// @param t Interpolation factor between 0 and 1
    /// @return Interpolated value
    ///
    template<typename T>
    static T Lerp(const T& a,
                  const T& b,
                  const float t) noexcept
    {
        return a + (b - a) * t;
    }

    ///
    /// @brief Clamp a value
    /// @param x Value
    /// @param low Low limit
    /// @param high High limit
    /// @return Clamped value
    ///
    template<typename T>
    static T Clamp(const T& x,
                   const T& low,
                   const T& high) noexcept
    {
        return x < low ? low : (x > high ? high : x);
    }

    ///
    /// @brief Get transpose matrix
    /// @param matrix Matrix to compute transpose
    /// @return Transposed matrix
    ///
    static DirectX::XMMATRIX GetTransposeMatrix(const DirectX::XMFLOAT4X4& matrix) noexcept
    {
        const DirectX::XMMATRIX xmMatrix = XMLoadFloat4x4(&matrix);
        return DirectX::XMMatrixTranspose(xmMatrix);
    }

    ///
    /// @brief Store transpose matrix
    /// @param sourceMatrix Matrix to compute transpose
    /// @param destinationMatrix Output transpose matrix
    ///
    static void StoreTransposeMatrix(const DirectX::XMFLOAT4X4& sourceMatrix,
                                     DirectX::XMFLOAT4X4& destinationMatrix) noexcept
    {
        const DirectX::XMMATRIX xmMatrix = GetTransposeMatrix(sourceMatrix);
        DirectX::XMStoreFloat4x4(&destinationMatrix, xmMatrix);
    }

    ///
    /// @brief Get inverse matrix
    /// @param matrix Matrix to compute inverse
    /// @return Inverted matrix
    ///
    static DirectX::XMMATRIX GetInverseMatrix(const DirectX::XMFLOAT4X4& matrix) noexcept
    {
        const DirectX::XMMATRIX xmMatrix = XMLoadFloat4x4(&matrix);
        return DirectX::XMMatrixInverse(nullptr, xmMatrix);
    }

    ///
    /// @brief Stores inverse matrix
    /// @param sourceMatrix Matrix to compute inverse
    /// @param destinationMatrix Output inverted matrix
    ///
    static void StoreInverseMatrix(const DirectX::XMFLOAT4X4& sourceMatrix,
                                   DirectX::XMFLOAT4X4& destinationMatrix) noexcept
    {
        const DirectX::XMMATRIX xmMatrix = GetInverseMatrix(sourceMatrix);
        DirectX::XMStoreFloat4x4(&destinationMatrix, xmMatrix);
    }

    ///
    /// @brief Get inverse transpose matrix
    /// @param matrix Matrix to compute inverse transpose
    /// @return Inverse transposed matrix
    ///
    static DirectX::XMMATRIX GetInverseTransposeMatrix(const DirectX::XMFLOAT4X4& matrix) noexcept
    {
        const DirectX::XMMATRIX xmInverseMatrix = GetInverseMatrix(matrix);
        DirectX::XMFLOAT4X4 inverseMatrix;
        DirectX::XMStoreFloat4x4(&inverseMatrix, xmInverseMatrix);
        return GetTransposeMatrix(inverseMatrix);
    }

    ///
    /// @brief Stores inverse transpose matrix
    /// @param sourceMatrix Matrix to compute inverse transpose
    /// @param destinationMatrix Output inverse transposed matrix
    ///
    static void StoreInverseTransposeMatrix(const DirectX::XMFLOAT4X4& sourceMatrix,
                                            DirectX::XMFLOAT4X4& destinationMatrix) noexcept
    {
        const DirectX::XMMATRIX xmMatrix = GetInverseTransposeMatrix(sourceMatrix);
        DirectX::XMStoreFloat4x4(&destinationMatrix, xmMatrix);
    }

    ///
    /// @brief Computes a matrix
    /// @param m Output matrix
    /// @param tx X translation
    /// @param ty Y translation
    /// @param tz Z translation
    /// @param sx X scale
    /// @param sy Y scale
    /// @param sz Z scale
    /// @param rx X rotation
    /// @param ry Y rotation
    /// @param rz Z rotation
    /// @return Transposed matrix
    ///
    static void ComputeMatrix(DirectX::XMFLOAT4X4& m,
                              const float tx,
                              const float ty,
                              const float tz,
                              const float sx = 1.0f,
                              const float sy = 1.0f,
                              const float sz = 1.0f,
                              const float rx = 0.0f,
                              const float ry = 0.0f,
                              const float rz = 0.0f) noexcept;

    ///
    /// @brief Get identity matrix of 4x4
    /// @return Indentity matrix
    ///
    static DirectX::XMFLOAT4X4 GetIdentity4x4Matrix() noexcept
    {
        DirectX::XMFLOAT4X4 identityMatrix(
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f);

        return identityMatrix;
    }

    static const float Infinity;
    static const float Pi;
};
}

MathUtils.cpp

#include "MathUtils.h"

#include <cfloat>
#include <cmath>

using namespace DirectX;

namespace BRE {
const float MathUtils::Infinity{ FLT_MAX };
const float MathUtils::Pi{ 3.1415926535f };

void
MathUtils::ComputeMatrix(XMFLOAT4X4& m,
                         const float tx,
                         const float ty,
                         const float tz,
                         const float sx,
                         const float sy,
                         const float sz,
                         const float rx,
                         const float ry,
                         const float rz) noexcept
{
    XMStoreFloat4x4(&m,
                    XMMatrixScaling(sx, sy, sz) *
                    XMMatrixRotationX(rx) *
                    XMMatrixRotationY(ry) *
                    XMMatrixRotationZ(rz) *
                    XMMatrixTranslation(tx, ty, tz));
}
}

YamlUtils in Scene Loader library

This file is located in SceneLoader library and it is a helper to deserialize YAML files (the format of BRE scenes). It uses a third-party library called Yaml-Cpp and its implementation is the following

YamlUtils.h

#pragma once

#include <string>
#pragma warning( push )
#pragma warning( disable : 4127)
#include <yaml-cpp/yaml.h>
#pragma warning( pop ) 

#include <Utils\DebugUtils.h>

namespace BRE {
///
/// @brief Utilities to handle YAML format 
///
class YamlUtils {
public:
    YamlUtils() = delete;
    ~YamlUtils() = delete;
    YamlUtils(const YamlUtils&) = delete;
    const YamlUtils& operator=(const YamlUtils&) = delete;
    YamlUtils(YamlUtils&&) = delete;
    YamlUtils& operator=(YamlUtils&&) = delete;

    ///
    /// @brief Get scalar 
    /// @param node YAML node
    /// @param scalar Output scalar
    ///
    template<typename T>
    static void GetScalar(const YAML::Node& node, T& scalar) noexcept
    {
        BRE_ASSERT(node.IsDefined());
        BRE_ASSERT(node.IsScalar());
        scalar = node.as<T>();
    }

    ///
    /// @brief Get sequence
    /// @param node YAML node
    /// @param sequenceOutput Output sequence
    /// @param numElems Number of elements in the sequence
    ///
    template<typename T>
    static void GetSequence(const YAML::Node& node,
                            T* const sequenceOutput,
                            const size_t numElems) noexcept
    {
        BRE_ASSERT(sequenceOutput != nullptr);
        BRE_ASSERT(node.IsDefined());
        BRE_ASSERT(node.IsSequence());
        size_t currentNumElems = 0UL;
        for (const YAML::Node& seqNode : node) {
            BRE_ASSERT(seqNode.IsScalar());
            BRE_ASSERT(currentNumElems < numElems);
            sequenceOutput[currentNumElems] = seqNode.as<T>();
            ++currentNumElems;
        }
        BRE_ASSERT(currentNumElems == numElems);
    }
};
}

Shader Utils Library

The ShaderUtils library obviously has stuff related with shaders. For example, CBuffers has its respective structs in C++ and HLSL to represent constant buffers that are modified per object, per frame or that are immutable. Its implementation is the following

CBuffers.h

#pragma once

#include <DirectXMath.h>

#include <MathUtils\MathUtils.h>
#include <ApplicationSettings\ApplicationSettings.h>

namespace BRE {
///
/// @brief Constant buffer per object
///
struct ObjectCBuffer {
    ObjectCBuffer() = default;
    ~ObjectCBuffer() = default;
    ObjectCBuffer(const ObjectCBuffer&) = default;
    ObjectCBuffer(ObjectCBuffer&&) = default;
    ObjectCBuffer& operator=(ObjectCBuffer&&) = default;

    DirectX::XMFLOAT4X4 mWorldMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mInverseTransposeWorldMatrix{ MathUtils::GetIdentity4x4Matrix() };
    float mTextureScaleFactor{ 2.0f };
};

///
/// @brief Constant buffer per frame
///
struct FrameCBuffer {
    FrameCBuffer() = default;
    ~FrameCBuffer() = default;
    FrameCBuffer(const FrameCBuffer&) = default;
    const FrameCBuffer& operator=(const FrameCBuffer&);
    FrameCBuffer(FrameCBuffer&&) = default;
    FrameCBuffer& operator=(FrameCBuffer&&) = default;

    DirectX::XMFLOAT4X4 mViewMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mInverseViewMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mProjectionMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mInverseProjectionMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4 mEyeWorldPosition{ 0.0f, 0.0f, 0.0f, 1.0f };
};
}

CBuffers.cpp

#include "CBuffers.h"

namespace BRE {
const FrameCBuffer&
FrameCBuffer::operator=(const FrameCBuffer& instance)
{
    if (this == &instance) {
        return *this;
    }

    mViewMatrix = instance.mViewMatrix;
    mInverseViewMatrix = instance.mInverseViewMatrix;
    mProjectionMatrix = instance.mProjectionMatrix;
    mInverseProjectionMatrix = instance.mInverseProjectionMatrix;
    mEyeWorldPosition = instance.mEyeWorldPosition;

    return *this;
}
}

CBuffers.hlsli

#ifndef CBUFFERS_HEADER
#define CBUFFERS_HEADER

// Per object constant buffer data
struct ObjectCBuffer {
	float4x4 mWorldMatrix;
	float4x4 mInverseTransposeWorldMatrix;
	float mTexTransform;
};

// Per frame constant buffer data
struct FrameCBuffer {	
	float4x4 mViewMatrix;
	float4x4 mInverseViewMatrix;
	float4x4 mProjectionMatrix;
	float4x4 mInverseProjectionMatrix;	
	float4 mEyePositionWorldSpace;
};

#endif

Another example is MaterialProperties, that similarly to CBuffers, it has its structs in C++ and HLSL. Its implementation is the following

MaterialProperties.h

#pragma once

#include <cstring>

namespace BRE {
///
/// @brief Represents material properties like base color, metal mask and smoothness.
///
class MaterialProperties {
public:
    MaterialProperties() = default;
    MaterialProperties(const float baseColorR,
                       const float baseColorG,
                       const float baseColorB,
                       const float metalMask,
                       const float smoothness)
    {
        mBaseColor_MetalMask[0U] = baseColorR;
        mBaseColor_MetalMask[1U] = baseColorG;
        mBaseColor_MetalMask[2U] = baseColorB;
        mBaseColor_MetalMask[3U] = metalMask;
        mSmoothness = smoothness;
    }

    ~MaterialProperties() = default;
    MaterialProperties(const MaterialProperties&) = default;
    MaterialProperties(MaterialProperties&&) = default;

    const MaterialProperties& operator=(const MaterialProperties& instance)
    {
        if (this == &instance) {
            return *this;
        }

        memcpy(mBaseColor_MetalMask, instance.mBaseColor_MetalMask, sizeof(mBaseColor_MetalMask));
        mSmoothness = instance.mSmoothness;

        return *this;
    }

private:
    float mBaseColor_MetalMask[4U]{ 1.0f, 1.0f, 1.0f, 0.0f };
    float mSmoothness{ 1.0f };
    float mPadding[3U];
};
}

MaterialProperties.hlsli

#ifndef MATERIALPROPERTIES_H
#define MATERIALPROPERTIES_H

struct MaterialProperties {
    float4 mBaseColor_MetalMask;
    float mSmoothness;
};

#endif 

By last, Lighting.hlsli has algorithms and data structures related with the lighting pass (PBR stuff like F, G and D terms, for example). Its implementation is the following

Lighting.hlsli

#ifndef LIGHTING_HEADER
#define LIGHTING_HEADER

//
// Constants
//
#define PI 3.141592f
#define F0_NON_METALS 0.04f

//
// Lighting
//

//
// Specular term:
//

// fSpecular = F(l, h) * G(l, v, h) * D(h) / 4 * dotNL * dotNV
//
// D(h) is the microgeometry normal distribution function(NDF) evaluated at the half-vector h; in other words, the
// concentration (relative to surface area) of surface points which are oriented such that they could reflect
// light from l into v.
//
// G(l, v, h) is the geometry function; it tells us the percentage of surface points with m = h that 
// are not shadowed or masked, as a function of the light direction l and the view direction v.
//
// Therefore, the product of D(h) and G(l; v; h) gives us the concentration of active surface
// points, the surface points that actively participate in the reflectance by successfully 
// reflecting light from l into v.
//
// F(l; h) is the Fresnel reflectance of the active surface points as a function of the light
// direction l and the active microgeometry normal m = h.
//
// It tells us how much of the incoming light is reflected from each of the active surface points.
//
// Finally, the denominator 4 * dotNL * dotNV is a correction factor, 
// which accounts for quantities being transformed between the local space of the microgeometry
// and that of the overall macrosurface.
//

//
// Fresnel Reflectance:
//
// The Fresnel reflectance function computes the fraction of light reflected from an optically
// flat surface.
//
// Its value depends on two things : the incoming angle (the angle between the light vector and the surface
// normal - also referred to as the incident angle or angle of incidence) and the refractive index of the
// material.
//
// Since the refractive index may vary over the visible spectrum, the Fresnel reflectance is a
// spectral quantity - for production purposes, an RGB triple.
//
// We also know that each of the RGB values have to lie within the 0 to 1 range, since a surface cannot 
// reflect less than 0 % or more than 100 % of the incoming light.
//
// Since the Fresnel reflectance stays close to the value for 0 over most of the visible parts of a given
// 3D scene, we can think of this value(which we will denote F0) as the characteristic specular reflectance
// of the material.
// 
// This value has all the properties of what is typically thought of as a "color", it is
// composed of RGB values between 0 and 1, and it is a measure of selective reflectance of light.
// For this reason, we will also refer to this value as the specular color of the surface.
//

// Schlick fresnel:
// f0 is the normal incidence reflectance (F() at 0 degrees, used as specular color)
// f90 is the reflectance at 90 degrees
float3
F_Schlick(const float3 f0,
          const float f90,
          const float dotLH)
{
    return f0 + (f90 - f0) * pow(1.0f - dotLH, 5.0f);
}

float
Fd_Disney(const float dotVN,
          const float dotLN,
          const float dotLH,
          float linearRoughness)
{
    float energyBias = lerp(0.0f, 0.5f, linearRoughness);
    float energyFactor = lerp(1.0, 1.0 / 1.51, linearRoughness);
    float fd90 = energyBias + 2.0 * dotLH * dotLH * linearRoughness;
    float f0 = 1.0f;
    float lightScatter = F_Schlick(f0, fd90, dotLN).x;
    float viewScatter = F_Schlick(f0, fd90, dotVN).x;
    return lightScatter * viewScatter * energyFactor;
}

//
// Normal distribution Function:
//
// The microgeometry in most surfaces does not have uniform distributions of surface point orientations.
//
// More surface points have normals pointing "up" (towards the macroscopic surface normal n) than
// "sideways" (away from n). 
//
// The statistical distribution of surface orientations is defined via the microgeometry 
// normal distribution function D(m). 
//
// Unlike F(), the value of D() is not restricted to lie between 0 and 1, although values must be non-negative, 
// they can be arbitrarily large (indicating a very high concentration of surface points with normals 
// pointing in a particular direction).
//
// Also, unlike F(), the function D() is not spectral nor RGB valued, but scalar valued.
//
// In microfacet BRDF terms, D() is evaluated for the direction h, to help determine 
// the concentration of potentially active surface points(those for which m = h).
//
// The function D() determines the size, brightness, and shape of the specular highlight.
//

// GGX/Trowbridge-Reitz
// m is roughness
float
D_TR(const float m,
     const float dotNH)
{
    const float m2 = m * m;
    const float denom = dotNH * dotNH * (m2 - 1.0f) + 1.0f;
    return m2 / (PI * denom * denom);
}

//
// Geometry function:
//
// The geometry function G(l, v, h) represents the probability that surface points with a given microgeometry
// normal m will be visible from both the light direction l and the view direction v.
//
// In the microfacet BRDF, m is replaced with h (for similar reasons as in the previous two terms).
//
// Since the function G() represents a probability, its value is a scalar and constrained to lie between 0 and 1.
//
// The geometry function typically does not introduce any new parameters to the BRDF; it either has no parameters, or
// uses the roughness parameter(s) of the D() function.
//
// In many cases, the geometry function partially cancels out the dotNL * dotNV denominator in fSpecular equation, replacing it with some other expression.
// The geometry function is essential for BRDF energy conservation, without such a term the BRDF
// can reflect arbitrarily more light energy than it receives.
//
// A key part of the microfacet BRDF derivation relates to the ratio between the active surface area
// (the area covered by surface regions that reflect light energy from l to v) and 
// the total surface area of the macroscopic surface.If shadowing and masking
// are not accounted for, then the active area may exceed the total area, an obvious impossibility which
// can lead to the BRDF not conserving energy, in some cases by a huge amount

float
V_SmithGGXCorrelated(float dotNL,
                     float dotNV,
                     float alphaG)
{
    // Original formulation of G_SmithGGX Correlated
    // lambda_v = (-1 + sqrt ( alphaG2 * (1 - dotNL2 ) / dotNL2 + 1)) * 0.5 f;
    // lambda_l = (-1 + sqrt ( alphaG2 * (1 - dotNV2 ) / dotNV2 + 1)) * 0.5 f;
    // G_SmithGGXCorrelated = 1 / (1 + lambda_v + lambda_l );
    // V_SmithGGXCorrelated = G_SmithGGXCorrelated / (4.0 f * dotNL * dotNV );

    // This is the optimized version
    float alphaG2 = alphaG * alphaG;
    // Caution : the " dotNL *" and " dotNV *" are explicitely inversed , this is not a mistake .
    float Lambda_GGXV = dotNL * sqrt((-dotNV * alphaG2 + dotNV) * dotNV + alphaG2);
    float Lambda_GGXL = dotNV * sqrt((-dotNL * alphaG2 + dotNL) * dotNL + alphaG2);

    return 0.5f / (Lambda_GGXV + Lambda_GGXL);
}

float
G_SmithGGX(const float dotNL,
           const float dotNV,
           float alpha)
{
    const float alphaSqr = alpha * alpha;
    const float G_V = dotNV + sqrt((dotNV - dotNV * alphaSqr) * dotNV + alphaSqr);
    const float G_L = dotNL + sqrt((dotNL - dotNL * alphaSqr) * dotNL + alphaSqr);

    return rcp(G_V * G_L);
}

//
// Diffuse term:
//

// Lambertian diffuse term
float3
Fd_Lambert(const float3 diffuseColor)
{
    return diffuseColor / PI;
}

float
Fr_DisneyDiffuse(const float dotNV,
                 const float dotNL,
                 const float dotLH,
                 const float linearRoughness)
{
    const float energyBias = lerp(0, 0.5, linearRoughness);
    const float energyFactor = lerp(1.0, 1.0 / 1.51, linearRoughness);
    const float fd90 = energyBias + 2.0 * dotLH * dotLH * linearRoughness;
    const float3 f0 = float3 (1.0f, 1.0f, 1.0f);
    const float lightScatter = F_Schlick(f0, fd90, dotNL).r;
    const float viewScatter = F_Schlick(f0, fd90, dotNV).r;

    return lightScatter * viewScatter * energyFactor;
}

//
// Image Based Lighting
//

float3
DiffuseIBL(const float3 baseColor,
           const float metalMask,
           SamplerState textureSampler,
           TextureCube diffuseIBLCubeMap,
           const float3 normalWorldSpace)
{
    // When we sample a cube map, we need to use data in world space, not view space.
    const float3 diffuseReflection = diffuseIBLCubeMap.SampleLevel(textureSampler,
                                                                   normalWorldSpace,
                                                                   0).rgb;
    const float3 diffuseColor = (1.0f - metalMask) * baseColor;

    return diffuseColor * diffuseReflection;
}

float3
SpecularIBL(const float3 baseColor,
            const float metalMask,
            const float smoothness,
            SamplerState textureSampler,
            TextureCube specularIBLCubeMap,
            const float3 viewVectorViewSpace,
            const float3 positionWorldSpace,
            const float3 eyePositionWorldSpace,
            const float3 normalWorldSpace,
            const float3 normalViewSpace)
{
    // Compute incident vector. 
    // When we sample a cube map, we need to use data in world space, not view space.
    const float3 incidentVectorWorldSpace = positionWorldSpace - eyePositionWorldSpace;
    const float3 reflectionVectorWorldSpace = reflect(incidentVectorWorldSpace,
                                                      normalWorldSpace);

    // Our cube map has 10 mip map levels
    const int mipmap = (1.0f - smoothness) * 10.0f;
    const float3 specularReflection = specularIBLCubeMap.SampleLevel(textureSampler,
                                                                     reflectionVectorWorldSpace,
                                                                     mipmap).rgb;

    // Specular reflection color
    const float3 dielectricColor = float3(0.04f, 0.04f, 0.04f);
    const float3 f0 = lerp(dielectricColor, baseColor, metalMask);
    const float3 F = F_Schlick(f0,
                               1.0f,
                               dot(viewVectorViewSpace, normalViewSpace));

    return F * specularReflection;
}

//
// BRDF
//

float3
DiffuseBrdf(const float3 baseColor,
            const float metalMask)
{
    const float3 diffuseColor = (1.0f - metalMask) * baseColor;
    return Fd_Lambert(diffuseColor);
}

float3
SpecularBrdf(const float3 N,
             const float3 V,
             const float3 L,
             const float3 baseColor,
             const float smoothness,
             const float metalMask)
{
    const float roughness = 1.0f - smoothness;

    // Disney's reparametrization of roughness
    const float alpha = roughness * roughness;

    const float3 H = normalize(V + L);
    const float dotNL = abs(dot(N, V)) + 1e-5f;
    const float dotNV = abs(dot(N, V)) + 1e-5f; // avoid artifacts
    const float dotNH = saturate(dot(N, H));
    const float dotLH = saturate(dot(L, H));

    //
    // Specular term: (D * F * G) / (4 * dotNL * dotNV)
    //

    const float D = D_TR(roughness, dotNH);

    const float3 f0 = (1.0f - metalMask) * float3(F0_NON_METALS,
                                                  F0_NON_METALS,
                                                  F0_NON_METALS) + baseColor * metalMask;
    const float3 F = F_Schlick(f0, 1.0f, dotLH);

    // G / (4 * dotNL * dotNV)
#ifdef V_SMITH
    const float G_Correlated = V_SmithGGXCorrelated(dotNV,
                                                    dotNL,
                                                    alpha);
#else
    const float G_Correlated = G_SmithGGX(dotNL,
                                          dotNV,
                                          alpha);
#endif

    return D * F * G_Correlated;
}

#endif

Timer Library

The Timer library contains the class Timer. Its functionality is very basic and it is useful to get the elapsed frame time. Its implementation is the following

Timer.h

#pragma once

#include <cstdint>

namespace BRE {
///
/// @brief Timer class used mainly to get frame time.
///
class Timer {
public:
    Timer();
    ~Timer() = default;
    Timer(const Timer&) = delete;
    const Timer& operator=(const Timer&) = delete;
    Timer(Timer&&) = delete;
    Timer& operator=(Timer&&) = delete;

    /// @brief Get delta time in seconds.
    ///
    /// Get delta time in seconds between 2 ticks, used to know the elapsed time between frames.
    ///
    /// @return Time in seconds
    ///
    __forceinline float GetDeltaTimeInSeconds() const noexcept
    {
        return static_cast<float>(mDeltaTimeInSeconds);
    }

    /// @brief Reset timer
    ///
    ///  This should be called exactly before the game loop.
    ///
    void Reset() noexcept;

    /// @brief Ticks the timer
    ///
    /// This will cause that delta time in seconds is updated.
    /// You should call it every frame.
    ///
    void Tick() noexcept;

private:
    double mSecondsPerCount{ 0.0 };
    double mDeltaTimeInSeconds{ 0.0 };
    std::int64_t mStartTickTime{ 0 };
    std::int64_t mPreviousTickTime{ 0 };
};
}

Timer.cpp

#include "Timer.h"

#include <windows.h>

namespace BRE {
Timer::Timer()
{
    std::int64_t countsPerSecond;
    QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&countsPerSecond));
    mSecondsPerCount = 1.0 / static_cast<double>(countsPerSecond);
    Reset();
}

void
Timer::Reset() noexcept
{
    std::int64_t currentTime;
    QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currentTime));

    mStartTickTime = currentTime;
    mPreviousTickTime = currentTime;
}

void
Timer::Tick() noexcept
{
    std::int64_t currentTime;
    QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currentTime));

    mDeltaTimeInSeconds = (currentTime - mPreviousTickTime) * mSecondsPerCount;

    mPreviousTickTime = currentTime;

    // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the 
    // processor goes into a power save mode or we get shuffled to another
    // processor, then mGetDeltaTimeInSeconds can be negative.
    if (mDeltaTimeInSeconds < 0.0) {
        mDeltaTimeInSeconds = 0.0;
    }
}
}

Camera Library

The Camera library contains only the class Camera. According to the user input (mouse and keyboard), we update its position and orientation. In this way, we can recompute and get the view and perspective projection matrices. Its implementation is the following

Camera.h

#pragma once

#include <DirectXMath.h>

#include <MathUtils\MathUtils.h>

namespace BRE {
///
/// @brief Perspective Projection Camera
///
class Camera {
public:
    Camera() = default;
    ~Camera() = default;

    ///
    /// @brief Get the camera position
    /// @return 4 homogeneous coordinates of the position of the camera
    ///
    __forceinline DirectX::XMFLOAT4 GetPosition4f() const noexcept
    {
        return DirectX::XMFLOAT4(mPosition.x, mPosition.y, mPosition.z, 1.0f);
    }

    ///
    /// @brief Set camera frustum
    /// @param verticalFieldOfView Vertical field of view angle in radians
    /// @param aspectRatio Aspect ratio of the screen
    /// @param nearPlaneZ Z coordinate of the near plane
    /// @param farPlaneZ Z coordinate of the far plane
    ///
    void SetFrustum(const float verticalFieldOfView,
                    const float aspectRatio,
                    const float nearPlaneZ,
                    const float farPlaneZ) noexcept;

    ///
    /// @brief Set camera position
    /// @param cameraPosition 3D coordinates of the camera position
    ///
    void SetPosition(const DirectX::XMFLOAT3& cameraPosition) noexcept;

    ///
    /// @brief Set look at and up camera vectors
    /// @param lookVector 3D coordinates of the look vector
    /// @param upVector 3D coordinates of the up vector
    ///
    void SetLookAndUpVectors(const DirectX::XMFLOAT3& lookVector,
                             const DirectX::XMFLOAT3& upVector) noexcept;

    ///
    /// @brief Get the view matrix
    /// @return View matrix as a XMFLOAT4X4
    ///
    __forceinline const DirectX::XMFLOAT4X4& GetViewMatrix() const noexcept
    {
        return mViewMatrix;
    }

    ///
    /// @brief Get the inverse of the view matrix
    /// @return Inverse of the view matrix as a XMFLOAT4X4
    ///
    __forceinline const DirectX::XMFLOAT4X4& GetInverseViewMatrix() const noexcept
    {
        return mInverseViewMatrix;
    }

    ///
    /// @brief Get the projection matrix
    /// @return Projection matrix as a XMFLOAT4X4
    ///
    __forceinline const DirectX::XMFLOAT4X4& GetProjectionMatrix() const noexcept
    {
        return mProjectionMatrix;
    }

    ///
    /// @brief Get the inverse of the projection matrix
    /// @return Inverse of the projection matrix as a XMFLOAT4X4
    ///
    __forceinline const DirectX::XMFLOAT4X4& GetInverseProjectionMatrix() const noexcept
    {
        return mInverseProjectionMatrix;
    }

    ///
    /// @brief Camera strafe
    /// 
    /// If distance is positive, then we will strafe left. 
    /// Otherwise, we will strafe right.
    ///
    /// @param distance The distance to strafe
    ///
    void Strafe(const float distance) noexcept;

    ///
    /// @brief Camera walk
    ///
    /// If distance is positive, then we will walk forward.
    /// Otherwise, we will walk backward.
    ///
    /// @param distance The distance to walk
    ///
    void Walk(const float distance) noexcept;

    ///
    /// @brief Pitch camera
    /// @param angleInRadians Angle to pitch in radians
    ///
    void Pitch(const float angleInRadians) noexcept;

    ///
    /// @brief Rotate camera around Y axis.
    /// @param angleInRadians Angle to rotate in radians
    ///
    void RotateY(const float angleInRadians) noexcept;

    ///
    /// @brief Update camera view matrix
    ///
    /// This update depends on events like Pitch, 
    /// RotateY, Strafe, and Walk.
    ///
    void UpdateViewMatrix() noexcept;

private:
    DirectX::XMFLOAT3 mPosition = { 0.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT3 mRightVector = { 1.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT3 mUpVector = { 0.0f, 1.0f, 0.0f };
    DirectX::XMFLOAT3 mLookVector = { 0.0f, 0.0f, 1.0f };
    DirectX::XMFLOAT3 mVelocityVector{ 0.0f, 0.0f, 0.0f };

    DirectX::XMFLOAT4X4 mViewMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mInverseViewMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mProjectionMatrix{ MathUtils::GetIdentity4x4Matrix() };
    DirectX::XMFLOAT4X4 mInverseProjectionMatrix{ MathUtils::GetIdentity4x4Matrix() };
};
}

Camera.cpp

#include "Camera.h"

#include <ApplicationSettings\ApplicationSettings.h>

using namespace DirectX;

namespace BRE {

void
Camera::SetFrustum(const float verticalFieldOfView,
                   const float aspectRatio,
                   const float nearPlaneZ,
                   const float farPlaneZ) noexcept
{
    const XMMATRIX projectionMatrix(XMMatrixPerspectiveFovLH(verticalFieldOfView,
                                                             aspectRatio,
                                                             nearPlaneZ,
                                                             farPlaneZ));

    XMStoreFloat4x4(&mProjectionMatrix, projectionMatrix);
    MathUtils::StoreInverseMatrix(mProjectionMatrix, mInverseProjectionMatrix);
}

void
Camera::SetPosition(const XMFLOAT3& cameraPosition) noexcept
{
    const XMVECTOR xmCameraPosition(XMLoadFloat3(&cameraPosition));
    XMStoreFloat3(&mPosition, xmCameraPosition);
}

void
Camera::SetLookAndUpVectors(const XMFLOAT3& lookVector,
                            const XMFLOAT3& upVector) noexcept
{
    XMVECTOR xmLookVector(XMLoadFloat3(&lookVector));
    XMVECTOR xmUpVector(XMLoadFloat3(&upVector));

    xmLookVector = XMVector3Normalize(xmLookVector);
    const XMVECTOR rightVector(XMVector3Normalize(XMVector3Cross(xmUpVector,
                                                                 xmLookVector)));
    xmUpVector = XMVector3Cross(xmLookVector, rightVector);

    XMStoreFloat3(&mLookVector, xmLookVector);
    XMStoreFloat3(&mRightVector, rightVector);
    XMStoreFloat3(&mUpVector, xmUpVector);
}

void
Camera::Strafe(const float distance) noexcept
{
    // velocity += right * dist 
    XMVECTOR rightVector(XMLoadFloat3(&mRightVector));
    rightVector = XMVectorScale(rightVector, distance);
    XMVECTOR velocityVector = XMLoadFloat3(&mVelocityVector);
    velocityVector = XMVectorAdd(velocityVector, rightVector);
    XMStoreFloat3(&mVelocityVector, velocityVector);
}

void
Camera::Walk(const float distance) noexcept
{
    // velocity += look * dist 
    XMVECTOR lookVector(XMLoadFloat3(&mLookVector));
    lookVector = XMVectorScale(lookVector, distance);
    XMVECTOR velocityVector = XMLoadFloat3(&mVelocityVector);
    velocityVector = XMVectorAdd(velocityVector, lookVector);
    XMStoreFloat3(&mVelocityVector, velocityVector);
}

void
Camera::Pitch(const float angleInRadians) noexcept
{
    // Rotate up and look vector about the right vector.
    const XMMATRIX rightVector(XMMatrixRotationAxis(XMLoadFloat3(&mRightVector),
                                                    angleInRadians));
    XMStoreFloat3(&mUpVector,
                  XMVector3TransformNormal(XMLoadFloat3(&mUpVector),
                                           rightVector));
    XMStoreFloat3(&mLookVector,
                  XMVector3TransformNormal(XMLoadFloat3(&mLookVector),
                                           rightVector));
}

void
Camera::RotateY(const float angleInRadians) noexcept
{
    // Rotate the basis vectors about the world y-axis.
    const XMMATRIX rotationYMatrix(XMMatrixRotationY(angleInRadians));
    XMStoreFloat3(&mRightVector,
                  XMVector3TransformNormal(XMLoadFloat3(&mRightVector),
                                           rotationYMatrix));
    XMStoreFloat3(&mUpVector,
                  XMVector3TransformNormal(XMLoadFloat3(&mUpVector),
                                           rotationYMatrix));
    XMStoreFloat3(&mLookVector,
                  XMVector3TransformNormal(XMLoadFloat3(&mLookVector),
                                           rotationYMatrix));
}

void
Camera::UpdateViewMatrix() noexcept
{
    static float maxVelocitySpeed{ 100.0f }; // speed = velocity magnitude
    static float velocityDamp{ 0.1f }; // fraction of velocity retained per second

    const float secondsPerFrame = ApplicationSettings::sSecondsPerFrame;

    // Clamp velocity
    XMVECTOR velocityVector = XMLoadFloat3(&mVelocityVector);
    const float velocityLength = XMVectorGetX(XMVector3Length(velocityVector));
    const float velocitySpeed = MathUtils::Clamp(velocityLength,
                                                 0.0f,
                                                 maxVelocitySpeed);
    if (velocitySpeed > 0.0f) {
        velocityVector = XMVector3Normalize(velocityVector) * velocitySpeed;
    }

    // Apply velocity and pitch-way-roll
    XMVECTOR position = XMLoadFloat3(&mPosition);
    position = position + velocityVector * secondsPerFrame;

    // Damp velocity
    velocityVector = velocityVector * static_cast<float>(pow(velocityDamp,
                                                             secondsPerFrame));

    // Keep camera's axes orthogonal to each other and of unit length.
    XMVECTOR rightVector(XMLoadFloat3(&mRightVector));
    XMVECTOR lookVector(XMLoadFloat3(&mLookVector));
    lookVector = XMVector3Normalize(lookVector);
    XMVECTOR upVector = XMVector3Normalize(XMVector3Cross(lookVector,
                                                          rightVector));

    // Up vector and look vector are already orthonormal, 
    // so no need to normalize cross product.
    rightVector = XMVector3Cross(upVector, lookVector);

    XMStoreFloat3(&mRightVector, rightVector);
    XMStoreFloat3(&mUpVector, upVector);
    XMStoreFloat3(&mLookVector, lookVector);
    XMStoreFloat3(&mPosition, position);
    XMStoreFloat3(&mVelocityVector, velocityVector);

    XMMATRIX viewMatrix = XMMatrixLookToLH(position, lookVector, upVector);
    XMStoreFloat4x4(&mViewMatrix, viewMatrix);
    MathUtils::StoreInverseMatrix(mViewMatrix, mInverseViewMatrix);
}
}

Input Library

The Input library contains two classes: Mouse and Keyboard. They are used to update and get the state of keyboard keys, mouse buttons and cursor position. Also, its interface is very simple and its implementation is the following

Mouse.h

#pragma once

#define DIRECTINPUT_VERSION 0x0800
#include <cstdint>
#include <dinput.h>

namespace BRE {
///
/// @brief Responsible to handle mouse updates and events
///
class Mouse {
public:
    ///
    /// @brief Create mouse
    ///
    /// Must be called once
    ///
    /// @param directInput Direct Input
    /// @param windowHandle Window handle
    /// @return The created mouse
    ///
    static Mouse& Create(IDirectInput8& directInput,
                         const HWND windowHandle) noexcept;

    ///
    /// @brief Get mouse
    ///
    /// Create must be called first
    ///
    /// @return The mouse
    ///
    static Mouse& Get() noexcept;

    enum MouseButton {
        MouseButtonsLeft = 0,
        MouseButtonsRight,
        MouseButtonsMiddle,
        MouseButtonsX1
    };

    ~Mouse();
    Mouse(const Mouse&) = delete;
    const Mouse& operator=(const Mouse&) = delete;
    Mouse(Mouse&&) = delete;
    Mouse& operator=(Mouse&&) = delete;

    ///
    /// @brief Update mouse state
    ///
    void UpdateMouseState();

    ///
    /// @brief Get mouse current state
    ///
    /// @return Mouse state
    ///
    __forceinline const DIMOUSESTATE& GetCurrentState() const
    {
        return mCurrentState;
    }

    ///
    /// @brief Get mouse last state
    ///
    /// @return Mouse state
    ///
    __forceinline const DIMOUSESTATE& GetLastState() const
    {
        return mLastState;
    }

    ///
    /// @brief Get x mouse coordinate
    /// @return Coordinate
    ///
    __forceinline std::int32_t GetX() const
    {
        return mX;
    }

    ///
    /// @brief Get y mouse coordinate
    /// @return Coordinate
    ///
    __forceinline std::int32_t GetY() const
    {
        return mY;
    }

    ///
    /// @brief Get wheel value
    /// @return Wheel value
    ///
    __forceinline std::int32_t GetWheel() const
    {
        return mWheel;
    }

    ///
    /// @brief Checks if button is up
    /// @param button Button to check
    /// @return True if button is up. False, otherwise
    ///
    __forceinline bool IsButtonUp(const MouseButton button) const
    {
        return (mCurrentState.rgbButtons[button] & 0x80) == 0;
    }

    ///
    /// @brief Checks if button is down
    /// @param button Button to check
    /// @return True if button is down. False, otherwise
    ///
    __forceinline bool IsButtonDown(const MouseButton button) const
    {
        return (mCurrentState.rgbButtons[button] & 0x80) != 0;
    }

    ///
    /// @brief Checks if button was up
    /// @param button Button to check
    /// @return True if button was up. False, otherwise
    ///
    __forceinline bool WasButtonUp(const MouseButton button) const
    {
        return (mLastState.rgbButtons[button] & 0x80) == 0;
    }

    ///
    /// @brief Checks if button was down
    /// @param button Button to check
    /// @return True if button was down. False, otherwise
    ///
    __forceinline bool WasButtonDown(const MouseButton button) const
    {
        return (mLastState.rgbButtons[button] & 0x80) != 0;
    }

    ///
    /// @brief Checks if button was pressed this frame
    /// @param button Button to check
    /// @return True if button was pressed this frame. False, otherwise
    ///
    __forceinline bool WasButtonPressedThisFrame(const MouseButton button) const
    {
        return IsButtonDown(button) && WasButtonUp(button);
    }

    ///
    /// @brief Checks if button was released this frame
    /// @param button Button to check
    /// @return True if button was released this frame. False, otherwise
    ///
    __forceinline bool WasButtonReleasedThisFrame(const MouseButton button) const
    {
        return IsButtonUp(button) && WasButtonDown(button);
    }

    ///
    /// @brief Checks if button is held down
    /// @param button Button to check
    /// @return True if button is held down. False, otherwise
    ///
    __forceinline bool IsButtonHeldDown(const MouseButton button) const
    {
        return IsButtonDown(button) && WasButtonDown(button);
    }

private:
    explicit Mouse(IDirectInput8& directInput, const HWND windowHandle);

    IDirectInput8& mDirectInput;
    LPDIRECTINPUTDEVICE8 mDevice{ nullptr };
    DIMOUSESTATE mCurrentState{};
    DIMOUSESTATE mLastState{};
    std::int32_t mX{ 0 };
    std::int32_t mY{ 0 };
    std::int32_t mWheel{ 0 };
};
}

Mouse.cpp

#include "Mouse.h"

#include <memory>

#include <Utils/DebugUtils.h>

namespace BRE {
namespace {
std::unique_ptr<Mouse> gMouse{ nullptr };
}

Mouse&
Mouse::Create(IDirectInput8& directInput,
              const HWND windowHandle) noexcept
{
    BRE_ASSERT(gMouse == nullptr);
    gMouse.reset(new Mouse(directInput, windowHandle));
    return *gMouse.get();
}
Mouse&
Mouse::Get() noexcept
{
    BRE_ASSERT(gMouse != nullptr);
    return *gMouse.get();
}

Mouse::Mouse(IDirectInput8& directInput,
             const HWND windowHandle)
    : mDirectInput(directInput)
{
    BRE_ASSERT(gMouse == nullptr);

    BRE_CHECK_HR(mDirectInput.CreateDevice(GUID_SysMouse, &mDevice, nullptr));
    BRE_ASSERT(mDevice != nullptr);
    BRE_CHECK_HR(mDevice->SetDataFormat(&c_dfDIMouse));
    BRE_CHECK_HR(mDevice->SetCooperativeLevel(windowHandle,
                                              DISCL_FOREGROUND | DISCL_NONEXCLUSIVE));
    mDevice->Acquire();
}

Mouse::~Mouse()
{
    BRE_ASSERT(mDevice != nullptr);

    mDevice->Unacquire();
    mDevice->Release();
}

void
Mouse::UpdateMouseState()
{
    BRE_ASSERT(mDevice != nullptr);

    memcpy(&mLastState, &mCurrentState, sizeof(mCurrentState));
    if (FAILED(mDevice->GetDeviceState(sizeof(mCurrentState),
                                       &mCurrentState)) &&
        SUCCEEDED(mDevice->Acquire()) &&
        FAILED(mDevice->GetDeviceState(sizeof(mCurrentState),
                                       &mCurrentState))) {
        return;
    }

    mX += mCurrentState.lX;
    mY += mCurrentState.lY;
    mWheel += mCurrentState.lZ;
}
}

Keyboard.h

#pragma once

#include <cstdint>
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

namespace BRE {
///
/// @brief Responsible to handle keyboard updates and events
///
class Keyboard {
public:
    ///
    /// @brief Create keyboard
    ///
    /// Must be called once
    ///
    /// @param directInput Direct Input
    /// @param windowHandle Window handle
    /// @return The created keyboard
    ///
    static Keyboard& Create(IDirectInput8& directInput,
                            const HWND windowHandle) noexcept;

    ///
    /// @brief Get keyboard
    ///
    /// Create must be called first
    ///
    /// @return The keyboard
    ///
    static Keyboard& Get() noexcept;

    ~Keyboard();
    Keyboard(const Keyboard&) = delete;
    const Keyboard& operator=(const Keyboard&) = delete;
    Keyboard(Keyboard&&) = delete;
    Keyboard& operator=(Keyboard&&) = delete;

    ///
    /// @brief Update keys state
    ///
    void UpdateKeysState() noexcept;

    ///
    /// @brief Get keys current state
    ///
    /// @return Pointer to the first key in the array of keys
    ///
    __forceinline const std::uint8_t* GetKeysCurrentState() const noexcept
    {
        return mKeysCurrentState;
    }

    ///
    /// @brief Get keys last state
    ///
    /// @return Pointer to the first key in the array of keys
    ///
    __forceinline const std::uint8_t* GetKeysLastState() const noexcept
    {
        return mKeysLastState;
    }

    ///
    /// @brief Checks if key is up
    /// @param key Key to check
    /// @return True if key is up. False, otherwise
    ///
    __forceinline bool IsKeyUp(const std::uint8_t key) const noexcept
    {
        return (mKeysCurrentState[key] & 0x80) == 0U;
    }

    ///
    /// @brief Checks if key is down
    /// @param key Key to check
    /// @return True if key is down. False, otherwise
    ///
    __forceinline bool IsKeyDown(const std::uint8_t key) const noexcept
    {
        return (mKeysCurrentState[key] & 0x80) != 0U;
    }

    ///
    /// @brief Checks if key was up
    /// @param key Key to check
    /// @return True if key was up. False, otherwise
    ///
    __forceinline bool WasKeyUp(const std::uint8_t key) const noexcept
    {
        return (mKeysLastState[key] & 0x80) == 0U;
    }

    ///
    /// @brief Checks if key was down
    /// @param key Key to check
    /// @return True if key was down. False, otherwise
    ///
    __forceinline bool WasKeyDown(const std::uint8_t key) const noexcept
    {
        return (mKeysLastState[key] & 0x80) != 0U;
    }

    ///
    /// @brief Checks if key was pressed this frame
    /// @param key Key to check
    /// @return True if key was pressed this frame. False, otherwise
    ///
    __forceinline bool WasKeyPressedThisFrame(const std::uint8_t key) const noexcept
    {
        return IsKeyDown(key) && WasKeyUp(key);
    }

    ///
    /// @brief Checks if key was released this frame
    /// @param key Key to check
    /// @return True if key was released this frame. False, otherwise
    ///
    __forceinline bool WasKeyReleasedThisFrame(const std::uint8_t key) const noexcept
    {
        return IsKeyUp(key) && WasKeyDown(key);
    }

    ///
    /// @brief Checks if key is held down
    /// @param key Key to check
    /// @return True if key is held down. False, otherwise
    ///
    __forceinline bool IsKeyHeldDown(const std::uint8_t key) const noexcept
    {
        return IsKeyDown(key) && WasKeyDown(key);
    }

private:
    explicit Keyboard(IDirectInput8& directInput, const HWND windowHandle);

    static const uint32_t sNumKeys = 256U;

    IDirectInput8& mDirectInput;
    LPDIRECTINPUTDEVICE8 mDevice{ nullptr };
    std::uint8_t mKeysCurrentState[sNumKeys] = {};
    std::uint8_t mKeysLastState[sNumKeys] = {};
};
}

Keyboard.cpp

#include "Keyboard.h"

#include <memory>

#include <Utils/DebugUtils.h>

namespace BRE {
namespace {
std::unique_ptr<Keyboard> gKeyboard{ nullptr };
}

Keyboard&
Keyboard::Create(IDirectInput8& directInput,
                 const HWND windowHandle) noexcept
{
    BRE_ASSERT(gKeyboard == nullptr);
    gKeyboard.reset(new Keyboard(directInput, windowHandle));
    return *gKeyboard.get();
}

Keyboard&
Keyboard::Get() noexcept
{
    BRE_ASSERT(gKeyboard != nullptr);
    return *gKeyboard.get();
}

Keyboard::Keyboard(IDirectInput8& directInput,
                   const HWND windowHandle)
    : mDirectInput(directInput)
{
    BRE_ASSERT(gKeyboard == nullptr);

    BRE_CHECK_HR(mDirectInput.CreateDevice(GUID_SysKeyboard, &mDevice, nullptr));
    BRE_ASSERT(mDevice != nullptr);
    BRE_CHECK_HR(mDevice->SetDataFormat(&c_dfDIKeyboard));
    BRE_CHECK_HR(mDevice->SetCooperativeLevel(windowHandle, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE));
    mDevice->Acquire();
}

Keyboard::~Keyboard()
{
    mDevice->Unacquire();
    mDevice->Release();
}

void
Keyboard::UpdateKeysState() noexcept
{
    BRE_ASSERT(mDevice != nullptr);

    memcpy(mKeysLastState, mKeysCurrentState, sizeof(mKeysCurrentState));
    if (FAILED(mDevice->GetDeviceState(sizeof(mKeysCurrentState),
                                       reinterpret_cast<LPVOID>(mKeysCurrentState))) &&
        SUCCEEDED(mDevice->Acquire())) {
        mDevice->GetDeviceState(sizeof(mKeysCurrentState), reinterpret_cast<LPVOID>(mKeysCurrentState));
    }
}
}

Geometry Generator Library

This library contains the class GeometryGenerator which has methods to generate different shapes like spheres, cubes, cylinders, grids, etc. Its implementation is the following

GeometryGenerator.h

#pragma once

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

namespace BRE {
///
/// @brief Responsible to generate procedurally the geometry of common mathematical objects.
///
/// All triangles are generated outward facing.
///
namespace GeometryGenerator {
struct Vertex {
    Vertex() = default;

    ///
    /// @brief Vertex constructor
    /// @param position Position
    /// @param normal Normal
    /// @param tangent Tangent
    /// @param uv UV
    ///
    explicit Vertex(const DirectX::XMFLOAT3& position,
                    const DirectX::XMFLOAT3& normal,
                    const DirectX::XMFLOAT3& tangent,
                    const DirectX::XMFLOAT2& uv);

    ~Vertex() = default;
    Vertex(const Vertex&) = default;
    Vertex(Vertex&&) = default;
    Vertex& operator=(Vertex&&) = default;

    DirectX::XMFLOAT3 mPosition = { 0.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT3 mNormal = { 0.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT3 mTangent = { 0.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT2 mUV = { 0.0f, 0.0f };
};

struct MeshData {
    std::vector<Vertex> mVertices;
    std::vector<std::uint32_t> mIndices32;

    ///
    /// @brief Get indices of 16 bytes each
    /// @return List of indices
    ///
    std::vector<std::uint16_t>& GetIndices16() noexcept;

private:
    std::vector<std::uint16_t> mIndices16{};
};

///
/// @brief Creates a box centered at the origin
/// @param width Width
/// @param height Height
/// @param depth Depth
/// @param numSubdivisions Number of subdivisions. Controls the degree of tessellation
/// @param meshData Output mesh data of the box
///
void CreateBox(const float width,
               const float height,
               const float depth,
               const std::uint32_t numSubdivisions,
               MeshData& meshData) noexcept;

///
/// @brief Creates a sphere centered at the origin
/// @param radius Radius
/// @param sliceCount Slice count. Controls the degree of tessellation.
/// @param stackCount Stack count. Controls the degree of tessellation.
/// @param meshData Output mesh data of the sphere
///
void CreateSphere(const float radius,
                  const std::uint32_t sliceCount,
                  const std::uint32_t stackCount,
                  MeshData& meshData) noexcept;

///
/// @brief Creates a geosphere centered at the origin
/// @param radius Radius
/// @param numSubdivisions Number of subdivisions. Controls the degree of tessellation
/// @param meshData Output mesh data of the geosphere
///
void CreateGeosphere(const float radius,
                     const std::uint32_t numSubdivisions,
                     MeshData& meshData) noexcept;

///
/// @brief Creates a cylinder parallel to the y-axis, and centered about the origin.  
///
/// The bottom and top radius can vary to form various cone shapes rather than true
/// cylinders. The slices and stacks parameters control the degree of tessellation.
///
/// @param bottomRadius Bottom radius
/// @param topRadius Top radius
/// @param height Height
/// @param sliceCount Slice count.
/// @param stackCount Stack count.
/// @param meshData Output mesh data of the cylinder
///
void CreateCylinder(const float bottomRadius,
                    const float topRadius,
                    const float height,
                    const std::uint32_t sliceCount,
                    const std::uint32_t stackCount,
                    MeshData& meshData) noexcept;

///
/// @brief Creates a grid in the xz-plane.
///
/// Creates a row x columns grid in the xz-plane with rows and columns, centered
/// at the origin with the specified width and depth.
///
/// @param width Width
/// @param height Height
/// @param rows Rows
/// @param columns Columns
/// @param meshData Output mesh data of the grid
///
void CreateGrid(const float width,
                const float depth,
                const std::uint32_t rows,
                const std::uint32_t columns,
                MeshData& meshData) noexcept;
}
}

GeometryGenerator.cpp

#include "GeometryGenerator.h"

#include <algorithm>

#include <Utils\DebugUtils.h>

using namespace DirectX;

namespace BRE {
namespace {
///
/// @brief Get middle point between vertices
/// @param vertex0 First vertex
/// @param vertex1 Second vertex
/// @return The middle vertex
///
GeometryGenerator::Vertex GetMiddlePoint(const GeometryGenerator::Vertex& vertex0,
                                         const GeometryGenerator::Vertex& vertex1) noexcept
{
    const XMVECTOR point0(XMLoadFloat3(&vertex0.mPosition));
    const XMVECTOR point1(XMLoadFloat3(&vertex1.mPosition));

    const XMVECTOR normal0(XMLoadFloat3(&vertex0.mNormal));
    const XMVECTOR normal1(XMLoadFloat3(&vertex1.mNormal));

    const XMVECTOR tangent0(XMLoadFloat3(&vertex0.mTangent));
    const XMVECTOR tangent1(XMLoadFloat3(&vertex1.mTangent));

    const XMVECTOR uv0(XMLoadFloat2(&vertex0.mUV));
    const XMVECTOR uv1(XMLoadFloat2(&vertex1.mUV));

    // Compute the midpoints of all the attributes. Vectors need to be normalized
    // since linear interpolating can make them not unit length.  
    const XMVECTOR position(0.5f * (point0 + point1));
    const XMVECTOR normal(XMVector3Normalize(0.5f * (normal0 + normal1)));
    const XMVECTOR tangent(XMVector3Normalize(0.5f * (tangent0 + tangent1)));
    const XMVECTOR uv(0.5f * (uv0 + uv1));

    GeometryGenerator::Vertex middleVertex;
    XMStoreFloat3(&middleVertex.mPosition, position);
    XMStoreFloat3(&middleVertex.mNormal, normal);
    XMStoreFloat3(&middleVertex.mTangent, tangent);
    XMStoreFloat2(&middleVertex.mUV, uv);

    return middleVertex;
}

///
/// @brief Subdivide geometry
/// @param meshData Input/Output mesh data to subdivide
///
void
Subdivide(GeometryGenerator::MeshData& meshData) noexcept
{
    // Save a copy of the input geometry.
    GeometryGenerator::MeshData inputCopy{ meshData };

    meshData.mVertices.resize(0U);
    meshData.mIndices32.resize(0U);

    //       v1
    //       *
    //      / \
        //     /   \
	//  m0*-----*m1
//   / \   / \
	//  /   \ /   \
	// *-----*-----*
// v0    m2     v2

    const std::uint32_t numTriangles{ static_cast<std::uint32_t>(inputCopy.mIndices32.size()) / 3U };
    for (std::uint32_t i = 0; i < numTriangles; ++i) {
        const std::uint32_t i3{ i * 3U };
        const GeometryGenerator::Vertex v0{ inputCopy.mVertices[inputCopy.mIndices32[i3 + 0U]] };
        const GeometryGenerator::Vertex v1{ inputCopy.mVertices[inputCopy.mIndices32[i3 + 1U]] };
        const GeometryGenerator::Vertex v2{ inputCopy.mVertices[inputCopy.mIndices32[i3 + 2U]] };

        //
        // Generate the midpoints.
        //

        const GeometryGenerator::Vertex m0{ GetMiddlePoint(v0, v1) };
        const GeometryGenerator::Vertex m1{ GetMiddlePoint(v1, v2) };
        const GeometryGenerator::Vertex m2{ GetMiddlePoint(v0, v2) };

        //
        // Add new geometry.
        //

        meshData.mVertices.push_back(v0); // 0
        meshData.mVertices.push_back(v1); // 1
        meshData.mVertices.push_back(v2); // 2
        meshData.mVertices.push_back(m0); // 3
        meshData.mVertices.push_back(m1); // 4
        meshData.mVertices.push_back(m2); // 5

        const std::uint32_t i6 = i * 6U;

        meshData.mIndices32.push_back(i6 + 0U);
        meshData.mIndices32.push_back(i6 + 3U);
        meshData.mIndices32.push_back(i6 + 5U);

        meshData.mIndices32.push_back(i6 + 3U);
        meshData.mIndices32.push_back(i6 + 4U);
        meshData.mIndices32.push_back(i6 + 5U);

        meshData.mIndices32.push_back(i6 + 5U);
        meshData.mIndices32.push_back(i6 + 4U);
        meshData.mIndices32.push_back(i6 + 2U);

        meshData.mIndices32.push_back(i6 + 3U);
        meshData.mIndices32.push_back(i6 + 1U);
        meshData.mIndices32.push_back(i6 + 4U);
    }
}

///
/// @brief Build cylinder top cap
/// @param topRadius Top radius
/// @param height Height
/// @param sliceCount Slice count
/// @param meshData Output mesh data with the cylinder top cap
///
void BuildCylinderTopCap(const float topRadius,
                         const float height,
                         const std::uint32_t sliceCount,
                         GeometryGenerator::MeshData& meshData) noexcept
{
    const std::uint32_t baseIndex{ static_cast<std::uint32_t>(meshData.mVertices.size()) };

    const float y{ 0.5f * height };
    const float dTheta{ 2.0f * XM_PI / sliceCount };

    // Duplicate cap ring mVertices because the texture coordinates and normals differ.
    for (std::uint32_t i = 0U; i <= sliceCount; ++i) {
        const float x{ topRadius * cosf(i * dTheta) };
        const float z{ topRadius * sinf(i * dTheta) };

        // Scale down by the height to try and make top cap texture coordinate area
        // proportional to base.
        const float u{ x / height + 0.5f };
        const float v{ z / height + 0.5f };

        meshData.mVertices.push_back(
            GeometryGenerator::Vertex{ XMFLOAT3{ x, y, z }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ u, v } }
        );
    }

    // Cap center vertex.
    meshData.mVertices.push_back(
        GeometryGenerator::Vertex{ XMFLOAT3{ 0.0f, y, 0.0f }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.5f, 0.5f } }
    );

    // Index of center vertex.
    const std::uint32_t centerIndex{ static_cast<std::uint32_t>(meshData.mVertices.size()) - 1U };

    for (std::uint32_t i = 0U; i < sliceCount; ++i) {
        meshData.mIndices32.push_back(centerIndex);
        meshData.mIndices32.push_back(baseIndex + i + 1U);
        meshData.mIndices32.push_back(baseIndex + i);
    }
}

///
/// @brief Build cylinder bottom cap
/// @param bottomRadius Bottom radius
/// @param height Height
/// @param sliceCount Slice count
/// @param meshData Output mesh data that includes the cylinder bottom cap
///
void BuildCylinderBottomCap(const float bottomRadius,
                            const float height,
                            const std::uint32_t sliceCount,
                            GeometryGenerator::MeshData& meshData) noexcept
{
    // 
    // Build bottom cap.
    //

    const std::uint32_t baseIndex{ static_cast<std::uint32_t>(meshData.mVertices.size()) };
    const float y{ -0.5f * height };

    // mVertices of ring
    const float dTheta{ 2.0f * XM_PI / sliceCount };
    for (std::uint32_t i = 0U; i <= sliceCount; ++i) {
        const float x{ bottomRadius * cosf(i * dTheta) };
        const float z{ bottomRadius * sinf(i * dTheta) };

        // Scale down by the height to try and make top cap texture coord area
        // proportional to base.
        const float u{ x / height + 0.5f };
        const float v{ z / height + 0.5f };

        meshData.mVertices.push_back(
            GeometryGenerator::Vertex{ XMFLOAT3{ x, y, z }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ u, v } }
        );
    }

    // Cap center vertex.
    meshData.mVertices.push_back(
        GeometryGenerator::Vertex{ XMFLOAT3{ 0.0f, y, 0.0f }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.5f, 0.5f } }
    );

    // Cache the index of center vertex.
    const std::uint32_t centerIndex{ static_cast<std::uint32_t>(meshData.mVertices.size()) - 1U };

    for (std::uint32_t i = 0U; i < sliceCount; ++i) {
        meshData.mIndices32.push_back(centerIndex);
        meshData.mIndices32.push_back(baseIndex + i);
        meshData.mIndices32.push_back(baseIndex + i + 1U);
    }
}
}

namespace GeometryGenerator {
Vertex::Vertex(const XMFLOAT3& position,
               const XMFLOAT3& normal,
               const XMFLOAT3& tangent,
               const XMFLOAT2& uv)
    : mPosition(position)
    , mNormal(normal)
    , mTangent(tangent)
    , mUV(uv)
{}

std::vector<std::uint16_t>&
MeshData::GetIndices16() noexcept
{
    if (mIndices16.empty()) {
        mIndices16.resize(mIndices32.size());
        for (std::size_t i = 0; i < mIndices32.size(); ++i) {
            mIndices16[i] = static_cast<std::uint16_t>(mIndices32[i]);
        }
    }

    return mIndices16;
}

void
CreateBox(const float width,
          const float height,
          const float depth,
          const std::uint32_t numSubdivisions,
          MeshData& meshData) noexcept
{
    //
    // Create the vertices.
    //

    Vertex v[24];

    float w2{ 0.5f * width };
    float h2{ 0.5f * height };
    float d2{ 0.5f * depth };

    // Fill in the front face vertex data.
    v[0] = Vertex(XMFLOAT3{ -w2, -h2, -d2 }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f, }, XMFLOAT2{ 0.0f, 1.0f });
    v[1] = Vertex(XMFLOAT3{ -w2, +h2, -d2 }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[2] = Vertex(XMFLOAT3{ +w2, +h2, -d2 }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 0.0f });
    v[3] = Vertex(XMFLOAT3{ +w2, -h2, -d2 }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 1.0f });

    // Fill in the back face vertex data.
    v[4] = Vertex(XMFLOAT3{ -w2, -h2, +d2 }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 1.0f });
    v[5] = Vertex(XMFLOAT3{ +w2, -h2, +d2 }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 1.0f });
    v[6] = Vertex(XMFLOAT3{ +w2, +h2, +d2 }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[7] = Vertex(XMFLOAT3{ -w2, +h2, +d2 }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 0.0f });

    // Fill in the top face vertex data.
    v[8] = Vertex(XMFLOAT3{ -w2, +h2, -d2 }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 1.0f });
    v[9] = Vertex(XMFLOAT3{ -w2, +h2, +d2 }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[10] = Vertex(XMFLOAT3{ +w2, +h2, +d2 }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 0.0f });
    v[11] = Vertex(XMFLOAT3{ +w2, +h2, -d2 }, XMFLOAT3{ 0.0f, 1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 1.0f });

    // Fill in the bottom face vertex data.
    v[12] = Vertex(XMFLOAT3{ -w2, -h2, -d2 }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 1.0f });
    v[13] = Vertex(XMFLOAT3{ +w2, -h2, -d2 }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 1.0f });
    v[14] = Vertex(XMFLOAT3{ +w2, -h2, +d2 }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[15] = Vertex(XMFLOAT3{ -w2, -h2, +d2 }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT2{ 1.0f, 0.0f });

    // Fill in the left face vertex data.
    v[16] = Vertex(XMFLOAT3{ -w2, -h2, +d2 }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT2{ 0.0f, 1.0f });
    v[17] = Vertex(XMFLOAT3{ -w2, +h2, +d2 }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[18] = Vertex(XMFLOAT3{ -w2, +h2, -d2 }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT2{ 1.0f, 0.0f });
    v[19] = Vertex(XMFLOAT3{ -w2, -h2, -d2 }, XMFLOAT3{ -1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, -1.0f }, XMFLOAT2{ 1.0f, 1.0f });

    // Fill in the right face vertex data.
    v[20] = Vertex(XMFLOAT3{ +w2, -h2, -d2 }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT2{ 0.0f, 1.0f });
    v[21] = Vertex(XMFLOAT3{ +w2, +h2, -d2 }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT2{ 0.0f, 0.0f });
    v[22] = Vertex(XMFLOAT3{ +w2, +h2, +d2 }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT2{ 1.0f, 0.0f });
    v[23] = Vertex(XMFLOAT3{ +w2, -h2, +d2 }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT3{ 0.0f, 0.0f, 1.0f }, XMFLOAT2{ 1.0f, 1.0f });

    meshData.mVertices.assign(&v[0], &v[24]);

    //
    // Create the indices.
    //

    std::uint32_t i[36U];

    // Fill in the front face index data
    i[0] = 0; i[1] = 1; i[2] = 2;
    i[3] = 0; i[4] = 2; i[5] = 3;

    // Fill in the back face index data
    i[6] = 4; i[7] = 5; i[8] = 6;
    i[9] = 4; i[10] = 6; i[11] = 7;

    // Fill in the top face index data
    i[12] = 8; i[13] = 9; i[14] = 10;
    i[15] = 8; i[16] = 10; i[17] = 11;

    // Fill in the bottom face index data
    i[18] = 12; i[19] = 13; i[20] = 14;
    i[21] = 12; i[22] = 14; i[23] = 15;

    // Fill in the left face index data
    i[24] = 16; i[25] = 17; i[26] = 18;
    i[27] = 16; i[28] = 18; i[29] = 19;

    // Fill in the right face index data
    i[30] = 20; i[31] = 21; i[32] = 22;
    i[33] = 20; i[34] = 22; i[35] = 23;

    meshData.mIndices32.assign(&i[0], &i[36]);

    // Put a cap on the number of subdivisions.
    const std::uint32_t clampedNumSubdivisions{ std::min<std::uint32_t>(numSubdivisions, 6U) };

    for (std::uint32_t j = 0U; j < clampedNumSubdivisions; ++j) {
        Subdivide(meshData);
    }
}

void
GeometryGenerator::CreateSphere(const float radius,
                                const std::uint32_t sliceCount,
                                const std::uint32_t stackCount,
                                MeshData& meshData) noexcept
{
    BRE_ASSERT(stackCount >= 2);
    BRE_ASSERT(sliceCount >= 1);

    //
    // Compute the vertices stating at the top pole and moving down the stacks.
    //

    // Poles: note that there will be texture coordinate distortion as there is
    // not a unique point on the texture map to assign to the pole when mapping
    // a rectangular texture onto a sphere.
    Vertex topVertex{ XMFLOAT3{ 0.0f, +radius, 0.0f }, XMFLOAT3{ 0.0f, +1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 0.0f } };
    Vertex bottomVertex{ XMFLOAT3{ 0.0f, -radius, 0.0f }, XMFLOAT3{ 0.0f, -1.0f, 0.0f }, XMFLOAT3{ 1.0f, 0.0f, 0.0f }, XMFLOAT2{ 0.0f, 1.0f } };

    meshData.mVertices.push_back(topVertex);

    const float phiStep{ XM_PI / stackCount };
    const float thetaStep{ 2.0f * XM_PI / sliceCount };

    // Compute vertices for each stack ring (do not count the poles as rings).
    std::uint32_t count{ stackCount - 1U };
    for (std::uint32_t i = 1U; i <= count; ++i) {
        const float phi{ i * phiStep };

        // Vertices of ring.
        for (std::uint32_t j = 0U; j <= sliceCount; ++j) {
            const float theta{ j * thetaStep };

            Vertex v;

            // spherical to cartesian
            v.mPosition.x = radius * sinf(phi) * cosf(theta);
            v.mPosition.y = radius * cosf(phi);
            v.mPosition.z = radius * sinf(phi) * sinf(theta);

            // Partial derivative of P with respect to theta
            v.mTangent.x = -radius * sinf(phi) * sinf(theta);
            v.mTangent.y = 0.0f;
            v.mTangent.z = +radius * sinf(phi) * cosf(theta);

            const XMVECTOR T(XMLoadFloat3(&v.mTangent));
            XMStoreFloat3(&v.mTangent, XMVector3Normalize(T));

            const XMVECTOR p(XMLoadFloat3(&v.mPosition));
            XMStoreFloat3(&v.mNormal, XMVector3Normalize(p));

            v.mUV.x = theta / XM_2PI;
            v.mUV.y = phi / XM_PI;

            meshData.mVertices.push_back(v);
        }
    }

    meshData.mVertices.push_back(bottomVertex);

    //
    // Compute indices for top stack.  The top stack was written first to the vertex buffer
    // and connects the top pole to the first ring.
    //

    for (std::uint32_t i = 1U; i <= sliceCount; ++i) {
        meshData.mIndices32.push_back(0U);
        meshData.mIndices32.push_back(i + 1U);
        meshData.mIndices32.push_back(i);
    }

    //
    // Compute indices for inner stacks (not connected to poles).
    //

    // Offset the indices to the index of the first vertex in the first ring.
    // This is just skipping the top pole vertex.
    std::uint32_t baseIndex{ 1U };
    std::uint32_t ringVertexCount{ sliceCount + 1U };
    count = stackCount - 2U;
    for (std::uint32_t i = 0U; i < count; ++i) {
        for (std::uint32_t j = 0U; j < sliceCount; ++j) {
            meshData.mIndices32.push_back(baseIndex + i * ringVertexCount + j);
            meshData.mIndices32.push_back(baseIndex + i * ringVertexCount + j + 1U);
            meshData.mIndices32.push_back(baseIndex + (i + 1U) * ringVertexCount + j);

            meshData.mIndices32.push_back(baseIndex + (i + 1U) * ringVertexCount + j);
            meshData.mIndices32.push_back(baseIndex + i * ringVertexCount + j + 1U);
            meshData.mIndices32.push_back(baseIndex + (i + 1U) * ringVertexCount + j + 1U);
        }
    }

    //
    // Compute indices for bottom stack.  The bottom stack was written last to the vertex buffer
    // and connects the bottom pole to the bottom ring.
    //

    // South pole vertex was added last.
    const std::uint32_t southPoleIndex{ static_cast<std::uint32_t>(meshData.mVertices.size()) - 1U };

    // Offset the indices to the index of the first vertex in the last ring.
    baseIndex = southPoleIndex - ringVertexCount;

    for (std::uint32_t i = 0U; i < sliceCount; ++i) {
        meshData.mIndices32.push_back(southPoleIndex);
        meshData.mIndices32.push_back(baseIndex + i);
        meshData.mIndices32.push_back(baseIndex + i + 1);
    }
}

void
GeometryGenerator::CreateGeosphere(const float radius,
                                   const std::uint32_t numSubdivisions,
                                   MeshData& meshData) noexcept
{
    // Put a cap on the number of subdivisions.
    const std::uint32_t clampedNumSubdivisions{ std::min<std::uint32_t>(numSubdivisions, 6U) };

    // Approximate a sphere by tessellating an icosahedron.

    const float X{ 0.525731f };
    const float Z{ 0.850651f };

    XMFLOAT3 pos[12] =
    {
        XMFLOAT3{ -X, 0.0f, Z },  XMFLOAT3{ X, 0.0f, Z },
        XMFLOAT3{ -X, 0.0f, -Z }, XMFLOAT3{ X, 0.0f, -Z },
        XMFLOAT3{ 0.0f, Z, X },   XMFLOAT3{ 0.0f, Z, -X },
        XMFLOAT3{ 0.0f, -Z, X },  XMFLOAT3{ 0.0f, -Z, -X },
        XMFLOAT3{ Z, X, 0.0f },   XMFLOAT3{ -Z, X, 0.0f },
        XMFLOAT3{ Z, -X, 0.0f },  XMFLOAT3{ -Z, -X, 0.0f }
    };

    std::uint32_t k[60U] =
    {
        1,4,0,  4,9,0,  4,5,9,  8,5,4,  1,8,4,
        1,10,8, 10,3,8, 8,3,5,  3,2,5,  3,7,2,
        3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
        10,1,6, 11,0,9, 2,11,9, 5,2,9,  11,2,7
    };

    meshData.mVertices.resize(12U);
    meshData.mIndices32.assign(&k[0U], &k[60U]);

    for (std::uint32_t i = 0U; i < 12U; ++i) {
        meshData.mVertices[i].mPosition = pos[i];
    }

    for (std::uint32_t i = 0U; i < clampedNumSubdivisions; ++i) {
        Subdivide(meshData);
    }

    // Project mVertices onto sphere and scale.
    for (std::uint32_t i = 0U; i < meshData.mVertices.size(); ++i) {
        // Project onto unit sphere.
        const XMVECTOR n(XMVector3Normalize(XMLoadFloat3(&meshData.mVertices[i].mPosition)));

        // Project onto sphere.
        const XMVECTOR p(radius * n);

        XMStoreFloat3(&meshData.mVertices[i].mPosition, p);
        XMStoreFloat3(&meshData.mVertices[i].mNormal, n);

        // Derive texture coordinates from spherical coordinates.
        float theta{ atan2f(meshData.mVertices[i].mPosition.z, meshData.mVertices[i].mPosition.x) };

        // Put in [0, 2pi].
        if (theta < 0.0f) {
            theta += XM_2PI;
        }

        const float phi{ acosf(meshData.mVertices[i].mPosition.y / radius) };

        meshData.mVertices[i].mUV.x = theta / XM_2PI;
        meshData.mVertices[i].mUV.y = phi / XM_PI;

        // Partial derivative of P with respect to theta
        meshData.mVertices[i].mTangent.x = -radius * sinf(phi) * sinf(theta);
        meshData.mVertices[i].mTangent.y = 0.0f;
        meshData.mVertices[i].mTangent.z = +radius * sinf(phi) * cosf(theta);

        const XMVECTOR T(XMLoadFloat3(&meshData.mVertices[i].mTangent));
        XMStoreFloat3(&meshData.mVertices[i].mTangent, XMVector3Normalize(T));
    }
}

void
CreateCylinder(const float bottomRadius,
               const float topRadius,
               const float height,
               const std::uint32_t sliceCount,
               const std::uint32_t stackCount,
               MeshData& meshData) noexcept
{
    //
    // Build Stacks.
    // 

    const float stackHeight{ height / stackCount };

    // Amount to increment radius as we move up each stack level from bottom to top.
    const float radiusStep{ (topRadius - bottomRadius) / stackCount };

    const std::uint32_t ringCount{ stackCount + 1U };

    // Compute mVertices for each stack ring starting at the bottom and moving up.
    for (std::uint32_t i = 0U; i < ringCount; ++i) {
        const float y{ -0.5f * height + i * stackHeight };
        const float r{ bottomRadius + i * radiusStep };

        // mVertices of ring
        const float dTheta{ 2.0f * XM_PI / sliceCount };
        for (std::uint32_t j = 0U; j <= sliceCount; ++j) {
            Vertex vertex;

            const float c{ cosf(j * dTheta) };
            const float s{ sinf(j * dTheta) };

            vertex.mPosition = XMFLOAT3{ r * c, y, r * s };

            vertex.mUV.x = static_cast<float>(j) / sliceCount;
            vertex.mUV.y = 1.0f - static_cast<float>(i) / stackCount;

            // Cylinder can be parameterized as follows, where we introduce v
            // parameter that goes in the same direction as the v tex-coord
            // so that the bitangent goes in the same direction as the v tex-coord.
            //   Let r0 be the bottom radius and let r1 be the top radius.
            //   y(v) = h - hv for v in [0,1].
            //   r(v) = r1 + (r0-r1)v
            //
            //   x(t, v) = r(v)*cos(t)
            //   y(t, v) = h - hv
            //   z(t, v) = r(v)*sin(t)
            // 
            //  dx/dt = -r(v)*sin(t)
            //  dy/dt = 0
            //  dz/dt = +r(v)*cos(t)
            //
            //  dx/dv = (r0-r1)*cos(t)
            //  dy/dv = -h
            //  dz/dv = (r0-r1)*sin(t)

            // This is unit length.
            vertex.mTangent = XMFLOAT3{ -s, 0.0f, c };

            const float dr{ bottomRadius - topRadius };
            const XMFLOAT3 bitangent{ dr * c, -height, dr * s };

            const XMVECTOR T(XMLoadFloat3(&vertex.mTangent));
            const XMVECTOR B(XMLoadFloat3(&bitangent));
            const XMVECTOR N(XMVector3Normalize(XMVector3Cross(T, B)));
            XMStoreFloat3(&vertex.mNormal, N);

            meshData.mVertices.push_back(vertex);
        }
    }

    // Add one because we duplicate the first and last vertex per ring
    // since the texture coordinates are different.
    const std::uint32_t ringVertexCount{ sliceCount + 1U };

    // Compute indices for each stack.
    for (std::uint32_t i = 0U; i < stackCount; ++i) {
        for (std::uint32_t j = 0U; j < sliceCount; ++j) {
            meshData.mIndices32.push_back(i * ringVertexCount + j);
            meshData.mIndices32.push_back((i + 1U) * ringVertexCount + j);
            meshData.mIndices32.push_back((i + 1U) * ringVertexCount + j + 1U);

            meshData.mIndices32.push_back(i * ringVertexCount + j);
            meshData.mIndices32.push_back((i + 1U) * ringVertexCount + j + 1U);
            meshData.mIndices32.push_back(i * ringVertexCount + j + 1U);
        }
    }

    BuildCylinderTopCap(topRadius, height, sliceCount, meshData);
    BuildCylinderBottomCap(bottomRadius, height, sliceCount, meshData);
}

void
CreateGrid(const float width,
           const float depth,
           const std::uint32_t rows,
           const std::uint32_t columns,
           MeshData& meshData) noexcept
{
    const std::uint32_t vertexCount{ rows * columns };
    const std::uint32_t faceCount{ (rows - 1U) * (columns - 1U) * 2U };

    //
    // Create the mVertices.
    //

    const float halfWidth{ 0.5f * width };
    const float halfDepth{ 0.5f * depth };

    const float dx{ width / (columns - 1U) };
    const float dz{ depth / (rows - 1U) };

    const float du{ 1.0f / (columns - 1U) };
    const float dv{ 1.0f / (rows - 1U) };

    meshData.mVertices.resize(vertexCount);
    for (std::uint32_t i = 0U; i < rows; ++i) {
        const float z{ halfDepth - i * dz };
        for (std::uint32_t j = 0U; j < columns; ++j) {
            const float x{ -halfWidth + j * dx };

            meshData.mVertices[i * columns + j].mPosition = XMFLOAT3{ x, 0.0f, z };
            meshData.mVertices[i * columns + j].mNormal = XMFLOAT3{ 0.0f, 1.0f, 0.0f };
            meshData.mVertices[i * columns + j].mTangent = XMFLOAT3{ 1.0f, 0.0f, 0.0f };

            // Stretch texture over grid.
            meshData.mVertices[i * columns + j].mUV.x = j * du;
            meshData.mVertices[i * columns + j].mUV.y = i * dv;
        }
    }

    //
    // Create the indices.
    //
    // 3 indices per face
    meshData.mIndices32.resize(faceCount * 3U);

    // Iterate over each quad and compute indices.
    std::uint32_t k{ 0U };
    const std::uint32_t count1{ rows - 1U };
    const std::uint32_t count2{ columns - 1U };
    for (std::uint32_t i = 0U; i < count1; ++i) {
        for (std::uint32_t j = 0U; j < count2; ++j) {
            meshData.mIndices32[k] = i * columns + j;
            meshData.mIndices32[k + 1U] = i * columns + j + 1U;
            meshData.mIndices32[k + 2U] = (i + 1U) * columns + j;

            meshData.mIndices32[k + 3U] = (i + 1U) * columns + j;
            meshData.mIndices32[k + 4U] = i * columns + j + 1U;
            meshData.mIndices32[k + 5U] = (i + 1U) * columns + j + 1U;

            k += 6U; // next quad
        }
    }
}
}
}

Application Settings Library

In this library, we have a single static class that contains a lot of global settings of the application like screen resolution, render target formats, aspect ratio, near and far plane Z coordinates, the number of queued frames, viewport, etc. Its implementation is the following

ApplicationSettings.h

#pragma once

#include <cstdint>
#include <d3d12.h>

namespace BRE {
///
/// @brief Responsible to handle all main settings
///
class ApplicationSettings {
public:
    ApplicationSettings() = delete;
    ~ApplicationSettings() = delete;
    ApplicationSettings(const ApplicationSettings&) = delete;
    const ApplicationSettings& operator=(const ApplicationSettings&) = delete;
    ApplicationSettings(ApplicationSettings&&) = delete;
    ApplicationSettings& operator=(ApplicationSettings&&) = delete;

    ///
    /// @brief Get aspect ratio
    /// @return Aspect ratio
    ///
    __forceinline static float GetAspectRatio() noexcept
    {
        return static_cast<float>(sWindowWidth) / sWindowHeight;
    }

    static bool sIsFullscreenWindow;
    static std::uint32_t sCpuProcessorCount;
    static const std::uint32_t sSwapChainBufferCount{ 4U };
    static const std::uint32_t sQueuedFrameCount{ sSwapChainBufferCount - 1U };
    static std::uint32_t sWindowWidth;
    static std::uint32_t sWindowHeight;

    // Final buffer render target
    static const DXGI_FORMAT sFrameBufferRTFormat;
    static const DXGI_FORMAT sFrameBufferFormat;

    // Color buffer used for intermediate computations (light pass, post processing passes, etc)
    static const DXGI_FORMAT sColorBufferFormat;

    // Used when creating depth stencil buffer
    static const DXGI_FORMAT sDepthStencilFormat;

    // Used when creating a view to depth stencil buffer
    static const DXGI_FORMAT sDepthStencilViewFormat;

    // Used when creating a shader resource view to the depth stencil buffer
    static const DXGI_FORMAT sDepthStencilSRVFormat;

    static float sNearPlaneZ;
    static float sFarPlaneZ;
    static float sVerticalFieldOfView;

    static D3D12_VIEWPORT sScreenViewport;
    static D3D12_RECT sScissorRect;

    // Used to update physics. If you
    // want a fixed update time step, for example,
    // 60 FPS, then you should store 1.0f / 60.0f here
    static const float sSecondsPerFrame;
};
}

ApplicationSettings.cpp

#include "ApplicationSettings.h"

#include <cfloat>

#include <MathUtils/MathUtils.h>

namespace BRE {
bool ApplicationSettings::sIsFullscreenWindow{ true };
std::uint32_t ApplicationSettings::sCpuProcessorCount{ 4U }; // This should be changed according your processor
std::uint32_t ApplicationSettings::sWindowWidth{ 1920U };
std::uint32_t ApplicationSettings::sWindowHeight{ 1080U };

const DXGI_FORMAT ApplicationSettings::sFrameBufferRTFormat{ DXGI_FORMAT_R10G10B10A2_UNORM };
const DXGI_FORMAT ApplicationSettings::sFrameBufferFormat{ DXGI_FORMAT_R10G10B10A2_UNORM };

const DXGI_FORMAT ApplicationSettings::sColorBufferFormat{ DXGI_FORMAT_R16G16B16A16_FLOAT };
const DXGI_FORMAT ApplicationSettings::sDepthStencilFormat{ DXGI_FORMAT_R32_TYPELESS };
const DXGI_FORMAT ApplicationSettings::sDepthStencilViewFormat{ DXGI_FORMAT_D32_FLOAT };
const DXGI_FORMAT ApplicationSettings::sDepthStencilSRVFormat{ DXGI_FORMAT_R32_FLOAT };

float ApplicationSettings::sNearPlaneZ{ 1.0f };
float ApplicationSettings::sFarPlaneZ{ FLT_MAX };
float ApplicationSettings::sVerticalFieldOfView{ 0.25f * MathUtils::Pi };

D3D12_VIEWPORT ApplicationSettings::sScreenViewport{
    0.0f,
    0.0f,
    static_cast<float>(ApplicationSettings::sWindowWidth),
    static_cast<float>(ApplicationSettings::sWindowHeight),
    0.0f,
    1.0f };

D3D12_RECT ApplicationSettings::sScissorRect{
    0,
    0,
    static_cast<LONG>(ApplicationSettings::sWindowWidth),
    static_cast<LONG>(ApplicationSettings::sWindowHeight) };

const float ApplicationSettings::sSecondsPerFrame{ 1.0f / 60.0f };
}

Of course, the ideal situation is to have dynamic settings that can be changed in runtime, like any application. In my case, in addition to choosing scenes that are loaded at the beginning of the application and that cannot be modified in runtime, also for the sake of simplicity I choose to have immutable settings too. I wanted to reduce the complexity of certain areas, and this is one of them.

Environment Light Settings and GeometrySettings

Similar to ApplicationSettings but related with environment light and geometry respectively.

EnvironmentLightSettings.h

#pragma once

#include <cstdint>

namespace BRE {
///
/// @brief Responsible to handle all environment light settings
///
class EnvironmentLightSettings {
public:
    EnvironmentLightSettings() = delete;
    ~EnvironmentLightSettings() = delete;
    EnvironmentLightSettings(const EnvironmentLightSettings&) = delete;
    const EnvironmentLightSettings& operator=(const EnvironmentLightSettings&) = delete;
    EnvironmentLightSettings(EnvironmentLightSettings&&) = delete;
    EnvironmentLightSettings& operator=(EnvironmentLightSettings&&) = delete;
    
    // Ambient occlusion constants
    static std::uint32_t sSampleKernelSize;
    static std::uint32_t sNoiseTextureDimension;
    static float sOcclusionRadius;
    static float sSsaoPower;
};
}

EnvironmentLightSettings.cpp

#include "EnvironmentLightSettings.h"

namespace BRE {
// Ambient occlusion constants
std::uint32_t EnvironmentLightSettings::sSampleKernelSize{ 32U };
std::uint32_t EnvironmentLightSettings::sNoiseTextureDimension{ 4U };
float EnvironmentLightSettings::sOcclusionRadius{ 10.0f };
float EnvironmentLightSettings::sSsaoPower{ 2.0f };
}

GeometrySettings.h

#pragma once

#include <cstdint>

namespace BRE {
///
/// @brief Responsible to handle all geometry pass settings
///
class GeometrySettings {
public:
    GeometrySettings() = delete;
    ~GeometrySettings() = delete;
    GeometrySettings(const GeometrySettings&) = delete;
    const GeometrySettings& operator=(const GeometrySettings&) = delete;
    GeometrySettings(GeometrySettings&&) = delete;
    GeometrySettings& operator=(GeometrySettings&&) = delete;
    
    // Height mapping constants
    static float sMinTessellationDistance;
    static float sMaxTessellationDistance;
    static float sMinTessellationFactor;
    static float sMaxTessellationFactor;
    static float sHeightScale;
};
}

GeometrySettings.cpp

#include "GeometrySettings.h"

namespace BRE {
// Height mapping constants
float GeometrySettings::sMinTessellationDistance{ 25.0f };
float GeometrySettings::sMaxTessellationDistance{ 50.0f };
float GeometrySettings::sMinTessellationFactor{ 1.0f };
float GeometrySettings::sMaxTessellationFactor{ 5.0f };
float GeometrySettings::sHeightScale{ 3.5f };
}

Third-Party Libraries

Other libraries I use are the following:

  • Intel TBB for multithreading
  • Assimp for 3D model loading
  • Yaml-Cpp to deserialize YAML files (the format we use for our scenes)
  • DirectXTex for textures loading

Future Work

  • Maybe include a library for text rendering with DirectX12
  • Maybe include an UI library like WPF, Qt, or something similar to have some menus, combo boxes, or similar stuff
Advertisements

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