EEVEE-Next: Sunlight Extraction

Sun extraction convert part of light comming from the world to a
sun light which increases the quality of the rendering. The goal
of this feature is to workaround the limitation of the storage
techniques used for environment lighting inside EEVEE.

This first implementation works by clamping the world lighting and
summing the excess lighting and (and its incomming directions) to
deduce the sun position.
All the lighting is then transfered into this light power. The sun
angle is computed based on the directionnality of the excess lighting,
the more divergent the excess lighting is, the bigger the angle.

This has a few benefits:
- It's stable and behave well under animation. This is because we
average a lot of data.
- It's fast as it can be done inside the remap shader in one pass.
- It requires only one parameter, the clamp threshold.

However, it has some issue:
- It modifies the lighting as we change the incomming direction for
excess lighting away from the chosen sun direction. This could be fixed
by masking only lighting around the chosen sun direction (requires 2
passes, slower).
- Given that this only average the direction, it behaves poorly if there
two opposite bright light sources (it puts the sun in the middle). This
could be fixed by extracting more suns, but that becomes more complex
and requires even more passes.
- It looks bad if the summed lighting is not supposed to be a perfect
disk in specular reflections or if the sources are too divergent as the
disk is too big and the approximation fails short. This could be
mitigated by adding an upper bound to the sun radius. For now we
workaround this issue by exposing the sun angle parameter in the UI.

A more precise algorithm can be implemented in the future to avoid
having to deal with these limitations. A possibility is to use
importance sampling to randomize sun position. But that would be only
for final render.

Pull Request: https://projects.blender.org/blender/blender/pulls/121455
This commit is contained in:
Clément Foucault
2024-05-14 16:36:12 +02:00
committed by Clément Foucault
parent 981181ddde
commit ea5e1fef2a
30 changed files with 705 additions and 81 deletions

View File

@@ -702,14 +702,7 @@ class RENDER_PT_eevee_next_clamping(RenderButtonsPanel, Panel):
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = scene.eevee
col = layout.column()
col.prop(props, "clamp_world", text="World")
pass
class RENDER_PT_eevee_next_clamping_surface(RenderButtonsPanel, Panel):

View File

@@ -151,9 +151,8 @@ class EEVEE_WORLD_PT_volume(WorldButtonsPanel, Panel):
layout.label(text="No output node")
class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel):
bl_label = "Light Probe"
bl_translation_context = i18n_contexts.id_id
class EEVEE_WORLD_PT_settings(WorldButtonsPanel, Panel):
bl_label = "Settings"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@@ -163,13 +162,56 @@ class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel):
world = context.world
return world and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
pass
class EEVEE_WORLD_PT_lightprobe(WorldButtonsPanel, Panel):
bl_label = "Light Probe"
bl_parent_id = "EEVEE_WORLD_PT_settings"
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
def draw(self, context):
layout = self.layout
world = context.world
layout.use_property_split = True
layout.prop(world, "probe_resolution")
layout.prop(world, "probe_resolution", text="Resolution")
class EEVEE_WORLD_PT_sun(WorldButtonsPanel, Panel):
bl_label = "Sun"
bl_parent_id = "EEVEE_WORLD_PT_settings"
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
def draw(self, context):
layout = self.layout
world = context.world
layout.use_property_split = True
layout.prop(world, "sun_threshold", text="Threshold")
layout.prop(world, "sun_angle", text="Angle")
class EEVEE_WORLD_PT_sun_shadow(WorldButtonsPanel, Panel):
bl_label = "Shadow"
bl_parent_id = "EEVEE_WORLD_PT_sun"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
def draw_header(self, context):
world = context.world
self.layout.prop(world, "use_sun_shadow", text="")
def draw(self, context):
layout = self.layout
world = context.world
layout.use_property_split = True
layout.prop(world, "sun_shadow_maximum_resolution", text="Resolution Limit")
class WORLD_PT_viewport_display(WorldButtonsPanel, Panel):
@@ -193,7 +235,10 @@ classes = (
EEVEE_WORLD_PT_surface,
EEVEE_WORLD_PT_volume,
EEVEE_WORLD_PT_mist,
EEVEE_WORLD_PT_probe,
EEVEE_WORLD_PT_settings,
EEVEE_WORLD_PT_lightprobe,
EEVEE_WORLD_PT_sun,
EEVEE_WORLD_PT_sun_shadow,
WORLD_PT_viewport_display,
WORLD_PT_custom_props,
)