From now until the end of the year, I’m taking as many courses as I can before fully dedicating myself to my new project. I’m currently focusing on the GameDev.tv courses included in a Humble Bundle pack I recently purchased. These courses are hosted on GameDev.tv’s original platform, although most of them are also available on Udemy. Since I recently finished a shader course in Unity by Penny de Byl, this one seemed like the natural progression.
23 November 2024
Course "Unity Shader Graph: Create Procedural Shaders & Dynamic FX" by GameDev.tv
Course "Shader Development from Scratch for Unity" by Penny de Byl
However, my biggest issue isn’t learning something new but rather that all these concepts seem artificially cryptic, the documentation opaque, and the shader syntax contrived and unintuitive. I must admit that the little I’ve learned about Godot shaders seems to be the most intuitive I’ve come across so far, though even then, there are numerous “awkward” aspects. In Unity’s case, I know its shaders are extremely powerful, but everything feels rather chaotic and confusing to me.
Despite this, I’m determined to understand them and achieve a reasonable level of mastery. That’s why I’ve taken several courses on the topic, gradually making progress—though far less than their authors promised. The reason is that most courses simply list the steps to achieve a specific effect, but very few take the time to explain the concepts behind shaders that justify those steps.
Fortunately, Penny de Byl (whom I discovered through her excellent courses on game AI) makes an exceptional effort to explain these concepts in this course, "Shader Development from Scratch for Unity", available on Udemy. She doesn’t always succeed completely in explaining them, but at least she tries and does so from the most basic concepts to the more advanced ones.
The course focuses on 3D shaders, dedicating most of its time to how to apply effects to the textures of 3D objects. It avoids Unity’s visual language for shaders and instead uses the specific programming language of this engine. Contrary to what I might have expected, I’ve realized that I understand shaders much better when they’re implemented in code rather than in any of the visual languages used by game engines. Perhaps because I come from a programming background, I find it easier to read code from top to bottom than to navigate the spaghetti plate resulting from a visual language.
Out of the course’s nine sections, I think the first six are quite understandable. I found the explanation of how to use the Dot Product in shaders particularly enlightening. Sections 7 and 8 start to get tangled and seem much less well explained. As for the final section, which covers volumetric shaders, I must have been especially tired because I admit I understood very little of it. I’ll probably focus on other topics for a while and revisit that section later. Hopefully, on a second pass, when I’m fresher, what’s explained there will make more sense.
Although there are things I haven’t grasped, there are many others that I’ve encountered in other courses but only managed to understand here, thanks to Penny de Byl’s explanations. Additionally, the Q&A section for each class is almost as interesting as the class itself. I recommend reading all the questions asked by other students for each class; you’ll probably find others who’ve had the same doubts as you. The instructor is thorough and answers almost all the questions, which is very enlightening. Sometimes, she even acknowledges mistakes in the class or includes corrections and links to external resources to resolve doubts, which is especially valuable for tying all the concepts together.
Is the course worth it? Yes, without a doubt. If you find the course discounted on Udemy, I think it’s a great opportunity to dip your toes into the world of shaders and to discover an author with some truly interesting courses.
10 November 2024
Particle systems in Godot introduction (Part I) - The sakura flowers
A particle system is a component that emits a continuous stream of particles. Different effects can be achieved by adjusting the number of particles, their speed, direction, or visual appearance. My idea is to add three particle systems to the Japanese scene from the previous article: one to simulate cherry blossom petals falling, another to visually represent the wind, and another to create a rain effect.
In this article, we’ll see how to implement the falling cherry blossom petals. This is a simple example, but I think it’s useful to understand particle systems and to get comfortable using them. Also, with a few variations, this example can be adapted to similar effects, like autumn leaves falling.
As in previous examples, it’s important to download the project on my GitHub to follow along with the explanations. Make sure to download the exact commit; otherwise, you might see code that differs from this article.
The effect we want is for the cherry blossoms to fall softly from the top of the tree. To preserve the scene’s zen character, we’ll keep the number of petals low, although we’ll see how to increase it. In fact, we’ll subtly increase it when the wind’s strength rises. We’ll also see how to move the particle system in sync with the movement of the tree's canopy.
Configuring the Particle System
In Godot, a particle system is just another node, called GPUParticles2D (with an equivalent node in 3D). Since our system will be linked to the tree, it makes sense for the particle system node to be a child of the tree node.
Once we create the particle system node under the tree node, we can begin configuring it.
![]() |
Particle Emitter Node |
A particle emitter has two levels of configuration: node level and material level.
The most basic settings are configured at the node level:
![]() |
Particle emitter node configuration |
Emitting: Activates the particle emitter. If unchecked, the emitter will stop working.
Amount: Controls the number of particles emitted per unit of time. I set it to 1 for a subtle effect, but if you want to see the emitter in full effect, try setting it to 5 or 10.
Amount Ratio: Multiplies the value of Amount in each cycle, letting us vary the emitter’s flow without restarting it. Changing the Amount via script would restart the emitter, which looks unrealistic. But modifying Amount Ratio allows adjusting the flow without interruption.
Time--> Lifetime: Determines how long each particle remains visible, from birth to disappearance. I set it to 3 so the petals reach the ground before disappearing.
Drawing--> Visibility Rect: Specifies a rectangle within which the particles are visible. When particles leave this rectangle, they stop being visible.
Texture: Allows us to assign a sprite to give the particles a visual appearance. Instead of an individual sprite, I used a sprite sheet to generate an animation.
A sprite sheet is a single image composed of multiple individual images, useful for organizing all images in a few files rather than many individual ones. Engines are optimized for sprite sheets, which is more efficient than loading multiple individual image files.
![]() |
Sakura flowers sprite sheet |
You can make an sprite sheet gathering individual images into a single one, using an editor like Gimp or an specific tool like Texture Packer. I've used that one. Keep in mind thet, if you want to use your sprite sheet for an animation, you must sort horizontally the individual images and distribute then at even distances into the sprite sheet file.
To animate the sprite, configure the node at the material level by assigning a ParticleProcessMaterial resource to the Process Material field. Set Animation --> Speed to 1 and Animation --> Offset (min to 0, max to 1). This makes the sprite sheet animate at a constant speed as each particle falls. Each particle starts from a different image in the sprite sheet, creating variety and an organic look.
For variety in particle size, I set the Scale parameter to a random size between 0.03 and 0.05. I also set Lifetime Randomness to 0.07 to make particle lifetimes vary slightly.
Since this is a 2D scene, I disabled Z-axis movement using Disable Z. For the shape of the particle generator, I used a sphere instead of the default cube because the tree canopy resembles a sphere. The sphere radius is set in Emission Shape Radius, and I offset the sphere slightly to the right to match the tree’s asymmetrical canopy shape.
To make the particles fall, I set a gravity value for the Y-axis in Process Material --> Accelerations--> Gravity. After some testing, 50 felt like the best value.
Moving the Particle System
The configured system behaves as a spherical volume in which particles randomly appear and fall due to gravity. If our tree were static, we could stop here, but it’s not. In previous chapters, we implemented wind-induced tree sway. For realism, the particle system’s volume should move along with the canopy.
Instead of making the particle system a child of the tree (which won’t move as it’s only visually animated by a shader), we’ll add a script to the particle system to move it in sync with the canopy’s movement
The particle system’s script, located at Scripts/LeafEmitter.cs, exports four fields for configuring from the inspector:
![]() |
LeafEmitters.cs exported fields. |
minimumFlowerAmount: Number of petals emitted when the tree is at rest.
OscillationMultiplier: Defines the maximum amplitude of the particle system’s oscillatory movement.
WindStrength: Wind strength.
HorizontalOffset: Offsets the oscillation direction.
![]() |
LeafEmitter.cs process method |
The algorithm executed in each frame is simple: it calculates the oscillation (line 55) and uses it to shift the particle system’s horizontal position (line 57).
The method get_oscillation() calculates the oscillation:
![]() |
LeafEmitter.cs get_oscillation() method |
The get_oscillation() method uses the same algorithm as the tree shader to define the movement of the upper vertices of the texture.
Shader synchronization with particle system
To synchronize the shader with the particle system, we can use a script located on the tree that has access to both the tree shader and the particle emitter script.
The script is located at Scripts/Tree.cs.
The configuration fields it exports to the inspector are as follows:
![]() |
Exported Fields of Tree.cs |
The trick here is that these fields are actually properties that apply modifications to both the tree oscillation shader and the LeafEmitter particle emitter script.
For example, the WindStrength property in Tree.cs works like this:
![]() |
Tree.cs WindStrength property |
When a value is requested for this property, it returns the value set in the tree's oscillation shader (line 20). On the other hand, when you set a value, the property applies it to the shader (line 27) as well as to the particle emitter script (line 30).
The other two properties, HorizontalOffset and MaximumOscillationAmplitude, function similarly.
This way, any changes we make to the properties of Tree.cs are automatically replicated in the equivalent properties of LeafEmitter.
However, this method synchronizes all oscillation properties except for one: time. To ensure both Tree and LeafEmitter use the same time reference for their oscillations, we’ll manage the time count ourselves and pass it to both the tree shader and the particle emitter script.
The time count is handled in the _Process() method of the tree script:
![]() |
Process() Method in Tree.cs |
In line 102 of the method, we simply add the time elapsed since the last frame (delta).
![]() |
Time Property in Tree.cs |
The Time property is responsible for updating both the shader and the particle emitter. Line 83 updates the shader, while line 84 updates the particle emitter.
With the same algorithm, parameters, and time variable, both the tree and the particle emitter can oscillate in sync. In the screenshot below, I have selected the particle emitter node, with its position marked by a cross. Notice how it moves in rhythm with the tree.
![]() |
Sakura tree oscillation synced with particle emitter movement |
This concludes our introduction to particle emitters. In future articles, I will explain how to implement a rain effect, as well as a visual representation of the wind.