Tutorial 23: Fog

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

I will cover a very basic fog effect that is pretty simple to implement. The first step is to choose a color for the fog such as grey and then clear the back buffer to that color. Now the entire scene starts off being fogged. For each model that will be inside the fog you add a certain amount of fog color in the pixel shader based on the model's location inside the fog. There are a couple of different fog equations we can use, in this tutorial I will use a linear equation but we will talk about a couple equations.


Fog Equations

Linear fog adds a linear amount of fog based on the distance you are viewing the object from:

	Linear Fog = (FogEnd - ViewpointDistance) / (FogEnd - FogStart)

Exponential fog adds exponentially more fog the further away an object is inside the fog:

	Exponential Fog = 1.0 / 2.71828 power (ViewpointDistance * FogDensity)

Exponential 2 fog adds even more exponential fog than the previous equation giving a very thick fog appearance:

	Exponential Fog 2 = 1.0 / 2.71828 power ((ViewpointDistance * FogDensity) * (ViewpointDistance * FogDensity))

All three equations produce a fog factor. To apply that fog factor to the model's texture and produce a final pixel color value we use the following equation:

	Fog Color = FogFactor * TextureColor + (1.0 - FogFactor) * FogColor

For this tutorial we will generate the fog factor in the vertex shader and then calculate the final fog color in the pixel shader.


Framework

The framework for this tutorial includes a new FogShaderClass to handle the fog rendering. It is based on the TextureShaderClass and has just a few changes.

We will start the code section of this tutorial by examining the fog HLSL shader code first:


Fog.fx

////////////////////////////////////////////////////////////////////////////////
// Filename: fog.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
Texture2D shaderTexture;

We have two new variables that will be set outside the shader so we can know the start point and end point of the fog effect.

float fogStart;
float fogEnd;


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


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

The PixelInputType has been modified to have a fog factor variable. The fog factor is calculated in the vertex shader and is then sent into the pixel shader using this float variable.

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


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType FogVertexShader(VertexInputType input)
{
    PixelInputType output;
    float4 cameraPosition;

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

We first calculate the Z coordinate of the vertex in view space. We then use that with the fog end and start position in the fog factor equation to produce a fog factor that we send into the pixel shader.

    // Calculate the camera position.
    cameraPosition = mul(input.position, worldMatrix);
    cameraPosition = mul(cameraPosition, viewMatrix);

    // Calculate linear fog.    
    output.fogFactor = saturate((fogEnd - cameraPosition.z) / (fogEnd - fogStart));

    return output;
}


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FogPixelShader(PixelInputType input) : SV_Target
{
    float4 textureColor;
    float4 fogColor;
    float4 finalColor;
	
	
    // Sample the texture pixel at this location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);
    
    // Set the color of the fog to grey.
    fogColor = float4(0.5f, 0.5f, 0.5f, 1.0f);

The fog color equation then does a linear interpolation between the texture color and the fog color based on the fog factor.

    // Calculate the final color using the fog effect equation.
    finalColor = input.fogFactor * textureColor + (1.0 - input.fogFactor) * fogColor;
   	
    return finalColor;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 FogTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, FogVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, FogPixelShader()));
        SetGeometryShader(NULL);
    }
}

Fogshaderclass.h

The FogShaderClass is the same as the TextureShaderClass with some changes to accommodate for fog.

////////////////////////////////////////////////////////////////////////////////
// Filename: fogshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FOGSHADERCLASS_H_
#define _FOGSHADERCLASS_H_


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


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

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

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

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, 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;
	ID3D10EffectShaderResourceVariable* m_texturePtr;

There are two new pointers for the fog end and start location in the shader.

	ID3D10EffectScalarVariable* fogStartPtr;
	ID3D10EffectScalarVariable* fogEndPtr;
};

#endif

Fogshaderclass.cpp

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


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

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

Initialized the fog start and end pointers to null in the class constructor.

	fogStartPtr = 0;
	fogEndPtr = 0;
}


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


