VinexGames takes on grass, round 2: Back with a vengeance

I’ve been toying with different kinds of grass rendering and looking at how it’s done in other engines and games. So far I’m satisfied with the result, and here’s how it works:

When a new GrassGeneratorControl is created, it generates a grass patch model by spacing out a modified Quad mesh over a small area with random rotation and scaling. This grass patch is then batched and the resulting batched geometry is placed as needed across the terrain based on camera position, with random rotation. The grass itself is rendered with a custom shader that adds vertex-animated wind waving and responds to point lights and directional lights (spot lights are WIP). The texture for the grass is black and white, and is multiplied by a definable color or alternatively by a color map (for varying color overall or differently-colored grass for different areas). If the Control’s spatial is a TerrainQuad, the grass will be placed at the height and normal of the terrain, allowing for grass that aligns with the ground below it. If it’s any other spatial, it’s only placed at a height of 0 and an up-axis of (0, 1, 0).

So far I’m very satisfied with how it looks…but the performance is a little rough. With each batch containing around 45-50 triangles, and the LOD range reaching ~100 units from the camera (each batch being around 4-6 units across), I get around 300-400FPS on an R9-280X + i5-3570K - it’s not bad until you consider that the same scene without grass is >1500FPS avg. Batching could improve performance, but with how often the grass is changed, batching this much geometry would very quickly eat up the byte buffers (a problem I encountered in my last failed attempt at grass in jME), not to mention shredding framerate if the player is constantly moving. Bumping up the size of the pre-generated grass batches would increase framerate, at the cost of the ability to conform to terrain as well.

With all of these grass batches containing the same model and material, instancing would be a blessing, but unfortunately jME3.0 doesn’t support instancing. I’ve looked at bits and pieces of its source for 3.1, but I don’t think I could implement it with my limited knowledge of the inner workings of the engine. I would like to test this with instancing, but for now, I’m focusing on more features - i.e, customization most importantly, such as more kinds of foliage (flowers, rocks, leaves dotting the ground, etc.), as well as density maps and cut off ranges (height and slope cut offs).

Unfortunately, jME’s ScreenshotAppState is having a problem with the Alpha cut off, so all the grass in my screenshots is horribly bugged out. Will post images once I figure that problem out.

Instancing is no good for small objects like grass. Batching is way better. Even though instancing is only one draw call for a ‘chunk’ of grass, there is still per-object overhead inside the GPU. Batching wouldn’t have this issue.

This part I don’t understand. Why is the grass changed so often?

Anyway, have you looked at what I did in the IsoSurfaceDemo (open sourced). It places grass on any mesh, basically. Uses batching of 32x32 areas (as I recall). It also depth sorts the grass in the current zone to eliminate odd edge artifacts. The surrounding zones are just sorted for the zone the player is in.

The material takes care of wind and billboarding, etc., probably similar to what you already do.

Edit: and do note that I do use instancing for the trees as it does make sense there.

@pspeed Sorry, that was poor wording on my part. That should say “with how often the camera position is changed, and therefore the placement of grass models”.

I will definitely take a look at the IsoSurfaceDemo, thanks for letting me know about that! Tomorrow morning I plan to expand the system by batching larger groups (possibly batching a whole terrainquad worth of grass into 4-16 batches), then improving the shader to work with this (discarding pixels for far away grass blades that are irrelevant to render). Hopefully I can get some decent images as well, but it seems like the ScreenshotAppState doesn’t do well with alpha thresholds or mip maps or something. Thanks for the advice!

The old 3.0 screen shot app state wrote the alpha channel value to the resulting image.