Last modified on 20 June 2012, at 19:13

DirectX/10.0/Direct3D/Multitexturing and texture arrays

This tutorial will cover how to do multitexturing in DirectX 11 as well as how to implement texture arrays in DirectX 11. Multitexturing is the process of blending two different textures to create a final texture. The equation you use to blend the two textures can differ depending on the result you are trying to achieve. In this tutorial we will look at just combining the average pixel color of the two textures to create an evenly blended final texture.

Texture arrays is a new feature since DirectX 10 that allows you to have multiple textures active at once in the gpu. Past methods where only a single texture was ever active in the gpu caused a lot of extra processing to load and unload textures all the time. Most people got around this problem by using texture aliases (loading a bunch of textures onto one large texture) and just using different UV coordinates. However texture aliases are no longer needed with this new feature.

The first texture used in this tutorial we will call the base texture which looks like the following:

The second texture we will use to combine with the first one will be called the color texture. It looks like the following:

These two textures will be combined in the pixel shader on a pixel by pixel basis. The blending equation we will use will be the following:

  blendColor = basePixel * colorPixel * gammaCorrection;

Using that equation and the two textures we will get the following result:

Now you may be wondering why I didn't just add together the average of the pixels such as the following:

  blendColor = (basePixel * 0.5) + (colorPixel * 0.5);

The reason being is that the pixel color presented to us has been corrected to the gamma of the monitor. This makes the pixel values from 0.0 to 1.0 follow a non-linear curve. Therefore we need gamma correction when working in the pixel shader to deal with non-linear color values. If we don't correct for gamma and just do the average addition function we get a washed out result such as this:

Also note that most devices have different gamma values and most require a look up table or a gamma slider so the user can choose the gamma settings for their device. In this example I just choose 2.0 as my gamma value to make the tutorial simple.

We'll start the code section by first looking at the new multitexture shader which was originally based on the texture shader file with some slight changes.

Multitexture.vsEdit

The only change to the vertex shader is the name.

////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType MultiTextureVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    return output;
}

Multitexture.psEdit

////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////

We have added a two element texture array resource here for the two different textures that will be blended together. Texture arrays are more efficient that using single texture resources in terms of performance on the graphics card. Switching textures was very costly in earlier versions of DirectX forcing most engines to be written around texture and material switches. Texture arrays help reduce that performance cost.

Texture2D shaderTextures[2];
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};

The pixel shader is where all the work of this tutorial is done. We take a sample of the pixel from both textures at this current texture coordinate. After that we combine them using multiplication since they are non-linear due to gamma correction. We also multiply by a gamma value, we have used 2.0 in this example as it is close to most monitor's gamma value. Once we have the blended pixel we saturate it and then return it as our final result. Notice also the indexing method used to access the two textures in the texture array.

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 MultiTexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 color1;
    float4 color2;
    float4 blendColor;


    // Get the pixel color from the first texture.
    color1 = shaderTextures[0].Sample(SampleType, input.tex);

    // Get the pixel color from the second texture.
    color2 = shaderTextures[1].Sample(SampleType, input.tex);

    // Blend the two pixels together and multiply by the gamma value.
    blendColor = color1 * color2 * 2.0;
    
    // Saturate the final color.
    blendColor = saturate(blendColor);

    return blendColor;
}

Multitextureshaderclass.hEdit

The multitexture shader code is based on the TextureShaderClass with some slight modifications.

////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MULTITEXTURESHADERCLASS_H_
#define _MULTITEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: MultiTextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class MultiTextureShaderClass
{
private:
        struct MatrixBufferType
        {
                D3DXMATRIX world;
                D3DXMATRIX view;
                D3DXMATRIX projection;
        };

public:
        MultiTextureShaderClass();
        MultiTextureShaderClass(const MultiTextureShaderClass&);
        ~MultiTextureShaderClass();

        bool Initialize(ID3D11Device*, HWND);
        void Shutdown();
        bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);

private:
        bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
        void ShutdownShader();
        void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

        bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);
        void RenderShader(ID3D11DeviceContext*, int);

private:
        ID3D11VertexShader* m_vertexShader;
        ID3D11PixelShader* m_pixelShader;
        ID3D11InputLayout* m_layout;
        ID3D11Buffer* m_matrixBuffer;
        ID3D11SamplerState* m_sampleState;
};

#endif

Multitextureshaderclass.cppEdit

////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "multitextureshaderclass.h"