FogShaderClass::~FogShaderClass()
{
}


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

Load the fog.fx HLSL shader file.

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

	return true;
}


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

	return;
}

The Render function now takes in a float value for both the fog start and fog end locations. Render will set this parameters in the shader first and then call the shader to begin drawing.

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

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

	return;
}


bool FogShaderClass::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;
	}

Set the name of the technique to FogTechnique to correlate with the naming inside the HLSL shader file.

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("FogTechnique");
	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();

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

Setup the two new fog start and end pointers here.

	// Get pointers to the fog start and end variables inside the shader.
	fogStartPtr = m_effect->GetVariableByName("fogStart")->AsScalar();
	fogEndPtr = m_effect->GetVariableByName("fogEnd")->AsScalar();

	return true;
}


void FogShaderClass::ShutdownShader()
{

Release the two new fog pointers here in the ShutdownShader function.

	// Release the pointers to the fog variables inside the shader.
	fogStartPtr = 0;
	fogEndPtr = 0;

	// Release the pointer to the texture in the shader file.
	m_texturePtr = 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 FogShaderClass::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 FogShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix,
					 ID3D10ShaderResourceView* texture, float fogStart, float fogEnd)
{
	// 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);

	// Bind the texture.
	m_texturePtr->SetResource(texture);

Set the fog start and fog end float values inside the HLSL fog shader.

	// Set the start point for the fog effect inside the shader.
	fogStartPtr->SetFloat(fogStart);

	// Set the end point for the fog effect inside the shader.
	fogEndPtr->SetFloat(fogEnd);

	return;
}


void FogShaderClass::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"

Include the new FogShaderClass header in the GraphicsClass header file.

#include "fogshaderclass.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;

Create a new FogShaderClass object.

	FogShaderClass* m_FogShader;
};

#endif

Graphicsclass.cpp

I will just cover 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;

Initialized the new FogShaderClass object in the class constructor.

	m_FogShader = 0;
}


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

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

Create and initialize the new FogShaderClass object.

	// Create the fog shader object.
	m_FogShader = new FogShaderClass;
	if(!m_FogShader)
	{
		return false;
	}

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

	return true;
}


void GraphicsClass::Shutdown()
{

We release the FogShaderClass object inside the Shutdown function.

	// Release the fog shader object.
	if(m_FogShader)
	{
		m_FogShader->Shutdown();
		delete m_FogShader;
		m_FogShader = 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::Render()
{
	float fogColor, fogStart, fogEnd;
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	static float rotation = 0.0f;

Set the fog parameter values first.

	// Set the color of the fog to grey.
	fogColor = 0.5f;

	// Set the start and end of the fog.
	fogStart = 0.0f;
	fogEnd = 10.0f;

Clear the back buffer to the fog color, this is a very important part of the fog effect we are using.

	// Clear the scene to the color of the fog.
	m_D3D->BeginScene(fogColor, fogColor, fogColor, 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);

	// Update the rotation variable each frame.
	rotation += (float)D3DX_PI * 0.005f;
	if(rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	D3DXMatrixRotationY(&worldMatrix, rotation);

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

Finally render the model using the fog shader.

	// Render the model using the fog shader.
	m_FogShader->Render(m_D3D->GetDevice(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
			    m_Model->GetTexture(), fogStart, fogEnd);

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

	return true;
}

Summary

We have covered how to do a very basic linear fog effect and talked about some of the different equations you can use. If you are going to try different equations it is better to use a larger and more populated scene, something like terrain with some trees works very well.


To Do Exercises

1. Recompile and run the program. You should get a spinning cube inside fog. Press escape to quit.

2. Change the values for the fog start and end locations.

3. Clear the back buffer to black instead of the fog color to see how the effect actually works on the models.

4. Implement the two other fog equations I listed earlier.


Source Code

Visual Studio 2008 Project: dx10tut23.zip

Source Only: dx10src23.zip

Executable Only: dx10exe23.zip

Back to Tutorial Index