Introduction and Credits

There is a great amount of information about procedural trees on the internet, but it is hard to find examples that can be used in real-time rendering. This is a description of the techniques I follow to generate trees that can be rendered in a very fast way, in massive quantities. This process works really well for stylized trees but can be also adapted to a more realistic look. 

I would have never accomplished this without reading Simon Trümpler’s article on Airborne trees and @Cyanlynx’s tutorial about soft foliage shading. I also want to thank Z-Weaver for creating and publishing the SDF Presets for Blender Geometry Nodes for free.

I use Blender 3.3 Geometry Nodes for all the modeling, Substance Designer for the texture creation and Unity 2021 with URP as target engine. This is  going to alow me to compare materials creation in Shader Graph against writing HLSL code.

Anatomy of  Fast Rendering Trees

The main source of performance problems rendering many trees at the same time is transparency sorting. To avoid this, the trees are divided into 3 parts or materials:

  • Trunks and Branches (opaque)
  • Bases (opaque)
  • Foliage (opaque / alpha clipped)

 

This technique relies on the Bases meshes occluding big portions of Foliage, and helps to give an authentically fluffy look once we modify the normals. More on this later. Here is an example:

Image from Simon Trümpler’s article on Airborne trees

Tree Generator in Blender Geometry Nodes

Trunk and Branches

The first step is to generate splines to be used as Trunk(s), from which Branch splines will grow. This setup is standard in procedural tree generation and there are plenty of tutorials on Youtube. 

The basic idea is to use the Trunk Spline ParameterFactor output to modulate the rest of the parameters. So, for example, the higher the Trunk gets the thinner it becomes and the same applies to the Branches growing from it. We can drive the length, rotation and many other parameters by functions of the Factor of the Trunk.

Making Branches become thinner as the Trunk get higher and the tips of Branches thinner than their origin.

 Another important attribute of the Trunk is its tangent, which can be obtained with Curve Tangent and combined with Align Angle To Vectorto control the rotation of the branches and make them grow in a more natural way. As a side note there are many wrong examples on the internet on how to use Align Angle To Vector that will not work unless the instantiated objects are symmetrical in the X and Y axes, like the cone is usually used in those examples.  This will work in any case:

Correctly aligning branches to trunks tangents.

 A general rule in procedural content generation is to try to pack functionality in small reusable nodes. For instance, I have created a simple node that outputs a curve with a given length and some noise applied, and used that to create the main trunk, and reused it to create branches and roots.

Signed Distance Functions Bases

 SDFs are a powerful mathematical tool to describe shapes. What makes them so powerful is that merging and boolean operation between shapes comes out of the box. Alan Zucconi has a fantastic tutorial on the subject.

With this tool, creating the trees’ canopies is quite trivial, just instantiate spheres from certain points of the Trunk and Branch splines, and let SDF booleans merge them smoothly. Then we can dramatically change the look of the Bases with a few parameters, such as the radius of the spheres and the smoothness of the booleans.

Adjusting SDF Boolean Softness parameter

We can add a second layer of smaller spheres or some other primitive shape if we want more detailed / less rounded canopies.

Creating Bases with SDF’s spheres and Booleans

Foliage

Now we just have to instantiate quads on the surface of the canopies, align them with the surface normals and then adjust the rotation. You will also want to add some randomization. On mobile it may be better to have simple meshes with leaf shapes than alpha clipped quads, but I haven’t benchmarked this yet.

The trickiest part is probably getting the normals right. At first I tried using spherical normals, but I only managed to get nice shading simple canopies, maybe also applying to individual bases. With more complex canopies I got an almost flat look.

In order to fix the normal for arbitrary shapes, I used a Mesh To Volumewith the SDF Bases result as input, creating a single geometry island with the general shape of all the bases from which we can transfer the normals information to our foliage. This allows us to easily change the roundness and size of the “Normals Mesh”, giving us control over the final shading of the foliage.    

Transfering normals from «Normals Mesh» to Foliage

Shading Geometry Nodes in Blender

To import the trees into the engine, I wrote a small export script with the following steps:

  • Split the geometry by material.
  • Apply basic uvs.
  • Transfer normals.
  • Pack everything into a new collection.

I managed to import the trees into Unity and apply the correct shaders, but this didn’t allow me to see anything close to the final look while tweaking the trees in Blender.

In Geometry Nodes, there is no way yet to set the output normals, but there is a simple workaround: using the Transfer Attributenode, we can store the normals as an attribute that we can later read from Shader Nodes.

Something similar happens with the uvs information, in this case related to the instancing of the leaf quads. Fortunately, we can use the same approach to extend the uvs to Shader Nodes. 

Foliage Ramp Shading for Blender preview

Shading Trees in Unity Shader Graph

I used the Smooth Lambertian shading suggested in @Cyanlynx’s tutorial. Ramp Shading will also work nicely. Even the standard Lit shader may work, adjusting the darkest areas with some Emissive.

I also added random color information in the RGB channels of the leaves texture, leaving the alpha channel for clipping. Those random colors can be slightly blended over the material to get a subtle layer of detail and make the result more vivid. 

An important thing when placing a high number of trees in a scene is having small variations that help break repetition. An easy way to achieve this is using the world position of the trees to generate small randomness in different parameters. For instance, I offset the HUE of the random colors by a factor based on this.

Alpha To Coverage

If you are planning to use MSAA, you can take advantage of Alpha To Coverage, which will improve the quality of the foliage, making it look consistent from different distances.  This article by the great @bgolus on the subject is a must read.

Bending with the Wind

I also wanted the trees to slightly bend with the wind. In order to achieve this, I used the Rotate Around Axisnode with an angle factor based on the vertex Y position in object space and the tree origin as pivot point. This way the top part of the tree bends smoothly. This, of course, has to be applied to every material of the tree.However, once we transform the vertex positions we have to recalculate the normals. I created a subgraph called Rotate Around Axis Fix Normals, which applies the same rotation to the vertex tangent and bitangent and computes the corrected normals using their cross product. This approach works for any vertex deformation expressed with a mathematical formula.

Rotate Around Axis Fix Normals



Once again I used the object world position to randomize the phase of the wind sine wave function and rotation direction, thus avoiding perfectly synchronized trees.

Next Steps

  • Properly merge Trunks, Branches and Roots.  I tested using Mesh To Volume and Mesh to SDF but they need very high densities and slow down the graph. Maybe this can be done as an additional step before exporting. A quick work around is to do it in a sculpting software.
  • Use Geometry Proximitynode to allow to attract or repeal branches from referenced objects.
  • Bake pivot information for specific branches.

Conclusions

Blender is improving exponentially and Geometry Nodes, even if it still lacks some functionality, it’s an incredibly powerful tool, also for game development.

Been able to use volumetrics and SDF make organic procedural a lot simpler and intuitive. 

Shader Graph it’s really handy to quickly create materials and it’s a lot harder to mess up things than writing HLSL, making shader creation much more accessible.

The code generated may look like garbage, but nowadays compilers will optimize it to incredible levels. For big projects it’s probably still better to have full control of your shader’s code or at least generate HLSL libraries with common functions that can be use from Shader Graph, using Custom Nodes,  or tradional Shader Lab