Tutorial 33: Fire

This tutorial will cover how to implement a fire shader in DirectX 10 using HLSL and C++. The code in this tutorial is based on the previous tutorials.

One of the most realistic ways to create a fire effect in DirectX is to use a noise texture and to perturb the sampling of that texture in the same way we have for water, glass, and ice. The only major difference is in the manipulation of the noise texture and the specific way we perturb the texture coordinates.

First start with a grey scale noise texture such as follows:

These noise textures can be procedurally generated with several different graphics manipulation programs. The key is to produce one that has noise properties specific to a good looking fire.

The second texture needed for fire effect will be a texture composed of fire colored noise and a flame outline. For example the following texture is composed of a texture that uses perlin noise with fire colors and a texture of a small flame. If you look closely at the middle bottom part of the texture you can see the umbra and penumbra of the flame:

And finally you will need an alpha texture for transparency of the flame so that the final shape is very close to that of a small flame. This can be rough as the perturbation will take care of making it take shape of a good flame:

Now that we have the three textures required for the fire effect we can explain how the shader works. The first thing we do is take the noise texture and create three separate textures from it. The three separate textures are all based on the original noise texture except that they are scaled differently. These scales are called octaves as they are simply repeated tiles of the original noise texture to create numerous more detailed noise textures. The following three noise textures are scaled (tiled) by 1, 2, and 3:

We are also going to scroll all three noise textures upwards to create an upward moving noise which will correlate with a fire that burns upwards. The scroll speed will be set differently for all three so that each moves at a different rate. We will eventually combine the three noise textures so having them move at different rates adds dimension to the fire.

The next step is to convert the three noise textures from the (0, 1) range into the (-1, +1) range. This has the effect of removing some of the noise and creating noise that looks closer to fire. For example the first noise texture now looks like the following:

With all three noise textures in the (-1, +1) range we can now add distortion to each texture along the x and y coordinates. The distortion modifies the noise textures by scaling down the noise similar to how the refraction scale in the water, glass, and ice shaders did so. Once each noise texture is distorted we can then combine all three noise textures to produce a final noise texture.

Note that the distortion here is only applied to the x and y coordinates since we will be using them as a look up table for the x and y texture sampling just like we do with normal maps. The z coordinate is ignored as it has no use when sampling textures in 2D.

Remember also that each frame the three noise textures are scrolling upwards at different rates so combining them creates an almost organized flowing noise that looks like fire.

Now the next very important step is to perturb the original fire color texture. In other shaders such as water, glass, and ice we usually use a normal map at this point to perturb the texture sampling coordinates. However in fire we use the noise as our perturbed texture sampling coordinates. But before we do that we want to also perturb the noise itself. We will use a distortion scale and bias to distort the noise higher at the top of the texture and less at the bottom of the texture to create a solid flame at the base and flame licks at the top. In other words we perturb the noise along the Y axis using an increased amount of distortion as we go up the noise texture from the bottom.

We now use the perturbed final noise texture as our look up table for texture sampling coordinates that will be used to sample the fire color texture. Note that we use Clamp instead of Wrap for the fire color texture or we will get flame licks wrapping around to the bottom which would ruin the look. The fire color texture sampled using the perturbed noise now looks like the following:

Now that we have an animated burning square that looks fairly realistic we need a way of shaping it into more of a flame. To do so we turn on blending and use the alpha texture we created earlier. The trick is to sample the alpha texture using the same perturbed noise to make the alpha look like a burning flame. We also need to use Clamp instead of Wrap for sampling to prevent the flames from wrapping around to the bottom. When we do so we get the following result from the sampled perturbed alpha:

To complete the effect we set the perturbed alpha value to be the alpha channel of the perturbed fire color texture and the blending takes care of the rest:

When you see this animated it looks very realistic.


Framework

The frame work has one new class called FireShaderClass which is just the TextureShaderClass updated for the fire effect.

We will start the code section by examining the HLSL fire shader.


Fire.fx

////////////////////////////////////////////////////////////////////////////////
// Filename: fire.fx
////////////////////////////////////////////////////////////////////////////////


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

The three textures for the fire effect are the fire color texture, the noise texture, and the alpha texture.

Texture2D fireTexture;
Texture2D noiseTexture;
Texture2D alphaTexture;

The frameTime variable is updated each frame so the shader has access to an incremental time that is used for scrolling the different noise textures.

