Skip to content

Add Rectangular Area Light Source#108219

Open
CookieBadger wants to merge 1 commit intogodotengine:masterfrom
CookieBadger:area-light-integration
Open

Add Rectangular Area Light Source#108219
CookieBadger wants to merge 1 commit intogodotengine:masterfrom
CookieBadger:area-light-integration

Conversation

@CookieBadger
Copy link
Contributor

@CookieBadger CookieBadger commented Jul 2, 2025

This PR adds a rectangular area light source, implemented via Linearly Transformed Cosines (LTC) with textures. It makes use of a Lookup-Table added in the form of two .dds files, and adds some fields to the LightData struct in the light storage, and adds a new atlas for area light textures.

LTC-Polynomial_r020_compressed
LTCHeitz_compressed
image
image
black_hole
image
image

  • Integrate lights in VolumetricFog
  • Integrate lights in LightMaps
  • Integrate lights in VoxelGI
  • Integrate lights in SDFGI
  • Integrate material effects (Clearcoat, Toon Diffuse & Specular, Rim, Backlight, Transmittance/SSS)
  • Implement texturing the light quad

Here's a list of steps on how to improve the feature in the future:

Here's how shadows can be improved:

  • Add shadows in Compatibility renderer (requires adding paraboloid or alternative shadow mapping to the GLES3 shader).
  • Improve accuracy of paraboloid rendering in Forward renderers (currently area lights use the optimized version that was implemented for omni lights, but a more accurate represenation might better prevent leaks)
  • Add monte carlo shadows in lightmaps - it would need some parameter for how many rays should be traced. This would be a way to actually get realistic shadows with area lights, so maybe something we would want.

One known issue is that specular light can leak through to a face that faces away from the area when viewing at a steep angle and a low roughness value. This is a literal edge case, and fixing it would likely require additional horizon clipping, so maybe we can live with the leak for now. It is present in the source LTC implementation, and the implementation in Unity, and I didn't notice until very recently.

