A critical part of any stealth mechanic system is figuring out the light level at a specific point in space. Godot has no built-in solution for this; I don’t blame them for not providing one, since it’s a pretty niche use case, and light is not volumetric like in real life, so it’s not as simple as puttign a point in space and saying “test light level”. When you look up how to do this online, the major suggestions are to: 1. Bake lightmaps and sample them at the position you are standing. 2. If lightmaps are not possible (such as procedural geometry or heightmap terrains), do something like the following, where the light reflects off an octahedron, and 2 low-resolution cameras are pointed at it to look at the light value:
This, on its surface, seems fine. But, there are caveats to the second solution - especially if you use this outdoors, something sneaky happens. Directional lights often have shadows if you want to imitate sunlight. What is easy to overlook is that this shadowmap, which is a rather expensive operation, is rendered every time a camera renders. This may seem obvious, but this happens for every camera. You may begin to see the problem. It turns out that directional shadow maps are being rendered twice for every stealth check. This adds up extremely quickly with multiple NPCs.
So, is there a good generic solution for indoor and outdoor scenes?
The solution is actually in a completely different direction.
- On all lights you expect to contribute meaningfully to stealth (which is probably most of them), you put an Area3D with a sphere collider set to the radius of the light, cone of the spotlight, etc. Then, when determining light level, you can take your distance to all the lights that your point is 1. inside and 2. in direct line of sight to (read: raycast to the light) and do a simple attenuation calculation to see their effect on the point. Add these all together for your brightness (not average, as light is, of course, addititve).
- As for the sunlight, there are 2 ways you can go:
- If you want to assume everything that can cast a shadow has a physics collider (buildings, mountains), you can go the cheap route and raycast towards the sun (the opposite direction of the light’s direction); if it hits anything, it’s in sunlight, if not, then it’s in shadow.
- A much more accurate and precise, but also more expensive, method, is to use a very limited camera to take a picture of the sky, which will take into account things that may not have colliders but make a visual impact, such as leaves:
- Set up an orthagonal camera pointing towards the sun - pointed opposite of the sun’s direction.
- Ensure the camera has basically everything except texture rendering disabled. No filtering, shadows, anything expensive. We will need to know if something is blocking the sky visually or not.
- Set the camera resolution super low, but not 1x1. Maybe something like 16x16.
- Make an environment for the camera where the sky is set to a transparent background. If we take a picture of the sky and average the transparency, we will get an approximate coverage of the area, with the more transparent the image is, the more of the sky we can see.
Then, the light level calculation is as follows: Sample the sky and lights surrounding the point. Add the sky and the lights together to get your total fake luminance level.
With this, you can avoid rendering the sun shadows unnecessarily. I plan to at some point provide scripts for this / an add-on for Skelerealms to do this for you.