float frameTime;

The scrollSpeeds variable is a 3 float array that contains three different scrolling speeds. The x value is the scroll speed for the first noise texture. The y value is the scroll speed for the second noise texture. And the z value is the scroll speed for the third noise texture.

float3 scrollSpeeds;

The scales variable is a 3 float array that contains three different scales (or octaves) for the three different noise textures. The x, y, and z are generally set to 1, 2, and 3. This makes the first noise texture a single tile. It also makes the second noise texture tiled twice in both directions. And finally it makes the third noise texture tiled three times in both directions.

float3 scales;

The three distortion arrays contain a x and y value for distorting the three different noise textures by individual x and y parameters.

float2 distortion1;
float2 distortion2;
float2 distortion3;

The distortion scale and bias are used for perturbing the final combined noise texture to make it take the shape of a flame.

float distortionScale;
float distortionBias;


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

We add a second sample state that uses Clamp. The Wrap used in the first sample state would cause the fire to wrap around which ruins the effect.

SamplerState SampleType2
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};


/////////////////////
// BLENDING STATES //
/////////////////////

This shader requires blending as we use a perturbed alpha texture for sampling and creating see through parts of the final fire effect.

BlendState AlphaBlendingOn
{
    BlendEnable[0] = TRUE;
    DestBlend = INV_SRC_ALPHA;
    SrcBlend = SRC_ALPHA;
};


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

