Chapter 16. Materials

In this chapter, you develop a set of types to encapsulate and simplify the interactions with effects. Then you exercise these types through a set of demo applications. You also create a reusable component for rendering skyboxes.

Motivation

If you examine the last several demos (the triangle demo, the cube demo, and the two model demos), you’ll see quite a bit of duplicated code: the shader compilation, the effect object creation, the technique, pass and variable lookup, input layout creation, and vertex and index buffer creation—all this code is repeated. If you’re like me, you’re itching to modularize this code. Furthermore, the types and data members for interfacing with the BasicEffect shader (the BasicEffectVertex structure, mEffect, mTechnique, mPass, and mWvpVariable) are duplicated between demos. The BasicEffect shader is one of the simplest effects you can apply. Consider some of the more sophisticated shaders you’ve authored. You might have a dozen or more shader variables and multiple techniques and passes. Clearly, you don’t want to duplicate the data members and initialization of such shaders each time you want to use them. This calls for a reusable data structure that can represent a specific effect.

Recall the NVIDIA FX Composer terminology for describing an instance of an effect: material. We adopt this terminology to create a set of data structures that support interactions with a given effect. A Material class is the root structure for this system. Along the way, you modularize the systems for compiling shaders and creating vertex buffers, and you learn about compiling shaders at build time.

The material system consists of the following classes: Effect, Technique, Pass, Variable, and Material. The Effect, Technique, Pass, and Variable classes are thin wrappers around corresponding Effects 11 types (they add functionality and simplify the Effects 11 library usage). A Material object references a single Effect. The next few sections discuss these types in detail.

The Effect Class

The Effect class wraps the ID3DX11Effect type (the root interface of the Effects 11 library) and exposes the contained techniques and shader variables through standard template library (STL) maps. The Effect class also encapsulates the code for compiling and loading shaders. Essentially, this is just a helper class that makes using effects a bit easier. Listing 16.1 presents the declaration of the Effect class.

Listing 16.1 The Effect.h Header File


#pragma once

#include "Common.h"
#include "Technique.h"
#include "Variable.h"

namespace Library
{
    class Game;

    class Effect
    {
    public:
        Effect(Game& game);
        virtual ~Effect();

        static void CompileEffectFromFile(ID3D11Device* direct3DDevice, ID3DX11Effect** effect, const std::wstring& filename);
        static void LoadCompiledEffect(ID3D11Device* direct3DDevice, ID3DX11Effect** effect, const std::wstring& filename);

        Game& GetGame();
        ID3DX11Effect* GetEffect() const;
        void SetEffect(ID3DX11Effect* effect);
        const D3DX11_EFFECT_DESC& EffectDesc() const;
        const std::vector<Technique*>& Techniques() const;
        const std::map<std::string, Technique*>& TechniquesByName()
const;
        const std::vector<Variable*>& Variables() const;
        const std::map<std::string, Variable*>& VariablesByName()
const;

        void CompileFromFile(const std::wstring& filename);
        void LoadCompiledEffect(const std::wstring& filename);

    private:
        Effect(const Effect& rhs);
        Effect& operator=(const Effect& rhs);

        void Initialize();

        Game& mGame;
        ID3DX11Effect* mEffect;
        D3DX11_EFFECT_DESC mEffectDesc;
        std::vector<Technique*> mTechniques;
        std::map<std::string, Technique*> mTechniquesByName;
        std::vector<Variable*> mVariables;
        std::map<std::string, Variable*> mVariablesByName;
    };
}


The most interesting elements of the Effect class are the CompileFromFile(), LoadCompiledEffect(), and Initialize() methods. Listing 16.2 shows these implementations. The remaining implementation is made of single-line accessors, the destructor, and nonstatic pass-through methods for compiling and loading effects. For brevity, the listing omits these. You can find the full source code on the companion website.

Listing 16.2 The Effect Class Implementation (Abbreviated)


void Effect::CompileEffectFromFile(ID3D11Device* direct3DDevice,
ID3DX11Effect** effect, const std::wstring& filename)
{
    UINT shaderFlags = 0;

#if defined( DEBUG ) || defined( _DEBUG )
    shaderFlags |= D3DCOMPILE_DEBUG;
    shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

    ID3D10Blob* compiledShader = nullptr;
    ID3D10Blob* errorMessages = nullptr;
    HRESULT hr = D3DCompileFromFile(filename.c_str(), nullptr, nullptr,
nullptr, "fx_5_0", shaderFlags, 0, &compiledShader, &errorMessages);
    if (errorMessages != nullptr)
    {
        GameException ex((char*)errorMessages->GetBufferPointer(), hr);
        ReleaseObject(errorMessages);

        throw ex;
    }

    if (FAILED(hr))
    {
        throw GameException("D3DX11CompileFromFile() failed.", hr);
    }

    hr = D3DX11CreateEffectFromMemory(compiledShader-
>GetBufferPointer(), compiledShader->GetBufferSize(), NULL,
direct3DDevice, effect);
    if (FAILED(hr))
    {
        throw GameException("D3DX11CreateEffectFromMemory() failed.",
hr);
    }

    ReleaseObject(compiledShader);
}

