Adding Flowers to the Terrain

This article describes a new node type in our geometry processing system, the ConditionalOffsetGeometryNode.

In general the algorithm takes a triangle mesh as input and discards all primitives that do not satisfy a certain user-defined condition. The output consists of the remaining triangles that have been offset along the corresponding vertex normals. The condition is checked for vertex attributes which may have been defined in a previous stage of the geometry pipeline. To keep things simple, our condition is to require all vertex normals to not deviate from a reference normal by more than a small amount (we chose a reference normal that points upwards and picked $$\alpha_{\max}=\frac{\pi}{12}=15^\circ $$).

In a first step the angles between the reference normal and all vertex normals are computed and a flag is set for each vertex to separate goodies from baddies. We end up with information on which normals (and thus corresponding vertices) are good:

For triangles, we observe the following:

  • If none of the three vertices is flagged, the triangle will not be part of the output mesh and is discarded.
  • If at least one vertex is good, the triangle needs to be part of the output mesh and is processed further.

A first approach is to offset all vertices that have been flagged as good along the direction their normal points to. Note that in this case we used a constant offset which is equal for all vertices though letting each offset depend on arbitrary vertex attributes is also fully feasible. That would leave us with something similar to this (offset in lime green):

The boundary triangles of the offset output mesh (which are depicted in orange color) can be of very high slope, especially for small triangles! It would be desirable to smooth the offsets so that the slope along one single triangle edge is kept low:

A basic vertex smoothing algorithm, i.e. choosing the offsets so that all vertices are moved to the center of gravity of their one-ring neighborhood (direct neighbors) and themselves, cannot be applied here for two reasons:

  1. In our scenario, smoothing converges to an offset of zero
  2. The naive smoothing approach does not take into account edge length or triangle size, so a non-steep surface cannot be guaranteed

Smoothing

Until convergence, we iterate over all triangles. For each triangle we process all three vertices in the following manner:

  1. Let the processed vertex be $$A~$$ and the offset (not applied yet!) $$ o_A $$. The other two vertices are named $$B~$$ and $$C~$$ with offsets $$o_B~$$ and $$o_C~$$, respectively.
  2. If $$ o_A \lt o_B~$$ and $$ o_A \lt o_C $$, $$o_A~$$ shall not be smoothed any further in this step.
  3. Choose a maximum slope (we picked $$0.05 = 5\%$$).
  4. Set currentOffset := $$o_A~$$.
  5. if $$o_A \gt o_B$$, compute slope $$\cdot ~distance(A, B)$$. If this value is smaller than currentOffset, update that variable accordingly.
  6. Act analogously if $$o_A \gt o_C$$.
  7. Compute $$D$$, the point on line segment $$BC~$$ closest to A.
  8. If $$o_D$$, the offset at $$D$$, contains a smaller value than currentOffset, update the latter accordingly.
  9. Finally, set $$o_A :=~$$ currentOffset.

When converged, we obtain vertex offsets like this:

Offsets range from 0 (red) to the user-defined value (green).

Application - Ground Flowers

The screenshot is the result of texturing the output triangles with flowers.

One might ask why we did not simply bake the flowers into the terrain texture or do an additional flower texture lookup while shading the terrain surface. There are three reasons:

  1. The presented approach allows for a bit of parallax, i.e. the flowers can really be above the ground and therefore have an influence on e.g. our SSAO pass.
  2. We can control the offset mesh independently from underlying terrain material.
  3. It's about more than just flowers ;-)

Smooth Flowers

Actually the screenshot from above includes one more trick.

Right now, the offsets are interpolated smoothly and the "edge vertices" touch the underlying terrain, but that does not mean the flowers fade out like they to in the depicted scenario. In fact, the flowers are as "edgy" as they would be if we had no differences in the offsets at all:

For smooth flower fields, we compose a flower texture with different amounts of blossoms for different alpha values:

There are three input blossoms: Violet, white and yellow ones. The final texture contains several of each type with different opacities.

In the fragment shader, we texture the mesh like this:

// vOffset is the per-vertex-offset, maxOffset the value specified by the user
float p = clamp(vOffset / maxOffset, 0, 1); 

// The lower the vertex offset, the lower is the discard threshold // Setting the second parameter (0.5) to a lower value, results in a denser // flower distribution if(textureColor.a < mix(0.9, 0.5, p)) discard;

Problems and Future Work

As our terrain consists of chunks, difficulties can occur at boundaries. Chunks are processed separately and have no information on their neighbors. In future, each chunk will need some knowledge on its direct neighbors, so the challenge is to improve the chunk system architecture.

Our method is kept rather simple and the global condition depends on a reference normal only. When we dig into the flower field, there will be flowers on the ground of the hole where the vertex normals meet that condition! To fix problems of this kind, the node output can also depend on terrain metadata that prevent flowers from growing underground.

Other Applications

However, there certainly are several other applications beside terrain foliage.

Think of puddles...

ground fog...

or imagine a situation where you desperately need some smilies to walk on...