Tech Talk

1. Sprite Shadows in Godot

This is a three part series discussing and demonstrating methods of creating realistic sprite shadows and their performance for mobile platforms.

Part 1 - How to create shadows for sprites in Godot using Sprite3Ds and a DirectionalLight in a 3D environment
Part 2 - How to simulate shadows by using shadow textures
Part 3 - How to optimize the performance of the Sprite2D based solution by minimizing the number of draw calls.

Part 1 - Sprite3D based solution

Shadows for 2D sprites make them pop off the screen and give the scene a sense of depth. Without shadows, the sprites can look too "flat" and get lost in the background of the scene. Ideally, shadows should appear as if there is a light source shining towards the sprites projecting shadows onto the background. When sprites move or rotate, corresponding shadows should also move or rotate in a realistic way.

Below is an example of the desired outcome:

All projects discussed here can be found on GitHub.

A straight forward way to accomplish shadows for sprites in Godot Engine is to use a 3D scene with Sprite3D objects, a DirectionalLight and a QuadMesh for a background. An example of such a project can be found in the GitHub repo for this series in the directory called "Sprite3dShadows". The project is relatively simple. The only tricky part is that the Sprite3D objects have to have the "Alpha Cut" flag set to "Opaque Pre-Pass" to make the sprites cast shadows on the background.

You can see what the shadows look like below:


There are two problems with this solution:

  1. The shadows don't look very good. In the example project I kept most of the light and shadow related settings at their default, so there may be ways to make the shadows look better, though probably at the cost of decreased performance.
  2. 150 draw calls are required for only 25 visible objects in the scene (i.e. 24 fish + the background quad). That might not seem like a problem for desktop games as desktops (especially gaming desktops) are capable of drawing many more than 150 draw calls without any issues. However, when designing games for mobile platforms, the number of draw calls has a significant impact on performance. The mobile game below would not be possible using this technique as it would require hundreds to thousands of draw calls to support so many sprites with shadows.


Between the ugly shadows and poor performance of this solution, it doesn't seem like a viable option for creating shadows for sprites on mobile.

In the next post, I will introduce a better way to give shadows to sprites by simulating them with textures in a 2D scene.

Part 2 - Shadow Simulation

In my previous post (Part 1), I showed how Sprite3D objects can be used to create shadows for sprites in Godot. There were problems with the Sprite3D based solution. Let's try to solve those problems by simulating shadows using textures in a 2D scene in this post.

Typical 2D Sprite Shadows

The typical way to add shadows to a 2D sprite would be to add the shadows directly to the sprite textures themselves. The problem with this approach is that if the sprites rotate, the illusion of the shadows can be broken as the shadows move with the rotation of the main sprite. An example of what this looks like can be seen below:


The project described above can be found in the "Sprite2dShadowsUnrealistic" folder on GitHub.

More Realistic 2D Sprite Shadows

The shadows can be made more realistic by separating the shadow texture from the main color texture for each object. In the "Sprite2dShadowsRealistic" project, you can see how a Fish object can be created that has both a "Color Sprite" and a "Shadow Sprite". The Fish objects have an attached script that updates the global position and rotation of the "Shadow Sprite" to make the shadow appear as if it is being cast by a fixed light source. An example of what this looks like can be seen below:


These shadows look a lot better than the shadows created with Sprite3D objects. The performance is also better at 49 draw calls (i.e. 2 for each Fish object and 1 for the ColorRect background). However, performance could still be a problem on mobile, especially if there are hundreds of sprites instead of only 24.

In the next post, I will describe how to solve the remaining issue by utilizing a TextureAtlas.

Part 3 - Performance Improvement using a TextureAtlas

In the previous post (Part 2), I showed how realistic looking shadows could be created for sprites by using textures in a 2D scene. While the shadows created look much better than the Sprite3D based solution from (Part 1), the problem of the relatively large number of draw calls remains. 2 draw calls per sprite in the previous post is a significant improvement over 6 draw calls per sprite in (Part 1), but we can still do better.