The PixelInputType structure has been changed to take three different texture coordinates. We use it for sampling the same noise texture in three different ways to basically create three different noise textures from one.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 texCoords1 : TEXCOORD1;
    float2 texCoords2 : TEXCOORD2;
    float2 texCoords3 : TEXCOORD3;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType FireVertexShader(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;

Here is where we create three different texture sampling values so that the same noise texture can be used to create three different noise textures. For each texture coordinate we first scale it by the scale value which then tiles the texture a number of times depending on the value in the scales array. After that we scroll the three different y coordinates upwards using the frame time and the value in the scrollSpeeds array. The scroll speed for all three will be different which gives the fire dimension.

    // Compute texture coordinates for first noise texture using the first scale and upward scrolling speed values.
    output.texCoords1 = (input.tex * scales.x);
    output.texCoords1.y = output.texCoords1.y + (frameTime * scrollSpeeds.x);

    // Compute texture coordinates for second noise texture using the second scale and upward scrolling speed values.
    output.texCoords2 = (input.tex * scales.y);
    output.texCoords2.y = output.texCoords2.y + (frameTime * scrollSpeeds.y);

    // Compute texture coordinates for third noise texture using the third scale and upward scrolling speed values.
    output.texCoords3 = (input.tex * scales.z);
    output.texCoords3.y = output.texCoords3.y + (frameTime * scrollSpeeds.z);

    return output;
}


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FirePixelShader(PixelInputType input) : SV_Target
{
    float4 noise1;
    float4 noise2;
    float4 noise3;
    float4 finalNoise;
    float perturb;
    float2 noiseCoords;
    float4 fireColor;
    float4 alphaColor;

First create three different noise values by sampling the noise texture three different ways. Afterward move the texture pixel value into the (-1, +1) range.

    // Sample the same noise texture using the three different texture coordinates to get three different noise scales.
    noise1 = noiseTexture.Sample(SampleType, input.texCoords1);
    noise2 = noiseTexture.Sample(SampleType, input.texCoords2);
    noise3 = noiseTexture.Sample(SampleType, input.texCoords3);

    // Move the noise from the (0, 1) range to the (-1, +1) range.
    noise1 = (noise1 - 0.5f) * 2.0f;
    noise2 = (noise2 - 0.5f) * 2.0f;
    noise3 = (noise3 - 0.5f) * 2.0f;

Now scale down the x and y sampling coordinates by the distortion amount. After they are distorted all three texture values are combined into a single value which represents the final noise value for this pixel.

    // Distort the three noise x and y coordinates by the three different distortion x and y values.
    noise1.xy = noise1.xy * distortion1.xy;
    noise2.xy = noise2.xy * distortion2.xy;
    noise3.xy = noise3.xy * distortion3.xy;

    // Combine all three distorted noise results into a single noise result.
    finalNoise = noise1 + noise2 + noise3;

We now perturb the final noise result to create a fire look to the overall noise texture. Note that we perturb it more at the top and less as it moves to the bottom. This creates a flickering flame at the top and as it progresses downwards it creates a more solid flame base.

    // Perturb the input texture Y coordinates by the distortion scale and bias values.  
    // The perturbation gets stronger as you move up the texture which creates the flame flickering at the top effect.
    perturb = ((1.0f - input.tex.y) * distortionScale) + distortionBias;

    // Now create the perturbed and distorted texture sampling coordinates that will be used to sample the fire color texture.
    noiseCoords.xy = (finalNoise.xy * perturb) + input.tex.xy;

Sample both the fire color texture and the alpha texture by the perturbed noise sampling coordinates to create the fire effect.

    // Sample the color from the fire texture using the perturbed and distorted texture sampling coordinates.
    // Use the clamping sample state instead of the wrap sample state to prevent flames wrapping around.
    fireColor = fireTexture.Sample(SampleType2, noiseCoords.xy);

    // Sample the alpha value from the alpha texture using the perturbed and distorted texture sampling coordinates.
    // This will be used for transparency of the fire.
    // Use the clamping sample state instead of the wrap sample state to prevent flames wrapping around.
    alphaColor = alphaTexture.Sample(SampleType2, noiseCoords.xy);

Combine the alpha and the fire color to create the transparent blended final fire effect.

    // Set the alpha blending of the fire to the perturbed and distored alpha texture value.
    fireColor.a = alphaColor;
	
    return fireColor;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////

Note that alpha blending is turned on in the FireTechnique. Without this the alpha has no effect.

technique10 FireTechnique
{
    pass pass0
    {
        SetBlendState(AlphaBlendingOn, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
        SetVertexShader(CompileShader(vs_4_0, FireVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, FirePixelShader()));
        SetGeometryShader(NULL);
    }
}

Fireshaderclass.h

The FireShaderClass is just the TextureShaderClass modified for the fire effect.

////////////////////////////////////////////////////////////////////////////////
// Filename: fireshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FIRESHADERCLASS_H_
#define _FIRESHADERCLASS_H_


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


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

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

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

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

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

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

The fire effect requires three textures. A fire color texture, a noise texture, and an alpha texture are used.

	ID3D10EffectShaderResourceVariable* m_fireTexturePtr;
	ID3D10EffectShaderResourceVariable* m_noiseTexturePtr;
	ID3D10EffectShaderResourceVariable* m_alphaTexturePtr;

The effect also animates and requires a incrementing time value which the m_frameTimePtr is used for.

	ID3D10EffectScalarVariable* m_frameTimePtr;

The fire effect is made up of three scrolling noise textures in an array. The m_scrollSpeedsPtr provides a pointer to these three scroll speeds.

	ID3D10EffectVectorVariable* m_scrollSpeedsPtr;

The scales or octaves are created using the array values that are pointed to by m_scalesPtr.

	ID3D10EffectVectorVariable* m_scalesPtr;

The distortion pointers are used to access the three distortion arrays in the shader.

	ID3D10EffectVectorVariable* m_distortion1Ptr;
	ID3D10EffectVectorVariable* m_distortion2Ptr;
	ID3D10EffectVectorVariable* m_distortion3Ptr;

The distortion scale and bias in the shader are accessed using the two following pointers.

	ID3D10EffectScalarVariable* m_distortionScalePtr;
	ID3D10EffectScalarVariable* m_distortionBiasPtr;
};

#endif

Fireshaderclass.cpp

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

All the private pointers in the class are initialized to null in the class constructor.

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

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

	m_fireTexturePtr = 0;
	m_noiseTexturePtr = 0;
	m_alphaTexturePtr = 0;

	m_frameTimePtr = 0;
	m_scrollSpeedsPtr = 0;
	m_scalesPtr = 0;
	m_distortion1Ptr = 0;
	m_distortion2Ptr = 0;
	m_distortion3Ptr = 0;
	m_distortionScalePtr = 0;
	m_distortionBiasPtr = 0;
}


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


FireShaderClass::~FireShaderClass()
{
}


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

The fire.fx HLSL file is loaded here.

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

	return true;
}


void FireShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}

The Render function takes in all the numerous input variables that are used to render and tweak the look of the fire. They are first set in the shader using the SetShaderParameters function. Once all the values are set the shader is used for rendering by then calling the RenderShader function.

void FireShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
			     D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView* fireTexture, 
			     ID3D10ShaderResourceView* noiseTexture, ID3D10ShaderResourceView* alphaTexture, float frameTime,
			     D3DXVECTOR3 scrollSpeeds, D3DXVECTOR3 scales, D3DXVECTOR2 distortion1, D3DXVECTOR2 distortion2,
			     D3DXVECTOR2 distortion3, float distortionScale, float distortionBias)
{
	// Set the shader parameters that will be used for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, fireTexture, noiseTexture, alphaTexture, frameTime, scrollSpeeds,
			    scales, distortion1, distortion2, distortion3, distortionScale, distortionBias);

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

	return;
}


bool FireShaderClass::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 is set to FireTechnique here.

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

	// Now setup the layout of the data that goes into 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 they can be updated from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

All of the new pointers are initialized here so that they can access the values inside the fire shader.

	// Get pointers to the texture resources inside the shader.
	m_fireTexturePtr = m_effect->GetVariableByName("fireTexture")->AsShaderResource();
	m_noiseTexturePtr = m_effect->GetVariableByName("noiseTexture")->AsShaderResource();
	m_alphaTexturePtr = m_effect->GetVariableByName("alphaTexture")->AsShaderResource();

	// Get the pointer to the frame time inside the shader.
	m_frameTimePtr = m_effect->GetVariableByName("frameTime")->AsScalar();

	// Get a pointer to the scrolling speeds array inside the shader.
	m_scrollSpeedsPtr = m_effect->GetVariableByName("scrollSpeeds")->AsVector();

	// Get a pointer to the noise scaling array inside the shader.
	m_scalesPtr = m_effect->GetVariableByName("scales")->AsVector();

	// Get the pointers to the three noise distortion arrays inside the shader.
	m_distortion1Ptr = m_effect->GetVariableByName("distortion1")->AsVector();
	m_distortion2Ptr = m_effect->GetVariableByName("distortion2")->AsVector();
	m_distortion3Ptr = m_effect->GetVariableByName("distortion3")->AsVector();

	// Get the pointers to the perturbation scale and bias variables inside the shader.
	m_distortionScalePtr = m_effect->GetVariableByName("distortionScale")->AsScalar();
	m_distortionBiasPtr = m_effect->GetVariableByName("distortionBias")->AsScalar();

	return true;
}


void FireShaderClass::ShutdownShader()
{

The ShutdownShader function releases all the pointers that were used to access values inside the fire shader.

	// Release the perturbation scale and bias pointers.
	m_distortionScalePtr = 0;
	m_distortionBiasPtr = 0;

	// Release the pointers to the distortion arrays in the shader.
	m_distortion1Ptr = 0;
	m_distortion2Ptr = 0;
	m_distortion3Ptr = 0;

	// Release the pointer to the noise scaling array in the shader.
	m_scalesPtr = 0;

	// Release the pointer to the scroll speed array in the shader.
	m_scrollSpeedsPtr = 0;

	// Release the pointer to the frame time in the shader.
	m_frameTimePtr = 0;

	// Release the pointers to the textures in the shader.
	m_fireTexturePtr = 0;
	m_noiseTexturePtr = 0;
	m_alphaTexturePtr = 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 FireShaderClass::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 FireShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
					  ID3D10ShaderResourceView* fireTexture, ID3D10ShaderResourceView* noiseTexture, 
					  ID3D10ShaderResourceView* alphaTexture, float frameTime, D3DXVECTOR3 scrollSpeeds,
					  D3DXVECTOR3 scales, D3DXVECTOR2 distortion1, D3DXVECTOR2 distortion2, 
					  D3DXVECTOR2 distortion3, float distortionScale, float distortionBias)
{
	// 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);

Set the three textures in the fire shader.

	// Bind the textures.
	m_fireTexturePtr->SetResource(fireTexture);
	m_noiseTexturePtr->SetResource(noiseTexture);
	m_alphaTexturePtr->SetResource(alphaTexture);

All the remainder of the fire shader variables are set here before rendering. Each one of these allows the fire effect to be tweaked to produce a desired look.

	// Set the frame time in the shader.
	m_frameTimePtr->SetFloat(frameTime);

	// Set the scolling speed array for the three noise textures in the shader.
	m_scrollSpeedsPtr->SetFloatVector((float*)&scrollSpeeds);

	// Set the scaling array for the three noise textures in the shader.
	m_scalesPtr->SetFloatVector((float*)&scales);

	// Set the three distortion arrays for the three noise textures in the shader.
	m_distortion1Ptr->SetFloatVector((float*)&distortion1);
	m_distortion2Ptr->SetFloatVector((float*)&distortion2);
	m_distortion3Ptr->SetFloatVector((float*)&distortion3);

	// Set the perturbation scale and bias in the shader.
	m_distortionScalePtr->SetFloat(distortionScale);
	m_distortionBiasPtr->SetFloat(distortionBias);

	return;
}