void Effect::LoadCompiledEffect(ID3D11Device* direct3DDevice,
ID3DX11Effect** effect, const std::wstring& filename)
{
    std::vector<char> compiledShader;
    Utility::LoadBinaryFile(filename, compiledShader);

    HRESULT hr = D3DX11CreateEffectFromMemory(&compiledShader.front(), compiledShader.size(), NULL, direct3DDevice, effect);
    if (FAILED(hr))
    {
        throw GameException("D3DX11CreateEffectFromMemory() failed."
, hr);
      }
}
void Effect::Initialize()
{
    HRESULT hr = mEffect->GetDesc(&mEffectDesc);
    if (FAILED(hr))
    {
        throw GameException("ID3DX11Effect::GetDesc() failed.", hr);
    }

    for (UINT i = 0; i < mEffectDesc.Techniques; i++)
    {
        Technique* technique = new Technique(mGame, *this,
mEffect->GetTechniqueByIndex(i));
        mTechniques.push_back(technique);
        mTechniquesByName.insert(std::pair<std::string,
Technique*>(technique->Name(), technique));
    }

    for (UINT i = 0; i < mEffectDesc.GlobalVariables; i++)
    {
        Variable* variable = new Variable(*this,
mEffect->GetVariableByIndex(i));
        mVariables.push_back(variable);
        mVariablesByName.insert(std::pair<std::string,
Variable*>(variable->Name(), variable));
    }
}


You’ve seen the code in the Effect::CompileEffectFromFile() method before. This is just runtime shader compilation encapsulated within a class that stores the associated ID3DX11Effect object. New is the Effect::LoadCompiledEffect() method, which loads a compiled shader object into memory and invokes the same D3DX11CreateEffectFromMemory() method to instantiate an effect object. A compiled shader object is the output of the compilation process and can be saved to a file with a .cso extension. So how do you produce a .cso file to load?

First, add the BasicEffect.fx file to one of your Visual Studio projects, either the Library project or the Game project. Then open the Property Pages for the project and navigate to Configuration Properties, HLSL Compiler, General. Set the Shader Type parameter to Effect (/fx) and the Shader Model parameter to Shader Model 5 (/5_0), as in Figure 16.1. Be sure to do this for both the debug and release configurations.

Image

Figure 16.1 Visual Studio’s property pages for HLSL compiler parameters.

Now when you build your Visual Studio project, .cso files will be built for the included effects. By default, these .cso files will be written to the same directory as your library or executable (the Visual Studio $(OutDir) macro). If you prefer a different location, modify the Configuration Properties, HLSL Compiler, Output Files, Object File Name parameter. The projects on the book’s companion website use this setting:

$(OutDir)ContentEffects\%(Filename).cso

Furthermore, if you include your source .fx files in your Library project, be sure that all .cso files are copied to a path accessible by your application’s executable. A post-build step can handle this.

The last Effect topic to discuss is the Initialize() method (see Listing 16.2). This method retrieves the D3DX11_EFFECT_DESC structure (the description structure for the effect), iterates through the available techniques and shader variables, and instantiates their wrapping classes.

The Technique Class

The Technique class is the simplest of the Effects 11 wrapping types. It just exposes the
name of the technique and its associated passes. Listings 16.3 presents the declaration of the Technique class.

Listing 16.3 The Technique.h Header File


#pragma once

#include "Common.h"
#include "Pass.h"

namespace Library
{
    class Game;
    class Effect;

    class Technique
    {
    public:
        Technique(Game& game, Effect& effect, ID3DX11EffectTechnique* technique);
        ~Technique();

        Effect& GetEffect();
        ID3DX11EffectTechnique* GetTechnique() const;
        const D3DX11_TECHNIQUE_DESC& TechniqueDesc() const;
        const std::string& Name() const;
        const std::vector<Pass*>& Passes() const;
        const std::map<std::string, Pass*>& PassesByName() const;

    private:
        Technique(const Technique& rhs);
        Technique& operator=(const Technique& rhs);

        Effect& mEffect;
        ID3DX11EffectTechnique* mTechnique;
        D3DX11_TECHNIQUE_DESC mTechniqueDesc;
        std::string mName;
        std::vector<Pass*> mPasses;
        std::map<std::string, Pass*> mPassesByName;
    };
}


The Technique class has only one interesting element: the constructor, which iterates through the technique’s passes, instantiates the wrapping Pass objects, and adds them to the STL containers. Listing 16.4 presents this implementation.

Listing 16.4 The Technique Class Constructor


Technique::Technique(Game& game, Effect& effect,
ID3DX11EffectTechnique* technique)
    : mEffect(effect), mTechnique(technique), mTechniqueDesc(),
      mName(), mPasses(), mPassesByName()
{
    mTechnique->GetDesc(&mTechniqueDesc);
    mName = mTechniqueDesc.Name;

    for (UINT i = 0; i < mTechniqueDesc.Passes; i++)
    {
        Pass* pass = new Pass(game, *this,
mTechnique->GetPassByIndex(i));
        mPasses.push_back(pass);
        mPassesByName.insert(std::pair<std::string, Pass*>(pass-
>Name(), pass));
    }
}


The Pass Class

The Pass class follows the same patterns as the Effect and Technique classes. It wraps the corresponding Effects 11 type (ID3DX11EffectPass) and adds a bit of functionality. Specifically, the Pass class encapsulates input layout creation. Using a Pass object to create the input layout makes sense because it contains the input signature (the definition of the shader inputs for the associated vertex shader). Listing 16.5 presents the declaration of the Pass class.

Listing 16.5 The Pass.h Header File


#pragma once

#include "Common.h"