The main sources for this technique are https://eheitzresearch.wordpress.com/415-2/ and https://blog.selfshadow.com/publications/s2016-advances/
In theory, it can be extended to arbitrary polygons, disks, and lines, to serve as the light surface (https://eheitzresearch.wordpress.com/757-2/), but I would worry about adding those in the future.

In the process of making this PR I investigated different techniques for lighting and shading. I investigated a Monte-Carlo sampling approach, and a most-representative point sampling approach, but both were outperformed by LTC, and contemporary research of non-raytraced dynamic area lights still seems to also focus around LTC.
I also investigated shadow sampling with many shadow maps on a new atlas, and adaptive sampling techniques, but those proved to be unusable due to the added implementation complexity. I figured the most suitable shadow technique was using 4 shadow maps per light and directional sampling with PCF to obtain a smooth result, but the shape of the penumbra was strangely warped, and the performance was not good. If you are curious, you may check it out at https://github.com/CookieBadger/godot/tree/area-light-quadriscopic-shadows. Another way to shadow area lights would of course be ray tracing (https://eheitzresearch.wordpress.com/705-2/), but I'm not aware of any developments of the engine in that direction. So, I decided to just integrate PCSS shadows for area lights, which are not physically accurate, but can look decent.

You can find a writeup and builds with several example scenes at https://github.com/CookieBadger/master-thesis-artifacts, but those already outdated as they are based on Godot 4.3. You'll also find a Thesis.pdf there, which is my Master's Thesis about this topic.

Any help with improving and maintaing this feature will be greatly appreciated.

@Calinou
Copy link
Member

Calinou commented Jul 2, 2025

Also, CI seems to fail due to some GDExtension stuff that I am unfamiliar with, I'd appreciate a pointer to the fix.

The failures are because you reordered some enums around (specifically the Light3D PARAM_* enum), which breaks compatibility with existing projects. Make sure you only add new values at the end; never move existing values or remove them.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected. This is a great start 🙂

Testing project: test_pr_108219.zip

Some feedback:

  • Area width and height should use a single area Vector2 property instead of two separate float properties. While doing so, make sure to use PROPERTY_HINT_LINK for the property so you can adjust the width proportionally to the current height (and vice versa). You can clamp the value to positive numbers in the set_area() setter (this ensures it also affects script-based usage).
  • Gizmo behavior and icons look good to me.
  • I've only tested this in Forward+, as Mobile will print a lot of errors and not render anything. On Compatibility, area lights are not supported but rendering works correctly otherwise.
    • There should be a node configuration warning on AreaLight3D when in Compatibility, similar to the Decal node's existing node configuration warning.
  • Like with lights that use a dual paraboloid shadow mode, meshes need to be subdivided enough (relative to the size of the area light) for shadows to look correct:
area_shadow_subdivided.mp4

This is most noticeable on shadows with a low blur value:

area_shadow_subdivided_no_blur.mp4
  • Light/shadow distance fade works correctly.
  • Volumetric fog doesn't take area lights into account yet.
  • Hard positional shadow quality looks the same as Soft Very Low. Ideally, Hard should disable shadow blurring entirely like it does with other light types to maximize performance on low-end GPUs.
  • High and Ultra soft shadow qualities can be very expensive with lights that have a high blur value (even 1.0 is quite blurry out of the box), so beware. Consider using TAA or FSR2 to get temporal smoothing (and keep shadow filter quality at its default Low), which will give you smoother shadows without an increased performance cost.
  • Shadow opacity will behave differently to other light types, as the shadow won't fade in an uniform manner across the whole light. This may be desired behavior, but it might be worth documenting to avoid surprises:
area_shadow_opacity.mp4
  • When you move some shadow casters, they can sometimes be in a position where they will self-shadow. This is most noticeable at low blur values (below 0.3 in the test project). Reloading the scene or restarting the project does not fix the self-shadowing, so this does not appear to be an issue related to shadow updates sometimes not occurring when they should.

https://github.com/user-attachments/assets/131dee5c-c6c1-40d0-bcfc-2d8e68a7dbc

The issue will occur even if you're moving another shadow caster:

area_shadow_self_moving_other_shadow_caster.mp4
  • Specular contribution is set to 0.5 by default, which leads to area lights not having specular contributions as strong as omni and spot lights:

image

It's more in line with other light types when set to 1.0, so I suggest changing the default to that:

image

@LiveTrower
Copy link
Contributor

I believe that, for now, to provide some level of mobile support and compatibility, area lights will only be usable through lightmap baking. I don't think anyone has the time to research and implement real-time area lights with acceptable performance, especially on mobile devices.

@CookieBadger CookieBadger force-pushed the area-light-integration branch 2 times, most recently from 70f8cd3 to ff28cb1 Compare July 15, 2025 16:20
@CookieBadger
Copy link
Contributor Author

@Calinou do you have any further details about the self-shadow bug? I was unable to reproduce it on my machine with your project.

@Calinou
Copy link
Member

Calinou commented Jul 15, 2025

@Calinou do you have any further details about the self-shadow bug? I was unable to reproduce it on my machine with your project.

I've just tested the PR and can't reproduce the issue anymore, so I guess it's fixed now.

@Calinou Calinou mentioned this pull request Jul 18, 2025
@CookieBadger CookieBadger force-pushed the area-light-integration branch 5 times, most recently from e432279 to c964f93 Compare July 25, 2025 21:34
@CookieBadger
Copy link
Contributor Author

@LiveTrower I would see no reason, why area lights shouldn't work on mobile, or in compatibility. In fact, they should work now (just need to fix up the shadows in compat).

@LiveTrower
Copy link
Contributor

@CookieBadger I was talking about performance because when it comes to mobile rendering and compatibility, it's a very sensitive subject. That's why many advanced features found in Forward+ rendering are left out of these renders.

You'll probably need to make a highly optimized version for these renders and prove with extensive testing that it's feasible on mobiles that support Vulkan or OpenGL, as well as on very old PC hardware that only runs Godot in compatibility mode because it lacks Vulkan or DirectX 12.

I'm not an expert, and I won't have the final say, so it would be better if @clayjohn gave you his opinion since he knows more about this.

@Calinou
Copy link
Member

Calinou commented Jul 26, 2025

I expect area lights will perform somewhat OK on Mobile and Compatibility with shadows disabled, but I would avoid enabling shadows for them (but this applies to all light types in general when working with mobile/low-end platforms). PCSS-style variable penumbra shadows don't work in Mobile and Compatibility anyway, so the Light3D Size property will only affect the size of the specular lobe and the light's diffuse appearance anyway.

Last time I checked, there are some remnants of PCSS-style shadows in the Mobile renderer, but these were meant to be removed due to performance concerns: #55882

@CookieBadger CookieBadger force-pushed the area-light-integration branch from 9637918 to bfe26f2 Compare July 26, 2025 16:48
@CookieBadger
Copy link
Contributor Author

CookieBadger commented Jul 26, 2025

@Calinou I agree, lighting computations are just mildly more complex than for point lights. They require two texture lookups, but the textures are constant, so should not be an issue even in Compatibility. Shadows in Compatibility don't work anyways since there is no dual paraboloid shadow pass, so I disabled them now. For the mobile renderer, shadows do work, but as you say, variable penumbra doesn't. Would you rather note that in documentation, or disable shadows entirely there?

@Calinou
Copy link
Member

Calinou commented Jul 28, 2025

For the mobile renderer, shadows do work, but as you say, variable penumbra doesn't. Would you rather note that in documentation, or disable shadows entirely there?

I think you can keep shadows there, just document it in the class reference.

@CookieBadger
Copy link
Contributor Author

CookieBadger commented Dec 21, 2025

I took the time to install Linux and tried to reproduce, but still to no avail.

Are the physical camera properties on the Lightmap node needed for the error to occur?

Are these spots somehow affected by energy and attenuation of the directional light or the area light, and if so, how?
What happens when you change texel_size, subdiv, or quality on the Lightmap node, disable the denoiser, or don´t use_textures_for_bounces? What happens when you set bounces to 0?

Does this issue go away if you change certain properties on the area light or the directional light? Or if you scale the model up by a factor of 100? Or if you remove the sky on the Environment node, or change the sky contribution on the ambient light?

@Felipe-Sena
Copy link

Sponza

Changing texel_scale from 4 to 2 increases the size and amount of the artifacts:

texel_scale at 4 (2048x2048) (low quality)
image

texel_scale at 2 (1024x1024) (low quality)
image

Disabling denoising (JNLM) still has the same issue, but now the black squares are small dots (1024x1024) (low quality):
image

Increasing the quality doesn't get rid of these (although the black dots may not be visible sometimes when not using the denoiser).

(1024x1024) (ultra quality) (denoised):
image

(1024x1024) (ultra quality) (not denoised) [the black speck is the bottom of the LightmapGI editor icon]:
image

Moving the light will drastically change the position of the artifacts, they also slightly change per bake:

(1024x1024) (ultra quality) (denoised):
image

Undoing the transform will have a different artifact pattern:
(1024x1024) (ultra quality) (denoised):
image

Changing the size of the area light doesn't change too much (and since the pattern changes between bakes it's hard to know if the changes seen are because of the area light changing or the "noise")