One key to improving performance for 2D sprites in Godot (or in any game engine that uses OpenGL ES) is to use draw call batching to minimize the number of draw calls for the sprites in the scene. In Godot, the way to enable draw call batching is to import the textures used for the sprites as a TextureAtlas. An explanation of optimization using batching in Godot can be found here. The process for importing textures as a TextureAtlas in Godot is as follows:

  1. Select the sprites you would like to group into an atlas in the FileSystem dock.
  2. Go to the "Import" tab of the "Scene" dock and change the "Import As" drop-down from "Texture" to "TextureAtlas"
  3. Click the folder icon next to "Atlas File" and select the name and location of the atlas file you would like to create
  4. Click "Reimport"

Godot will need to close and re-open the editor to enable the TextureAtlas. After the atlas is created, whenever you assign a texture to a Sprite the atlas will be used instead at runtime.

The "Sprite2dShadowsRealisticWithAtlas" folder contains a project with the sprites grouped into a TextureAtlas. The use of the TextureAtlas decreased the number of draw calls from 49 (or 2 per Fish + 1 for the background) to just 2 (1 for all of the Fish + 1 for the background). You can see the results below:


Conclusion

In this series, I demonstrated how to create realistic looking shadows for sprites in Godot Engine. The solution with the best performance is to use 2D Sprites with separate shadow textures and a TextureAtlas. The performance aspect may not seem important if you target desktop platforms, however, for mobile game development optimizing graphics performance can have a huge impact. Optimizing draw calls for mobile (OpenGL ES based) games improves overall performance, reduces battery consumption and gives more head room for additional graphics, animations, and effects.

These techniques were used in our mobile game BebeBoop available on the App Store. Turn it up to "Bird Speed" and you can understand how important it is to do draw call optimization, especially when running in "Marathon Mode".

Bonus Part 4 - Draw Call Batching Continued

In the previous post (Part 3), I showed how important draw call batching is to the performance of OpenGL ES based mobile games. In this post I show another example where draw call batching enables graphics in a mobile game that otherwise wouldn't be feasible.

Our game BebeBoop (available in the App Store for iOS) has a "Poster" called "Noodles" that features a real-time hourglass in the background made up of the hourglass itself and 180 individual "grains" (noodle bowl toppings such as peas and carrots). The "grains" are all Godot RigidBody2D based objects with their associated sprites, collision shapes ... etc. Between the hourglass in the background and the other objects in the scene, there are hundreds of sprites (3D objects rendered to 2D) all moving according to physics and interacting with each other. The video below provides a good demonstration:

While testing BebeBoop I regularly watch the performance monitors (found on the "Monitors" tab of the "Debugger" panel) watching for resource leaks and sub-optimal draw-calls. The screenshots below demonstrate the difference in the number of draw calls with and without batching and at different play speeds.

Performance monitors with batching, normal speed
With draw-call batching, any speed
Performance monitors without batching, normal speed
Without draw-call batching, normal ("cat") speed
Performance monitors without batching, bird speed
Without draw-call batching, fastest ("bird") speed with 1K+ additional objects

In both cases there were thousands of objects with hundreds of visible sprites. With batching enabled (and TextureAtlases properly configured, as described in Part 3 there were only two to three dozen draw calls. Without batching the same scene requires several hundred draw calls, that is over 1,100% more draw calls for the same graphics. On a desktop PC several hundred draw calls is nothing, however, on a mobile device two hundred plus draw calls will have a much larger impact on frame rate and battery consumption.

2. Godot Add-on - Custom Image Equalizer

Custom Image Equalizer is an add-on for Godot Engine providing a graphical equalizer Control using a custom image as the graphical elements of the equalizer. See below for a demo:


This has been tested on version 3.5.2-stable and uses GDScript. Source code and instructions for use can be found on GitHub.

This add-on was used (in a modified form) in our mobile game BebeBoop in the Posters "DJ Meow Meow" and "Raining Teddies" as well as the Karaoke version of "Raining Teddies" on YouTube.