A few days ago I wrote an article about our previous Destructible Terrain Prototype. This prototype featured waving grass consisting of automatically generated geometry. The grass is created by a simple Geometry Shader that operates on the original terrain triangles and is therefore a lightweight drop-in solution for grass. In my opinion, this technique is best suited for mid-range grass as it provides noticeable parallax (which is important in mid and near range) with moderate geometrical detail (disqualifying it for high-quality near range foliage).
The rest of the article will explain the key ideas of our technique, but first of all have a look at the following screenshots:
As mentioned above, this technique relies on a Geometry Shader that generates additional geometry out of the existing terrain triangles.
A quick reminder: Every vertex of the terrain (i.e. three per triangle) is processed independently by the Vertex Shader (VS). All three vertices of a triangle are then passed into the Geometry Shader (GS), providing access to all triangle vertices at once. The GS can then create or discard a variable number of new primitives (points, lines, triangles) that are then passed into the Fragment Shader (FS), which is executed for every fragment (i.e. potential pixel) that those primitives would cover.
The basic idea is now to generate quads that represent a "slice" of grass. Let's start by extruding all edges of a triangle along the triangle normal:
(The colors of the grass are just its UV texture coordinates mapped to red and green.) This results in quite regular and aesthetically unpleasant grass:
As each triangle edge is shared by two triangles, this also results in overlapping quads. We can refine this by not extruding edges but rather the line from each vertex to the center of the other two vertices, already resulting in denser quad distributions:
But still, the regularity is obvious. A lot of automatic generated geometry can be aesthetically improved easily by introducing controlled randomness. Noise functions are especially suited for this task and we will cover them in future devblog posts. One simple improvement at this step is to randomly offset the quads in the tangent plane of the triangle:
These irregularities make the quads substantially more natural. Given some distance, the result is already acceptable, but as we extrude the quads along the triangle normals, we observe plenty of "holes" in the immediate vicinity (or in general, if we look from above). In order to conceal these cavities, we can simply offset the upper edge of the quads along the triangle tangent plane by another random amount:
With these tweaks the result can now be added to the terrain to create visually appealing grass foliage:
Introducing "wind" to create waving grass can simply be accomplished by making the offset of the upper quad edge time-dependent (I can recommend some sine-combinations). The height and color of the grass can be varied by low-frequency noise to create even more irregularities. Making the grass "fade-off" if the triangle normal is not the up-vector prevents grass growing sideways or upside down. You could also discard one or two of the three quads per triangle to increase the performance in the distance.
The rendering of the grass has some pitfalls, so here is the solution we came up with:
Apparently, we want to texturize our quads with some kind of grass texture. As the quads don't have the shape of "grass slices", we would also like to have alpha-transparency to get smooth grass. Now here is the problem: Alpha-blending requires sorting of the grass, which is prohibitively slow on the GPU and thus needs to be circumvented. Order-independent-transparency is also not an option given the amount of overdraw and the lack of proper GPU support. Resorting to non-order-dependent blending modes had no appealing results in our tests.
We settled with the following trade-off: We start by drawing the grass with a strict alpha-test (e.g. more than 90% opaque) and enabled depth buffer write:
This causes a lot of aliasing and with increasing mip-map-level, a lot of grass is discarded, resulting in quite sparse grass in the background. Conversely, disabling the depth buffer write and drawing the complete grass with alpha-blending enabled would cause plenty of wrong-order artifacts:
Thus, our compromise solution is to draw the opaque parts first and after that, all alpha-transparent parts with enabled alpha-blending. As a result, the opaque pass prevents most of the wrong-order artifacts and we still get smooth grass:
Of course, the wrong-order artifacts remain in some cases, but they are mostly unnoticeable. If you can provide some coarse sorting (e.g. sorted terrain-chunks), the more nasty cases of super-near grass being rendered behind far-distant grass cannot occur anymore.
Problems and Future Work
Finally some words on what still needs to be done. Given the mentioned coarse sorting, the overall alpha-blending problem should be sufficiently solved.
The computational cost of this grass technique can get quite high if you have a lot of terrain geometry or high fragment overdraw, but these problems can be dealt with by a good Level-of-Detail management.
As mentioned in the introduction, the geometric detail of the grass is too low to provide high-quality foliage in the immediate camera vicinity. We are already devising a solution for this that utilizes instancing (and even tessellation on more sophisticated graphics card) for the desired geometric detail.
And a last problem: The transition between grass quads and the ground can be quite distinctive, requiring careful adjustment of the aforementioned transition.