Using another light source with the area light (both set to static) completely removes the issues (1024x1024) (medium quality) (denoised):

image

Changing the scale of the root node (100x) also didn't change anything, artifacts persist. BUT, changing the scale of the model by 100x made the artifacts appear all over the model (1024x1024) (medium quality) (denoised):

image

Increasing the range of the area light so it's AABB intersects with the mesh will make the artifacts disappear a bit though (1024x1024) (medium quality) (denoised):

image

Changing the amount of bounces doesn't change anything, I don't know if changing the amount to 0 removes the issue or if it's just impossible to see, since the outside of the mesh becomes fully black.

And finally, baking with another type of light doesn't cause any issues (1024x1024) (medium quality) (denoised):

image

Custom mesh

On another mesh, I can very rarely replicate the same issue, the area light here barely has enough range to touch the mesh (1024x1024) (medium quality) (denoised):

image

Changing the lightmap scale, quality, or the range of the light is enough to hide the issue.


Things that don't affect anything:

Enabling or disabling use_textures_for_bounces
Probe subdivisions


Notes

I did not use any lightmap camera attributes, but my previous comment was using these:
image

This is a fresh project and I didn't change any settings.

@CookieBadger
Copy link
Contributor Author

Thanks for the detailed reports @Felipe-Sena, I will investigate with those findings!

@CookieBadger
Copy link
Contributor Author

Issue should be fixed now. Special thanks to @jcostello and @Felipe-Sena for the help in debugging this.

Turns out that the light direction r_light_dir was not set to any value, and it seems like my machine zero-initializes it, while other machines leave it at a random value, resulting in NaNs.

Let me know if you find any other issue!

@jcostello
Copy link
Contributor

The solution fix the issue :)

@Glorax

This comment was marked as off-topic.

@Sp1cyP3pp3r

This comment was marked as off-topic.

@retrotails
Copy link
Contributor

Added built-ins for using area lights in custom shaders.

