Tutorial 34: Billboarding

Billboarding in DirectX 11 is the process of using a single textured quad to represent 3D geometry at generally far distances. This is done for performance gain and sometimes out of necessity to represent a fairly complex scene that could normally not be rendered due to the high polygon requirements.

To illustrate the use of billboarding take for example the scene of a forest with thousands of trees in it. To render in real time a complex forest with high polygon trees is generally beyond the capabilities of most video cards. So what is done instead is the 50 or so closest trees will be rendered normally and then the remainder of trees are rendered as billboards. This allows the scene polygon count to remain low while still rendering in real time the entire forest. Now as the user moves through the forest and moves past the 50 closest trees then the next 50 trees will do a gradual blend between the billboard and the actual full polygon tree model. This way the closest trees are always high detailed models and the distant trees are always low polygon billboards. The blend process between the model and the billboard quad helps eliminate the pop that would otherwise occur. This process can be used for many other objects such as buildings, clouds, mountains, structures, planetary bodies, particles, and so forth.

Now what differentiates the different billboarding techniques is how the textured quad is rotated. Sometimes the quad is rotated in accordance with the user's view, most particle systems use this. Other times it is rotated to always face the screen in a 2D manner such as 3D text. However in this tutorial we are going to cover a third method which instead keeps the quad rotated to always face the user based upon the user's position in the world. This is the method you would use for the tree example.


Framework

The frame work for this tutorial does not introduce any new classes. Almost all the billboarding work is done in the GraphicsClass.


Graphicsclass.h

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


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


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


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

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

private:
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextureShaderClass* m_TextureShader;

This tutorial will use two models. The first model is the model of a flat grid on a floor, this is to provide perspective when the user moves left to right. The second model is a quad made out of two triangles with a texture on it, this will be the billboard model that will be rotated to always face the user.

	ModelClass *m_FloorModel, *m_BillboardModel;
};

#endif

Graphicsclass.h

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


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

Initialize the two models to null in the class constructor.

	m_FloorModel = 0;
	m_BillboardModel = 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;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the texture shader object.
	m_TextureShader = new TextureShaderClass;
	if(!m_TextureShader)
	{
		return false;
	}

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

Initialize the floor model first.

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

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

Initialize the billboard model next.

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

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

	return true;
}


void GraphicsClass::Shutdown()
{

Release the floor and billboard model in the Shutdown function.

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

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

	// Release the texture shader object.
	if(m_TextureShader)
	{
		m_TextureShader->Shutdown();
		delete m_TextureShader;
		m_TextureShader = 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(float positionX, float positionY, float positionZ)
{
	bool result;

Set the position of the camera based on the position input from the SystemClass. The user will be using the left and right arrow keys to strafe, therefore the billboard model needs rotate in relation to the updated position of the camera.

	// Update the position of the camera.
	m_Camera->SetPosition(positionX, positionY, positionZ);

	// Render the graphics scene.
	result = Render();
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, translateMatrix;
	bool result;
	D3DXVECTOR3 cameraPosition, modelPosition;
	double angle;
	float rotation;


	// 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_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

First render the floor model normally.

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

	// Render the floor model using the texture shader.
	result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_FloorModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
					 m_FloorModel->GetTexture());
	if(!result)
	{
		return false;
	}

Now retrieve and set the position of the camera and the position of the billboard model in the world.

	// Get the position of the camera.
	cameraPosition = m_Camera->GetPosition();

	// Set the position of the billboard model.
	modelPosition.x = 0.0f;
	modelPosition.y = 1.5f;
	modelPosition.z = 0.0f;

Next determine the rotation for the billboard so it faces the camera based on the camera's position.

	// Calculate the rotation that needs to be applied to the billboard model to face the current camera position using the arc tangent function.
	angle = atan2(modelPosition.x - cameraPosition.x, modelPosition.z - cameraPosition.z) * (180.0 / D3DX_PI);

	// Convert rotation into radians.
	rotation = (float)angle * 0.0174532925f;

Use the rotation to first rotate the world matrix accordingly, and then translate to the position of the billboard in the world.

	// Setup the rotation the billboard at the origin using the world matrix.
	D3DXMatrixRotationY(&worldMatrix, rotation);

	// Setup the translation matrix from the billboard model.
	D3DXMatrixTranslation(&translateMatrix, modelPosition.x, modelPosition.y, modelPosition.z);

	// Finally combine the rotation and translation matrices to create the final world matrix for the billboard model.
	D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translateMatrix); 

Now render the billboard model.

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

	// Render the model using the texture shader.
	result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_BillboardModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
					 m_BillboardModel->GetTexture());
	if(!result)
	{
		return false;
	}

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

	return true;
}

Summary

As you move left and right the billboard model turns to always face the camera based on the current position of the camera. Note that there are alternative billboarding techniques to achieve other billboarding effects.


To Do Exercises

1. Recompile and run the program. Use the left and right arrow keys to strafe to either side and the billboard will rotate accordingly.

2. Change the billboard texture to a picture of a tree with alpha blending enabled.

3. Create a small forest using the tree billboards on flat terrain.

4. Create a blend effect to transition between the tree billboard and the actual tree model based on distance of the camera from each tree.

5. Investigate view based billboarding.


Source Code

Source Code and Data Files: dx11src34.zip

Executable: dx11exe34.zip

Back to Tutorial Index