Progress – Grass

After the job of converting my code from XNA to SharpDX DirectX11 I can now use a Geometry Shader to generate arbitrary geometry from a small list of inputs. In my case I am passing the centre location of a set of tiles (one Vector2 per tile) and then passing that to a GS for it to loop around the implied grid within the tile and output grass blades where they match a fractal noise algorithm.

I was suprised to find that the GS was not massively faster than passing the tile full of grass through an Hardware Instanced render call – however the thing it gives me that the single call does not is

  1. Less memory consumption on the CPU
  2. Less resource transfer between the CPU and GPU
  3. Much finer degree of control over how many grass blades are actually rendered within the tile – this is calculated based on the distance from the viewer.

The strategy I am using to render the grass is outlined in the Outerra project “Procedural Grass” but instead of using their explict LOD level tiles, I just output less geometry from my GS based on a runtime calculation on the distance to camera. For distant tiles I output less grass by subdividing the implied grid by bigger and bigger steps.

This is done using a simple calculation;

// tileDist is now 0->1 with 0.1 nearest and 1.0 furthest.
float tileDist = length(float2(centrePosition.x, centrePosition.z) - Param_CameraPosition.xz) / Param_MaxBladeVisibleDistance;
if (tileDist > 1)
// Minimum increment distance of numbers of world units between each blade
float minBladePositionIncrement = (cellWorldSize / bladesPerCell);
// Steps must be in powers of two
static int stepCount[3] = { 1, 2, 4 };
int step = stepCount[floor(tileDist * 3)];

float bladePositionIncrement = minBladePositionIncrement * step;

I can then easily loop using this in my GS

for (float bladeX = centrePosition.x - halfcellWorldSize; bladeX < centrePosition.x + halfcellWorldSize ; bladeX += bladePositionIncrement)
   for (float bladeZ = centrePosition.z - halfcellWorldSize; bladeZ < centrePosition.z + halfcellWorldSize ; bladeZ += bladePositionIncrement)
       // Emit grass blade

The increase in bladePositionIncrement need to be power-of-two to ensure that blades rendered at a lower LOD level (i.e. more sparsely) continue to get rendered at higher LOD levels – otherwise blades would pop-in and pop-out of existence as they get nearer the camera.

Getting the grass to sit accuately on the surface of my geometry landform was puzzling at first. My landscape is a series of tiles of specific geometry grids with inclusions for rivers, rock features etc. and is not generated from a heightmap at runtime. Without a runtime heightmap I cannot tell what height my grass should be painted at. I need to be able to query the geometry as to what height a world coordiante was painted at.

The answer to this was to add an additional Render Target when I paint the landscape which is a memory texture of type R32_Float. Instead of painting my landscape into that Render Target I paint the interpolated height of the pixel being painted. This is a very similar technique to deferred pipeline rendering where the backbuffer is sometimes painted into a seperate texture for shadow mapping.

I wanted an accurate heightmap of the immediate location where grass would be visible so I limited my render target to a world rectangle centred on the camera whose max radius was the visible distance of the grass. I only needed a texture of 256×256 to fit a world rectangle of 100m x 100m into it with a reasonable degree of accuracy – interpolation would almost always yield a correct world height within that.

At the same time as rendering a heightmap I rendered out a further target texture of type R8_Unorm which was my planting mask. During landscape tile rendering I query the overall planting scheme baked into the landscape vertexes to select the correct texture to paint (meadow, fields, scarp etc). I use this information to calculate whether grass is supposed to exist on that pixel and write that information into the planting mask texture. This is used during the grass rendering step to quickly drop out of the geometry shader if it samples the planting mask and finds it shouldnt be rendering anything there. This is a step which could not easily be done without the geometry shader pipeline.

Woodland Scene



Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s