In this terrain tutorial we will cover one of the methods for implementing foliage in DirectX 11 using HLSL and C++.
There are many different methods for rendering foliage on terrain.
The first method is to just render a number of complete plant models.
A second method that is commonly used is to use two or three quads intersecting each other with a plant texture to create the illusion of a complete plant model.
The third method used is just a single quad with a plant texture placed hundreds of times on the ground and rotated towards the viewer.
And finally a fourth method uses a combination of all three methods paired with some LOD settings based on the viewer's position.
The first method obviously looks the best but seriously limits how much foliage can be used.
The second method is a good solution because it gives the illusion of a full model with a very small polygon count.
The third method has an even lower polygon count and allows for a large amount of foliage to be placed.
And finally combining the three methods can give the best possible results.
For this tutorial we will implement the third method.
When rendering foliage you generally do not need to render it at great distances, in fact most engines won't render foliage past 50 meters or so.
It is good to set a distance value that you can manipulate to see what works best for your own engine.
Also most engines animate foliage by default since it is trivial to do so.
Some simply rotate it back and forth, and others use more advanced systems such as perlin noise textures
to simulate different velocities of coherent speeds to create wind blowing through fields of grass.
It is really up to you how far you want to take the animation of your foliage.
As most systems use a plant texture with an alpha channel it becomes necessary to use a good blending method.
You can sort by depth and render in reverse order but it is a lot easier and artifact-free to just use the DirectX alpha-to-coverage blending method.
We will be using the alpha-to-coverage blending mode for this tutorial.
Finally we will be using instancing to give each grass quad its own unique properties.
In this tutorial we will give each grass quad a unique location, rotation, and color.
And because we use instancing we only put a single grass quad on the pipeline and the rest is just instance data.
We will start the tutorial by looking at the new FoliageClass.
Foliageclass.h
The FoliageClass encapsulates everything to do with the foliage including the model, texture, instancing data, and animation.
This class contains just the basics to keep the tutorial simple.
////////////////////////////////////////////////////////////////////////////////
// Filename: foliageclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FOLIAGECLASS_H_
#define _FOLIAGECLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <time.h>
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: FoliageClass
////////////////////////////////////////////////////////////////////////////////
class FoliageClass
{
private:
The VertexType will contain just the position and texture coordinates.
All other information about the foliage will be located in the instance buffer.
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
};
This structure will be used to define the attributes of our foliage that will be stored in an array.
In this tutorial it will have an X and Z position, as well as a color.
struct FoliageType
{
float x, z;
float r, g, b;
};
The foliage will be instanced for position, rotation (rotated to the viewer, and rotated according to the wind), and the color of each grass quad.
The position and two rotations are stored in the single matrix.
struct InstanceType
{
D3DXMATRIX matrix;
D3DXVECTOR3 color;
};
public:
FoliageClass();
FoliageClass(const FoliageClass&);
~FoliageClass();
bool Initialize(ID3D11Device*, WCHAR*, int);
void Shutdown();
void Render(ID3D11DeviceContext*);
bool Frame(D3DXVECTOR3, ID3D11DeviceContext*);
int GetVertexCount();
int GetInstanceCount();
ID3D11ShaderResourceView* GetTexture();
private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
bool LoadTexture(ID3D11Device*, WCHAR*);
void ReleaseTexture();
bool GeneratePositions();
private:
int m_foliageCount;
We will store the position and color of each piece of foliage in the m_foliageArray.
We will use the data here to build the instance array.
FoliageType* m_foliageArray;
We will store all the matrices and colors for each piece of foliage in the m_foliageArray.
Each frame we will update it and then copy it into the instance buffer for rendering.
InstanceType* m_Instances;
We use a vertex and instance buffer to render the foliage.
We don't require an index buffer when we are using instance buffers.
ID3D11Buffer *m_vertexBuffer, *m_instanceBuffer;
int m_vertexCount, m_instanceCount;
TextureClass* m_Texture;
We have a couple variables for storing the wind direction and rotation.
float m_windRotation;
int m_windDirection;
};
#endif
Foliageclass.cpp
///////////////////////////////////////////////////////////////////////////////
// Filename: foliageclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "foliageclass.h"
FoliageClass::FoliageClass()
{
m_foliageArray = 0;
m_Instances = 0;
m_vertexBuffer = 0;
m_instanceBuffer = 0;
m_Texture = 0;
}
FoliageClass::FoliageClass(const FoliageClass& other)
{
}
FoliageClass::~FoliageClass()
{
}
bool FoliageClass::Initialize(ID3D11Device* device, WCHAR* textureFilename, int fCount)
{
bool result;
// Set the foliage count.
m_foliageCount = fCount;
At the beginning of the Initialize function we call GeneratePositions to load the m_foliageArray with random foliage positions.
It will also generate the unique colors for each piece of foliage and store it all in the array.
This has to be called first since the InitializeBuffers function that follows will use this array to create the instance buffer for rendering the foliage.
// Generate the positions of the foliage.
result = GeneratePositions();
if(!result)
{
return false;
}
// Initialize the vertex and instance buffer that hold the geometry for the foliage model.
result = InitializeBuffers(device);
if(!result)
{
return false;
}
// Load the texture for this model.
result = LoadTexture(device, textureFilename);
if(!result)
{
return false;
}
// Set the initial wind rotation and direction.
m_windRotation = 0.0f;
m_windDirection = 1;
return true;
}
void FoliageClass::Shutdown()
{
// Release the model texture.
ReleaseTexture();
// Release the vertex and instance buffers.
ShutdownBuffers();
// Release the foliage array.
if(m_foliageArray)
{
delete [] m_foliageArray;
m_foliageArray = 0;
}
return;
}
void FoliageClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and instance buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
The Frame function is called every frame and updates the direction of the wind and the rotation of the foliage with respect to the wind rotation.
The camera position is also taken into account and a final matrix composed of the position, X rotation, and Z rotation of each piece of foliage is created and stored in the m_Instances array.
Once all of the foliage rotations are calculated and stored we then copy the m_Instances array into the instance buffer which is used for rendering this frame.
bool FoliageClass::Frame(D3DXVECTOR3 cameraPosition, ID3D11DeviceContext* deviceContext)
{
D3DXMATRIX rotateMatrix, translationMatrix, rotateMatrix2, finalMatrix;
D3DXVECTOR3 modelPosition;
int i;
double angle;
float rotation, windRotation;
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
InstanceType* instancesPtr;
// Update the wind rotation.
if(m_windDirection == 1)
{
m_windRotation += 0.1f;
if(m_windRotation > 10.0f)
{
m_windDirection = 2;
}
}
else
{
m_windRotation -= 0.1f;
if(m_windRotation < -10.0f)
{
m_windDirection = 1;
}
}
// Load the instance buffer with the updated locations.
for(i=0; i<m_foliageCount; i++)
{
// Get the position of this piece of foliage.
modelPosition.x = m_foliageArray[i].x;
modelPosition.y = -0.1f;
modelPosition.z = m_foliageArray[i].z;
// 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;
// Setup the X rotation of the billboard.
D3DXMatrixRotationY(&rotateMatrix, rotation);
// Get the wind rotation for the foliage.
windRotation = m_windRotation * 0.0174532925f;
// Setup the wind rotation.
D3DXMatrixRotationX(&rotateMatrix2, windRotation);
// Setup the translation matrix.
D3DXMatrixTranslation(&translationMatrix, modelPosition.x, modelPosition.y, modelPosition.z);
// Create the final matrix and store it in the instances array.
D3DXMatrixMultiply(&finalMatrix, &rotateMatrix, &rotateMatrix2);
D3DXMatrixMultiply(&m_Instances[i].matrix, &finalMatrix, &translationMatrix);
}
// Lock the instance buffer so it can be written to.
result = deviceContext->Map(m_instanceBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the instance buffer.
instancesPtr = (InstanceType*)mappedResource.pData;
// Copy the instances array into the instance buffer.
memcpy(instancesPtr, (void*)m_Instances, (sizeof(InstanceType) * m_foliageCount));
// Unlock the instance buffer.
deviceContext->Unmap(m_instanceBuffer, 0);
return true;
}
int FoliageClass::GetVertexCount()
{
return m_vertexCount;
}
int FoliageClass::GetInstanceCount()
{
return m_instanceCount;
}
ID3D11ShaderResourceView* FoliageClass::GetTexture()
{
return m_Texture->GetTexture();
}
bool FoliageClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
D3D11_BUFFER_DESC vertexBufferDesc, instanceBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, instanceData;
HRESULT result;
int i;
D3DXMATRIX matrix;
We create a single quad for the foliage. It requires just position and texture coordinates.
Since we are using instancing we will just re-render this same quad for each piece of foliage that we have.
// Set the number of vertices in the vertex array.
m_vertexCount = 6;
// Create the vertex array.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}
// Load the vertex array with data.
vertices[0].position = D3DXVECTOR3(0.0f, 0.0f, 0.0f); // Bottom left.
vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top left.
vertices[1].texture = D3DXVECTOR2(0.0f, 0.0f);
vertices[2].position = D3DXVECTOR3(1.0f, 0.0f, 0.0f); // Bottom right.
vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
vertices[3].position = D3DXVECTOR3(1.0f, 0.0f, 0.0f); // Bottom right.
vertices[3].texture = D3DXVECTOR2(1.0f, 1.0f);
vertices[4].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top left.
vertices[4].texture = D3DXVECTOR2(0.0f, 0.0f);
vertices[5].position = D3DXVECTOR3(1.0f, 1.0f, 0.0f); // Top right.
vertices[5].texture = D3DXVECTOR2(1.0f, 0.0f);
// Set up the description of the vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the vertex data.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
// Now finally create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}
// Release the array now that the vertex buffer has been created and loaded.
delete [] vertices;
vertices = 0;
Here we setup the instance buffer.
We are instancing the position and rotation, as well as the color.
The position and rotation are stored in a single matrix.
This matrix is updated each frame according to the camera position and the wind rotation.
At first we just use an identity matrix since we won't be rendering the very first instance buffer until it is updated during the Frame function.
We do however copy the color information into the instance array as the color is going to stay the same.
The instance array is used to create the initial instance buffer.
Note that we set the usage of the instance buffer to dynamic since we will be updating the instance buffer from the instance array every single frame.
// Set the number of instances in the array.
m_instanceCount = m_foliageCount;
// Create the instance array.
m_Instances = new InstanceType[m_instanceCount];
if(!m_Instances)
{
return false;
}
// Setup an initial matrix.
D3DXMatrixIdentity(&matrix);
// Load the instance array with data.
for(i=0; i<m_instanceCount; i++)
{
m_Instances[i].matrix = matrix;
m_Instances[i].color = D3DXVECTOR3(m_foliageArray[i].r, m_foliageArray[i].g, m_foliageArray[i].b);
}
// Set up the description of the instance buffer.
instanceBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
instanceBufferDesc.ByteWidth = sizeof(InstanceType) * m_instanceCount;
instanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
instanceBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
instanceBufferDesc.MiscFlags = 0;
instanceBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the instance data.
instanceData.pSysMem = m_Instances;
instanceData.SysMemPitch = 0;
instanceData.SysMemSlicePitch = 0;
// Create the instance buffer.
result = device->CreateBuffer(&instanceBufferDesc, &instanceData, &m_instanceBuffer);
if(FAILED(result))
{
return false;
}
return true;
}
void FoliageClass::ShutdownBuffers()
{
// Release the instance buffer.
if(m_instanceBuffer)
{
m_instanceBuffer->Release();
m_instanceBuffer = 0;
}
// Release the vertex buffer.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}
// Release the instance array.
if(m_Instances)
{
delete [] m_Instances;
m_Instances = 0;
}
return;
}
void FoliageClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int strides[2];
unsigned int offsets[2];
ID3D11Buffer* bufferPointers[2];
// Set the buffer strides.
strides[0] = sizeof(VertexType);
strides[1] = sizeof(InstanceType);
// Set the buffer offsets.
offsets[0] = 0;
offsets[1] = 0;
// Set the array of pointers to the vertex and instance buffers.
bufferPointers[0] = m_vertexBuffer;
bufferPointers[1] = m_instanceBuffer;
// Set the vertex and instance buffers to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 2, bufferPointers, strides, offsets);
// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
deviceContext->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return;
}
bool FoliageClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
bool result;
// Create the texture object.
m_Texture = new TextureClass;
if(!m_Texture)
{
return false;
}
// Initialize the texture object.
result = m_Texture->Initialize(device, filename);
if(!result)
{
return false;
}
return true;
}
void FoliageClass::ReleaseTexture()
{
// Release the texture object.
if(m_Texture)
{
m_Texture->Shutdown();
delete m_Texture;
m_Texture = 0;
}
return;
}
The GeneratePositions function randomly assigns positions to each piece of foliage on the stone plane.
It also generates a random color for each piece of foliage.
This data is used to instance each piece of foliage for rendering.
bool FoliageClass::GeneratePositions()
{
int i;
float red, green;
// Create an array to store all the foliage information.
m_foliageArray = new FoliageType[m_foliageCount];
if(!m_foliageArray)
{
return false;
}
// Seed the random generator.
srand((int)time(NULL));
// Set random positions and random colors for each piece of foliage.
for(i=0; i<m_foliageCount; i++)
{
m_foliageArray[i].x = ((float)rand() / (float)(RAND_MAX)) * 9.0f - 4.5f;
m_foliageArray[i].z = ((float)rand() / (float)(RAND_MAX)) * 9.0f - 4.5f;
red = ((float)rand() / (float)(RAND_MAX)) * 1.0f;
green = ((float)rand() / (float)(RAND_MAX)) * 1.0f;
m_foliageArray[i].r = red + 1.0f;
m_foliageArray[i].g = green + 0.5f;
m_foliageArray[i].b = 0.0f;
}
return true;
}
Foliage.vs
The foliage HLSL shader uses instancing to render many unique pieces of foliage from a single two triangle model.
////////////////////////////////////////////////////////////////////////////////
// Filename: foliage.vs
////////////////////////////////////////////////////////////////////////////////
In the MatrixBuffer you will notice there is no world matrix.
The reason for that is that it needs to be instanced and read in from the instance buffer to give each piece of foliage its own unique position and rotation.
/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix viewMatrix;
matrix projectionMatrix;
};
//////////////
// TYPEDEFS //
//////////////
The VertexInputType contains the position and texture coordinates as usual.
What is new is the instanced data for the world matrix and the instanced data for the foliage color.
Notice we need to specify row major ordering for the instanced world matrix.
struct VertexInputType
{
float4 position : POSITION;
float2 tex : TEXCOORD0;
row_major matrix instanceWorld : WORLD;
float3 instanceColor : TEXCOORD1;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 foliageColor : TEXCOORD1;
};
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType FoliageVertexShader(VertexInputType input)
{
PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;
The position is first multiplied by our new instanced world matrix.
Each piece of foliage has a unique world matrix composed of their position and rotation.
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, input.instanceWorld);
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 send the instanced foliage color into the pixel shader.
// Send the instanced foliage color into the pixel shader.
output.foliageColor = input.instanceColor;
return output;
}
Foliage.ps
////////////////////////////////////////////////////////////////////////////////
// Filename: foliage.ps
////////////////////////////////////////////////////////////////////////////////
/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 foliageColor : TEXCOORD1;
};
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FoliagePixelShader(PixelInputType input) : SV_TARGET
{
float4 textureColor;
float4 color;
// Sample the pixel color from the texture using the sampler at this texture coordinate location.
textureColor = shaderTexture.Sample(SampleType, input.tex);
// Combine the texture and the foliage color.
color = textureColor * float4(input.foliageColor, 1.0f);
// Saturate the final color result.
color = saturate(color);
return color;
}
Foliageshaderclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: foliageshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FOLIAGESHADERCLASS_H_
#define _FOLIAGESHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Class name: FoliageShaderClass
////////////////////////////////////////////////////////////////////////////////
class FoliageShaderClass
{
private:
struct MatrixBufferType
{
D3DXMATRIX view;
D3DXMATRIX projection;
};
public:
FoliageShaderClass();
FoliageShaderClass(const FoliageShaderClass&);
~FoliageShaderClass();
bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, int, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);
private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);
void RenderShader(ID3D11DeviceContext*, int, int);
private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
ID3D11SamplerState* m_sampleState;
};
#endif
Foliageshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: foliageshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "foliageshaderclass.h"
FoliageShaderClass::FoliageShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
m_sampleState = 0;
}
FoliageShaderClass::FoliageShaderClass(const FoliageShaderClass& other)
{
}
FoliageShaderClass::~FoliageShaderClass()
{
}
bool FoliageShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;
We load in the foliage HLSL files here.
// Initialize the vertex and pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/foliage.vs", L"../Engine/foliage.ps");
if(!result)
{
return false;
}
return true;
}
void FoliageShaderClass::Shutdown()
{
// Shutdown the vertex and pixel shaders as well as the related objects.
ShutdownShader();
return;
}
The Render function requires the vertex and instance count instead of the index count. This is because we are rendering using instance buffers and not index buffers.
bool FoliageShaderClass::Render(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
bool result;
// Set the shader parameters that it will use for rendering.
result = SetShaderParameters(deviceContext, viewMatrix, projectionMatrix, texture);
if(!result)
{
return false;
}
// Now render the prepared buffers with the shader.
RenderShader(deviceContext, vertexCount, instanceCount);
return true;
}
bool FoliageShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
HRESULT result;
ID3D10Blob* errorMessage;
ID3D10Blob* vertexShaderBuffer;
ID3D10Blob* pixelShaderBuffer;
D3D11_INPUT_ELEMENT_DESC polygonLayout[7];
unsigned int numElements;
D3D11_BUFFER_DESC matrixBufferDesc;
D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;
// Compile the vertex shader code.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FoliageVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have written something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
}
// If there was nothing in the error message then it simply could not find the shader file itself.
else
{
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
}
return false;
}
// Compile the pixel shader code.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FoliagePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have written something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
}
// If there was nothing in the error message then it simply could not find the file itself.
else
{
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
}
return false;
}
// Create the vertex shader from the buffer.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
if(FAILED(result))
{
return false;
}
// Create the pixel shader from the buffer.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
if(FAILED(result))
{
return false;
}
// Create the vertex input layout description.
polygonLayout[0].SemanticName = "POSITION";
polygonLayout[0].SemanticIndex = 0;
polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[0].InputSlot = 0;
polygonLayout[0].AlignedByteOffset = 0;
polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[0].InstanceDataStepRate = 0;
polygonLayout[1].SemanticName = "TEXCOORD";
polygonLayout[1].SemanticIndex = 0;
polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
polygonLayout[1].InputSlot = 0;
polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[1].InstanceDataStepRate = 0;
Our polygon layout is fairly different when using an instanced matrix.
We basically need to set four 32 bit floats for the four rows of the matrix.
We also set the input slot class to be instanced data.
The semantic index also needs to be incremented for each R32G32B32A32 because each is using WORLD.
polygonLayout[2].SemanticName = "WORLD";
polygonLayout[2].SemanticIndex = 0;
polygonLayout[2].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[2].InputSlot = 1;
polygonLayout[2].AlignedByteOffset = 0;
polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
polygonLayout[2].InstanceDataStepRate = 1;
polygonLayout[3].SemanticName = "WORLD";
polygonLayout[3].SemanticIndex = 1;
polygonLayout[3].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[3].InputSlot = 1;
polygonLayout[3].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[3].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
polygonLayout[3].InstanceDataStepRate = 1;
polygonLayout[4].SemanticName = "WORLD";
polygonLayout[4].SemanticIndex = 2;
polygonLayout[4].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[4].InputSlot = 1;
polygonLayout[4].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[4].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
polygonLayout[4].InstanceDataStepRate = 1;
polygonLayout[5].SemanticName = "WORLD";
polygonLayout[5].SemanticIndex = 3;
polygonLayout[5].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[5].InputSlot = 1;
polygonLayout[5].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[5].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
polygonLayout[5].InstanceDataStepRate = 1;
This part of the polygon layout is for the instanced color.
It's also the second time we use the TEXCOORD semantic so we need to set the semantic index accordingly.
polygonLayout[6].SemanticName = "TEXCOORD";
polygonLayout[6].SemanticIndex = 1;
polygonLayout[6].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[6].InputSlot = 1;
polygonLayout[6].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[6].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
polygonLayout[6].InstanceDataStepRate = 1;
// Get a count of the elements in the layout.
numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
// Create the vertex input layout.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(),
&m_layout);
if(FAILED(result))
{
return false;
}
// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;
pixelShaderBuffer->Release();
pixelShaderBuffer = 0;
// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
if(FAILED(result))
{
return false;
}
// Create a texture sampler state description.
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
// Create the texture sampler state.
result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
if(FAILED(result))
{
return false;
}
return true;
}
void FoliageShaderClass::ShutdownShader()
{
// Release the sampler state.
if(m_sampleState)
{
m_sampleState->Release();
m_sampleState = 0;
}
// Release the matrix constant buffer.
if(m_matrixBuffer)
{
m_matrixBuffer->Release();
m_matrixBuffer = 0;
}
// Release the layout.
if(m_layout)
{
m_layout->Release();
m_layout = 0;
}
// Release the pixel shader.
if(m_pixelShader)
{
m_pixelShader->Release();
m_pixelShader = 0;
}
// Release the vertex shader.
if(m_vertexShader)
{
m_vertexShader->Release();
m_vertexShader = 0;
}
return;
}
void FoliageShaderClass::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;
}
bool FoliageShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader.
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
// Lock the constant buffer so it can be written to.
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the constant buffer.
dataPtr = (MatrixBufferType*)mappedResource.pData;
// Copy the matrices into the constant buffer.
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// Unlock the constant buffer.
deviceContext->Unmap(m_matrixBuffer, 0);
// Set the position of the constant buffer in the vertex shader.
bufferNumber = 0;
// Now set the constant buffer in the vertex shader with the updated values.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
// Set shader texture resource in the pixel shader.
deviceContext->PSSetShaderResources(0, 1, &texture);
return true;
}
void FoliageShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount)
{
// Set the vertex input layout.
deviceContext->IASetInputLayout(m_layout);
// Set the vertex and pixel shaders that will be used to render the geometry.
deviceContext->VSSetShader(m_vertexShader, NULL, 0);
deviceContext->PSSetShader(m_pixelShader, NULL, 0);
// Set the sampler state in the pixel shader.
deviceContext->PSSetSamplers(0, 1, &m_sampleState);
We are rendering instanced data so we need to use the DrawInstanced function.
// Render the geometry.
deviceContext->DrawInstanced(vertexCount, instanceCount, 0, 0);
return;
}
Applicationclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 100.0f;
const float SCREEN_NEAR = 0.1f;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "d3dclass.h"
#include "shadermanagerclass.h"
#include "timerclass.h"
#include "positionclass.h"
#include "cameraclass.h"
#include "fpsclass.h"
#include "userinterfaceclass.h"
#include "modelclass.h"
#include "foliageclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: ApplicationClass
////////////////////////////////////////////////////////////////////////////////
class ApplicationClass
{
public:
ApplicationClass();
ApplicationClass(const ApplicationClass&);
~ApplicationClass();
bool Initialize(HINSTANCE, HWND, int, int);
void Shutdown();
bool Frame();
private:
bool HandleMovementInput(float);
bool Render();
bool RenderSceneToTexture();
private:
InputClass* m_Input;
D3DClass* m_Direct3D;
ShaderManagerClass* m_ShaderManager;
TimerClass* m_Timer;
PositionClass* m_Position;
CameraClass* m_Camera;
FpsClass* m_Fps;
UserInterfaceClass* m_UserInterface;
We are using a simple plane as the ground model to render the foliage on.
ModelClass* m_GroundModel;
This is our foliage object.
FoliageClass* m_Foliage;
};
#endif
Applicationclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "applicationclass.h"
ApplicationClass::ApplicationClass()
{
m_Input = 0;
m_Direct3D = 0;
m_ShaderManager = 0;
m_Timer = 0;
m_Position = 0;
m_Camera = 0;
m_Fps = 0;
m_UserInterface = 0;
m_GroundModel = 0;
m_Foliage = 0;
}
ApplicationClass::ApplicationClass(const ApplicationClass& other)
{
}
ApplicationClass::~ApplicationClass()
{
}
bool ApplicationClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight)
{
bool result;
// Create the input object.
m_Input = new InputClass;
if(!m_Input)
{
return false;
}
// Initialize the input object.
result = m_Input->Initialize(hinstance, hwnd, screenWidth, screenHeight);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
return false;
}
// Create the Direct3D object.
m_Direct3D = new D3DClass;
if(!m_Direct3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize DirectX 11.", L"Error", MB_OK);
return false;
}
// Create the shader manager object.
m_ShaderManager = new ShaderManagerClass;
if(!m_ShaderManager)
{
return false;
}
// Initialize the shader manager object.
result = m_ShaderManager->Initialize(m_Direct3D, hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the shader manager object.", L"Error", MB_OK);
return false;
}
// Create the timer object.
m_Timer = new TimerClass;
if(!m_Timer)
{
return false;
}
// Initialize the timer object.
result = m_Timer->Initialize();
if(!result)
{
MessageBox(hwnd, L"Could not initialize the timer object.", L"Error", MB_OK);
return false;
}
// Create the position object.
m_Position = new PositionClass;
if(!m_Position)
{
return false;
}
// Set the initial position.
m_Position->SetPosition(0.0f, 1.5f, -4.0f);
m_Position->SetRotation(15.0f, 0.0f, 0.0f);
// Create the camera object.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}
// Set the initial position of the camera and build the matrices needed for rendering.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
m_Camera->Render();
m_Camera->RenderBaseViewMatrix();
// Create the fps object.
m_Fps = new FpsClass;
if(!m_Fps)
{
return false;
}
// Initialize the fps object.
m_Fps->Initialize();
// Create the user interface object.
m_UserInterface = new UserInterfaceClass;
if(!m_UserInterface)
{
return false;
}
// Initialize the user interface object.
result = m_UserInterface->Initialize(m_Direct3D, screenWidth, screenHeight);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the user interface object.", L"Error", MB_OK);
return false;
}
We create the ground model here.
// Create the ground model object.
m_GroundModel = new ModelClass;
if(!m_GroundModel)
{
return false;
}
// Initialize the ground model object.
result = m_GroundModel->Initialize(m_Direct3D->GetDevice(), "../Engine/data/plane01.txt", L"../Engine/data/rock015.dds");
if(!result)
{
MessageBox(hwnd, L"Could not initialize the ground model object.", L"Error", MB_OK);
return false;
}
And the foliage object is created here.
// Create the foliage object.
m_Foliage = new FoliageClass;
if(!m_Foliage)
{
return false;
}
// Initialize the foliage object.
result = m_Foliage->Initialize(m_Direct3D->GetDevice(), L"../Engine/data/grass.dds", 500);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the foliage object.", L"Error", MB_OK);
return false;
}
return true;
}
void ApplicationClass::Shutdown()
{
// Release the foliage object.
if(m_Foliage)
{
m_Foliage->Shutdown();
delete m_Foliage;
m_Foliage = 0;
}
// Release the ground model object.
if(m_GroundModel)
{
m_GroundModel->Shutdown();
delete m_GroundModel;
m_GroundModel = 0;
}
// Release the user interface object.
if(m_UserInterface)
{
m_UserInterface->Shutdown();
delete m_UserInterface;
m_UserInterface = 0;
}
// Release the fps object.
if(m_Fps)
{
delete m_Fps;
m_Fps = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
// Release the position object.
if(m_Position)
{
delete m_Position;
m_Position = 0;
}
// Release the timer object.
if(m_Timer)
{
delete m_Timer;
m_Timer = 0;
}
// Release the shader manager object.
if(m_ShaderManager)
{
m_ShaderManager->Shutdown();
delete m_ShaderManager;
m_ShaderManager = 0;
}
// Release the Direct3D object.
if(m_Direct3D)
{
m_Direct3D->Shutdown();
delete m_Direct3D;
m_Direct3D = 0;
}
// Release the input object.
if(m_Input)
{
m_Input->Shutdown();
delete m_Input;
m_Input = 0;
}
return;
}
bool ApplicationClass::Frame()
{
bool result;
float posX, posY, posZ, rotX, rotY, rotZ;
D3DXVECTOR3 cameraPosition;
// Update the system stats.
m_Timer->Frame();
m_Fps->Frame();
// Read the user input.
result = m_Input->Frame();
if(!result)
{
return false;
}
// Check if the user pressed escape and wants to exit the application.
if(m_Input->IsEscapePressed() == true)
{
return false;
}
// Do the frame input processing.
result = HandleMovementInput(m_Timer->GetTime());
if(!result)
{
return false;
}
// Get the view point position/rotation.
m_Position->GetPosition(posX, posY, posZ);
m_Position->GetRotation(rotX, rotY, rotZ);
// Do the frame processing for the user interface.
result = m_UserInterface->Frame(m_Fps->GetFps(), posX, posY, posZ, rotX, rotY, rotZ, m_Direct3D->GetDeviceContext());
if(!result)
{
return false;
}
Each frame before rendering we need to update the rotation of the foliage using the camera position and the wind direction.
The wind direction is inside the foliage object.
// Get the position of the camera.
cameraPosition = m_Camera->GetPosition();
// Do the frame processing for the foliage.
result = m_Foliage->Frame(cameraPosition, m_Direct3D->GetDeviceContext());
if(!result)
{
return false;
}
// Render the graphics.
result = Render();
if(!result)
{
return false;
}
return result;
}
bool ApplicationClass::HandleMovementInput(float frameTime)
{
bool keyDown;
float posX, posY, posZ, rotX, rotY, rotZ;
// Set the frame time for calculating the updated position.
m_Position->SetFrameTime(frameTime);
// Handle the input.
keyDown = m_Input->IsLeftPressed();
m_Position->TurnLeft(keyDown);
keyDown = m_Input->IsRightPressed();
m_Position->TurnRight(keyDown);
keyDown = m_Input->IsUpPressed();
m_Position->MoveForward(keyDown);
keyDown = m_Input->IsDownPressed();
m_Position->MoveBackward(keyDown);
keyDown = m_Input->IsAPressed();
m_Position->MoveUpward(keyDown);
keyDown = m_Input->IsZPressed();
m_Position->MoveDownward(keyDown);
keyDown = m_Input->IsPgUpPressed();
m_Position->LookUpward(keyDown);
keyDown = m_Input->IsPgDownPressed();
m_Position->LookDownward(keyDown);
// Get the view point position/rotation.
m_Position->GetPosition(posX, posY, posZ);
m_Position->GetRotation(rotX, rotY, rotZ);
// Set the position of the camera.
m_Camera->SetPosition(posX, posY, posZ);
m_Camera->SetRotation(rotX, rotY, rotZ);
return true;
}
bool ApplicationClass::Render()
{
D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix, baseViewMatrix;
// Clear the scene.
m_Direct3D->BeginScene(0.0f, 0.65f, 1.0f, 1.0f);
// Generate the view matrix based on the camera's position.
m_Camera->Render();
// Get the matrices from the camera and d3d objects.
m_Direct3D->GetWorldMatrix(worldMatrix);
m_Camera->GetViewMatrix(viewMatrix);
m_Direct3D->GetProjectionMatrix(projectionMatrix);
m_Direct3D->GetOrthoMatrix(orthoMatrix);
m_Camera->GetBaseViewMatrix(baseViewMatrix);
// Render the ground model.
m_GroundModel->Render(m_Direct3D->GetDeviceContext());
m_ShaderManager->RenderTextureShader(m_Direct3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_GroundModel->GetColorTexture());
Using alpha-to-coverage alpha blending is key to rendering the foliage correctly.
It handles the depth ordering transparency for us and is free of artifacts.
// Turn on the alpha-to-coverage blending.
m_Direct3D->EnableAlphaToCoverageBlending();
// Render the foliage.
m_Foliage->Render(m_Direct3D->GetDeviceContext());
m_ShaderManager->RenderFoliageShader(m_Direct3D->GetDeviceContext(), m_Foliage->GetVertexCount(), m_Foliage->GetInstanceCount(), viewMatrix, projectionMatrix, m_Foliage->GetTexture());
// Turn off the alpha blending.
m_Direct3D->TurnOffAlphaBlending();
// Render the user interface.
m_UserInterface->Render(m_Direct3D, m_ShaderManager, worldMatrix, baseViewMatrix, orthoMatrix);
// Present the rendered scene to the screen.
m_Direct3D->EndScene();
return true;
}
Summary
So now we can render instanced foliage that has unique color, position, and rotation for each piece using just a single quad.
To Do Exercises
1. Compile and run the program. Use the arrow keys and A, Z, PgUp, PgDn to examine the foliage.
2. Modify the instanced color of the foliage.
3. Increase and decrease the foliage count.
4. Add another flower foliage object that is larger, has different animation speed, and is less in number (by modifying the FoliageClass to be more generic).
5. Add a static non-animated foliage such as small bushes.
6. Add instancing for the size (scale) of each piece of foliage.
7. Try the intersecting foliage method instead of the single quad to see the difference (make sure to turn off back face culling since you don't rotate it to face the camera).
8. Try a more advanced wind animation such as perlin noise to create gusts of wind in a field of grass.
9. Setup a LOD for distance so that foliage is always rendered in the immediate area but not past 25 meters.
10. Setup a density parameter so you can have low, medium, and high density levels of foliage (for low just render every 3rd foliage in the array, for medium every 2nd).
11. Implement the foliage in your own terrain engine; use a quad tree to determine the height to place each piece of foliage at.
12. Create "foliage maps" similar to color maps, and place foliage randomly within the allowed color areas instead of giving each foliage a specific or completely random location on the terrain.
Source Code
Source Code and Data Files: tersrc19.zip
Executable: terexe19.zip