Tutorial 17: Multitexturing and Texture Arrays

This tutorial will cover how to do multitexturing in DirectX 10 as well as how to implement texture arrays in DirectX 10. 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 in 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 in DirectX 10.

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.fx

////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;

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 previous versions of DirectX forcing most engines to be written around texture and material switches. Texture arrays help reduce that performance cost.

Texture2D shaderTextures[2];


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};


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

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

    return blendColor;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 MultiTextureTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, MultiTextureVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, MultiTexturePixelShader()));
        SetGeometryShader(NULL);
    }
}

Multitextureshaderclass.h

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

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


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10.h>
#include <fstream>
using namespace std;


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

	bool Initialize(ID3D10Device*, HWND);
	void Shutdown();
	void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView**);

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

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView**);
	void RenderShader(ID3D10Device*, int);

private:
	ID3D10Effect* m_effect;
	ID3D10EffectTechnique* m_technique;
	ID3D10InputLayout* m_layout;

	ID3D10EffectMatrixVariable* m_worldMatrixPtr;
	ID3D10EffectMatrixVariable* m_viewMatrixPtr;
	ID3D10EffectMatrixVariable* m_projectionMatrixPtr;

Here is the new texture array resource pointer.

	ID3D10EffectShaderResourceVariable* m_textureArrayPtr;
};

#endif

Multitextureshaderclass.cpp

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


MultiTextureShaderClass::MultiTextureShaderClass()
{
	m_effect = 0;
	m_technique = 0;
	m_layout = 0;

	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

We initialize the new texture array resource pointer to null in the class constructor.

	m_textureArrayPtr = 0;
}


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


MultiTextureShaderClass::~MultiTextureShaderClass()
{
}


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

The multitexture.fx HLSL shader file is loaded in the Initialize function.

	// Initialize the shader that will be used to draw the model.
	result = InitializeShader(device, hwnd, L"../Engine/multitexture.fx");
	if(!result)
	{
		return false;
	}

	return true;
}

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

void MultiTextureShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	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.

void MultiTextureShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
				     D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView** textureArray)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, textureArray);

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

	return;
}


bool MultiTextureShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	D3D10_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D10_PASS_DESC passDesc;


	// Initialize the error message.
	errorMessage = 0;

	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
					    device, NULL, NULL, &m_effect, &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, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

The name of the technique has been changed to MultiTextureTechnique to match the technique name in the HLSL file.

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("MultiTextureTechnique");
	if(!m_technique)
	{
		return false;
	}

	// Now setup the layout of the data that goes into the shader.
	// 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 = D3D10_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 = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

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

	// Get the description of the first pass described in the shader technique.
	m_technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

Here we setup the pointer to the texture array resource in the shader.

	// Get pointer to the texture array resource inside the shader.
	m_textureArrayPtr = m_effect->GetVariableByName("shaderTextures")->AsShaderResource();

	return true;
}


void MultiTextureShaderClass::ShutdownShader()
{

Here we release the pointer to the texture array.

	// Release the pointer to the texture in the shader file.
	m_textureArrayPtr = 0;

	// Release the pointers to the matrices inside the shader.
	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

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

	// Release the pointer to the shader technique.
	m_technique = 0;

	// Release the pointer to the shader.
	if(m_effect)
	{
		m_effect->Release();
		m_effect = 0;
	}

	return;
}


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 << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->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;
}


void MultiTextureShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
						  ID3D10ShaderResourceView** textureArray)
{
	// Set the world matrix variable inside the shader.
	m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader.
	m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader.
	m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);

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

	// Bind the texture array.
	m_textureArrayPtr->SetResourceArray(textureArray, 0, 2);

	return;
}


void MultiTextureShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	

	// Set the input layout.
	device->IASetInputLayout(m_layout);

	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and render the triangles.
	for(i=0; i<techniqueDesc.Passes; ++i)
	{
		m_technique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

Texturearrayclass.h

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 <d3d10.h>
#include <d3dx10.h>


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

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

	ID3D10ShaderResourceView** GetTextureArray();

private:

This is the two element texture array private variable.

	ID3D10ShaderResourceView* m_textures[2];
};

#endif

Texturearrayclass.cpp

////////////////////////////////////////////////////////////////////////////////
// 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(ID3D10Device* device, WCHAR* filename1, WCHAR* filename2)
{
	HRESULT result;


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

	// Load the second texture in.
	result = D3DX10CreateShaderResourceViewFromFile(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.

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

Modelclass.h

ModelClass has been modified to use texture arrays.

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


//////////////
// INCLUDES //
//////////////
#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(ID3D10Device*, char*, WCHAR*, WCHAR*);
	void Shutdown();
	void Render(ID3D10Device*);

	int GetIndexCount();
	ID3D10ShaderResourceView** GetTextureArray();

private:
	bool InitializeBuffers(ID3D10Device*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D10Device*);

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

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

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

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

	TextureArrayClass* m_TextureArray;
};

#endif

Modelclass.cpp

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(ID3D10Device* 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 buffer that hold the geometry for the triangle.
	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();

	// Release 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.

ID3D10ShaderResourceView** 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(ID3D10Device* 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.h

////////////////////////////////////////////////////////////////////////////////
// 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.cpp

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 Direct3D 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->GetDevice());

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->GetDevice(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
				     m_Model->GetTextureArray());

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

	return true;
}

Summary

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 Exercises

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.


Source Code

Visual Studio 2008 Project: dx10tut17.zip

Source Only: dx10src17.zip

Executable Only: dx10exe17.zip

Back to Tutorial Index