A Basic Effect Material

A Basic Effect Material

理解材質系統結構最好的方法就是在應用程序中使用該系統。在這一節,我們將會創建一個material類用於表示BasicEffect.fx shader,並使用該類渲染一個sphere object。列表16.11列出了BasicMaterial類的聲明代碼,以及一個對應的BasicMaterialVertex結構體。

列表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(XMFLOAT4 position, 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;
    };
}


BasicMaterialVertex結構體用於替代在之前所創建的幾個示例中重複的BasicEffectVertex結構體。結構體的名稱以及在BasicMaterial.h頭文件中所處的位置表明了相應的用途。
在BasicMaterial類中,除了從Material基類繼承的成員之外,幾乎沒有其他的成員變量或函數。首先使用MATERIAL_VARIABLE_DECLARATION宏聲明瞭一個WorldViewProject shader變量。然後重寫了繼承而來的Material::Initialize()和Material::VertexSize()函數,以及mesh-version(通過Mesh對象創建vertex buffer)的Material::CreateVertexBuffer()函數。還增加一個新的CreateVertexBuffer()函數,用於從一個BasicMaterialVertex類對象數組創建一個vertex buffer。這些變量和函數聲明是Material類的所有派生類的通常設計方法。
列表16.12列出了BasicMaterial類的實現代碼。

列表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, ARRAYSIZE(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);
    }
}


在BasicMaterial類的構造函數中,使用字符串main11作爲technique的默認名稱,並使用MATERIAL_VARIABLE_INITIALIZATION宏初始化變量WorldViewProjection。然後使用MATERIAL_VARIABLE_DEFINITION宏定義了一個用於訪問WorldViewProjection變量的成員函數BasciMaterail::WorldViewProjection()。所有以這種宏定義的方式創建的訪問函數都返回一個Variable引用類型的對象,該對象存儲在對應的Effect類中。通過使用MATERIAL_VARIABLE_RETRIVE宏可以從Effect類中檢索World-ViewProjection變量值並存儲到對應的成員變量中。以這種方式獲取變量值有兩點好處:第一,把檢索到的變量值緩存可以重複的變量查找操作;第二,可以(在運行時)驗證要查找的effect是否包含在effect中。
接下來,創建一個D3D11_INPUT_ELEMENT_DESC結構體對象數組,並使用該數組作爲調用Material::CreateInputLayout()函數的參數。
下面開始分析BasicMaterial::CreateVertexBuffer()函數。這個mesh-version(通過Mesh對象創建vertex buffer)的函數與上一章的model和textured model示例代碼具有相同的結構。把這些代碼放到BasciMaterial類中,只是一種正確的封裝方式。在mesh-version的BasciMaterial::CreateVertexBuffer()函數中,又調用了BasicMaterailVertex版本(通過BasicMaterialVertex創建vertex buffer)的BasicMaterial::CreateVertexBuffer()函數,該函數纔是真正創建vertex buffer的地方。使用這個種方法就可以只使用三個CreateVertexBuffer()函數中的任何一個,創建一個vertex buffer用於BasicEffect.fx shader。(其中model-version的CreateVertexBuffer()函數繼承自Material類)。
最後,在BasicMaterial類中實現了VertexSize()函數,該函數返回BasicMaterialVertex結構體的大小。

A Basic Material Demo

下面使用Material類材質系統重新編寫上一章的model示例,可以減少大量代碼。

列表16.13列出了MaterialDemo類的聲明代碼。

列表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;	
    };
}


通過比較MaterialDemo類與上一章的ModelDemo類的聲明代碼,可以發現在MaterialDemo中已經完全去掉了mEffect,mTechnique,mPass,mWvpVariable和mInputLayout成員變量。同樣,BasicEffectVertex結構體的聲明代碼也去掉了。
與聲明代碼一樣,Material類的實現代碼也是非常簡單,如列表16.14所示。

列表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);
    }
}


在MaterialDemo的實現代碼中,已經刪除了編譯shader;查詢techniques、passes和variables;以及創建vertex buffers的全部代碼。但是該示例中依然保持了與Model示例的一樣演示功能。這種新的設計模式將會用於後面所示的示例中。

A Skybox Material

最後通過創建一個skybox材質以及對應的示例程序,加強對本章知識點的理解。使用列表16.15中的代碼創建一個SkyboxMaterial類。該類對應於第8章“Gleaming the Cube”中所編寫的Skybox.fx shader。

列表16.15 Declaration of the SkyboxMaterial Class

#pragma once

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

namespace Library
{
	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;
	};
}


列表16.16中列出了SkyboxMaterial類的部分實現代碼,其中爲了簡潔沒有列出CreateVertexBuffer()和VertexSize()函數。在本書的配套網站上提供了完整的實現代碼。

列表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));
}


SkyboxMaterial類與BasicMaterial類的實現代碼有很多相似的地方。不同之處是shader變量以及input layout,另外在SkyboxMaterial不用再創建vertex結構體,因爲在裏只有一個shader輸入,即vertex position,這是一個XMFLOAT4類型的變量。

A Skybox Component

下面創建一個可複用的Skybox組件用於演示SkyboxMaterial類的用法,Skybox component的聲明代碼如列表16.7所示。

列表16.17 Declaration of the Skybox Class

#pragma once

#include "Common.h"
#include "DrawableGameComponent.h"

namespace Library
{
	class Effect;
	class SkyboxMaterial;

	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;
	};
}


Skybox類的構造函數中包含了用於初始化Skybox::mCubeMapFileName變量的參數cubeMapFileName,以及初始Skybox::mScaleMatrix變量的參數scale(通過調用XMMatrixScaling()函數)。其他的實現代碼都包含在Initialize(),Update()和Draw()函數中(見列表16.18)。

列表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);
}


在Skybox::Initialize()函數中,加載了一個Skybox.cso文件,以及一個包含了cubemap的DDS格式文件。在Skybox::Update()函數中,通過把縮放矩陣和一個由camera的position創建的平移矩陣相乘得到world matrix(回顧一下前面講過的,skybox必須總是位於camera的中心)。最後在Skybox::Draw()函數中完成渲染輸出。其中,在使用SkyboxMaterial的兩個公有函數WorldViewProjection()和SkyboxTexture(),修改對應的成員變量時使用了<<運算符語法。

使用下面的代碼,在game components的vector容器中增加一個Skybox類的實例對象:

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


該示例程序的輸出結果如圖16.2所示。

圖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.)

總結

本章主要創建了一個材質系統用於更簡單的完成應用程序與effects的交互。其中編寫了一組類effects、techniques、passes、variables,用於封裝Effects 11庫中對應的接口類型,以及一個可擴展的Material類用於把這些類整合到一起。最後,通過兩個示例程序演示了材質系統。在第一個示例中,重新編寫了上一章的model示例。而在第二示例中,創建了一個可複用的skybox組件。
在一下章,將會創建一組用於表示directional lights、point light以及spot-lights的類。並使用本章創建的材質系統支持第6章“Lighting Models”和第7章“Additional Lighting Models”所編寫的lighting shaders,並編寫對應的示例程序演示在場景中使用光照。

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.


1、以調試模式執行MaterialDemo工程,單步查看Effect,Technique,Pass,Variable以及Material類的初始化過程。通過這種練習可以更好地理解材質系統的設計方法與實現過程。
2、編寫一個material類(TextureMappingMaterial)用於處理TextureMapping.fx effect。然後縮寫一個示例程序,使用一種紋理渲染sphere.obj模型。(也就是要使用材質系統重新編寫上一章的TexturedModel示例)。注意:本書的配套網站上提供了大量的textures文件。
發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章