#6 Let there be light!
Welcome to the sixth devlog of “Package Party”!
Lighting is everything in 3D games. (I learned that the hard way, after years of asking myself: “why do my 3D games always look far worse than those made by other people?” The answer: one very strong directional light does not make a good scene.)
Before I started making all these levels, I wanted to setup the lighting and basic environment first.
This technique comes from photography and filmmaking, but it’s equally valid in games (because it’s also a very visual art form).
The idea is simple. You use three lights:
- Main light: this is like the “sun” in your game. It usually comes from above, at a slight angle, has a white/yellowish color, and is quite strong.
- Secondary light: this one basically lights the shadows that the first light created. (If the sun comes from the top right, this one will usually come from the left.) It’s lower, softer, usually a darker/cooler color. It makes the shadows less harsh and reduces lighting contrast.
- Tertiary light: this one comes straight from the back, although it’s usually also pointing down at a slight angle. I know this as “rim lighting”. It creates a slight glow/highlight around the edges of objects, which separates them better within the scene and creates the right kind of contrast.
(Note that this is not the official terminology for this technique. I just never cared to learn the names of these lights. Maybe I should.)
This technique simply creates a nice balance of contrast, light, shadows, definition in almost all cases. Below is a video where I slowly toggle the three lights on/off on the scene. (The rim light is quite bright here, you'd usually want to tone it down some more.)
In the real world, lighting bounces off surfaces. A light ray can bounce many times, and every time it takes a bit of color from the previous object with it, which creates shadows that we perceive as “realistic”.
Ideally, the game engine would do the same … but that’s far too expensive. Instead, it’s approximated with ambient light. Light that illuminates everything from all sides. It’s usually very soft and has a cool color, which means you don’t consciously notice this “fake light” in the scene.
But it really helps make the scene come alive.
There is, however, one thing that is too unrealistic to ignore. Sometimes, objects have crevices, overhangs, holes – parts where light simply cannot go. Ambient light makes those parts light up like fireworks, which looks unrealistic and can completely ruin the 3D effect.
To fix this, we use ambient occlusion. This is also very expensive to calculate every frame, so we bake it into the models. A baked ambient occlusion texture simply shows the following information: how easy it is for light to reach certain areas of the object. The darker a spot on the texture, the more this part is “occluded”. By applying this texture, models look more defined and realistic, at nearly no cost to us. (The program that bakes the texture usually needs to work VERY HARD for at least 10-20 seconds.)
Watch the lighting/shading on the wooden fence as I turn ambient occlusion on/off. It's rather subtle, but the fence is more realistic and more defined with AO turned on, which helps A LOT when it's part of a larger level.
Unsurprisingly, almost all models in my game have this ambient occlusion texture.
But here comes our first problem …
Ambient Occlusion in Godot
An AO texture can only influence the object itself. Luckily, Godot offers a general ambient occlusion: SSAO (Screen Space Ambient Occlusion)
I used it, it looks amazing … until I noticed my game lagging like crazy. The level was very simple, the code took only a few microseconds to execute, I didn’t understand why I was getting only 10 frames per second. (It should be 60 FPS. Certainly on such a tiny, simple game.)
So I did A LOT of research, went through some antique forum posts, and finally found a list of performance issues with Godot. This list basically stated “these things are not implemented well, don’t use them”. Which is always encouraging to hear about the game engine you’re using :p
This is the part of the list that benefit me:
- Don’t use SSAO
- Don’t use a (Procedural) sky. Just set a solid color.
- Godot doesn’t have occlusion culling. (WHY NOT?!) Remove game objects yourself if they are not (fully) visible.
- Directional Lights are very easy to compute … their SHADOWS however, are quite a problem.
So I turned off the first two things, removed objects wherever I could, and turned down the shadow quality on the lights. Then I realized I could completely turn off the shadow on the other two lights (not the main light), and it actually looked better.
Voila, the game shot up to 60 FPS (most of the time).
This really should be stressed somewhere, or, even better, fixed as soon as possible. I’m not an expert, but I’m certain there are many standard/common optimizations that aren’t hard to implement, but are sorely needed by Godot. I really hope that version 3.2 (or 4.0) increases 3D performance, because 10 FPS on an extremely simple scene on mostly default settings is … not really usable.
Godot Performance – Part Two
Anyway, I fixed that. Then I noticed there were STILL some performance issues. It wasn’t my code: both the physics and my custom code took so little time that the profiler could hardly display it. (Sidenote: I have lots of experience with optimizing code, but not so much with optimizing visuals. This can also be a pitfall, as I always just assume that the code is not the problem.)
So I read some more, dug up some extinct articles on Godot, and found something:
- Godot doesn’t do mesh batching. (What’s that? It means combining different objects into one, so the computer only needs to do one “draw call”. Godot is currently very bad at optimizing draw calls, so it’s no surprise that his came up. But it IS a surprise that Godot doesn’t optimize this, because draw calls are the main culprit when any game lags.)
- The GLES3 renderer is far better than GLES2 … but not for older hardware, or Intel integrated processors. Both of which my laptop has.
The first issue is not easy to solve. I need to combine meshes myself as much as I can. But, if I do it to much, I can’t benefit from frustum culling (not rendering objects outside the camera). Because, if the meshes I combined are very far apart, they will always be visible at any point, and I lose my optimizations.
I’m currently looking into the MultiMeshInstance node (which would allow cheap instancing of many near-identical meshes, such as trees and grass and stuff) and techniques I can use in my modeling software (Blender 2.8) to improve this. Additionally, once I have my terrain editor finished, it should improve the situation, because I can combine the whole terrain into as many or as few meshes as I desire.
The second issue is … just very strange. I switched the project to GLES2, and whaddayathink? It runs much smoother. But it also looks awful, because it’s an older version of the shading language, missing some features.
I’m torn between these two options. I think I’ll continue developing on GLES3, as it’s the future and most people will have better systems than I do. But if need be, I might create a GLES2 build for the older systems, where I tweak settings to make it look better.
SIDE NOTE: I also tested the game on an even older iMac that’s been used as the family computer for yeaaaars. In windowed mode, it barely reaches 10-20 FPS. In fullscreen mode, it gets the full 60 FPS (with some dips to lower numbers, mostly when I instance a new package). Promising, but also disappointing. So yeah, performance optimization will be a recurring theme during these devlogs.
That was it for the sixth devlog! This was a bit of a sad one, as I continually ran into (performance) issues on an unfinished level 1. I still have my doubts, but I think it should be possible to make this game, even the larger and more complex levels, in this engine. And if not, I will find a way. Or wait for the Engine update before releasing it :p
Get Package Party
Leave a comment
Log in with itch.io to leave a comment.