Skip to main content
  1. Articles/

Simulating falling autumn leaves in Blender

In this article I’ll be explaining how to create a small looping animation of falling leaves in Blender using geometry nodes instead of the traditional particle system approach. We’ll break down the simulation into sections, and explain each part in detail. This is written as a tutorial, so you’ll get the most out of it if you know the basics of Blender already.

The finished animation

This guide is designed to help bridge the gap between simple geometry instancing and more complex physics simulations. We are going to ditch the traditional Particle System functionality and build our own falling leaf engine completely inside Geometry Nodes.

While that might sound intimidating, the setup here is focused on aesthetics rather than accuracy. We’ll build a “good enough” physics model that looks great and loops perfectly. If you have a basic grasp of Blender nodes and want to see an example of the Simulation Zone in action, this project is the perfect playground.

You can download the packed Blender file containing all the textures here: ↓ falling_leaf_simulation.blend


The inspiration #

In autumn last year I was chatting with my partner about how spectacular the colour of the trees and falling leaves were in our local park. It got me thinking about making a small animation in Blender using geometry nodes. Currently, state-of-the-art AI video generation in this space is weak. Making the independent, chaotic motion of leaves believable is difficult.

I also wanted to push my understanding of Blender’s increasingly sophisticated geometry nodes functionality, and more broadly improve my ability to develop within node-based environments. In the past I’ve been hostile to node-based programming, thinking it was a poor substitute for writing code directly in an IDE. However as time has gone on, and I’ve experienced working within teams where changes with time and people cause programs to atrophy, I’ve come to appreciate the benefits. Node-based programming can provide a great API to maintain backward compatibility while allowing feature development, bug fixes and general improvements.

Implementing a falling leaves simulation is something you’ve been able to do in Blender for a long time. You’d stick down a particle system, get it to instantiate the leaves with some randomisation, and then apply some wind or turbulence in order to get them to fall in a suitably realistic way. This has worked well since circa 2008, but we’re now at a point where we can recreate all the functionality we need ourselves using geometry nodes, and extend it in ways which would have been impossible with the particle system.

Specifically for our purposes, geometry nodes received an update in Blender 3.6 which allows you to run simulation loops during your animation. This is the key feature which unlocks our ability to use geometry nodes for this project.

It is however a double-edged sword. You have all the freedom you could possibly want, but you need to start from first principles with a blank canvas, and build everything yourself. This can be intimidating, and the examples you can find online don’t often deal with simulations; they tend to be more concerned with procedural geometry. This ended up being the challenge I set myself. Could I learn enough to implement a complete animation?


Source textures #

I looked for photographs of autumn leaf scenes, and found this one on Freepik. It suits our needs by being geometrically simple, has nice even lighting, and a very chaotic bed of leaves on the floor. The dynamic falling leaves could easily blend in once they land.

For the leaves, I collected a handful from my local park and photographed them outside on a plain white piece of paper. It looks like the leaves in the background photograph are larger Maple leaves, but sadly I don’t live near any Maple trees. You work with what you have!

A selection of the yellow leaves I collected

Bringing these textures into Blender and mapping them onto some planes, they looked good, but presented a problem. They were too flat.

The leaf textures brought into Blender

Crumple the leaves #

If you animate perfectly flat planes, it doesn’t look very realistic. As they rotate to be side on to the camera, they disappear. To prevent this, we need to add some geometric distortion to simulate the drying and crumpling of real autumn leaves.

The geometry nodes to distort the mesh

We can create a simple geometry node network which subdivides the plane, and then uses a Noise Texture node to push the normals of the mesh around a bit. Set the scale, detail and roughness of the noise settings to get a nice random look to the leaves.

The distorted mesh
The textured mesh
The leaves now have a better side profile

The scene #

The scene setup is simple. There’s not enough information in the image to make solving the camera perspective easy, but given just how simple the scene is, we can eyeball it.

The 3D scene

There’s a camera, a thin emitter plane at the top, and a collider plane at the bottom. The emitter plane is tilted slightly to keep it out of the camera frustum, and the collider plane is flat, and approximates the ground position. There’s no specific light source, we use an environment texture to give us approximate lighting conditions. This suits the overcast day in the photograph.

I’ve also placed the collection of leaf objects out of view of the camera so that I can work on the geometry and materials easily.

The camera setup is the only difficult element. The source photograph luckily has EXIF tags, including the lens used. Unfortunately the lens is a variable zoom, and has a range of 24mm to 70mm. This means we need to eyeball the camera settings to get a similar perspective. Again, luckily for us the simplicity of the scene makes this more forgiving.


The emitter geometry nodes #

Now we come to the heart of our falling leaf simulation: the geometry nodes for the emitter. Let’s break down this fairly intimidating node network into sections.

The entire geometry node simulation network
The entire geometry node simulation network. You can click to view the full-size image.