MultiTextureShaderClass::MultiTextureShaderClass()
{
        m_vertexShader = 0;
        m_pixelShader = 0;
        m_layout = 0;
        m_matrixBuffer = 0;
        m_sampleState = 0;
}


MultiTextureShaderClass::MultiTextureShaderClass(const MultiTextureShaderClass& other)
{
}


MultiTextureShaderClass::~MultiTextureShaderClass()
{
}


bool MultiTextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
        bool result;

The multitexture HLSL shader files are loaded here in the Initialize function.

  // Initialize the vertex and pixel shaders.
        result = InitializeShader(device, hwnd, L"../Engine/multitexture.vs", L"../Engine/multitexture.ps");
        if(!result)
        {
                return false;
        }

        return true;
}

Shutdown calls the ShutdownShader function to release the shader related interfaces.

void MultiTextureShaderClass::Shutdown()
{
        // Shutdown the vertex and pixel shaders as well as the related objects.
        ShutdownShader();

        return;
}

The Render function now takes as input a pointer to the texture array. This will give the shader access to the two textures for blending operations.

bool MultiTextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, 
                                     D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray)
{
        bool result;


        // Set the shader parameters that it will use for rendering.
        result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray);
        if(!result)
        {
                return false;
        }

        // Now render the prepared buffers with the shader.
        RenderShader(deviceContext, indexCount);

        return true;
}

The InitializeShader function loads the vertex and pixel shader as well as setting up the layout, matrix buffer, and sample state.

bool MultiTextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
        HRESULT result;
        ID3D10Blob* errorMessage;
        ID3D10Blob* vertexShaderBuffer;
        ID3D10Blob* pixelShaderBuffer;
        D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
        unsigned int numElements;
        D3D11_BUFFER_DESC matrixBufferDesc;
        D3D11_SAMPLER_DESC samplerDesc;


        // Initialize the pointers this function will use to null.
        errorMessage = 0;
        vertexShaderBuffer = 0;
        pixelShaderBuffer = 0;

The multitexture vertex shader is loaded here.

  // Compile the vertex shader code.
        result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "MultiTextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
                                       0, NULL, &vertexShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // If the shader failed to compile it should have writen something to the error message.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
                }
                // If there was  nothing in the error message then it simply could not find the shader file itself.
                else
                {
                        MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

The multitexture pixel shader is loaded here.

  // Compile the pixel shader code.
        result = D3DX11CompileFromFile(psFilename, NULL, NULL, "MultiTexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
                                       0, NULL, &pixelShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // If the shader failed to compile it should have writen something to the error message.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
                }
                // If there was  nothing in the error message then it simply could not find the file itself.
                else
                {
                        MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

        // Create the vertex shader from the buffer.
        result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, 
                                            &m_vertexShader);
        if(FAILED(result))
        {
                return false;
        }

        // Create the vertex shader from the buffer.
        result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, 
                                           &m_pixelShader);
        if(FAILED(result))
        {
                return false;
        }

        // Create the vertex input layout description.
        // This setup needs to match the VertexType stucture in the ModelClass and in the shader.
        polygonLayout[0].SemanticName = "POSITION";
        polygonLayout[0].SemanticIndex = 0;
        polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
        polygonLayout[0].InputSlot = 0;
        polygonLayout[0].AlignedByteOffset = 0;
        polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[0].InstanceDataStepRate = 0;

        polygonLayout[1].SemanticName = "TEXCOORD";
        polygonLayout[1].SemanticIndex = 0;
        polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
        polygonLayout[1].InputSlot = 0;
        polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[1].InstanceDataStepRate = 0;

        // Get a count of the elements in the layout.
        numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

        // Create the vertex input layout.
        result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), 
                                           vertexShaderBuffer->GetBufferSize(), &m_layout);
        if(FAILED(result))
        {
                return false;
        }

        // Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
        vertexShaderBuffer->Release();
        vertexShaderBuffer = 0;

        pixelShaderBuffer->Release();
        pixelShaderBuffer = 0;

        // Setup the description of the matrix dynamic constant buffer that is in the vertex shader.
        matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
        matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
        matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        matrixBufferDesc.MiscFlags = 0;
        matrixBufferDesc.StructureByteStride = 0;

        // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class.
        result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Create a texture sampler state description.
        samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = 1;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
        samplerDesc.BorderColor[0] = 0;
        samplerDesc.BorderColor[1] = 0;
        samplerDesc.BorderColor[2] = 0;
        samplerDesc.BorderColor[3] = 0;
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        // Create the texture sampler state.
        result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
        if(FAILED(result))
        {
                return false;
        }

        return true;
}

