Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
This example shows how to use the MeshPlacer class, to place objects (vegetation in this case) along a specified mesh (a single terrain mesh (grid 250x250).
http://alwyndippenaar.blogspot.com/
This example shows how to use the MeshPlacer class, to place objects (vegetation in this case) along a specified mesh (a single terrain mesh (grid 250x250).
The MeshPlacer requires a gridsize and optional grid boundaries (x,y,z) to indicate how dens/sparse objects should be placed along the grid.
This example also provides a random oscillating mechanism to simulate wind along the x axis, so vegetation appear to move with the “wind”.
Source Code – MeshPlacer
Objects (vegetation) are automtically clipped at certain distances from the camera, and each specific type of vegetation (tree, bush, grass, poplar) can be clipped at varying distances (eg. Trees are clipped much further than bushes and grass).
The exmaple uses dual sided quads, with Alpha Blending to display the vegetation textures with an Alpha channel to appear transparent.
The AlwynD3Dframework code will not be explained as this is standard and has been explained in previous demonstrations (init, mesh pointer arrays, render, map files, render etc..), but the MeshPlacer and oscillating code will be explained in further detail, as well as a few minor changes to the HLSL files, because normal lighting can not be used on the vegetation, as corners that are more lit than others in the dual quads, appear extremely unnatural and make te dual quads much more visible and pertinent, so a new gammacorrection value is introduced for the vegetation (dual quad meshes), and the quads are rendered unlit, even though surface normal information is still written to the GPU, the new gammacorrection value is used to simulate lighting.
The AlwynMeshPlacer is responsible for taking a vector of positions(DX10VertexNormal) and providing an array of output positions (D3DXVECTOR3), which can then be used to place objects, that will follow the vertices within a specified grid.
//This class provides the capability to create and place meshes along an x/z grid with a given density
//and y constraints, along the vertices of a given mesh, to allow for creation of InstancedMeshes along this grid,
//and could be used for eg. to fill an area with vegetation, or trees ,that will clip (including LOD) at provided distances.
class DLLEXPORT AlwynMeshPlacer
{
public:
//Constructor.
AlwynMeshPlacer();
//Destructor.
~AlwynMeshPlacer();
//Used for output.
wchar_t outputBuffer[2048];
//Adds an output position to the output positions dynamic array.
void addOutputPosition(
D3DXVECTOR3 ***outputPositions,
int *outputPositionsCount,
D3DXVECTOR3 *outputPosition);
//This function will determine positions where to place meshes using an xz grid
//and constrained via the y axis using min and max bounds.
bool determineMeshPositions(
AlwynD3DDevice *device, //A pointer to the default rendering device.
vector<DX10VertexNormal> *verts, //The vertices to use for xz(y) grid.
InstancedLightMesh *meshToReplicate, //The mesh to replicate and place along the calculated grid.
D3DRECT *xzRectangle, //The xz grid boundaries.
float gridSize, //The grid cell size.
float yBoundsMin, //Y Bounds used to include/exclude vertices based on y coordinate, along with xz grid.
float yBoundsMax,
D3DXVECTOR3 ***outputPositions, //The pointer to an array of D3DXVECTOR3 pointers, that will indicate where to place each mesh.
int *outputPositionsCount); //The total count in the above array.
};
After the terrain has been read in from the *.map file in this demo, the various vegetation meshes are placed and created, provided with the terrain meshes vertices.
The createBushes function will be explained here.
The meshplacer is invoked first, along with the vertices from the terrain mesh, after it has been read in from an exported *.obj file, the gridsize for the bushes are set to 30, and the y bounds are constrained to -30 and +30, and vertices with y coordinates that fall outside of the provided grid (in this case, actually NULL) or that fall outside of the y bounds, are not considered by the mesh placer.
The vegetation is then loaded from a simple *.obj file exported from blender that merely provide 2 dual quads, that are contain UV and normal data, although the surface normal data is actually not used in this demo for vegetation.
For each output position provided as output by the mesh placer process, an instancedMesh is created
(with a single instance in this case) for each position, a mesh object (vegetation) is created, and placed at the specified position, using the standard framework mechanism to create InstancedMeshes from an *.obj file (getAdapter()->adaptToInstancedLightMesh).
Each mesh is rotated slightly along the y axis when placed to not to appear too linear.
(meshrotation[0] = D3DXVECTOR3(0.0f, rand_FloatRange(1.0f, 90.0f), 0.0f);
)
Each mesh is also scaled according to what the vegetation type should be, as the default *.obj file imported are precise 1.0x1.0 dual quads, they can be scaled to meet the requirement by
(meshscale[0] = D3DXVECTOR3(5.0f, 5.0f, 5.0f);
)
Another important note is the technique provided to these meshes are different than the standard lighting HLSL techniques, (getTechniquePosTexNormLightSingle(),
)
This technique performs simple sampling in the pixel shader against a single decal, and then multiplies the result float4 by the second gammacorrection value specified for this demo’s unlit geometry.
The vegetation type is determined by the decal/texture provided to the above function
(getGlobalDecals()->decals[2]) The decals are automaticaly loaded from the *.map file and are indexed as follows:
#Decals Indexed from 0
[Decals]
textures\pangea.dds //0
textures\pangea_LOD.dds //1
bush\textures\fern.png //2
bush\textures\chrismasTree.png //3
bush\textures\grasspatch.png //4
bush\textures\poplar.png //5
#For multi texture.
textures\rock.dds //mix
For each mesh that is placed, the (matrixDataUpdateable = true) is set, to indicate that the framework should update the instance’s vertex buffer information from the matrix data provided (in this case, it will be a simply xRotation matrix, multiplied into the existing matrix data, on each loop), this will be used to sway the vegetation in the wind, from the render loop.
For each mesh, the (clipArea->clipRadius = 100.0f) is also getting set, to various values per vegetation type, this value will be used to determine how far from view the mesh should be clipped, so vegetation appear in a “bubble” around the camera, and as the camera translates, they fall into, and out of view.
The (frameworkAddToPointerInstancedLightMeshArray) adds the dyamic array to the framework’s main arrays, and will then be automatically clip checked, rendered, and vertex buffer (instance matrix data) updated as part of the main framework loop, so the demo can only focus on what it specifically requires (“swaying the vegetation”).
//Creates bushes, along the vertices within a grid, following the terrain.
bool createBushes(AlwynD3DDevice *device, vector<DX10VertexNormal> *verts)
{
//Perform mesh placing here.
AlwynMeshPlacer *placer = new AlwynMeshPlacer();
D3DXVECTOR3 **outputPositions = NULL;
int outputPositionsCount = 0;
if (!placer->determineMeshPositions(
device,
verts,
NULL,
NULL,
30.0f,
-30.0f,
30.0f,
&outputPositions,
&outputPositionsCount))
return false;
if (!outputPositions)
{
device->logger->debug("addLightMes -- FAIL: outputPositions == NULL\n");
return false;
}
//Load and place bush meshes.
XFileMesh *bushMesh = new XFileMesh(device, L"C:/Alwyn/dev/blender/MeshPlacer/bush/models/pl_Bush.obj");
if (!bushMesh->loadXFile())
{
device->logger->debug("addLightMes -- bushMesh->loadXFile: -- FAIL\n");
return false;
}
int meshPlaceGeometryLightCount = 0;
InstancedLightMesh **meshPlaceGeometryLight = NULL;
meshPlaceGeometryLightCount = outputPositionsCount;
swprintf_s(device->t_buffer, L"\tmeshPlaceGeometryLightCount: %i\n", meshPlaceGeometryLightCount);
device->logger->debug(device->t_buffer);
meshPlaceGeometryLight = (InstancedLightMesh **) malloc(sizeof(InstancedLightMesh *) * meshPlaceGeometryLightCount);
for (int i=0; i<meshPlaceGeometryLightCount; i++)
{
meshPlaceGeometryLight[i] = NULL;
swprintf_s(device->t_buffer, L"\t\t\tmeshposition #%i (%f, %f, %f)\n", i, outputPositions[i]->x, outputPositions[i]->y, outputPositions[i]->z);
device->logger->debug(device->t_buffer);
}
for (int i=0; i<outputPositionsCount; i++)
{
int inputElements = getLayoutPosTexNormCInstancedSize();
AlwynD3DVertexInputLayout *meshiInputLayout = new AlwynD3DVertexInputLayout(inputElements);
meshiInputLayout->layoutDesc = getLayoutPosTexNormCInstanced();
//Set the position, scale and rotation for each instance.
D3DXVECTOR3 *meshposition = new D3DXVECTOR3[1];
D3DXVECTOR3 *meshrotation = new D3DXVECTOR3[1];
D3DXVECTOR3 *meshscale = new D3DXVECTOR3[1];
//Adjust randomly by max 3.
meshposition[0].x = outputPositions[i]->x;
meshposition[0].y = outputPositions[i]->y - 1.0f;
meshposition[0].z = outputPositions[i]->z;
meshrotation[0] = D3DXVECTOR3(0.0f, rand_FloatRange(1.0f, 90.0f), 0.0f);
meshscale[0] = D3DXVECTOR3(5.0f, 5.0f, 5.0f);
if (!getAdapter()->adaptToInstancedLightMesh(
bushMesh,
getIShader(),
getTechniquePosTexNormLightSingle(),
meshiInputLayout->layoutDesc,
getGlobalDecals()->decals[2],
1,
meshposition,
meshrotation,
meshscale,
meshiInputLayout,
&meshPlaceGeometryLight[i],
false,
false))
return false;
meshPlaceGeometryLight[i]->matrixDataUpdateable = true;
meshPlaceGeometryLight[i]->clipArea->clipRadius = 100.0f;
wcscpy_s(meshPlaceGeometryLight[i]->name, L"bush");
meshPlaceGeometryLight[i]->clipArea->midpoint = meshposition[0];
incrCounts(
meshPlaceGeometryLight[i]->verticesCount,
meshPlaceGeometryLight[i]->indicesCount,
meshPlaceGeometryLight[i]->faceCount);
}
frameworkAddToPointerInstancedLightMeshArray(device, meshPlaceGeometryLight, meshPlaceGeometryLightCount);
delete bushMesh;
free(outputPositions);
delete placer;
return true;
}
The oscillating “wind” value is done as follows.
First 2 variables to hold the current oscilating value, and which direction to oscilate to, for this demo, it is only swaying along the x axis, but could be expanded further.
//An oscillating value, to simulate the grass, poplar and bush,
//moving in the wind.
bool windrotationOscil = true;
float windrotation = 0.0f;
The render loop is responsible for rotating the meshes. This is simply done by adding or subtracting from a float value, by small increments/decrements, this value can also be changed to acquire the fps or timer for a smooth value across different frame rates, but for this demo it is not a requirement.
The demo then simply iterates all meshes, finds the meshes that are eligible to “sway” (by name, and that are currently in view, meshes that are currently clipped from view, are not swayed).
Then the actual sway is extremely simple, create a (D3DXMatrixRotationX ) and it then gets multiplied into the instanced mesh’s current matrix data, which will automatically by the framework get rewritten to the instance’s vertex buffer, and the actual rotation will be performed in the shader (the HLSL, handled in previous demonstrations) by being multiplied to each vertex.
//Render loop callback.
void frameworkRender(AlwynD3DDevice *device)
{
//Simulate wind, and slightly rotate/swing the vegetation.
if (windrotationOscil)
{
windrotation += rand_FloatRange(0.001f, 0.09f);
if (windrotation >= 0.5f)
windrotationOscil = false;
} else
{
windrotation -= rand_FloatRange(0.001f, 0.09f);
if (windrotation <= -0.5f)
windrotationOscil = true;
}
for (int i=0; i<getPointerArrayInstancedLightClipMeshCount(); i++)
{
if (getPointerArrayInstancedLightClipMesh()[i])
{
for (int i2=0; i2<getPointerArrayInstancedLightClipMesh()[i]->arrayCount; i2++)
{
if (getPointerArrayInstancedLightClipMesh()[i]->pointerArray[i2])
{
InstancedLightMesh *lm = getPointerArrayInstancedLightClipMesh()[i]->pointerArray[i2];
if (wcscmp(lm->name, L"bush") == 0 ||
wcscmp(lm->name, L"grass") == 0 ||
wcscmp(lm->name, L"poplar") == 0)
{
//Only if it is not clipped.
if (lm->clipArea->renderClip)
{
D3DXMatrixRotationX(getTmp(), device->deg2Rad(rand_FloatRange(windrotation-0.05f, windrotation+0.05f)));
device->multiplyMatrix(getTmp2(), getTmp(), &lm->_matrixData->matrixData[0]);
device->copyMatrix(&lm->_matrixData->matrixData[0], getTmp());
}
}
}
}
}
}
}
The HLSL decal sampler is shown here, the full HLSL code is available from GITHUB (links above).
The (gammaCorrectionUnlit) value is a float shader variable value, set to 1.0.
///Returns a textured pixel.
float4 sampleDecal_TriLinearSingleNoMulti(float2 tex)
{
return decal[0].Sample(DecalSampler, tex) * gammaCorrectionUnlit;
};
Read more about:
BlogsYou May Also Like