The key to this setup is the Simulation Zone. Unlike standard geometry nodes which evaluate from scratch every frame, a Simulation Zone allows us to carry over data from the previous frame. By combining this with Named Attributes to store values like velocity and rotation, we can update the state of our leaves iteratively to create a physics simulation.

A high level overview of the steps taken per-frame are:

  • Randomly instantiate additional leaves
  • Update the velocity from the fixed acceleration
  • Mix in a simulated wind component to the velocity, driven by a noise texture
  • Update the instance position from the velocity
  • Update the instance rotation from the rotational velocity
  • Calculate the collision with a floor plane, and exponentially damp the acceleration and velocity components
  • Set a lifespan fade value to use in the shader
  • Remove leaf instances at the end of the lifespan

Spawning leaves #

Rate-limit the leaf spawns

The first thing we need to do is control how many leaves we spawn. One of the first surprising things I encountered was that when using the Distribute Points on Faces node, we need to control the spawn count by the density of the points, not the number of points. This means that scaling the spawning zone geometry will change the number of leaves spawned per second.

The most simple way I found to control this in a way which is compatible with our requirement to have a looping animation is the following:

  1. Take the frame number and Modulo it with your looping frame count
  2. Use that integer as a seed for a Random Value
  3. Set a threshold, and if the random number is Less Than a chosen value, use the result output as the Selection input to Distribute Points on Faces

Setting it up this way gives us an upper limit to the emission rate. If we exposed Density directly, it creates opportunities for UI accidents, and a slip of the mouse can change the value from 0.01 to 10, and you’re suddenly instantiating 10,000 leaves each frame, tanking the application performance. Setting a reasonable ceiling value, and then giving a percentage control to that maximum is much safer for artistic experimentation.

Randomisation #

In order to give us an output which is capable of looping, all random numbers are seeded with the frame number modulo the repeat count. This gives us repeatable behaviour over a set number of frames.

Attribute setup #

Initialise the Named Attributes we will be using

Now we need to set the initial values for the Named Attributes we’ll be using during the simulation. The input values are primarily sourced from the Group Input node so we can easily experiment with different values outside of the GN editor.

lifespan
The duration of our leaves in frames
accel
The static acceleration vector applied to each leaf. In our case it only experiences a negative Z acceleration due to gravity
vel
The initial velocity given to each leaf, zero in our case
maxAngSpeed
The upper limit on how fast the leaves can spin and tumble
normAngVel
The angular velocity of each leaf, expressed as a Vec3. This is given a random vec3 from -1 to 1. This is multiplied with maxAngSpeed to calculate its rotation speed

For completeness there is one more attribute we use, but it’s calculated and set dynamically later in the network.

leafFadeOut
A 0-1 float which is an expression of inverse leaf alpha. This is used in the leaf shader.

Instantiation #

Now we create our leaf instances.

We instance our leaves as you might in a standard Geometry Node setup, but with a slight twist. Because we’re using a Simulation Zone, we have an existing source of geometry instances inherited from the previous frame. We want to merge these existing leaves in with our new instances, and then feed them into our network so that old and new are treated equally.

Cheap turbulence simulation #

Cheap and easy velocity manipulation

To provide a simple way to simulate the way leaves fall, we will sample a 3D fBM Noise Texture and add this to our leaf velocity, acting as a pseudo acceleration force. Before this is added to the leaf velocity, we scale it by an exposed value, allowing us to easily dial this strength up and down according to our needs.

Rather than this being directly added to the vel attribute each frame, we need to take into account when this turbulence needs to be applied, and when it doesn’t.

Soft ground collision #

Our leaves will eventually reach the ground, and must stop moving. Aesthetically it’s much more believable if the leaves come to a rest with a slight deceleration, rather than coming to a halt instantaneously. This means we need to create a system of soft collision. The linear acceleration and velocity as well as the rotational velocity should be arrested by their proximity to the ground collider.

It’s important to note that we’re not using a heavy Rigid Body physics engine. Instead, we are creating a position-dependent damping field. Think of it as the air getting thicker the closer the leaf gets to the ground, eventually freezing it in place.

This field will give us a coefficient we can use to multiply the acceleration, velocity and rotation with, giving us an asymptotic decay function. We effectively lerp the system to a halt by using a fractional value.

Our nodes to calculate the effect of the damping field

Above is the group of nodes we will use to calculate the effect of the damping field. Note that we use Relative as the transformation applied to the output. We’re in the coordinate space of the emitter, so we need to bring the ground collider into that space to get the correct proximity values.

The Geometry Proximity node can be set to Faces because we’re a single flat plane. Lastly we clamp the output of the proximity node to a maximum of 1, giving us our damping field coefficient, ready to multiply our acceleration, velocity and rotation by.

For some more flexibility we could divide the proximity by a scalar to give longer range to the collision. For our leaf collision purposes, a proximity of 1 is sufficient because the scale of the leaves is small.
Employing our soft collider calculation

