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)
{
    return;
}
// 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

 



					

Everyone Needs a Fractal Noise

There are many places where fractal noise is useful in rendering. Taking this screenshot;Fractal

The mottled appearance of the distant meadow and the placing of the grass blades are controlled by a 3 sample fractal. Normally noise is added to a scene by building a Perlin or Simplex noise texture and sampling from it. However the repetition of this is easy to see and multi-sampling using offset coordinates is the only way to achieve this.

Using a Fractal noise function can be more efficient – not least becasue it doesn’t need a noise texture passed down into the GPU. There are quite a few examples out there for OpenGL and these are easy to convert into DirectX HLSL.

The one I used was https://github.com/ashima/webgl-noise which produces a Simplex noise, and call it with the sample Step 4 – Fractal Noise. Calling the noise function from the Fractal function gives you a good library. I wont post the original code here because these sites are worth visiting – there is more in there than just the noise functions.