Skip to content

Add Rectangular Area Light Source#108219

Merged
Repiteo merged 1 commit into
godotengine:masterfrom
CookieBadger:area-light-integration
Apr 14, 2026
Merged

Add Rectangular Area Light Source#108219
Repiteo merged 1 commit into
godotengine:masterfrom
CookieBadger:area-light-integration

Conversation

@CookieBadger
Copy link
Copy Markdown
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.

image

Image above made by: @passivestar

LTCHeitz_compressed
Screenshot 2026-03-15 004153
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread doc/classes/AreaLight3D.xml
@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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

@QbieShay
Copy link
Copy Markdown
Contributor

QbieShay commented Mar 19, 2026

Doing a very naive toon implementation, the edges are noisy. I think this is entirely normal, but should be documented
image

EDIT: adding omni as a ref
image

@CookieBadger CookieBadger force-pushed the area-light-integration branch 2 times, most recently from bae9ab4 to 0be00ab Compare March 21, 2026 22:34
@CookieBadger
Copy link
Copy Markdown
Contributor Author

CookieBadger commented Mar 21, 2026

How do you show the area light plane? I see it being shown in some of these examples but can't find a setting for displaying this (and thus the texture assigned to it) in the node anywhere.

@WickedInsignia You add it manually (MeshInstance3D, Plane mesh, emissive material, texture). I understand the need for showing it when experimenting, but I wouldn't know how the best way to automate that right now, and in a production 3D environment you would rarely ever want the light plane visible, but rather have some 3D model, like a billboard there instead.

  1. The documentation of the light_size property needs to be updated with matching description for area lights.

@QbieShay good catch, I updated it now.

  1. I think i'm in a cursed scenario, but shadows look kind of strange

See @Calinou's answer; compare with dual paraboloid omni light shadows. Improving shadow rendering is out of scope for this PR.

  1. Fog seems to be overall alright. There are some edge cases that puzzle me a bit, but i can't tell whether this is expected or nay

The only caveat I'm aware of, is that the fog has some minimum distance to be visible, to avoid NaNs. Unless you are referring to something that's visibly wrong, I'd say let's see if we get some more specific issues of where it needs tweaking.

  1. In the area light documentation, it should be added what to do with light shaders. I couldn't find the information anywhere in-engine.

Is the information for point lights anywhere in-engine? The info for custom shaders with area lights should be added where custom shaders for all other lights are too, I'd say. See godotengine/godot-docs#11816.

  1. Attenuation behaves strangely, i don't quite understand. It seems to act behind the light at high value.
    image

As you can see with omni lights, when you increase the light's attenuation, the energy that is distributed at a point at a distance of less than 1.0m increases exponentially with distance^(-attenuation). This means that a surface that is very close to the light is very very bright. Since in your scene you have bloom turned on, the brightness is smeared around those pixels that have a very high brightness, making it seem like there is light on the backside.

  1. as much as i am enjoying toying with negative attenuation, i don't think it should be in the range. We can leave it with or_less if we want to, but it shouldn't be presented to the user as a valid "side" of the range

As @Calinou said, this needs to be an entirely different issue + PR.

  1. Lightmaps seem to bake alright but somebody should check

Lightmap tests were quite thorough, since it had a bunch of problems previously, although somebody could double check that my fix for (8.) didn't break anything.

(8.) Baking a lightmap with a texture assigned to an area light in compatibility mode causes the engine to throw errors and render the scene black

good catch, fixed.

(9) I am not sure normalize should be on by default, since it makes the light less bright and its behaviour less obvious. I would put it off by default.

The general sentiment that I got was that it should be enabled. Blender got it enabled by default, so I'd say the current state aligns with expectations more.

(10) Doing a very naive toon implementation, the edges are noisy. I think this is entirely normal, but should be documented

This would go with godotengine/godot-docs#11816 then. @QbieShay, could you also send me that shader, such that I can check it? Maybe I can find out what's going on. EDIT: not an issue, just self-shadowing.

@QbieShay
Copy link
Copy Markdown
Contributor

As you can see with omni lights, when you increase the light's attenuation, the energy that is distributed at a point at a distance of 1.0m increases exponentially with distance^(-attenuation). This means that a surface that is very close to the light is very very bright. Since in your scene you have bloom turned on, the brightness is smeared around those pixels that have a very high brightness, making it seem like there is light on the backside.

I will send you my scene. The screenshot in point 5 has light speckles very far behind the area light

@CookieBadger
Copy link
Copy Markdown
Contributor Author

CookieBadger commented Mar 22, 2026

The screenshot in point 5 has light speckles very far behind the area light

This appears to be reflected light due to SDFGI and high indirect energy of the light, and can be observed for spotlights as well, also in prior versions (compare area lights 4.7 left and spotlights in 4.6 right), so it most likely is intended behavior. @QbieShay and me have been dicussing this on discord for a good while. It is hard to debug this, likely due to #116780.

image

Doing a very naive toon implementation, the edges are noisy. I think this is entirely normal, but should be documented

You have shadows turned on, and light is very close to the object, so you get some self shadowing - modify light bias or move the light back, or disable soft shadows (better for a toon scenario anyways) and the shader will be smooth.

image

Copy link
Copy Markdown
Contributor

@QbieShay QbieShay left a comment

Choose a reason for hiding this comment

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

Tested with a testing project and Jamser's bistro. Found some issues, but turned out to be unrelated to area lights. Looks good to me

Comment thread scene/3d/light_3d.h Outdated
@CookieBadger CookieBadger force-pushed the area-light-integration branch 3 times, most recently from 0706332 to ae5168c Compare April 3, 2026 20:49
@CookieBadger
Copy link
Copy Markdown
Contributor Author

CookieBadger commented Apr 3, 2026

@clayjohn rebased and addressed your comment.
EDIT: updated docs accordingly.

@CookieBadger CookieBadger force-pushed the area-light-integration branch from ae5168c to 7b7bd1d Compare April 3, 2026 23:37
@akien-mga akien-mga requested a review from clayjohn April 7, 2026 12:06
@WickedInsignia
Copy link
Copy Markdown

WickedInsignia commented Apr 8, 2026

This inspired me to build a showcase scene to test how it plays with complex geo, textures etc.
Shadows would benefit from some dedicated quality options but otherwise looks fantastic and wonderful work!
Heatsink_AreaLight_Godot_LoganPreshaw_02
Heatsink_AreaLight_Godot_LoganPreshaw_01
Heatsink_AreaLight_Godot_LoganPreshaw_03
Heatsink_AreaLight_Godot_LoganPreshaw_05

Copy link
Copy Markdown
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.

Amazing work! I don't have anything to add after my last few reviews. The current state of this PR is amazing and I consider it ready to merge.

Let's get this in for 4.7!!

@Repiteo
Copy link
Copy Markdown
Contributor

Repiteo commented Apr 14, 2026

Thanks! Fantastic job!

@JekSun97
Copy link
Copy Markdown
Contributor

You add it manually (MeshInstance3D, Plane mesh, emissive material, texture). I understand the need for showing it when experimenting, but I wouldn't know how the best way to automate that right now, and in a production 3D environment you would rarely ever want the light plane visible, but rather have some 3D model, like a billboard there instead

I think in another PR we could add a Gizmo as a translucent plane with a direction arrow in the center, this would improve ease of use.

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

Projects

None yet