namespace Library
{
    class Game;
    class Technique;

    class Pass
    {
    public:
        Pass(Game& game, Technique& technique, ID3DX11EffectPass*
pass);

        Technique& GetTechnique();
        ID3DX11EffectPass* GetPass() const;
        const D3DX11_PASS_DESC& PassDesc() const;
        const std::string& Name() const;

        void CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC*
inputElementDesc, UINT numElements,  ID3D11InputLayout **inputLayout);
        void Apply(UINT flags, ID3D11DeviceContext* context);

    private:
        Pass(const Pass& rhs);
        Pass& operator=(const Pass& rhs);

        Game& mGame;
        Technique& mTechnique;
        ID3DX11EffectPass* mPass;
        D3DX11_PASS_DESC mPassDesc;
        std::string mName;
    };
}


The interesting elements of the Pass class implementation are the constructor and the CreateInputLayout()method (see Listing 16.6).

Listing 16.6 The Pass Class Implementation (Abbreviated)


Pass::Pass(Game& game, Technique& technique, ID3DX11EffectPass* pass)
    : mGame(game), mTechnique(technique), mPass(pass), mPassDesc(),
      mName()
{
    mPass->GetDesc(&mPassDesc);
    mName = mPassDesc.Name;
}

void Pass::CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC*
inputElementDesc, UINT numElements,  ID3D11InputLayout **inputLayout)
{
    HRESULT hr = mGame.Direct3DDevice()->CreateInputLayout(input
ElementDesc, numElements, mPassDesc.pIAInputSignature, mPassDesc.
IAInputSignatureSize, inputLayout);
    if (FAILED(hr))
    {
        throw GameException("ID3D11Device::CreateInputLayout()
failed."
, hr);
    }
}


The Variable Class

The Variable class encapsulates the ID3DX11EffectVariable type and adds some friendly syntax for updating a shader variable. Recall that the ID3DX11EffectVariable interface represents any type of shader variable. Therefore, the Variable class exposes the associated D3DX11_EFFECT_VARIABLE_DESC and D3DX11_EFFECT_TYPE_DESC structures that provide details for a variable’s semantic, annotations, and the internals of the variable’s actual data type. Listing 16.7 presents the declaration of the Variable class.

Listing 16.7 The Variable.h Header File


#pragma once

#include "Common.h"

namespace Library
{
    class Effect;

    class Variable
    {
    public:
        Variable(Effect& effect, ID3DX11EffectVariable* variable);

        Effect& GetEffect();
        ID3DX11EffectVariable* GetVariable() const;
        const D3DX11_EFFECT_VARIABLE_DESC& VariableDesc() const;
        ID3DX11EffectType* Type() const;
        const D3DX11_EFFECT_TYPE_DESC& TypeDesc() const;
        const std::string& Name() const;

        Variable& operator<<(CXMMATRIX value);
        Variable& operator<<(ID3D11ShaderResourceView* value);
        Variable& operator<<(FXMVECTOR value);
        Variable& operator<<(float value);

    private:
        Variable(const Variable& rhs);
        Variable& operator=(const Variable& rhs);

        Effect& mEffect;
        ID3DX11EffectVariable* mVariable;
        D3DX11_EFFECT_VARIABLE_DESC mVariableDesc;
        ID3DX11EffectType* mType;
        D3DX11_EFFECT_TYPE_DESC mTypeDesc;
        std::string mName;
    };
}


The most interesting aspects of the Variable class implementation are the << operator overloads. These methods provide a syntax for updating shader variables that draws its inspiration from the iostream library. For example, to update the WorldViewProjection projection matrix of a BasicEffect shader, you would write code such as the following:

XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
mBasicMaterial->WorldViewProjection() << wvp;

In this example, the mBasicMaterial::WorldViewProjection() accessor returns a reference to a Variable object. Listing 16.8 presents the code for these overloaded operators.

Listing 16.8 Overloaded Operators for the Variable Class


Variable& Variable::operator<<(CXMMATRIX value)
{
    ID3DX11EffectMatrixVariable* variable = mVariable->AsMatrix();
    if (variable->IsValid() == false)
    {
        throw GameException("Invalid effect variable cast.");
    }

    variable->SetMatrix(reinterpret_cast<const float*>(&value));

    return *this;
}

Variable& Variable::operator<<(ID3D11ShaderResourceView* value)
{
    ID3DX11EffectShaderResourceVariable* variable =
mVariable->AsShaderResource();
    if (variable->IsValid() == false)
    {
        throw GameException("Invalid effect variable cast.");
    }

    variable->SetResource(value);

    return *this;
}

Variable& Variable::operator<<(FXMVECTOR value)
{
    ID3DX11EffectVectorVariable* variable = mVariable->AsVector();
    if (variable->IsValid() == false)
    {
        throw GameException("Invalid effect variable cast.");
    }

    variable->SetFloatVector(reinterpret_cast<const float*>(&value));

    return *this;
}

Variable& Variable::operator<<(float value)
{
    ID3DX11EffectScalarVariable* variable = mVariable->AsScalar();
    if (variable->IsValid() == false)
    {
        throw GameException("Invalid effect variable cast.");
    }

    variable->SetFloat(value);

    return *this;
}


With the presented syntax, you get the benefit of runtime type checking, but you can store generic Variable objects within the Effect class. Note that Listing 16.8 doesn’t include overloads for every possible data type—just those that are useful for the next few demos.

The Material Class

