Monday, August 1, 2016

A little grass

Playform has had grass for a while now, but I kept putting off the blog post. It still needs a lot of work, to be sure, but I think even shoddy grass can do a lot to help the sense of detail in a virtual world.

The approach is roughly taken from this GPU Gems article (which does a much better job, just for the record). The basic method is to arrange 2D grass billboards into cross-shaped patterns (see this diagram).

The billboards work equally well in each direction, so we turn off backface culling when we render grass (instead of placing redundant billboards facing in the opposite directions). The resulting tuft maintains a reasonable 3D look even as you walk around it, although they do look odd from above.


To start off, we just place one of these "tufts" pointing straight up, at the center of every polygon that supports grass:


This already works pretty well, but there are some problems. First of all, the grass in the background doesn't properly cover the terrain. We'll deal with that later. Another problem is that the tufts don't connect properly to the terrain! Because billboards are placed in cross-shaped patterns, the base of each tuft is 2D; if we place a tuft on a polygon, the base of the tuft should sit flat on the polygon, but that conflicts with our goal of having grass point straight up.

First of all, let's rotate the tufts so that they sit neatly on their underlying terrain polygons:


That also adds some variation into the grass direction, which improves the realism a bit. But now the grass always points straight out from the terrain surface, which creates a weird effect on steeper slopes.

To control where grass points without rotating the base, we apply a shear effect to each tuft. First, we decide which direction we want the tuft to point, in world coordinates. Directly upward seems like a good start. Then we transform that direction back into model space, and find the shear that would cause the normal vector (which, in model space, is straight up) to become that desired model-space vector (or some multiple of it).

There's no viable shear when the desired direction is more than 90 degrees away from the surface normal. To deal with this in a somewhat reasonable way, we find the angle between the normal and the desired direction, and feed that through a function to smoothly clamp it back to [0, 90). The particular function probably isn't that important; I just use acos(e^(cos(x)-1).


Shearing a vector changes its length, stretching the grass. Let's reset vector lengths after shearing:


We still have the problem that the grass tufts in the background are too small. More generally, if we're placing one grass tuft per terrain polygon, we want to make sure it roughly covers the polygon. So we apply a shader effect to scale the grass tuft based on the size of the polygon (again, the specific function probably doesn't make a huge difference).


Lastly, we can add a wind effect. I'm lazy, and I have this WebGL noise function lying around (which produces smooth noise given a point). We construct the input using the grass tuft position and the current time (plus some arbitrary offsets and trial-and-errored scaling factors), and we can get back two random angles, one clamped to [-180, 180], and the other clamped to [45, 135]. Taken together, these two angles can describe a vector that points within 45 degrees of straight up. Then we just apply a shear effect to make the grass point that way, similar to the one we're already applying to make it point upward.


And now we have rolling hills of waving grass! It's not winning any award, and there are definitely areas for improvement, but I think it will do the job for at least a little while.