ShutdownShader releases all the interfaces that were setup in the InitializeShader function.

void MultiTextureShaderClass::ShutdownShader()
{
        // Release the sampler state.
        if(m_sampleState)
        {
                m_sampleState->Release();
                m_sampleState = 0;
        }

        // Release the matrix constant buffer.
        if(m_matrixBuffer)
        {
                m_matrixBuffer->Release();
                m_matrixBuffer = 0;
        }

        // Release the layout.
        if(m_layout)
        {
                m_layout->Release();
                m_layout = 0;
        }

        // Release the pixel shader.
        if(m_pixelShader)
        {
                m_pixelShader->Release();
                m_pixelShader = 0;
        }

        // Release the vertex shader.
        if(m_vertexShader)
        {
                m_vertexShader->Release();
                m_vertexShader = 0;
        }

        return;
}

The OutputShaderErrorMessage function writes out an error to a file if there is an issue compiling the vertex or pixel shader HLSL files.

void MultiTextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
        char* compileErrors;
        unsigned long bufferSize, i;
        ofstream fout;


        // Get a pointer to the error message text buffer.
        compileErrors = (char*)(errorMessage->GetBufferPointer());

        // Get the length of the message.
        bufferSize = errorMessage->GetBufferSize();

        // Open a file to write the error message to.
        fout.open("shader-error.txt");

        // Write out the error message.
        for(i=0; i<bufferSize; i++)
        {
                fout Release();
        errorMessage = 0;

        // Pop a message up on the screen to notify the user to check the text file for compile errors.
        MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

        return;
}

SetShaderParameters sets the matrices and texture array in the shader before rendering.

bool MultiTextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, 
                                                  D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
                                                  ID3D11ShaderResourceView** textureArray)
{
        HRESULT result;
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        MatrixBufferType* dataPtr;
        unsigned int bufferNumber;


        // Transpose the matrices to prepare them for the shader.
        D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
        D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
        D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

        // Lock the matrix constant buffer so it can be written to.
        result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        if(FAILED(result))
        {
                return false;
        }

        // Get a pointer to the data in the constant buffer.
        dataPtr = (MatrixBufferType*)mappedResource.pData;

        // Copy the matrices into the constant buffer.
        dataPtr->world = worldMatrix;
        dataPtr->view = viewMatrix;
        dataPtr->projection = projectionMatrix;

        // Unlock the matrix constant buffer.
        deviceContext->Unmap(m_matrixBuffer, 0);

        // Set the position of the matrix constant buffer in the vertex shader.
        bufferNumber = 0;

        // Now set the matrix constant buffer in the vertex shader with the updated values.
        deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

Here is where the texture array is set before rendering. The PSSetShaderResources function is used to set the texture array. The first parameter is where to start in the array. The second parameter is how many textures are in the array that is being passed in. And the third parameter is a pointer to the texture array.

  // Set shader texture array resource in the pixel shader.
        deviceContext->PSSetShaderResources(0, 2, textureArray);

        return true;
}

The RenderShader function sets the layout, shaders, and sampler. It then draws the model using the shader.

void MultiTextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
        // Set the vertex input layout.
        deviceContext->IASetInputLayout(m_layout);

        // Set the vertex and pixel shaders that will be used to render this triangle.
        deviceContext->VSSetShader(m_vertexShader, NULL, 0);
        deviceContext->PSSetShader(m_pixelShader, NULL, 0);

        // Set the sampler state in the pixel shader.
        deviceContext->PSSetSamplers(0, 1, &m_sampleState);

        // Render the triangles.
        deviceContext->DrawIndexed(indexCount, 0, 0);

        return;
}

Texturearrayclass.hEdit

The TextureArrayClass replaces the TextureClass that was used before. Instead of having just a single texture it can now have multiple textures and give calling objects access to those textures. For this tutorial it just handles two textures but it can easily be expanded.

////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTUREARRAYCLASS_H_
#define _TEXTUREARRAYCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx11tex.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureArrayClass
////////////////////////////////////////////////////////////////////////////////
class TextureArrayClass
{
public:
        TextureArrayClass();
        TextureArrayClass(const TextureArrayClass&);
        ~TextureArrayClass();

        bool Initialize(ID3D11Device*, WCHAR*, WCHAR*);
        void Shutdown();

        ID3D11ShaderResourceView** GetTextureArray();

private:

This is the two element texture array private variable.

  ID3D11ShaderResourceView* m_textures[2];
};