The Effect class contains Variable and Technique objects, and a Technique contains Pass objects. This type of system needs one more class that pulls everything together: Material. The Material class references an Effect and adds helper methods for creating input layouts and vertex buffers that are specific to the associated Effect. The Material class is intended as a base class in which the derived class represents one particular effect. In the upcoming demos, for example, you will create BasicMaterial and SkyboxMaterial classes that aid the interaction with the BasicEffect.fx and Skybox.fx effects. These derived classes cache the variables of an effect and expose them through public accessors. This is accomplished through a set of macros that makes creating new materials simple. In fact, you could create a utility to read a .fx file and generate the corresponding .h and .cpp files for the material.

Listing 16.9 presents the declaration of the Material class.

Listing 16.9 The Material.h Header File


#pragma once

#include "Common.h"
#include "Effect.h"

namespace Library
{
    class Model;
    class Mesh;

    class Material : public RTTI
    {
        RTTI_DECLARATIONS(Material, RTTI)

    public:
        Material();
        Material(const std::string& defaultTechniqueName);
        virtual ~Material();

        Variable* operator[](const std::string& variableName);
        Effect* GetEffect() const;
        Technique* CurrentTechnique() const;
        void SetCurrentTechnique(Technique* currentTechnique);
        const std::map<Pass*, ID3D11InputLayout*>& InputLayouts()
const;

        virtual void Initialize(Effect* effect);
        virtual void CreateVertexBuffer(ID3D11Device* device, const
Model& model, std::vector<ID3D11Buffer*>& vertexBuffers) const;
        virtual void CreateVertexBuffer(ID3D11Device* device, const
Mesh& mesh, ID3D11Buffer** vertexBuffer) const = 0;
        virtual UINT VertexSize() const = 0;

    protected:
        Material(const Material& rhs);
        Material& operator=(const Material& rhs);

        virtual void CreateInputLayout(const std::string&
techniqueName, const std::string& passName, D3D11_INPUT_ELEMENT_DESC*
inputElementDescriptions, UINT inputElementDescriptionCount);

        Effect* mEffect;
        Technique* mCurrentTechnique;
        std::string mDefaultTechniqueName;
        std::map<Pass*, ID3D11InputLayout*> mInputLayouts;
    };

    #define MATERIAL_VARIABLE_DECLARATION(VariableName)           
        public:                                                   
            Variable& VariableName() const;                       
        private:                                                  
            Variable* m ## VariableName;

    #define MATERIAL_VARIABLE_DEFINITION(Material, VariableName)  
        Variable& Material::VariableName() const                  
        {                                                         
            return *m ## VariableName;                            
        }

    #define MATERIAL_VARIABLE_INITIALIZATION(VariableName) m ##
VariableName(NULL)

    #define MATERIAL_VARIABLE_RETRIEVE(VariableName)              
        m ## VariableName = mEffect->VariablesByName().
at(#VariableName);
}


A Material is initialized after instantiation, through the Material::Initialize() method. Initialization associates the effect to the material and sets the notion of a current technique (the Material::mCurrentTechnique member). The current technique is simply the effect technique that should be applied during rendering if not otherwise specified. The Material::Initialize() method sets the current technique through the Material::mDefaultTechniqueName data member (provided on instantiation).

The Material::mInputsLayouts data member is an STL map that’s populated through the Material::CreateInputLayout() method. It uses the associated Pass object as the map’s key and should be used for calls to ID3DX11DeviceContext::IASetInputLayout(). The following code provides sample usage:

Pass* pass = mBasicMaterial->CurrentTechnique()->Passes().at(0);
ID3D11InputLayout* inputLayout = mBasicMaterial->InputLayouts().
at(pass);
direct3DDeviceContext->IASetInputLayout(inputLayout);

Two Material::CreateVertexBuffer() methods are in use here, one that builds a set of vertex buffers from a Model object and another that builds a single vertex buffer from a Mesh. The latter is a pure virtual method that must be specified for each derived class. This makes sense because each effect (represented by the material) will likely have a unique input signature. The model-version of Material::CreateVertexBuffer() iteratively invokes the mesh-version to create a collection of vertex buffers.

Material::VertexSize() is another pure virtual method that’s intended to return the size (in bytes) of a vertex that’s compatible with the material.

Finally, examine the four macros at the end of Listing 16.9:

MATERIAL_VARIABLE_DECLARATION
MATERIAL_VARIABLE_DEFINITION
MATERIAL_VARIABLE_INITIALIZATION
MATERIAL_VARIABLE_RETRIEVE

These macros are used within the derived material classes to expose cached Variable objects through public accessors. We cover their usage shortly.

Listing 16.10 presents the full implementation of the Material class.

Listing 16.10 The Material.cpp File


#include "Material.h"
#include "GameException.h"
#include "Model.h"

namespace Library
{
    RTTI_DEFINITIONS(Material)

    Material::Material()
        : mEffect(nullptr), mCurrentTechnique(nullptr),
          mDefaultTechniqueName(), mInputLayouts()
    {
    }

    Material::Material(const std::string& defaultTechniqueName)
        : mEffect(nullptr), mCurrentTechnique(nullptr),
          mDefaultTechniqueName(defaultTechniqueName), mInputLayouts()
    {
    }

    Material::~Material()
    {
        for (std::pair<Pass*, ID3D11InputLayout*> inputLayout :
mInputLayouts)
        {
            ReleaseObject(inputLayout.second);
        }
    }