I'm trying to get a shader with a custom light() to match the behavior of StandardMaterial3D. I think * ALBEDO in the example is incorrect so I removed it, which made the diffuse look the same, but the specular is still too bright. I added / PI which makes for a close match when looking straight on, but oblique angles get incorrect. things get worse with high roughness. is this a known limitation, or is there a more correct formula?
left half of the cylinder is StandardMaterial3D, right half is shader (except the oblique one because the camera is facing the other way)
2026-01-25

also, I think the voxelGI cell size calculation is wrong, because changing the resolution of voxelGI affects the brightness. (very similar to #106673 )

@CookieBadger
Copy link
Contributor Author

@retrotails I see what you are struggling with. I changed it, such that the fresnel effect is accounted for in LIGHT_AREA_SPECULAR.

I also changed the voxelGI attenuation again. It does not precisely match up with point lights, but I guess it is close enough now, and independent of resolution.

@CookieBadger CookieBadger force-pushed the area-light-integration branch from 6b8eda9 to 5375765 Compare January 27, 2026 09:55
@CookieBadger
Copy link
Contributor Author

Rebased and fixed projector texture not being ignored.

Since it was asked, I hope to get this PR merged for 4.7, but afaik it depends on whether @clayjohn has the capacity to review it.

@wagnerfs
Copy link
Contributor

This is looking very promising!
I got this PR because I need a scene I imported from Unreal that makes heavy use of area lights, so I put it to test, everything seems perfect except for shadow bleeding through walls.

image image

I placed a spotlight after just to make sure it wasn't a general Godot issue and it seems shadows are being cast fine:

image

@Calinou
Copy link
Member

Calinou commented Jan 29, 2026

I got this PR because I need a scene I imported from Unreal that makes heavy use of area lights, so I put it to test, everything seems perfect except for shadow bleeding through walls.

Make sure the wall geometry is subdivided enough (you'll probably need 7x7 for this scene at least), so that the dual parabolid correction can work correctly. Check in wireframe mode how it looks currently.

Regardless, area light shadows are still emitted from a single point (located at the center of the area), then perspective-corrected using dual parabolid correction. This means it's an approximation that can't be as accurate as a raytraced shadow.

Baked shadows with LightmapGI should be more precise when the light's GI mode is set to Static.

@wagnerfs
Copy link
Contributor

wagnerfs commented Jan 30, 2026

@Calinou You're right, I deleted my other post because I didn't notice the depth subdivision was 0.
Subdividing does the trick.

EDIT: Adding up, the reason why it was leaking was the auto generated LODs, disabling LODs on walls also fixed it.

@CookieBadger CookieBadger force-pushed the area-light-integration branch from 5375765 to 9ae91e6 Compare February 12, 2026 13:12
@CookieBadger CookieBadger requested a review from a team as a code owner February 12, 2026 13:12
Copy link
Member

@clayjohn clayjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of little comments from the rendering meeting review!

@CookieBadger
Copy link
Contributor Author

CookieBadger commented Feb 23, 2026

Since I was asked, here is what acos_approx(x) looks like:
image

As to why this approximation makes sense: the maximum error is a lot smaller than the resolution of our lookup table that we are sampling with the result of the cosine calculation.

@CookieBadger CookieBadger force-pushed the area-light-integration branch 2 times, most recently from ff89495 to a666de1 Compare February 25, 2026 15:14
@CookieBadger
Copy link
Contributor Author

CookieBadger commented Feb 26, 2026

Implemented area lights in SDFGI. For textures to work, I had to add another uniform set in gi.h: SDFGI, which is updated per frame. The algorithm should be tweaked in the future, as it currently does not match point lights that well, especially when textures are used.

Commits have been squashed.

Update: rebased again due to new conflicts.
sdfgi_on

@CookieBadger CookieBadger force-pushed the area-light-integration branch 3 times, most recently from 60bf361 to 085bbe5 Compare February 26, 2026 23:21
Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected with all renderers. I've also tested SDFGI and volumetric fog.

Documentation looks good to me.

Some feedback:

  • The Projector property is visible in the AreaLight3D inspector, but it has no effect (even when shadows are enabled). The Texture property is used instead.

@CookieBadger CookieBadger force-pushed the area-light-integration branch from 085bbe5 to c8ee270 Compare February 27, 2026 20:22
@CookieBadger
Copy link
Contributor Author

Update: projector rect property is now hidden for area lights.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet