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,
)

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 36
#define BLENDER_FILE_SUBVERSION 37
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -3383,7 +3383,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
SceneEEVEE default_scene_eevee = *DNA_struct_default_get(SceneEEVEE);
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.shadow_resolution_scale = default_scene_eevee.shadow_resolution_scale;
scene->eevee.clamp_world = 10.0f;
}
}
}
@ -3548,6 +3547,16 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 37)) {
const World *default_world = DNA_struct_default_get(World);
LISTBASE_FOREACH (World *, world, &bmain->worlds) {
world->sun_threshold = default_world->sun_threshold;
world->sun_angle = default_world->sun_angle;
world->sun_shadow_maximum_resolution = default_world->sun_shadow_maximum_resolution;
world->flag |= WO_USE_SUN_SHADOW;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -521,6 +521,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl
engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl
engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl
engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl
engines/eevee_next/shaders/eevee_light_eval_lib.glsl
engines/eevee_next/shaders/eevee_light_iter_lib.glsl
engines/eevee_next/shaders/eevee_light_lib.glsl
@ -564,6 +565,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_reflection_probe_mapping_lib.glsl
engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl
engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl
engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl
engines/eevee_next/shaders/eevee_renderpass_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl

View File

@ -175,6 +175,9 @@ void Instance::view_update()
void Instance::begin_sync()
{
/* Needs to be first for sun light parameters. */
world.sync();
materials.begin_sync();
velocity.begin_sync(); /* NOTE: Also syncs camera. */
lights.begin_sync();
@ -192,7 +195,6 @@ void Instance::begin_sync()
motion_blur.sync();
hiz_buffer.sync();
main_view.sync();
world.sync();
film.sync();
render_buffers.sync();
ambient_occlusion.sync();

View File

@ -15,6 +15,7 @@
#include "eevee_light.hh"
#include "BLI_math_rotation.h"
#include "DNA_defaults.h"
namespace blender::eevee {
@ -45,12 +46,14 @@ static eLightType to_light_type(short blender_light_type,
/** \name Light Object
* \{ */
void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
void Light::sync(ShadowModule &shadows,
float4x4 object_to_world,
char visibility_flag,
const ::Light *la,
float threshold)
{
using namespace blender::math;
const ::Light *la = (const ::Light *)ob->data;
eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF);
if (assign_if_different(this->type, new_type)) {
shadow_discard_safe(shadows);
@ -59,7 +62,6 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
this->color = float3(&la->r) * la->energy;
float3 scale;
float4x4 object_to_world = ob->object_to_world();
object_to_world.view<3, 3>() = normalize_and_get_size(object_to_world.view<3, 3>(), scale);
/* Make sure we have consistent handedness (in case of negatively scaled Z axis). */
@ -72,10 +74,10 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
shape_parameters_set(la, scale, threshold);
const bool diffuse_visibility = (ob->visibility_flag & OB_HIDE_DIFFUSE) == 0;
const bool glossy_visibility = (ob->visibility_flag & OB_HIDE_GLOSSY) == 0;
const bool transmission_visibility = (ob->visibility_flag & OB_HIDE_TRANSMISSION) == 0;
const bool volume_visibility = (ob->visibility_flag & OB_HIDE_VOLUME_SCATTER) == 0;
const bool diffuse_visibility = (visibility_flag & OB_HIDE_DIFFUSE) == 0;
const bool glossy_visibility = (visibility_flag & OB_HIDE_GLOSSY) == 0;
const bool transmission_visibility = (visibility_flag & OB_HIDE_TRANSMISSION) == 0;
const bool volume_visibility = (visibility_flag & OB_HIDE_VOLUME_SCATTER) == 0;
float shape_power = shape_radiance_get();
float point_power = point_radiance_get();
@ -325,16 +327,37 @@ void LightModule::begin_sync()
sun_lights_len_ = 0;
local_lights_len_ = 0;
if (use_sun_lights_ && inst_.world.sun_threshold() > 0.0) {
/* Create a placeholder light to be fed by the GPU after sunlight extraction.
* Sunlight is disabled if power is zero. */
::Light la = blender::dna::shallow_copy(
*(const ::Light *)DNA_default_table[SDNA_TYPE_FROM_STRUCT(Light)]);
la.type = LA_SUN;
/* Set on the GPU. */
la.r = la.g = la.b = -1.0f; /* Tag as world sun light. */
la.energy = 1.0f;
la.sun_angle = inst_.world.sun_angle();
la.shadow_maximum_resolution = inst_.world.sun_shadow_max_resolution();
SET_FLAG_FROM_TEST(la.mode, inst_.world.use_sun_shadow(), LA_SHADOW);
Light &light = light_map_.lookup_or_add_default(world_sunlight_key);
light.used = true;
light.sync(inst_.shadows, float4x4::identity(), 0, &la, light_threshold_);
sun_lights_len_ += 1;
}
}
void LightModule::sync_light(const Object *ob, ObjectHandle &handle)
{
const ::Light *la = static_cast<const ::Light *>(ob->data);
if (use_scene_lights_ == false) {
return;
}
if (use_sun_lights_ == false) {
if (static_cast<const ::Light *>(ob->data)->type == LA_SUN) {
if (la->type == LA_SUN) {
return;
}
}
@ -343,7 +366,7 @@ void LightModule::sync_light(const Object *ob, ObjectHandle &handle)
light.used = true;
if (handle.recalc != 0 || !light.initialized) {
light.initialized = true;
light.sync(inst_.shadows, ob, light_threshold_);
light.sync(inst_.shadows, ob->object_to_world(), ob->visibility_flag, la, light_threshold_);
}
sun_lights_len_ += int(is_sun_light(light.type));
local_lights_len_ += int(!is_sun_light(light.type));
@ -428,6 +451,7 @@ void LightModule::end_sync()
culling_tile_buf_.resize(total_word_count_);
culling_pass_sync();
update_pass_sync();
debug_pass_sync();
}
@ -444,6 +468,7 @@ void LightModule::culling_pass_sync()
{
auto &sub = culling_ps_.sub("Select");
sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_SELECT));
sub.bind_ubo("sunlight_buf", &inst_.world.sunlight);
sub.bind_ssbo("light_cull_buf", &culling_data_buf_);
sub.bind_ssbo("in_light_buf", light_buf_);
sub.bind_ssbo("out_light_buf", culling_light_buf_);
@ -483,6 +508,20 @@ void LightModule::culling_pass_sync()
}
}
void LightModule::update_pass_sync()
{
auto &pass = update_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LIGHT_SHADOW_SETUP));
pass.bind_ssbo("light_buf", &culling_light_buf_);
pass.bind_ssbo("light_cull_buf", &culling_data_buf_);
pass.bind_ssbo("tilemaps_buf", &inst_.shadows.tilemap_pool.tilemaps_data);
pass.bind_resources(inst_.uniform_data);
/* TODO(fclem): Dispatch for all light. */
pass.dispatch(int3(1, 1, 1));
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
void LightModule::debug_pass_sync()
{
if (inst_.debug_mode == eDebugMode::DEBUG_LIGHT_CULLING) {
@ -512,6 +551,7 @@ void LightModule::set_view(View &view, const int2 extent)
culling_data_buf_.push_update();
inst_.manager->submit(culling_ps_, view);
inst_.manager->submit(update_ps_);
}
void LightModule::debug_draw(View &view, GPUFrameBuffer *view_fb)

View File

@ -76,7 +76,11 @@ struct Light : public LightData, NonCopyable {
}
#endif
void sync(ShadowModule &shadows, const Object *ob, float threshold);
void sync(ShadowModule &shadows,
float4x4 object_to_world,
char visibility_flag,
const ::Light *la,
float threshold);
void shadow_ensure(ShadowModule &shadows);
void shadow_discard_safe(ShadowModule &shadows);
@ -113,6 +117,7 @@ class LightModule {
/** Map of light objects data. Converted to flat array each frame. */
Map<ObjectKey, Light> light_map_;
ObjectKey world_sunlight_key;
/** Flat array sent to GPU, populated from light_map_. Source buffer for light culling. */
LightDataBuf light_buf_ = {"Lights_no_cull"};
/** Luminous intensity to consider the light boundary at. Used for culling. */
@ -148,6 +153,9 @@ class LightModule {
/** Total number of words the tile buffer needs to contain for the render resolution. */
uint total_word_count_ = 0;
/** Update light on the GPU after culling. Ran for each sample. */
PassSimple update_ps_ = {"LightUpdate"};
/** Debug Culling visualization. */
PassSimple debug_draw_ps_ = {"LightCulling.Debug"};
@ -176,6 +184,7 @@ class LightModule {
private:
void culling_pass_sync();
void update_pass_sync();
void debug_pass_sync();
};

View File

@ -68,15 +68,20 @@ class LookdevWorld {
return &world;
}
float background_opacity_get()
float background_opacity_get() const
{
return parameters_.background_opacity;
}
float background_blur_get()
float background_blur_get() const
{
return parameters_.blur;
}
float intensity_get() const
{
return parameters_.intensity;
}
};
/** \} */

View File

@ -39,11 +39,13 @@ void SphereProbeModule::begin_sync()
PassSimple &pass = remap_ps_;
pass.init();
pass.specialize_constant(shader, "extract_sh", &extract_sh_);
pass.specialize_constant(shader, "extract_sun", &extract_sh_);
pass.shader_set(shader);
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_texture("atlas_tx", &probes_tx_);
pass.bind_image("atlas_img", &probes_tx_);
pass.bind_ssbo("out_sh", &tmp_spherical_harmonics_);
pass.bind_ssbo("out_sun", &tmp_sunlight_);
pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
@ -74,6 +76,17 @@ void SphereProbeModule::begin_sync()
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.dispatch(1);
}
{
PassSimple &pass = sum_sun_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SUNLIGHT));
pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
pass.bind_ssbo("in_sun", &tmp_sunlight_);
pass.bind_ssbo("sunlight_buf", &instance_.world.sunlight);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_UNIFORM);
}
{
PassSimple &pass = select_ps_;
pass.init();
@ -227,6 +240,7 @@ void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoo
if (extract_spherical_harmonics) {
instance_.manager->submit(sum_sh_ps_);
instance_.manager->submit(sum_sun_ps_);
/* All volume probe that needs to composite the world probe need to be updated. */
instance_.volume_probes.update_world_irradiance();
}

View File

@ -46,6 +46,8 @@ class SphereProbeModule {
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
/** Sum irradiance information optionally extracted during `remap_ps_`. */
PassSimple sum_sh_ps_ = {"Probe.SumSphericalHarmonics"};
/** Sum sunlight information optionally extracted during `remap_ps_`. */
PassSimple sum_sun_ps_ = {"Probe.SumSunlight"};
/** Copy volume probe irradiance for the center of sphere probes. */
PassSimple select_ps_ = {"Probe.Select"};
/** Convolve the octahedral map to fill the Mip-map levels. */
@ -86,6 +88,10 @@ class SphereProbeModule {
/** Final buffer containing the spherical harmonics for the world. */
StorageBuffer<SphereProbeHarmonic, true> spherical_harmonics_ = {"spherical_harmonics_"};
/** Intermediate buffer to store sun light. */
StorageArrayBuffer<SphereProbeSunLight, SPHERE_PROBE_MAX_HARMONIC, true> tmp_sunlight_ = {
"tmp_sunlight_"};
/**
* True if the next redraw will trigger a light-probe sphere update.
* As syncing the draw passes for rendering has a significant overhead,

View File

@ -63,7 +63,7 @@ void Sampling::init(const Scene *scene)
auto clamp_value_load = [](float value) { return (value > 0.0) ? value : 1e20; };
clamp_data_.world = clamp_value_load(scene->eevee.clamp_world);
clamp_data_.sun_threshold = clamp_value_load(inst_.world.sun_threshold());
clamp_data_.surface_direct = clamp_value_load(scene->eevee.clamp_surface_direct);
clamp_data_.surface_indirect = clamp_value_load(scene->eevee.clamp_surface_indirect);
clamp_data_.volume_direct = clamp_value_load(scene->eevee.clamp_volume_direct);

View File

@ -189,6 +189,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_light_culling_tile";
case LIGHT_CULLING_ZBIN:
return "eevee_light_culling_zbin";
case LIGHT_SHADOW_SETUP:
return "eevee_light_shadow_setup";
case RAY_DENOISE_SPATIAL:
return "eevee_ray_denoise_spatial";
case RAY_DENOISE_TEMPORAL:
@ -225,6 +227,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_reflection_probe_irradiance";
case SPHERE_PROBE_SELECT:
return "eevee_reflection_probe_select";
case SPHERE_PROBE_SUNLIGHT:
return "eevee_reflection_probe_sunlight";
case SHADOW_CLIPMAP_CLEAR:
return "eevee_shadow_clipmap_clear";
case SHADOW_DEBUG:

View File

@ -81,6 +81,7 @@ enum eShaderType {
LIGHT_CULLING_SORT,
LIGHT_CULLING_TILE,
LIGHT_CULLING_ZBIN,
LIGHT_SHADOW_SETUP,
LIGHTPROBE_IRRADIANCE_BOUNDS,
LIGHTPROBE_IRRADIANCE_OFFSET,
@ -106,9 +107,10 @@ enum eShaderType {
RAY_TRACE_SCREEN,
SPHERE_PROBE_CONVOLVE,
SPHERE_PROBE_IRRADIANCE,
SPHERE_PROBE_REMAP,
SPHERE_PROBE_SELECT,
SPHERE_PROBE_IRRADIANCE,
SPHERE_PROBE_SUNLIGHT,
SHADOW_CLIPMAP_CLEAR,
SHADOW_DEBUG,

View File

@ -1560,6 +1560,13 @@ struct SphereProbeHarmonic {
};
BLI_STATIC_ASSERT_ALIGN(SphereProbeHarmonic, 16)
struct SphereProbeSunLight {
float4 direction;
packed_float3 radiance;
float _pad0;
};
BLI_STATIC_ASSERT_ALIGN(SphereProbeSunLight, 16)
/** \} */
/* -------------------------------------------------------------------- */
@ -1725,7 +1732,7 @@ BLI_STATIC_ASSERT_ALIGN(HiZData, 16)
* \{ */
struct ClampData {
float world;
float sun_threshold;
float surface_direct;
float surface_indirect;
float volume_direct;

View File

@ -9,6 +9,7 @@
#include "BKE_lib_id.hh"
#include "BKE_node.hh"
#include "BKE_world.h"
#include "BLI_math_rotation.h"
#include "DEG_depsgraph_query.hh"
#include "NOD_shader.h"
@ -76,6 +77,21 @@ World::~World()
return default_world_;
}
::World *World::scene_world_get()
{
return (inst_.scene->world != nullptr) ? inst_.scene->world : default_world_get();
}
float World::sun_threshold()
{
float sun_threshold = scene_world_get()->sun_threshold;
if (inst_.use_studio_light()) {
/* Do not call `lookdev_world_.intensity_get()` as it might not be initialized yet. */
sun_threshold *= inst_.v3d->shading.studiolight_intensity;
}
return sun_threshold;
}
void World::sync()
{
bool has_update = false;
@ -100,11 +116,8 @@ void World::sync()
else if (has_volume_absorption_) {
bl_world = default_world_get();
}
else if (inst_.scene->world != nullptr) {
bl_world = inst_.scene->world;
}
else {
bl_world = default_world_get();
bl_world = scene_world_get();
}
bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?

View File

@ -47,6 +47,12 @@ class DefaultWorldNodeTree {
* \{ */
class World {
public:
/**
* Buffer containing the sun light for the world.
* Filled by #LightProbeModule and read by #LightModule. */
UniformBuffer<LightData> sunlight = {"sunlight"};
private:
Instance &inst_;
@ -88,12 +94,32 @@ class World {
return has_volume_scatter_;
}
float sun_threshold();
float sun_angle()
{
return scene_world_get()->sun_angle;
}
float sun_shadow_max_resolution()
{
return scene_world_get()->sun_shadow_maximum_resolution;
}
bool use_sun_shadow()
{
return scene_world_get()->flag & WO_USE_SUN_SHADOW;
}
private:
void sync_volume();
/* Returns a dummy black world for when a valid world isn't present or when we want to suppress
* any light coming from the world. */
::World *default_world_get();
/* Returns either the scene world or the default world if scene has no world. */
::World *scene_world_get();
};
/** \} */

View File

@ -21,6 +21,22 @@ void main()
/* Sun lights are packed at the end of the array. Perform early copy. */
if (is_sun_light(light.type)) {
/* First sun-light is reserved for world light. Perform copy from dedicated buffer. */
bool is_world_sun_light = light.color.r < 0.0;
if (is_world_sun_light) {
light.color = sunlight_buf.color;
light.object_to_world = sunlight_buf.object_to_world;
/* NOTE: Use the radius from UI instead of auto sun size for now. */
// light.power = sunlight_buf.power;
#if USE_LIGHT_UNION
// light.sun.radius = sunlight_buf.sun.radius;
// light.sun.shadow_angle = sunlight_buf.sun.shadow_angle;
#else
// light.do_not_access_directly.radius_squared =
// sunlight_buf.do_not_access_directly.radius_squared;
// light.do_not_access_directly._pad1 = sunlight_buf.do_not_access_directly._pad1;
#endif
}
/* NOTE: We know the index because sun lights are packed at the start of the input buffer. */
out_light_buf[light_cull_buf.local_lights_len + l_idx] = light;
return;

View File

@ -0,0 +1,273 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Setup tilemap positionning for each shadow casting light.
* Dispatched one thread per light.
*/
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
int shadow_directional_coverage_get(int level)
{
return 1 << level;
}
void orthographic_sync(int tilemap_id,
Transform light_tx,
int2 origin_offset,
int clipmap_level,
eShadowProjectionType projection_type)
{
if (all(equal(tilemaps_buf[tilemap_id].grid_shift, int2(0)))) {
/* Only replace shift if it is not already dirty. */
tilemaps_buf[tilemap_id].grid_shift = tilemaps_buf[tilemap_id].grid_offset - origin_offset;
}
tilemaps_buf[tilemap_id].grid_offset = origin_offset;
mat3x3 object_to_world_transposed = mat3x3(tilemaps_buf[tilemap_id].viewmat);
if (!all(equal(object_to_world_transposed[0], light_tx.x.xyz)) ||
!all(equal(object_to_world_transposed[1], light_tx.y.xyz)) ||
!all(equal(object_to_world_transposed[2], light_tx.z.xyz)))
{
tilemaps_buf[tilemap_id].grid_shift = int2(SHADOW_TILEMAP_RES);
}
float level_size = shadow_directional_coverage_get(clipmap_level);
float half_size = level_size / 2.0;
float tile_size = level_size / float(SHADOW_TILEMAP_RES);
vec2 center_offset = vec2(origin_offset) * tile_size;
/* object_mat is a rotation matrix. Reduce imprecision by taking the transpose which is also the
* inverse in this particular case. */
tilemaps_buf[tilemap_id].viewmat[0] = vec4(light_tx.x.xyz, 0.0);
tilemaps_buf[tilemap_id].viewmat[1] = vec4(light_tx.y.xyz, 0.0);
tilemaps_buf[tilemap_id].viewmat[2] = vec4(light_tx.z.xyz, 0.0);
tilemaps_buf[tilemap_id].viewmat[3] = vec4(0.0, 0.0, 0.0, 1.0);
tilemaps_buf[tilemap_id].projection_type = projection_type;
tilemaps_buf[tilemap_id].half_size = half_size;
tilemaps_buf[tilemap_id].center_offset = center_offset;
tilemaps_buf[tilemap_id].winmat = projection_orthographic(
-half_size + center_offset.x,
half_size + center_offset.x,
-half_size + center_offset.y,
half_size + center_offset.y,
/* Near/far is computed on GPU using casters bounds. */
-1.0,
1.0);
}
void cascade_sync(inout LightData light)
{
int level_min = light_sun_data_get(light).clipmap_lod_min;
int level_max = light_sun_data_get(light).clipmap_lod_max;
int level_range = level_max - level_min;
int level_len = level_range + 1;
vec3 ws_camera_position = uniform_buf.camera.viewinv[3].xyz;
vec3 ws_camera_forward = uniform_buf.camera.viewinv[2].xyz;
float camera_clip_near = uniform_buf.camera.clip_near;
float camera_clip_far = uniform_buf.camera.clip_far;
/* All tile-maps use the first level size. */
float level_size = shadow_directional_coverage_get(level_min);
float half_size = level_size / 2.0;
float tile_size = level_size / float(SHADOW_TILEMAP_RES);
/* Ideally we should only take the intersection with the scene bounds. */
vec3 ws_far_point = ws_camera_position - ws_camera_forward * camera_clip_far;
vec3 ws_near_point = ws_camera_position - ws_camera_forward * camera_clip_near;
vec3 ls_far_point = transform_direction_transposed(light.object_to_world, ws_far_point);
vec3 ls_near_point = transform_direction_transposed(light.object_to_world, ws_near_point);
float2 local_view_direction = normalize(ls_far_point.xy - ls_near_point.xy);
float2 farthest_tilemap_center = local_view_direction * half_size * level_range;
/* Offset for smooth level transitions. */
light.object_to_world.x.w = ls_near_point.x;
light.object_to_world.y.w = ls_near_point.y;
light.object_to_world.z.w = ls_near_point.z;
/* Offset in tiles from the scene origin to the center of the first tile-maps. */
int2 origin_offset = int2(round(ls_near_point.xy / tile_size));
/* Offset in tiles between the first and the last tile-maps. */
int2 offset_vector = int2(round(farthest_tilemap_center / tile_size));
int2 base_offset_pos = (offset_vector * (1 << 16)) / max(level_range, 1);
/* \note cascade_level_range starts the range at the unique LOD to apply to all tile-maps. */
for (int i = 0; i < level_len; i++) {
/* Equal spacing between cascades layers since we want uniform shadow density. */
int2 level_offset = origin_offset + shadow_cascade_grid_offset(base_offset_pos, i);
orthographic_sync(light.tilemap_index + i,
light.object_to_world,
level_offset,
level_min,
SHADOW_PROJECTION_CASCADE);
}
vec2 clipmap_origin = vec2(origin_offset) * tile_size;
#if USE_LIGHT_UNION
/* Used as origin for the clipmap_base_offset trick. */
light.sun.clipmap_origin = clipmap_origin;
/* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */
light.sun.clipmap_base_offset_pos = base_offset_pos;
light.sun.clipmap_base_offset_neg = ivec2(0);
#else
/* Used as origin for the clipmap_base_offset trick. */
light.do_not_access_directly._pad3 = clipmap_origin;
/* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */
light.do_not_access_directly._pad0_reserved = intBitsToFloat(base_offset_pos.x);
light.do_not_access_directly._pad1_reserved = intBitsToFloat(base_offset_pos.y);
light.do_not_access_directly._pad7 = intBitsToFloat(0);
light.do_not_access_directly.shadow_projection_shift = intBitsToFloat(0);
#endif
}
void clipmap_sync(inout LightData light)
{
vec3 ws_camera_position = uniform_buf.camera.viewinv[3].xyz;
vec3 ls_camera_position = transform_direction_transposed(light.object_to_world,
ws_camera_position);
int level_min = light_sun_data_get(light).clipmap_lod_min;
int level_max = light_sun_data_get(light).clipmap_lod_max;
int level_len = level_max - level_min + 1;
vec2 clipmap_origin;
for (int lod = 0; lod < level_len; lod++) {
int level = level_min + lod;
/* Compute full offset from world origin to the smallest clipmap tile centered around the
* camera position. The offset is computed in smallest tile unit. */
float tile_size = float(1 << level) / float(SHADOW_TILEMAP_RES);
int2 level_offset = int2(round(ls_camera_position.xy / tile_size));
orthographic_sync(light.tilemap_index + lod,
light.object_to_world,
level_offset,
level,
SHADOW_PROJECTION_CLIPMAP);
clipmap_origin = vec2(level_offset) * tile_size;
}
int2 pos_offset = int2(0);
int2 neg_offset = int2(0);
for (int lod = 0; lod < level_len - 1; lod++) {
/* Since offset can only differ by one tile from the higher level, we can compress that as a
* single integer where one bit contains offset between 2 levels. Then a single bit shift in
* the shader gives the number of tile to offset in the given tile-map space. However we need
* also the sign of the offset for each level offset. To this end, we split the negative
* offsets to a separate int. */
int2 lvl_offset_next = tilemaps_buf[light.tilemap_index + lod + 1].grid_offset;
int2 lvl_offset = tilemaps_buf[light.tilemap_index + lod].grid_offset;
int2 lvl_delta = lvl_offset - (lvl_offset_next << 1);
pos_offset |= max(lvl_delta, int2(0)) << lod;
neg_offset |= max(-lvl_delta, int2(0)) << lod;
}
/* Used for selecting the clipmap level. */
light.object_to_world.x.w = ls_camera_position.x;
light.object_to_world.y.w = ls_camera_position.y;
light.object_to_world.z.w = ls_camera_position.z;
#if USE_LIGHT_UNION
/* Used as origin for the clipmap_base_offset trick. */
light.sun.clipmap_origin = clipmap_origin;
/* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */
light.sun.clipmap_base_offset_pos = pos_offset;
light.sun.clipmap_base_offset_neg = neg_offset;
#else
/* Used as origin for the clipmap_base_offset trick. */
light.do_not_access_directly._pad3 = clipmap_origin;
/* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */
light.do_not_access_directly._pad0_reserved = intBitsToFloat(pos_offset.x);
light.do_not_access_directly._pad1_reserved = intBitsToFloat(pos_offset.y);
light.do_not_access_directly._pad7 = intBitsToFloat(neg_offset.x);
light.do_not_access_directly.shadow_projection_shift = intBitsToFloat(neg_offset.y);
#endif
}
#if 0
void cubeface_sync(int tilemap_id, vec3 jitter_offset)
{
/* Update corners. */
viewmat = shadow_face_mat[cubeface] * from_location<float4x4>(float3(0.0, 0.0, -shift)) *
invert(object_mat);
/* Update corners. */
corners[0] += jitter_offset;
corners[1] += jitter_offset;
corners[2] += jitter_offset;
corners[3] += jitter_offset;
/* Set dirty. */
grid_shift = int2(SHADOW_TILEMAP_RES);
}
#endif
void main()
{
uint l_idx = gl_GlobalInvocationID.x;
if (l_idx >= light_cull_buf.items_count) {
return;
}
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
if (is_sun_light(light.type)) {
/* Distant lights. */
#if 0 /* Jittered shadows. */
vec3 position_on_light = random_position_on_light(light);
vec3 light_direction = normalize(position_on_light);
float3x3 object_to_world_transposed = transpose(from_up_axis(light_direction));
light.object_to_world.x.xyz = object_to_world_transposed[0];
light.object_to_world.y.xyz = object_to_world_transposed[1];
light.object_to_world.z.xyz = object_to_world_transposed[2];
#endif
if (light.type == LIGHT_SUN_ORTHO) {
cascade_sync(light);
}
else {
clipmap_sync(light);
}
}
#if 0 /* Jittered shadows. */
else {
/* Local lights. */
# if 0 /* Jittered shadows. */
vec3 position_on_light = random_position_on_light(light);
light_buf[l_idx].shadow_position = position_on_light;
int tilemap_count = 0;
if (is_area_light(light.type)) {
tilemap_count = 5;
}
else if (is_spot_light(light.type)) {
tilemap_count = (spot_angle > M_PI * 0.25) ? 5 : 1;
}
else {
tilemap_count = 6;
}
for (int i = 0; i < tilemap_count; i++) {
cubeface_sync(light.tilemap_id + i, position_on_light);
}
# endif
}
#endif
light_buf[l_idx] = light;
}

View File

@ -12,24 +12,6 @@
shared vec4 local_sh_coefs[gl_WorkGroupSize.x][4];
void spherical_harmonic_lds_store(uint index, SphericalHarmonicL1 sh)
{
local_sh_coefs[index][0] = sh.L0.M0;
local_sh_coefs[index][1] = sh.L1.Mn1;
local_sh_coefs[index][2] = sh.L1.M0;
local_sh_coefs[index][3] = sh.L1.Mp1;
}
SphericalHarmonicL1 spherical_harmonic_lds_load(uint index)
{
SphericalHarmonicL1 sh;
sh.L0.M0 = local_sh_coefs[index][0];
sh.L1.Mn1 = local_sh_coefs[index][1];
sh.L1.M0 = local_sh_coefs[index][2];
sh.L1.Mp1 = local_sh_coefs[index][3];
return sh;
}
void main()
{
SphericalHarmonicL1 sh;

View File

@ -84,6 +84,10 @@ float octahedral_texel_solid_angle(ivec2 local_texel,
void main()
{
uint work_group_index = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;
const uint local_index = gl_LocalInvocationIndex;
const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;
SphereProbeUvArea world_coord = reinterpret_as_atlas_coord(world_coord_packed);
SphereProbeUvArea sample_coord = reinterpret_as_atlas_coord(probe_coord_packed);
SphereProbePixelArea write_coord = reinterpret_as_write_coord(write_coord_packed);
@ -107,8 +111,10 @@ void main()
radiance.rgb = mix(world_radiance.rgb, radiance.rgb, opacity);
}
float clamp_world = uniform_buf.clamp.world;
radiance = colorspace_brightness_clamp_max(radiance, clamp_world);
float sun_threshold = uniform_buf.clamp.sun_threshold;
vec3 radiance_clamped = colorspace_brightness_clamp_max(radiance, sun_threshold);
vec3 radiance_sun = radiance - radiance_clamped;
radiance = radiance_clamped;
if (!any(greaterThanEqual(local_texel, ivec2(write_coord.extent)))) {
float clamp_indirect = uniform_buf.clamp.surface_indirect;
@ -118,12 +124,42 @@ void main()
imageStore(atlas_img, texel, vec4(out_radiance, 1.0));
}
float sample_weight = octahedral_texel_solid_angle(local_texel, write_coord, sample_coord);
if (extract_sun) {
/* Parallel sum. Result is stored inside local_radiance[0]. */
local_radiance[local_index] = radiance_sun.xyzz * sample_weight;
for (uint stride = group_size / 2; stride > 0; stride /= 2) {
barrier();
if (local_index < stride) {
local_radiance[local_index] += local_radiance[local_index + stride];
}
}
barrier();
if (gl_LocalInvocationIndex == 0u) {
out_sun[work_group_index].radiance = local_radiance[0].xyz;
}
barrier();
/* Reusing local_radiance for directions. */
local_radiance[local_index] = vec4(normalize(direction), 1.0) * sample_weight *
length(radiance_sun.xyz);
for (uint stride = group_size / 2; stride > 0; stride /= 2) {
barrier();
if (local_index < stride) {
local_radiance[local_index] += local_radiance[local_index + stride];
}
}
barrier();
if (gl_LocalInvocationIndex == 0u) {
out_sun[work_group_index].direction = local_radiance[0];
}
barrier();
}
if (extract_sh) {
float sample_weight = octahedral_texel_solid_angle(local_texel, write_coord, sample_coord);
const uint local_index = gl_LocalInvocationIndex;
const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;
/* Parallel sum. Result is stored inside local_radiance[0]. */
local_radiance[local_index] = radiance.xyzz * sample_weight;
uint stride = group_size / 2;
@ -158,7 +194,6 @@ void main()
* instead of adding to it? */
spherical_harmonics_encode_signal_sample(L, local_radiance[0], sh);
/* Outputs one SH for each thread-group. */
uint work_group_index = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;
out_sh[work_group_index].L0_M0 = sh.L0.M0;
out_sh[work_group_index].L1_Mn1 = sh.L1.Mn1;
out_sh[work_group_index].L1_M0 = sh.L1.M0;

View File

@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/* Sum all Suns extracting during remapping to octahedral map.
* Dispatch only one thread-group that sums. */
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_mapping_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
shared vec3 local_radiance[gl_WorkGroupSize.x];
shared vec4 local_direction[gl_WorkGroupSize.x];
void main()
{
SphereProbeSunLight sun;
sun.radiance = vec3(0.0);
sun.direction = vec4(0.0);
/* First sum onto the local memory. */
uint valid_data_len = probe_remap_dispatch_size.x * probe_remap_dispatch_size.y;
const uint iter_count = uint(SPHERE_PROBE_MAX_HARMONIC) / gl_WorkGroupSize.x;
for (uint i = 0; i < iter_count; i++) {
uint index = gl_WorkGroupSize.x * i + gl_LocalInvocationIndex;
if (index >= valid_data_len) {
break;
}
sun.radiance += in_sun[index].radiance;
sun.direction += in_sun[index].direction;
}
/* Then sum across invocations. */
const uint local_index = gl_LocalInvocationIndex;
local_radiance[local_index] = sun.radiance;
local_direction[local_index] = sun.direction;
/* Parallel sum. */
const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;
for (uint stride = group_size / 2; stride > 0; stride /= 2) {
barrier();
if (local_index < stride) {
local_radiance[local_index] += local_radiance[local_index + stride];
local_direction[local_index] += local_direction[local_index + stride];
}
}
barrier();
if (gl_LocalInvocationIndex == 0u) {
sunlight_buf.color = local_radiance[0];
/* Normalize the sum to get the mean direction. The length of the vector gives us the size of
* the sun light. */
float len;
vec3 direction = normalize_and_get_length(local_direction[0].xyz / local_direction[0].w, len);
mat3x3 tx = transpose(from_up_axis(direction));
/* Convert to transform. */
sunlight_buf.object_to_world.x = vec4(tx[0], 0.0);
sunlight_buf.object_to_world.y = vec4(tx[1], 0.0);
sunlight_buf.object_to_world.z = vec4(tx[2], 0.0);
/* Auto sun angle. */
float sun_angle_cos = 2.0 * len - 1.0;
float sun_angle = acos(sun_angle_cos);
/* Compute tangent from cosine. */
float sun_angle_tan = sqrt(-1.0 + 1.0 / square(sun_angle_cos));
/* Clamp value to avoid float imprecision artifacts. */
float sun_radius = clamp(sun_angle_tan, 0.001, 20.0);
/* Convert irradiance to radiance. */
float shape_power = M_1_PI * (1.0 + 1.0 / square(sun_radius));
float point_power = 1.0;
sunlight_buf.power[LIGHT_DIFFUSE] = shape_power;
sunlight_buf.power[LIGHT_SPECULAR] = shape_power;
sunlight_buf.power[LIGHT_TRANSMISSION] = shape_power;
sunlight_buf.power[LIGHT_VOLUME] = point_power;
#if USE_LIGHT_UNION
sunlight_buf.sun.radius = sun_radius;
sunlight_buf.sun.shadow_angle = sun_angle;
#else
sunlight_buf.do_not_access_directly.radius_squared = sun_radius;
sunlight_buf.do_not_access_directly._pad1 = sun_angle;
#endif
}
}

View File

@ -30,6 +30,7 @@ GPU_SHADER_CREATE_INFO(eevee_light_culling_select)
.storage_buf(2, Qualifier::WRITE, "LightData", "out_light_buf[]")
.storage_buf(3, Qualifier::WRITE, "float", "out_zdist_buf[]")
.storage_buf(4, Qualifier::WRITE, "uint", "out_key_buf[]")
.uniform_buf(0, "LightData", "sunlight_buf")
.compute_source("eevee_light_culling_select_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_light_culling_sort)
@ -61,6 +62,15 @@ GPU_SHADER_CREATE_INFO(eevee_light_culling_tile)
.storage_buf(2, Qualifier::WRITE, "uint", "out_light_tile_buf[]")
.compute_source("eevee_light_culling_tile_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_light_shadow_setup)
.do_static_compilation(true)
.additional_info("eevee_shared", "draw_view", "draw_view_culling", "eevee_global_ubo")
.local_group_size(CULLING_SELECT_GROUP_SIZE)
.storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf")
.storage_buf(1, Qualifier::READ_WRITE, "LightData", "light_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.compute_source("eevee_light_shadow_setup_comp.glsl");
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -20,12 +20,14 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.local_group_size(SPHERE_PROBE_REMAP_GROUP_SIZE, SPHERE_PROBE_REMAP_GROUP_SIZE)
.specialization_constant(Type::BOOL, "extract_sh", true)
.specialization_constant(Type::BOOL, "extract_sun", true)
.push_constant(Type::IVEC4, "probe_coord_packed")
.push_constant(Type::IVEC4, "write_coord_packed")
.push_constant(Type::IVEC4, "world_coord_packed")
.sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx")
.sampler(1, ImageType::FLOAT_2D_ARRAY, "atlas_tx")
.storage_buf(0, Qualifier::WRITE, "SphereProbeHarmonic", "out_sh[SPHERE_PROBE_MAX_HARMONIC]")
.storage_buf(1, Qualifier::WRITE, "SphereProbeSunLight", "out_sun[SPHERE_PROBE_MAX_HARMONIC]")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "atlas_img")
.compute_source("eevee_reflection_probe_remap_comp.glsl")
.additional_info("eevee_shared", "eevee_global_ubo")
@ -40,6 +42,15 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_irradiance)
.do_static_compilation(true)
.compute_source("eevee_reflection_probe_irradiance_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_sunlight)
.local_group_size(SPHERE_PROBE_SH_GROUP_SIZE)
.push_constant(Type::IVEC3, "probe_remap_dispatch_size")
.storage_buf(0, Qualifier::READ, "SphereProbeSunLight", "in_sun[SPHERE_PROBE_MAX_HARMONIC]")
.storage_buf(1, Qualifier::WRITE, "LightData", "sunlight_buf")
.additional_info("eevee_shared")
.do_static_compilation(true)
.compute_source("eevee_reflection_probe_sunlight_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select)
.local_group_size(SPHERE_PROBE_SELECT_GROUP_SIZE)
.storage_buf(0,

View File

@ -230,7 +230,6 @@
.motion_blur_max = 32, \
.motion_blur_steps = 1, \
\
.clamp_world = 10.0f, \
.clamp_surface_indirect = 10.0f, \
\
.shadow_cube_size = 512, \

View File

@ -1891,12 +1891,10 @@ typedef struct SceneEEVEE {
int shadow_step_count;
float shadow_resolution_scale;
float clamp_world;
float clamp_surface_direct;
float clamp_surface_indirect;
float clamp_volume_direct;
float clamp_volume_indirect;
char _pad[4];
int ray_tracing_method;

View File

@ -16,6 +16,8 @@
#define _DNA_DEFAULT_World \
{ \
.flag = WO_USE_SUN_SHADOW, \
\
.horr = 0.05f, \
.horg = 0.05f, \
.horb = 0.05f, \
@ -28,6 +30,9 @@
.mistdist = 25.0f, \
\
.probe_resolution = LIGHT_PROBE_RESOLUTION_1024, \
.sun_threshold = 10.0f, \
.sun_angle = DEG2RADF(0.526f), \
.sun_shadow_maximum_resolution = 0.001f, \
}
/** \} */

View File

@ -67,6 +67,13 @@ typedef struct World {
* Resolution of the world probe when baked to a texture. Contains `eLightProbeResolution`.
*/
int probe_resolution;
/** Threshold for sun extraction. */
float sun_threshold;
/** Angle for sun extraction. */
float sun_angle;
/** Maximum resolution for extracted sun shadow. */
float sun_shadow_maximum_resolution;
char _pad4[4];
/** Old animation system, deprecated for 2.5. */
struct Ipo *ipo DNA_DEPRECATED;
@ -120,6 +127,10 @@ enum {
* converted manually. (Ref: #119734).
*/
WO_USE_EEVEE_FINITE_VOLUME = 1 << 3,
/**
* Use shadowing from the extracted sun light.
*/
WO_USE_SUN_SHADOW = 1 << 4,
};
/** #World::probe_resolution. */

View File

@ -1942,11 +1942,6 @@ static void rna_SceneEEVEE_gi_cubemap_resolution_update(Main * /*main*/,
FOREACH_SCENE_OBJECT_END;
}
static void rna_SceneEEVEE_clamp_world_update(Main * /*main*/, Scene *scene, PointerRNA * /*ptr*/)
{
DEG_id_tag_update(&scene->world->id, ID_RECALC_SHADING);
}
static void rna_SceneEEVEE_clamp_surface_indirect_update(Main * /*main*/,
Scene *scene,
PointerRNA * /*ptr*/)
@ -8073,17 +8068,6 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
/* Clamping */
prop = RNA_def_property(srna, "clamp_world", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(
prop,
"Clamp World",
"If non-zero, the maximum value for world contribution to the scene lighting. "
"Higher values will be scaled down to avoid too "
"much light bleeding at the cost of accuracy");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_SceneEEVEE_clamp_world_update");
prop = RNA_def_property(srna, "clamp_surface_direct", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Clamp Surface Direct",

View File

@ -11,6 +11,8 @@
#include "RNA_define.hh"
#include "BLI_math_rotation.h"
#include "rna_internal.hh"
#include "DNA_lightprobe_types.h"
@ -282,6 +284,37 @@ void RNA_def_world(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Resolution", "Resolution when baked to a texture");
RNA_def_property_update(prop, 0, "rna_World_draw_update");
prop = RNA_def_property(srna, "sun_threshold", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Sun Threshold",
"If non-zero, the maximum value for world contribution that will be "
"recorded inside the world light probe. The excess contribution is "
"converted to a sun light. This reduces the light bleeding caused by "
"very bright light sources");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_update(prop, 0, "rna_World_draw_update");
prop = RNA_def_property(srna, "sun_angle", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_range(prop, DEG2RADF(0.0f), DEG2RADF(180.0f));
RNA_def_property_ui_text(
prop, "Sun Angle", "Angular diameter of the Sun as seen from the Earth");
RNA_def_property_update(prop, 0, "rna_World_draw_update");
prop = RNA_def_property(srna, "use_sun_shadow", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", WO_USE_SUN_SHADOW);
RNA_def_property_ui_text(prop, "Use Shadow", "Enable sun shadow casting");
RNA_def_property_update(prop, 0, "rna_World_draw_update");
prop = RNA_def_property(srna, "sun_shadow_maximum_resolution", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0001f, 0.020f, 0.05f, 4);
RNA_def_property_ui_text(prop,
"Shadows Resolution Limit",
"Maximum size of a shadow map pixel. Higher values use less memory at "
"the cost of shadow quality");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_World_draw_update");
rna_def_lighting(brna);
rna_def_world_mist(brna);
}