    Variable* Material::operator[](const std::string& variableName)
    {
        const std::map<std::string, Variable*>& variables =
mEffect->VariablesByName();
        Variable* foundVariable = nullptr;

        std::map<std::string, Variable*>::const_iterator found =
variables.find(variableName);
        if (found != variables.end())
        {
            foundVariable = found->second;
        }

        return foundVariable;
    }

    Effect* Material::GetEffect() const
    {
        return mEffect;
    }

    Technique* Material::CurrentTechnique() const
    {
        return mCurrentTechnique;
    }

    void Material::SetCurrentTechnique(Technique* currentTechnique)
    {
        mCurrentTechnique = currentTechnique;
    }

    const std::map<Pass*, ID3D11InputLayout*>& Material::InputLayouts()
const
    {
        return mInputLayouts;
    }

    void Material::Initialize(Effect* effect)
    {
        mEffect = effect;
        assert(mEffect != nullptr);

        Technique* defaultTechnique = nullptr;
        assert(mEffect->Techniques().size() > 0);
        if (mDefaultTechniqueName.empty() == false)
        {
            defaultTechnique = mEffect->TechniquesByName().
at(mDefaultTechniqueName);
            assert(defaultTechnique != nullptr);
        }
        else
        {
            defaultTechnique = mEffect->Techniques().at(0);
        }

        SetCurrentTechnique(defaultTechnique);
    }

    void Material::CreateVertexBuffer(ID3D11Device* device, const
Model& model, std::vector<ID3D11Buffer*>& vertexBuffers) const
    {
        vertexBuffers.reserve(model.Meshes().size());
        for (Mesh* mesh : model.Meshes())
        {
            ID3D11Buffer* vertexBuffer;
            CreateVertexBuffer(device, *mesh, &vertexBuffer);
            vertexBuffers.push_back(vertexBuffer);
        }
    }

    void Material::CreateInputLayout(const std::string&
techniqueName, const std::string& passName, D3D11_INPUT_ELEMENT_DESC*
inputElementDescriptions, UINT inputElementDescriptionCount)
    {
        Technique* technique = mEffect->TechniquesByName().
at(techniqueName);
        assert(technique != nullptr);

        Pass* pass = technique->PassesByName().at(passName);
        assert(pass != nullptr);

        ID3D11InputLayout* inputLayout;
        pass->CreateInputLayout(inputElementDescriptions, inputElementDescriptionCount, &inputLayout);

        mInputLayouts.insert(std::pair<Pass*, ID3D11InputLayout*>(pass, inputLayout));
    }
}


A Basic Effect Material

The best way to understand the material system is through application. In this section, you create a material for the BasicEffect.fx shader and use it to render a sphere. Listing 16.11 presents the declaration of the BasicMaterial class and an associated BasicMaterialVertex structure.

Listing 16.11 The BasicMaterial.h Header File


#pragma once

#include "Common.h"
#include "Material.h"

namespace Library
{
    typedef struct _BasicMaterialVertex
    {
        XMFLOAT4 Position;
        XMFLOAT4 Color;

        _BasicMaterialVertex() { }

        _BasicMaterialVertex(const XMFLOAT4& position, const XMFLOAT4&
color)
            : Position(position), Color(color) { }
    } BasicMaterialVertex;

    class BasicMaterial : public Material
    {
        RTTI_DECLARATIONS(BasicMaterial, Material)

        MATERIAL_VARIABLE_DECLARATION(WorldViewProjection)

    public:
        BasicMaterial();

        virtual void Initialize(Effect* effect) override;
        virtual void CreateVertexBuffer(ID3D11Device* device, const
Mesh& mesh, ID3D11Buffer** vertexBuffer) const override;
        void CreateVertexBuffer(ID3D11Device* device,
BasicMaterialVertex* vertices, UINT vertexCount, ID3D11Buffer**
vertexBuffer) const;
        virtual UINT VertexSize() const override;
    };
}


The BasicMaterialVertex replaces the duplicated BasicEffectVertex structures you created in previous demos. Its name and residence within the BasicMaterial.h file indicates its intended usage.

The BasicMaterial class has little declaration outside what it inherits from the base Material type. It declares the single WorldViewProjection shader variable through the MATERIAL_VARIABLE_DECLARATION macro. Then it overrides the Material::Initialize() and Material::VertexSize() methods, as well as the mesh-version of the Material::CreateVertexBuffer() method. It also adds a new CreateVertexBuffer() method that builds a vertex buffer from an array of BasicMaterialVertex objects. These declarations set the pattern for all derived material classes.

Listing 16.12 presents the implementation of the BasicMaterial class.

Listing 16.12 The BasicMaterial.cpp File


#include "BasicMaterial.h"
#include "GameException.h"
#include "Mesh.h"
#include "ColorHelper.h"

namespace Library
{
    RTTI_DEFINITIONS(BasicMaterial)

    BasicMaterial::BasicMaterial()
        : Material("main11"),
          MATERIAL_VARIABLE_INITIALIZATION(WorldViewProjection)
    {
    }

    MATERIAL_VARIABLE_DEFINITION(BasicMaterial, WorldViewProjection)

    void BasicMaterial::Initialize(Effect* effect)
    {
        Material::Initialize(effect);

        MATERIAL_VARIABLE_RETRIEVE(WorldViewProjection)

        D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_
APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };

        CreateInputLayout("main11", "p0", inputElementDescriptions, ARR
AYSIZE(inputElementDescriptions));
    }

    void BasicMaterial::CreateVertexBuffer(ID3D11Device* device, const
Mesh& mesh, ID3D11Buffer** vertexBuffer) const
    {
        const std::vector<XMFLOAT3>& sourceVertices = mesh.Vertices();

        std::vector<BasicMaterialVertex> vertices;
        vertices.reserve(sourceVertices.size());
        if (mesh.VertexColors().size() > 0)
        {
            std::vector<XMFLOAT4>* vertexColors = mesh.VertexColors().
at(0);
            assert(vertexColors->size() == sourceVertices.size());

            for (UINT i = 0; i < sourceVertices.size(); i++)
            {
                XMFLOAT3 position = sourceVertices.at(i);
                XMFLOAT4 color = vertexColors->at(i);
                vertices.push_back(BasicMaterialVertex(XMFLOAT4
(position.x, position.y, position.z, 1.0f), color));
            }
        }
        else
        {
            XMFLOAT4 color = XMFLOAT4(reinterpret_cast<const
float*>(&ColorHelper::White));
            for (UINT i = 0; i < sourceVertices.size(); i++)
            {
                XMFLOAT3 position = sourceVertices.at(i);
                vertices.push_back(BasicMaterialVertex(XMFLOAT4
(position.x, position.y, position.z, 1.0f), color));
            }
        }

        CreateVertexBuffer(device, &vertices[0], vertices.size(),
vertexBuffer);
    }

    void BasicMaterial::CreateVertexBuffer(ID3D11Device* device,
BasicMaterialVertex* vertices, UINT vertexCount, ID3D11Buffer**
vertexBuffer) const
    {
        D3D11_BUFFER_DESC vertexBufferDesc;
        ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
        vertexBufferDesc.ByteWidth = VertexSize() * vertexCount;
        vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

        D3D11_SUBRESOURCE_DATA vertexSubResourceData;
        ZeroMemory(&vertexSubResourceData, sizeof
(vertexSubResourceData));
        vertexSubResourceData.pSysMem = vertices;
        if (FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexSubResourceData, vertexBuffer)))
        {
            throw GameException("ID3D11Device::CreateBuffer()
failed."
);
        }
    }

    UINT BasicMaterial::VertexSize() const
    {
        return sizeof(BasicMaterialVertex);
    }
}


The BasicMaterial constructor specifies main11 as the default technique name and uses the MATERIAL_VARIABLE_INITIALIZATION macro to initialize the WorldViewProjection variable. Next, a BasicMaterial::WorldViewProjection() accessor is defined through the MATERIAL_VARIABLE_DEFINITION macro. All accessors created in this way return a reference to a Variable object stored within the associated Effect class. The World-ViewProjection variable reference is retrieved from the Effect class and cached using the MATERIAL_VARIABLE_RETRIEVE macro. Such variable retrieval has two benefits: First, caching obviates the need for repeated variable lookups. Second, it validates (at runtime) the expected variables within the effect.

Next, the normal array of D3D11_INPUT_ELEMENT_DESC structures is created and passed to the Material::CreateInputLayout() method.

Now inspect the BasicMaterial::CreateVertexBuffer() methods. The mesh-version of this method has the same structure as for the model and textured model demos. Placing this code within the BasicMaterial class merely encapsulates it properly. The mesh-version calls the BasicMaterialVertex version of the BasicMaterial::CreateVertexBuffer() method, which actually creates the vertex buffer. In this way, you can build a vertex buffer for the BasicEffect.fx shader with a plain array of BasicMaterialVertex objects, a Mesh, or a Model object (using the model-version of the CreateVertexBuffer() method inherited from the Material class).

Finally, the BasicMaterial class provides an implementation for the VertexSize() method, which returns the size of the BasicMaterialVertex structure.

A Basic Material Demo

With the material system in place, you can re-create the model demo with much less code. Listing 16.13 presents the declaration of the MaterialDemo class.

Listing 16.13 The MaterialDemo.h Header File


#pragma once

#include "DrawableGameComponent.h"

using namespace Library;

namespace Library
{
    class Effect;
    class BasicMaterial;
}

namespace Rendering
{
    class MaterialDemo : public DrawableGameComponent
    {
        RTTI_DECLARATIONS(MaterialDemo, DrawableGameComponent)

    public:
        MaterialDemo(Game& game, Camera& camera);
        ~MaterialDemo();

        virtual void Initialize() override;
        virtual void Draw(const GameTime& gameTime) override;

    private:
        MaterialDemo();
        MaterialDemo(const MaterialDemo& rhs);
        MaterialDemo& operator=(const MaterialDemo& rhs);

        Effect* mBasicEffect;
        BasicMaterial* mBasicMaterial;
        ID3D11Buffer* mVertexBuffer;
        ID3D11Buffer* mIndexBuffer;
        UINT mIndexCount;

        XMFLOAT4X4 mWorldMatrix;
    };
}


Compare the MaterialDemo declaration to the ModelDemo class of the last chapter. Gone are the mEffect, mTechnique, mPass, mWvpVariable, and mInputLayout class members. The BasicEffectVertex structure declaration has likewise been removed.

The class implementation, in Listing 16.14, is correspondingly simplified.

Listing 16.14 The MaterialDemo.cpp File