void FireShaderClass::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;
}

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"

We add the header for the new FireShaderClass.

#include "fireshaderclass.h"


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

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

private:
	void Render();

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

The private FireShaderClass object is added here also.

	FireShaderClass* m_FireShader;
};

#endif

Graphicsclass.cpp

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


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

Initialize the new fire shader object to null in the class constructor.

	m_FireShader = 0;
}


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


GraphicsClass::~GraphicsClass()
{
}


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

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

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

Load a square model for the fire. Also load the three textures that will be used to create the fire effect for this model.

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

Create and initialize the new fire shader object.

	// Create the fire shader object.
	m_FireShader = new FireShaderClass;
	if(!m_FireShader)
	{
		return false;
	}

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

	return true;
}


void GraphicsClass::Shutdown()
{

Shutdown and release the new fire shader object in the Shutdown function.

	// Release the fire shader object.
	if(m_FireShader)
	{
		m_FireShader->Shutdown();
		delete m_FireShader;
		m_FireShader = 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;
}


void GraphicsClass::Frame()
{
	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	// Now render the scene.
	Render();

	return;
}


void GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	D3DXVECTOR3 scrollSpeeds, scales;
	D3DXVECTOR2 distortion1, distortion2, distortion3;
	float distortionScale, distortionBias;
	static float frameTime = 0.0f;

Each frame increment the time. This is used to scroll the three different noise textures in the shader. Note that if you don't lock the FPS to 60 then you will need to determine the difference of time each frame and update a timer to keep the fire burning at a consistent speed regardless of the FPS.

	// Increment the frame time counter.
	frameTime += 0.01f;
	if(frameTime > 1000.0f)
	{
		frameTime = 0.0f;
	}

Set the three scroll speeds, scales, and distortion values for the three different noise textures.

	// Set the three scrolling speeds for the three different noise textures.
	scrollSpeeds = D3DXVECTOR3(1.3f, 2.1f, 2.3f);

	// Set the three scales which will be used to create the three different noise octave textures.
	scales = D3DXVECTOR3(1.0f, 2.0f, 3.0f);

	// Set the three different x and y distortion factors for the three different noise textures.
	distortion1 = D3DXVECTOR2(0.1f, 0.2f);
	distortion2 = D3DXVECTOR2(0.1f, 0.3f);
	distortion3 = D3DXVECTOR2(0.1f, 0.1f);

Set the bias and scale that are used for perturbing the noise texture into a flame form.

	// The the scale and bias of the texture coordinate sampling perturbation.
	distortionScale = 0.8f;
	distortionBias = 0.5f;

	// 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, and projection matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

Put the square model on the graphics pipeline.

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

Render the square model using the flame shader.

	// Render the fire model using the fire shader.
	m_FireShader->Render(m_D3D->GetDevice(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
			     m_Model->GetTexture1(), m_Model->GetTexture2(), m_Model->GetTexture3(), frameTime, scrollSpeeds, scales,
			     distortion1, distortion2, distortion3, distortionScale, distortionBias);

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

	return;
}

Summary

The fire shader produces an incredibly realistic fire effect. It is also highly customizable giving it the ability to produce almost any type of fire flame by just modifying one or more of the many tweakable shader variables. The flame will still need to be bill boarded or rendered a couple times at different angles in the same location to be used realistically in a 3D setting.


To Do Exercises

1. Recompile and run the program. You should get an animated fire effect. Press escape to quit.

2. Modify the many different shader values to see the different effects they produce. Start with scale and bias. You may also want to comment out certain parts of the fire shader to see the effect they have on the different textures.

3. Create your own noise texture and see how it changes the fire.

4. Create your own alpha texture to modify the overall shape of the flame.


Source Code

Visual Studio 2008 Project: dx10tut33.zip

Source Only: dx10src33.zip

Executable Only: dx10exe33.zip

Back to Tutorial Index