#endif

Texturearrayclass.cppEdit

////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "texturearrayclass.h"

The class constructor initializes the texture array elements to null.

TextureArrayClass::TextureArrayClass()
{
        m_textures[0] = 0;
        m_textures[1] = 0;
}


TextureArrayClass::TextureArrayClass(const TextureArrayClass& other)
{
}


TextureArrayClass::~TextureArrayClass()
{
}

Initialize takes in the two texture file names and creates two texture resources in the texture array from those files.

bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
        HRESULT result;


        // Load the first texture in.
        result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL);
        if(FAILED(result))
        {
                return false;
        }

        // Load the second texture in.
        result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL);
        if(FAILED(result))
        {
                return false;
        }

        return true;
}

The Shutdown function releases each element in the texture array.

void TextureArrayClass::Shutdown()
{
        // Release the texture resources.
        if(m_textures[0])
        {
                m_textures[0]->Release();
                m_textures[0] = 0;
        }

        if(m_textures[1])
        {
                m_textures[1]->Release();
                m_textures[1] = 0;
        }

        return;
}

GetTextureArray returns a pointer to the texture array so calling objects can have access to the textures in the texture array.

ID3D11ShaderResourceView** TextureArrayClass::GetTextureArray()
{
        return m_textures;
}

Modelclass.hEdit

ModelClass has been modified to now use texture arrays.

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;

The TextureArrayClass header file is included instead of the previous TextureClass header file.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "texturearrayclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
        struct VertexType
        {
                D3DXVECTOR3 position;
                D3DXVECTOR2 texture;
        };

        struct ModelType
        {
                float x, y, z;
                float tu, tv;
                float nx, ny, nz;
        };

public:
        ModelClass();
        ModelClass(const ModelClass&);
        ~ModelClass();

        bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*);
        void Shutdown();
        void Render(ID3D11DeviceContext*);

        int GetIndexCount();
        ID3D11ShaderResourceView** GetTextureArray();

private:
        bool InitializeBuffers(ID3D11Device*);
        void ShutdownBuffers();
        void RenderBuffers(ID3D11DeviceContext*);

        bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*);
        void ReleaseTextures();

        bool LoadModel(char*);
        void ReleaseModel();

private:
        ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
        int m_vertexCount, m_indexCount;
        ModelType* m_model;

We now have a TextureArrayClass variable instead of a TextureClass variable.

  TextureArrayClass* m_TextureArray;
};

#endif

Modelclass.cppEdit

I will just cover the functions that have changed since the previous tutorials.

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
        m_vertexBuffer = 0;
        m_indexBuffer = 0;
        m_model = 0;

We initialize the new TextureArray variable in the class constructor.

  m_TextureArray = 0;
}


bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2)
{
        bool result;


        // Load in the model data,
        result = LoadModel(modelFilename);
        if(!result)
        {
                return false;
        }

        // Initialize the vertex and index buffers.
        result = InitializeBuffers(device);
        if(!result)
        {
                return false;
        }

We call the LoadTextures function which takes in multiple file names for textures that will be loaded into the texture array and rendered as a blended result on this model.

  // Load the textures for this model.
        result = LoadTextures(device, textureFilename1, textureFilename2);
        if(!result)
        {
                return false;
        }

        return true;
}


void ModelClass::Shutdown()
{

ReleaseTextures is called to release the texture array in the Shutdown function.

  // Release the model textures.
        ReleaseTextures();

        // Shutdown the vertex and index buffers.
        ShutdownBuffers();

        // Release the model data.
        ReleaseModel();

        return;
}

We have a new function called GetTextureArray which gives calling objects access to the texture array that is used for rendering this model.

ID3D11ShaderResourceView** ModelClass::GetTextureArray()
{
        return m_TextureArray->GetTextureArray();
}

LoadTextures has been changed to create a TextureArrayClass object and then initialize it by loading in the two textures that are given as input to this function.

bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
        bool result;


        // Create the texture array object.
        m_TextureArray = new TextureArrayClass;
        if(!m_TextureArray)
        {
                return false;
        }

        // Initialize the texture array object.
        result = m_TextureArray->Initialize(device, filename1, filename2);
        if(!result)
        {
                return false;
        }

        return true;
}

The ReleaseTextures function releases the TextureArrayClass object.

void ModelClass::ReleaseTextures()
{
        // Release the texture array object.
        if(m_TextureArray)
        {
                m_TextureArray->Shutdown();
                delete m_TextureArray;
                m_TextureArray = 0;
        }

        return;
}