#include "MaterialDemo.h"
#include "Game.h"
#include "GameException.h"
#include "MatrixHelper.h"
#include "Camera.h"
#include "Utility.h"
#include "Model.h"
#include "Mesh.h"
#include "BasicMaterial.h"

namespace Rendering
{
    RTTI_DEFINITIONS(MaterialDemo)

    MaterialDemo::MaterialDemo(Game& game, Camera& camera)
        : DrawableGameComponent(game, camera),
          mBasicMaterial(nullptr), mBasicEffect(nullptr), mWorldMatrix
(MatrixHelper::Identity),
          mVertexBuffer(nullptr), mIndexBuffer(nullptr), mIndexCount(0)
    {
    }

    MaterialDemo::~MaterialDemo()
    {
        DeleteObject(mBasicMaterial);
        DeleteObject(mBasicEffect);
        ReleaseObject(mVertexBuffer);
        ReleaseObject(mIndexBuffer);
    }

    void MaterialDemo::Initialize()
    {
        SetCurrentDirectory(Utility::ExecutableDirectory().c_str());

        // Load the model
        std::unique_ptr<Model> model(new Model(*mGame, "Content\
Models\Sphere.obj"
, true));

        // Initialize the material
        mBasicEffect = new Effect(*mGame);
        mBasicEffect->LoadCompiledEffect(L"Content\Effects\
BasicEffect.cso"
);
        mBasicMaterial = new BasicMaterial();
        mBasicMaterial->Initialize(mBasicEffect);

        // Create the vertex and index buffers
        Mesh* mesh = model->Meshes().at(0);
        mBasicMaterial->CreateVertexBuffer(mGame->Direct3DDevice(),
*mesh, &mVertexBuffer);
        mesh->CreateIndexBuffer(&mIndexBuffer);
        mIndexCount = mesh->Indices().size();
    }

    void MaterialDemo::Draw(const GameTime& gameTime)
    {
        ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
        direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_
TOPOLOGY_TRIANGLELIST);

        Pass* pass = mBasicMaterial->CurrentTechnique()->Passes().
at(0);
        ID3D11InputLayout* inputLayout = mBasicMaterial-
>InputLayouts().at(pass);
        direct3DDeviceContext->IASetInputLayout(inputLayout);

        UINT stride = mBasicMaterial->VertexSize();
        UINT offset = 0;
        direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);
        direct3DDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_
FORMAT_R32_UINT, 0);

        XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
        XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
        mBasicMaterial->WorldViewProjection() << wvp;

        pass->Apply(0, direct3DDeviceContext);

        direct3DDeviceContext->DrawIndexed(mIndexCount, 0, 0);
    }
}


All the code for explicitly compiling shaders; finding techniques, passes, and variables; and creating vertex buffers has been removed from the MaterialDemo implementation. But the functionality of the demo hasn’t been altered. This new format is the pattern for all future demos.

A Skybox Material

We can reinforce the content in this chapter by creating a skybox material and an associated demo. Create a SkyboxMaterial using the declaration in Listing 16.15. This matches the Skybox.fx shader you wrote in Chapter 8, “Gleaming the Cube.”

Listing 16.15 Declaration of the SkyboxMaterial Class


class SkyboxMaterial : public Material
{
    RTTI_DECLARATIONS(SkyboxMaterial, Material)

    MATERIAL_VARIABLE_DECLARATION(WorldViewProjection)
    MATERIAL_VARIABLE_DECLARATION(SkyboxTexture)

public:
    SkyboxMaterial();

    virtual void Initialize(Effect* effect) override;
    virtual void CreateVertexBuffer(ID3D11Device* device, const Mesh&
mesh, ID3D11Buffer** vertexBuffer) const override;
    void CreateVertexBuffer(ID3D11Device* device, XMFLOAT4* vertices,
UINT vertexCount, ID3D11Buffer** vertexBuffer) const;
    virtual UINT VertexSize() const override;
};


Listing 16.16 presents an abbreviated implementation of the SkyboxMaterial class. It omits the CreateVertexBuffer() and VertexSize() methods for brevity. Visit the companion website for complete source code.

Listing 16.16 Implementation of the SkyboxMaterial Class (Abbreviated)


SkyboxMaterial::SkyboxMaterial()
        : Material("main11"),
          MATERIAL_VARIABLE_INITIALIZATION(WorldViewProjection),
          MATERIAL_VARIABLE_INITIALIZATION(SkyboxTexture)
    {
    }

    MATERIAL_VARIABLE_DEFINITION(SkyboxMaterial, WorldViewProjection)
    MATERIAL_VARIABLE_DEFINITION(SkyboxMaterial, SkyboxTexture)

    void SkyboxMaterial::Initialize(Effect* effect)
    {
        Material::Initialize(effect);

        MATERIAL_VARIABLE_RETRIEVE(WorldViewProjection)
        MATERIAL_VARIABLE_RETRIEVE(SkyboxTexture)

        D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };

        CreateInputLayout("main11", "p0", inputElementDescriptions,
ARRAYSIZE(inputElementDescriptions));
    }


Notice the similarities between the SkyboxMaterial and the BasicMaterial implementations. The differences are the shader variables and the input layout. Also note that no vertex structure is created for the SkyboxMaterial. This is because only one shader input exists—the vertex position—and it’s of type XMFLOAT4.

A Skybox Component

To exercise the SkyboxMaterial class, create a reusable Skybox component with the declaration in Listing 16.17.

Listing 16.17 Declaration of the Skybox Class


