23 November 2024

Course "Unity Shader Graph: Create Procedural Shaders & Dynamic FX" by GameDev.tv

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.

Unlike Penny de Byl’s course, this one doesn’t implement shaders in code but instead uses Unity’s visual language for shaders, Shader Graph. Shader Graphs still feel confusing to me compared to code, but I have to admit that writing Godot’s shader code feels immensely simpler than Unity’s CG/HLSL. I suspect I’ll stick to code for shaders in Godot, but in Unity, I’ll likely rely on Shader Graphs (begrudgingly).

This course uses several examples to teach you how to work with Shader Graphs. These examples increase in complexity, starting with simple vertex displacement shaders and progressing to a more complex ocean wave effect, with others in between for simulating fire and snow. The fire, snow, and ocean shaders were the most valuable for me, though the snow example was explained in a rather confusing and rushed way, and it was the only one I couldn’t fully get to work.

Overall, the course achieves its goal of helping you overcome any fear of Shader Graphs. However, it suffers from the same issue as many other shader courses: focusing on how to implement specific shaders without explaining why they’re implemented that way. Too often, the course boils down to listing the nodes to deploy and how to connect them. While you can refer to Unity’s (excellent) documentation for each node, the documentation only explains the node’s inputs, outputs, and immediate functionality. It doesn’t provide the broader context or concepts behind them. In this sense, I think Penny de Byl’s course, which I mentioned in my previous review, makes a much greater effort to help you understand the concepts underpinning each shader implementation.

That said, I’ve progressed through the course, and generally, things have worked out. However, I feel like I’ve implemented many of these shaders more by intuition than by certainty. I’m not sure whether that intuition is thanks to the instructor or simply because I’ve taken several courses on the subject and, no matter how clumsy I remain in this discipline, some knowledge always sticks.

What I really liked about this course is its challenge-oriented structure, which I’ve seen in other GameDev.tv courses. It seems to be a hallmark of their teaching approach: explaining certain parts of an example and then, as a challenge, asking you to implement the next section on your own using what you just learned. After a pause, the instructor explains the solution so you can compare your work or see how it was done if you couldn’t figure it out. This dynamic makes the courses very engaging, keeps you interested, and forces you to apply the concepts immediately. It’s incredible how much you learn when you roll up your sleeves. I must admit I miss this approach when taking more traditional courses with a purely lecture-based format.

The course is in English, but the instructor has clear pronunciation, and you can understand him well. Subtitles (in English) are also available in case you need help with any words. Overall, it’s easy to follow.

Is it worth it? I wouldn’t buy it at full price. It seems overpriced to me. However, it might be worth it if you catch one of the frequent sales and get it for €10 or €12, or as part of a broader course bundle.


Course "Shader Development from Scratch for Unity" by Penny de Byl

I must admit that one of the things I find most challenging to learn about game development is shaders. No matter how proficient you are in Unity, Unreal, or Godot, with shaders, you essentially have to start from scratch. They are essential for making your game’s textures and visual effects truly appealing, but to begin using them, you need to master a set of concepts that seem barely related to other disciplines. It’s no surprise that in large teams, there are people entirely specialized in this area.

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

Flowers falling from sakura tree
In previous articles, we worked on a Japanese scene, using shaders to give the water reflection and movement, as well as to animate the tree. In this article, we’ll set shaders aside and introduce a deceptively simple tool that, when used well, can create multiple effects. This tool is called particle systems.

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
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
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
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.
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
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
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
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
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
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
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
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.