Graphicsclass.hEdit

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"

The header for the MultiTextureShaderClass is now included in the GraphicsClass.

#include "multitextureshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
        GraphicsClass();
        GraphicsClass(const GraphicsClass&);
        ~GraphicsClass();

        bool Initialize(int, int, HWND);
        void Shutdown();
        bool Frame();
        bool Render();

private:
        D3DClass* m_D3D;
        CameraClass* m_Camera;
        ModelClass* m_Model;

Here we create the new MultiTextureShaderClass object.

  MultiTextureShaderClass* m_MultiTextureShader;
};

#endif

Graphicsclass.cppEdit

We will cover just the functions that have changed since the previous tutorials.

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
        m_D3D = 0;
        m_Camera = 0;
        m_Model = 0;

We initialize the new multitexture shader object to null in the class constructor.

  m_MultiTextureShader = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
        bool result;
        D3DXMATRIX baseViewMatrix;

                
        // Create the Direct3D object.
        m_D3D = new D3DClass;
        if(!m_D3D)
        {
                return false;
        }

        // Initialize the Direct3D object.
        result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
                return false;
        }

        // Create the camera object.
        m_Camera = new CameraClass;
        if(!m_Camera)
        {
                return false;
        }

        // Initialize a base view matrix with the camera for 2D user interface rendering.
        m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
        m_Camera->Render();
        m_Camera->GetViewMatrix(baseViewMatrix);

        // Create the model object.
        m_Model = new ModelClass;
        if(!m_Model)
        {
                return false;
        }

The ModelClass object is now initialized differently. For this tutorial we load in the square.txt model as the effect we want to display works best on just a plain square. We also now load in two textures for the texture array instead of just a single texture like we did in previous tutorials.

  // Initialize the model object.
        result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", 
                                     L"../Engine/data/dirt01.dds");
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
                return false;
        }

Here we create and initialize the new multitexture shader object.

  // Create the multitexture shader object.
        m_MultiTextureShader = new MultiTextureShaderClass;
        if(!m_MultiTextureShader)
        {
                return false;
        }

        // Initialize the multitexture shader object.
        result = m_MultiTextureShader->Initialize(m_D3D->GetDevice(), hwnd);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the multitexture shader object.", L"Error", MB_OK);
                return false;
        }

        return true;
}


void GraphicsClass::Shutdown()
{

We release the multitexture shader in the Shutdown function.

  // Release the multitexture shader object.
        if(m_MultiTextureShader)
        {
                m_MultiTextureShader->Shutdown();
                delete m_MultiTextureShader;
                m_MultiTextureShader = 0;
        }

        // Release the model object.
        if(m_Model)
        {
                m_Model->Shutdown();
                delete m_Model;
                m_Model = 0;
        }

        // Release the camera object.
        if(m_Camera)
        {
                delete m_Camera;
                m_Camera = 0;
        }

        // Release the D3D object.
        if(m_D3D)
        {
                m_D3D->Shutdown();
                delete m_D3D;
                m_D3D = 0;
        }

        return;
}


bool GraphicsClass::Frame()
{

We set the position of the camera a bit closer to see the blending effect more clearly.

  // Set the position of the camera.
        m_Camera->SetPosition(0.0f, 0.0f, -5.0f);

        return true;
}


bool GraphicsClass::Render()
{
        D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;


        // Clear the buffers to begin the scene.
        m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

        // Generate the view matrix based on the camera's position.
        m_Camera->Render();

        // Get the world, view, projection, and ortho matrices from the camera and D3D objects.
        m_D3D->GetWorldMatrix(worldMatrix);
        m_Camera->GetViewMatrix(viewMatrix);
        m_D3D->GetProjectionMatrix(projectionMatrix);
        m_D3D->GetOrthoMatrix(orthoMatrix);

        // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
        m_Model->Render(m_D3D->GetDeviceContext());

We use the new multitexture shader to render the model. Notice that we send in the texture array from the ModelClass as input to the shader.

  // Render the model using the multitexture shader.
        m_MultiTextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
                                     m_Model->GetTextureArray());

        // Present the rendered scene to the screen.
        m_D3D->EndScene();

        return true;
}

SummaryEdit

We now have a shader that will combine two textures evenly and apply gamma correction. We also now know how to use texture arrays for improved graphics performance.

To Do ExercisesEdit

1. Recompile the code and run the program to see the resulting image. Press escape to quit.

2. Replace the two textures with two new ones to see the results.