class Skybox : public DrawableGameComponent
{
    RTTI_DECLARATIONS(Skybox, DrawableGameComponent)

public:
    Skybox(Game& game, Camera& camera, const std::wstring&
cubeMapFileName, float scale);
    ~Skybox();

    virtual void Initialize() override;
    virtual void Update(const GameTime& gameTime) override;
    virtual void Draw(const GameTime& gameTime) override;

private:
    Skybox();
    Skybox(const Skybox& rhs);
    Skybox& operator=(const Skybox& rhs);

    std::wstring mCubeMapFileName;
    Effect* mEffect;
    SkyboxMaterial* mMaterial;
    ID3D11ShaderResourceView* mCubeMapShaderResourceView;
    ID3D11Buffer* mVertexBuffer;
    ID3D11Buffer* mIndexBuffer;
    UINT mIndexCount;

    XMFLOAT4X4 mWorldMatrix;
    XMFLOAT4X4 mScaleMatrix;
};


The Skybox::mCubeMapFileName is initialized within the constructor, as is the Skybox::ScaleMatrix member (with a call to XMMatrixScaling()). The rest of the work is split between the Initialize(), Update() and Draw() methods (see Listing 16.18).

Listing 16.18 Implementation of the Skybox Class (Abbreviated)


void Skybox::Initialize()
{
    SetCurrentDirectory(Utility::ExecutableDirectory().c_str());

    std::unique_ptr<Model> model(new Model(*mGame, "Content\Models\
Sphere.obj"
, true));

    mEffect = new Effect(*mGame);
    mEffect->LoadCompiledEffect(L"Content\Effects\Skybox.cso");

    mMaterial = new SkyboxMaterial();
    mMaterial->Initialize(mEffect);

    Mesh* mesh = model->Meshes().at(0);
    mMaterial->CreateVertexBuffer(mGame->Direct3DDevice(), *mesh,
&mVertexBuffer);
    mesh->CreateIndexBuffer(&mIndexBuffer);
    mIndexCount = mesh->Indices().size();

    HRESULT hr = DirectX::CreateDDSTextureFromFile
(mGame->Direct3DDevice(), mCubeMapFileName.c_str(), nullptr,
&mCubeMapShaderResourceView);
    if (FAILED(hr))
    {
        throw GameException("CreateDDSTextureFromFile() failed.", hr);
    }
}

void Skybox::Update(const GameTime& gameTime)
{
    const XMFLOAT3& position = mCamera->Position();

    XMStoreFloat4x4(&mWorldMatrix, XMLoadFloat4x4(&mScaleMatrix) *
XMMatrixTranslation(position.x, position.y, position.z));
}

void Skybox::Draw(const GameTime& gametime)
{
    ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
    direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_
TOPOLOGY_TRIANGLELIST);

    Pass* pass = mMaterial->CurrentTechnique()->Passes().at(0);
    ID3D11InputLayout* inputLayout = mMaterial->InputLayouts().
at(pass);
    direct3DDeviceContext->IASetInputLayout(inputLayout);

    UINT stride = mMaterial->VertexSize();
    UINT offset = 0;
    direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);
    direct3DDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_
R32_UINT, 0);

    XMMATRIX wvp = XMLoadFloat4x4(&mWorldMatrix) * mCamera->
ViewMatrix() * mCamera->ProjectionMatrix();
    mMaterial->WorldViewProjection() << wvp;
    mMaterial->SkyboxTexture() << mCubeMapShaderResourceView;

    pass->Apply(0, direct3DDeviceContext);

    direct3DDeviceContext->DrawIndexed(mIndexCount, 0, 0);
}


Within the Skybox::Initialize() method, the Skybox.cso file is loaded, as is the DDS file containing the cubemap. The Skybox::Update() method updates the world matrix by concatenating the scale matrix and a translation matrix built from the camera’s position (recall that the skybox should always be centered on the camera). Finally, the Skybox::Draw() method renders the output. Note the updates to the two SkyboxMaterial variable accessors, WorldViewProjection() and SkyboxTexture(), using the << operator syntax.

Add an instance of the Skybox class to your game components vector with calls such as the following:

mSkybox = new Skybox(*this, *mCamera, L"Content\Textures\
Maskonaive2_1024.dds"
, 500.0f);
mComponents.push_back(mSkybox);

This produces output similar to Figure 16.2.

Image

Figure 16.2 Output of the skybox component, along with a textured model. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

Summary

In this chapter, you created a material system to encapsulate and simplify the interactions with effects. You wrote a set of classes that wrapped the Effects 11 library types for effects, techniques, passes, and variables, and an extensible Material class to bring all these types together. You exercised this system with two demos. For the first demo, you refactored the model demo of the last chapter. For the second demo, you created a reusable skybox component.

In the next chapter, you develop a set of types for directional lights, point lights, and spot-lights. You employ the material system to support the lighting shaders you authored in Chapter 6, “Lighting Models,” and Chapter 7, “Additional Lighting Models,” and you create demo applications to render scenes with interactive lighting.

Exercises

1. From within the debugger, walk through the code to initialize the Effect, Technique, Pass, Variable, and Material classes in the MaterialDemo project. This exercise should help you better understand the design and implementation of these systems.

2. Write a material class for the TextureMapping.fx effect. Then write a demo application to render the sphere.obj model mapped with a texture. Note: You can find many textures on the companion website.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.44.199