There are two ways we use the soft collider output. One is to directly use the fInFreefall value, which is 1 in freefall, and drops to a value below 1 during collision. We will demonstrate this later. The other is to convert our freefall value into an inverted collision boolean, where it has a value of 0 in freefall, and 1 during collision.

We use this collision factor to immediately kill any contribution the position noise field makes to our leaf velocity by use of a scale node. The output of the scale node in the above image is the velocity contribution of the noise field.

In physics terms, we are applying a drag coefficient that increases infinitely as distance approaches zero.

Updating velocity and position #

The power of simulation nodes, being able to update position and velocity

Now we come to update the leaf’s velocity and position. First we grab the accel attribute and multiply it by Delta Time to get the velocity contribution. We then add this to the velocity contribution from the turbulence simulation, and add that change in velocity to the existing vel attribute. The Scale node then scales the overall velocity by the fInFreefall value.

To describe what we’re doing in mathematical notation, we can define \(d\) as the distance to the collider, then we can define \(\lambda = \text{clamp}(d,0,1)\). This is what we assign to fInFreefall. Then within each iteration we will calculate \(\vec{v}_\text{damped} = \vec{v}_\text{original} \cdot \lambda\). This damping will be applied to the linear and angular velocity, as well as the acceleration.
Updating the acceleration and velocity attributes

In the above image, we can see the vel, accel and normAngVel attributes being updated. The velocity has already been scaled by the fInFreefall value, but the other two still need to be scaled before being updated.

Updating the rotation #

Updating the rotation of the leaf instances

In order to calculate the updated eulers, we need to scale the normAngVel by the maxAngSpeed and multiply it by Delta Time. This gives us the change in rotation. We then convert this to a rotation and use it in the Rotate Instances node.

Fading and deleting the leaves #

The last two things we need to do is fade out the leaves and delete them at the end of their lifespan. Note that while the attribute is named lifespan, it actually stores the end frame value—the specific frame number at which the leaf instance should be deleted.

Calculating the fade out value

The following pseudo code describes the fade out calculation, where k-prefixed variables are our constants defined for the whole network, and attr is the prefix for named attributes.

fadeStartFrame = attrLifespan - kFadeFrameCount
framesIntoFade = fadeStartFrame - currentFrameNumber
t = framesIntoFade / kFadeFrameCount
return MapRange(t, 0, 1, 1, 0)

This is stored in the leafFadeOut attribute, starting at 0, and rising to 1 when the leaf has faded out completely. It’s effectively an inverse Alpha calculation. We need to have a default value of 0 when the leaf is visible, which will be important once we come to implement the fade out in our leaf shader.

Deleting the leaves at the end of the lifespan

The deletion comparison is easy, we just compare the current frame number to our lifespan attribute. If the current frame number is greater than the lifespan attribute, we delete the leaf using the Delete Geometry node targetting the instances.


The leaf shader #

Using the fade out value to fade out the leaves

The leaf shader is set up in a simplistic way. The lighting in our scene is very uniform and almost blown out, certainly very saturated. In order to get the rendered output to match against the plate, it’s necessary to make the leaves slightly emissive. We also have basic hue, saturation and value controls, as well as a brightness and contrast control in case there needs to be any additional tweaking.

The only other point of note here is the use of the leafFadeOut attribute to control the opacity of the leaves. In order to be able to use this shader when there is no attribute set, we rely on the default value of 0 meaning that the leaf is fully opaque. Because the attribute is the inverse of what we need to use for the alpha attribute of the standard Principled BSDF shader, we need to invert it before using it.

The final shader with unique texture per leaf

Here we can see the same shader is used, we just vary the input texture for each different leaf.


Compositing #

The final composite of the leaves and the plate

Lastly we have the compositing setup. We just use some global Hue, Saturation and Value nodes to adjust the colour of the leaves to match the plate, and an Exposure node to adjust the brightness. The leaves are then composited over the plate using the alpha channel.

Looping video #

In order to create the looping video, we need to ensure that the output frame count is at least twice the loop count. We can then use ffmpeg to chop out the section we need.

ffmpeg -i /path/to/video.mp4 -vf "trim=start_frame=400:end_frame=600,setpts=PTS-STARTPTS" -an output.mp4

There may well be a way to achieve this step within Blender itself, but I knew how to achieve this using ffmpeg.


Conclusion and future work #

I’m happy with the final animation. The camera setup isn’t perfect, but the aesthetics look good nonetheless. There are a number of areas I’d like to explore further.

  • Grouping more nodes together to make the top level graph more manageable, and easier to reuse in a modular fashion
  • Use the baking capability to make playback quicker when you don’t need to change the parameters of the simulation
  • Avoid the ffmpeg step, and produce a looping video directly from Blender
A split view to show the leaves more easily