EEVEE-Next: Make light clamping consistent

This adds clamping at the light combine stage for both direct
and indirect light.

This allows for clamping direct and indirect light separately.
While direct light clamping might not be very desirable in
EEVEE, it might be wanted to reduce the flickering from distant
shiny bumpy surfaces, or for artistic reason.

This happens after applying the BSDF throughput just like cycles.
This is done in order to minimize the performance impact and
allows to split the clamp for direct light and indirect light.

The indirect light clamp value is still used in the ray-tracing
pipeline to clamp the ray intensity. But this differs from cycles
as we clamp the ray without the BSDF throughput here. Sphere probe
have the same issues. Some more energy loss is expected compared
to the direct light clamp.

Note that we still clamp the indirect light after applying BSDF
in case the BSDF is scaling the energy up above the threshold.

This also corrects the clamping for volume that now clamps after
applying the scattering term.

Also adds clamping to volume indirect lighting.

Since we use light probe volumes for both surface and volume
indirect lighting, we need to clamp them at sampling time.

Pull Request: https://projects.blender.org/blender/blender/pulls/120866
This commit is contained in:
Clément Foucault
2024-04-22 21:19:00 +02:00
committed by Clément Foucault
parent f1d4859e2a
commit d44ee7bf11
34 changed files with 530 additions and 267 deletions

View File

@@ -721,11 +721,9 @@ class RENDER_PT_eevee_next_clamping_surface(RenderButtonsPanel, Panel):
scene = context.scene
props = scene.eevee
# TODO(fclem): Add clamp properties
options = props.ray_tracing_options
layout.prop(options, "sample_clamp", text="Indirect Light")
# layout.prop(props, "clamp_surface_direct", text="Direct Light")
# layout.prop(props, "clamp_surface_indirect", text="Indirect Light")
col = layout.column(align=True)
col.prop(props, "clamp_surface_direct", text="Direct Light")
col.prop(props, "clamp_surface_indirect", text="Indirect Light")
class RENDER_PT_eevee_next_clamping_volume(RenderButtonsPanel, Panel):
@@ -743,9 +741,10 @@ class RENDER_PT_eevee_next_clamping_volume(RenderButtonsPanel, Panel):
layout.use_property_decorate = False
scene = context.scene
props = scene.eevee
layout.prop(props, "volumetric_light_clamp", text="Direct Light")
# layout.prop(props, "clamp_volumetric_direct", text="Direct Light")
# layout.prop(props, "clamp_volumetric_indirect", text="Indirect Light")
col = layout.column(align=True)
col.prop(props, "clamp_volume_direct", text="Direct Light")
col.prop(props, "clamp_volume_indirect", text="Indirect Light")
class RENDER_PT_eevee_next_sampling_shadows(RenderButtonsPanel, Panel):

View File

@@ -2829,7 +2829,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
scene->eevee.ray_tracing_options.screen_trace_quality = 0.25f;
scene->eevee.ray_tracing_options.screen_trace_thickness = 0.2f;
scene->eevee.ray_tracing_options.trace_max_roughness = 0.5f;
scene->eevee.ray_tracing_options.sample_clamp = 10.0f;
scene->eevee.ray_tracing_options.resolution_scale = 2;
}
}
@@ -3180,6 +3179,10 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
/* Keep legacy EEVEE old behavior. */
scene->eevee.flag |= SCE_EEVEE_VOLUME_CUSTOM_RANGE;
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.clamp_surface_indirect = 10.0f;
}
}
/**

View File

@@ -154,7 +154,7 @@ class Instance {
depth_of_field(*this),
cryptomatte(*this),
hiz_buffer(*this, uniform_data.data.hiz),
sampling(*this),
sampling(*this, uniform_data.data.clamp),
camera(*this, uniform_data.data.camera),
film(*this, uniform_data.data.film),
render_buffers(*this, uniform_data.data.render_pass),

View File

@@ -248,6 +248,7 @@ void VolumeProbeModule::set_view(View & /*view*/)
if (do_update_world_) {
grid_upload_ps_.init();
grid_upload_ps_.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_WORLD));
grid_upload_ps_.bind_resources(inst_.uniform_data);
grid_upload_ps_.bind_ssbo("harmonic_buf", &inst_.sphere_probes.spherical_harmonics_buf());
grid_upload_ps_.bind_ubo("grids_infos_buf", &grids_infos_buf_);
grid_upload_ps_.bind_ssbo("bricks_infos_buf", &bricks_infos_buf_);
@@ -364,6 +365,7 @@ void VolumeProbeModule::set_view(View & /*view*/)
grid_upload_ps_.init();
grid_upload_ps_.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_LOAD));
grid_upload_ps_.bind_resources(inst_.uniform_data);
grid_upload_ps_.push_constant("validity_threshold", grid->validity_threshold);
grid_upload_ps_.push_constant("dilation_threshold", grid->dilation_threshold);
grid_upload_ps_.push_constant("dilation_radius", grid->dilation_radius);

View File

@@ -86,14 +86,6 @@ void LightProbeModule::sync_volume(const Object *ob, ObjectHandle &handle)
grid.object_to_world = ob->object_to_world();
grid.cache = ob->lightprobe_cache;
/* Needed for display. */
// LightProbeGridCacheFrame *cache = grid.cache ? grid.cache->grid_static_cache : nullptr;
// if (cache != nullptr) {
// int3 grid_size = int3(cache->size);
// /* Offset sample placement so that border texels are on the edges of the volume. */
// float3 grid_scale = float3(grid_size) / float3(grid_size + 1);
// grid.object_to_world *= math::from_scale<float4x4>(grid_scale);
// }
grid.world_to_object = float4x4(
math::normalize(math::transpose(float3x3(grid.object_to_world))));

View File

@@ -528,9 +528,24 @@ void DeferredLayer::begin_sync()
this->gbuffer_pass_sync(inst_);
}
void DeferredLayer::end_sync()
void DeferredLayer::end_sync(bool is_first_pass, bool is_last_pass)
{
use_combined_lightprobe_eval = inst_.pipelines.data.use_combined_lightprobe_eval;
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
const bool has_transmit_closure = (closure_bits_ & (CLOSURE_REFRACTION | CLOSURE_TRANSLUCENT));
const bool has_reflect_closure = (closure_bits_ & (CLOSURE_REFLECTION | CLOSURE_DIFFUSE));
use_raytracing_ = (has_transmit_closure || has_reflect_closure) &&
(sce_eevee.flag & SCE_EEVEE_SSR_ENABLED) != 0;
use_clamp_direct_ = sce_eevee.clamp_surface_direct != 0.0f;
use_clamp_indirect_ = sce_eevee.clamp_surface_indirect != 0.0f;
/* The first pass will never have any surfaces behind it. Nothing is refracted except the
* environment. So in this case, disable tracing and fallback to probe. */
use_screen_transmission_ = use_raytracing_ && has_transmit_closure && !is_first_pass;
use_screen_reflection_ = use_raytracing_ && has_reflect_closure;
use_split_radiance_ = use_raytracing_ || (use_clamp_direct_ || use_clamp_indirect_);
use_feedback_output_ = use_raytracing_ && (!is_last_pass || use_screen_reflection_);
{
RenderBuffersInfoData &rbuf_data = inst_.render_buffers.data;
@@ -569,6 +584,7 @@ void DeferredLayer::end_sync()
pass.init();
{
const bool use_split_indirect = !use_raytracing_ && use_split_radiance_;
PassSimple::Sub &sub = pass.sub("Eval.Light");
/* Use depth test to reject background pixels which have not been stencil cleared. */
/* WORKAROUND: Avoid rasterizer discard by enabling stencil write, but the shaders actually
@@ -585,7 +601,8 @@ void DeferredLayer::end_sync()
* OpenGL and Vulkan implementation which aren't fully supporting the specialize
* constant. */
sub.specialize_constant(sh, "render_pass_shadow_enabled", rbuf_data.shadow_id != -1);
sub.specialize_constant(sh, "use_lightprobe_eval", use_combined_lightprobe_eval);
sub.specialize_constant(sh, "use_split_indirect", use_split_indirect);
sub.specialize_constant(sh, "use_lightprobe_eval", !use_raytracing_);
const ShadowSceneData &shadow_scene = inst_.shadows.get_data();
sub.specialize_constant(sh, "shadow_ray_count", &shadow_scene.ray_count);
sub.specialize_constant(sh, "shadow_ray_step_count", &shadow_scene.step_count);
@@ -593,6 +610,9 @@ void DeferredLayer::end_sync()
sub.bind_image("direct_radiance_1_img", &direct_radiance_txs_[0]);
sub.bind_image("direct_radiance_2_img", &direct_radiance_txs_[1]);
sub.bind_image("direct_radiance_3_img", &direct_radiance_txs_[2]);
sub.bind_image("indirect_radiance_1_img", &indirect_result_.closures[0]);
sub.bind_image("indirect_radiance_2_img", &indirect_result_.closures[1]);
sub.bind_image("indirect_radiance_3_img", &indirect_result_.closures[2]);
sub.bind_resources(inst_.uniform_data);
sub.bind_resources(inst_.gbuffer);
sub.bind_resources(inst_.lights);
@@ -621,8 +641,10 @@ void DeferredLayer::end_sync()
"render_pass_specular_light_enabled",
(rbuf_data.specular_light_id != -1) ||
(rbuf_data.specular_color_id != -1));
pass.specialize_constant(sh, "use_split_radiance", use_split_radiance_);
pass.specialize_constant(
sh, "use_radiance_feedback", use_feedback_output_ && use_clamp_direct_);
pass.specialize_constant(sh, "render_pass_normal_enabled", rbuf_data.normal_id != -1);
pass.specialize_constant(sh, "use_combined_lightprobe_eval", use_combined_lightprobe_eval);
pass.shader_set(sh);
/* Use stencil test to reject pixels not written by this layer. */
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL | DRW_STATE_STENCIL_NEQUAL);
@@ -631,11 +653,12 @@ void DeferredLayer::end_sync()
pass.bind_texture("direct_radiance_1_tx", &direct_radiance_txs_[0]);
pass.bind_texture("direct_radiance_2_tx", &direct_radiance_txs_[1]);
pass.bind_texture("direct_radiance_3_tx", &direct_radiance_txs_[2]);
pass.bind_texture("indirect_radiance_1_tx", &indirect_radiance_txs_[0]);
pass.bind_texture("indirect_radiance_2_tx", &indirect_radiance_txs_[1]);
pass.bind_texture("indirect_radiance_3_tx", &indirect_radiance_txs_[2]);
pass.bind_texture("indirect_radiance_1_tx", &indirect_result_.closures[0]);
pass.bind_texture("indirect_radiance_2_tx", &indirect_result_.closures[1]);
pass.bind_texture("indirect_radiance_3_tx", &indirect_result_.closures[2]);
pass.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx);
pass.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx);
pass.bind_image("radiance_feedback_img", &radiance_feedback_tx_);
pass.bind_resources(inst_.gbuffer);
pass.bind_resources(inst_.uniform_data);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
@@ -675,50 +698,28 @@ PassMain::Sub *DeferredLayer::material_add(::Material *blender_mat, GPUMaterial
return &pass->sub(GPU_material_get_name(gpumat));
}
void DeferredLayer::render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer,
bool is_first_pass)
GPUTexture *DeferredLayer::render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer,
GPUTexture *radiance_behind_tx)
{
if (closure_count_ == 0) {
return;
return nullptr;
}
RenderBuffers &rb = inst_.render_buffers;
/* The first pass will never have any surfaces behind it. Nothing is refracted except the
* environment. So in this case, disable tracing and fallback to probe. */
bool do_screen_space_refraction = !is_first_pass &&
(closure_bits_ & (CLOSURE_REFRACTION | CLOSURE_TRANSLUCENT));
bool do_screen_space_reflection = (closure_bits_ & (CLOSURE_REFLECTION | CLOSURE_DIFFUSE));
constexpr eGPUTextureUsage usage_read = GPU_TEXTURE_USAGE_SHADER_READ;
constexpr eGPUTextureUsage usage_write = GPU_TEXTURE_USAGE_SHADER_WRITE;
constexpr eGPUTextureUsage usage_rw = usage_read | usage_write;
if (do_screen_space_reflection) {
if (radiance_feedback_tx_.ensure_2d(rb.color_format, extent, usage_read)) {
radiance_feedback_tx_.clear(float4(0.0));
radiance_feedback_persmat_ = render_view.persmat();
}
}
else {
/* Dummy texture. Will not be used. */
radiance_feedback_tx_.ensure_2d(rb.color_format, int2(1), GPU_TEXTURE_USAGE_SHADER_READ);
}
if (do_screen_space_refraction) {
if (use_screen_transmission_) {
/* Update for refraction. */
inst_.hiz_buffer.update();
radiance_behind_tx_.ensure_2d(rb.color_format, extent, usage_read);
GPU_texture_copy(radiance_behind_tx_, rb.combined_tx);
}
else {
/* Dummy texture. Will not be used. */
radiance_behind_tx_.ensure_2d(rb.color_format, int2(1), GPU_TEXTURE_USAGE_SHADER_READ);
}
GPU_framebuffer_bind(prepass_fb);
@@ -739,53 +740,48 @@ void DeferredLayer::render(View &main_view,
(closure_count_ > i) ? extent : int2(1), DEFERRED_RADIANCE_FORMAT, usage_rw);
}
RayTraceResult indirect_result;
if (use_combined_lightprobe_eval) {
float4 data(0.0f);
/* Subsurface writes (black) to that texture. */
dummy_black_tx.ensure_2d(RAYTRACE_RADIANCE_FORMAT, int2(1), usage_rw, data);
for (int i = 0; i < 3; i++) {
indirect_radiance_txs_[i] = dummy_black_tx;
}
if (use_raytracing_) {
indirect_result_ = inst_.raytracing.render(
rt_buffer, radiance_behind_tx, closure_bits_, main_view, render_view);
}
else if (use_split_radiance_) {
indirect_result_ = inst_.raytracing.alloc_only(rt_buffer);
}
else {
indirect_result = inst_.raytracing.render(rt_buffer,
radiance_behind_tx_,
radiance_feedback_tx_,
radiance_feedback_persmat_,
closure_bits_,
main_view,
render_view,
do_screen_space_refraction);
for (int i = 0; i < 3; i++) {
indirect_radiance_txs_[i] = indirect_result.closures[i].get();
}
indirect_result_ = inst_.raytracing.alloc_dummy(rt_buffer);
}
GPU_framebuffer_bind(combined_fb);
inst_.manager->submit(eval_light_ps_, render_view);
inst_.subsurface.render(
direct_radiance_txs_[0], indirect_radiance_txs_[0], closure_bits_, render_view);
direct_radiance_txs_[0], indirect_result_.closures[0], closure_bits_, render_view);
radiance_feedback_tx_ = rt_buffer.feedback_ensure(!use_feedback_output_, extent);
if (use_feedback_output_ && use_clamp_direct_) {
/* We need to do a copy before the combine pass (otherwise we have a dependency issue) to save
* the emission and the previous layer's radiance. */
GPU_texture_copy(radiance_feedback_tx_, rb.combined_tx);
}
GPU_framebuffer_bind(combined_fb);
inst_.manager->submit(combine_ps_);
if (!use_combined_lightprobe_eval) {
indirect_result.release();
if (use_feedback_output_ && !use_clamp_direct_) {
/* We skip writting the radiance during the combine pass. Do a simple fast copy. */
GPU_texture_copy(radiance_feedback_tx_, rb.combined_tx);
}
indirect_result_.release();
for (int i = 0; i < ARRAY_SIZE(direct_radiance_txs_); i++) {
direct_radiance_txs_[i].release();
}
if (do_screen_space_reflection) {
GPU_texture_copy(radiance_feedback_tx_, rb.combined_tx);
radiance_feedback_persmat_ = render_view.persmat();
}
inst_.pipelines.deferred.debug_draw(render_view, combined_fb);
return use_feedback_output_ ? radiance_feedback_tx_ : nullptr;
}
/** \} */
@@ -800,8 +796,7 @@ void DeferredPipeline::begin_sync()
{
Instance &inst = opaque_layer_.inst_;
const bool use_raytracing = (inst.scene->eevee.flag & SCE_EEVEE_SSR_ENABLED);
inst.pipelines.data.use_combined_lightprobe_eval = !use_raytracing;
const bool use_raytracing = (inst.scene->eevee.flag & SCE_EEVEE_SSR_ENABLED) != 0;
use_combined_lightprobe_eval = !use_raytracing;
opaque_layer_.begin_sync();
@@ -810,8 +805,8 @@ void DeferredPipeline::begin_sync()
void DeferredPipeline::end_sync()
{
opaque_layer_.end_sync();
refraction_layer_.end_sync();
opaque_layer_.end_sync(true, refraction_layer_.is_empty());
refraction_layer_.end_sync(opaque_layer_.is_empty(), true);
debug_pass_sync();
}
@@ -892,26 +887,28 @@ void DeferredPipeline::render(View &main_view,
RayTraceBuffer &rt_buffer_opaque_layer,
RayTraceBuffer &rt_buffer_refract_layer)
{
GPUTexture *feedback_tx = nullptr;
DRW_stats_group_start("Deferred.Opaque");
opaque_layer_.render(main_view,
render_view,
prepass_fb,
combined_fb,
gbuffer_fb,
extent,
rt_buffer_opaque_layer,
true);
feedback_tx = opaque_layer_.render(main_view,
render_view,
prepass_fb,
combined_fb,
gbuffer_fb,
extent,
rt_buffer_opaque_layer,
feedback_tx);
DRW_stats_group_end();
DRW_stats_group_start("Deferred.Refract");
refraction_layer_.render(main_view,
render_view,
prepass_fb,
combined_fb,
gbuffer_fb,
extent,
rt_buffer_refract_layer,
false);
feedback_tx = refraction_layer_.render(main_view,
render_view,
prepass_fb,
combined_fb,
gbuffer_fb,
extent,
rt_buffer_refract_layer,
feedback_tx);
DRW_stats_group_end();
}

View File

@@ -18,6 +18,7 @@
#include "draw_shader_shared.hh"
#include "eevee_lut.hh"
#include "eevee_raytrace.hh"
#include "eevee_subsurface.hh"
namespace blender::eevee {
@@ -248,9 +249,13 @@ class DeferredLayer : DeferredLayerBase {
*/
TextureFromPool direct_radiance_txs_[3] = {
{"direct_radiance_1"}, {"direct_radiance_2"}, {"direct_radiance_3"}};
Texture dummy_black_tx = {"dummy_black_tx"};
/* NOTE: Only used when `use_split_radiance` is true. */
TextureFromPool indirect_radiance_txs_[3] = {
{"indirect_radiance_1"}, {"indirect_radiance_2"}, {"indirect_radiance_3"}};
/* Used when there is no indirect radiance buffer. */
Texture dummy_black = {"dummy_black"};
/* Reference to ray-tracing results. */
GPUTexture *indirect_radiance_txs_[3] = {nullptr};
GPUTexture *radiance_feedback_tx_ = nullptr;
/**
* Tile texture containing several bool per tile indicating presence of feature.
@@ -258,34 +263,52 @@ class DeferredLayer : DeferredLayerBase {
*/
Texture tile_mask_tx_ = {"tile_mask_tx_"};
/* TODO(fclem): This should be a TextureFromPool. */
Texture radiance_behind_tx_ = {"radiance_behind_tx"};
/* TODO(fclem): This shouldn't be part of the pipeline but of the view. */
Texture radiance_feedback_tx_ = {"radiance_feedback_tx"};
float4x4 radiance_feedback_persmat_;
RayTraceResult indirect_result_;
bool use_combined_lightprobe_eval = true;
bool use_split_radiance_ = true;
/* Output radiance from the combine shader instead of copy. Allow passing unclamped result. */
bool use_feedback_output_ = false;
bool use_raytracing_ = false;
bool use_screen_transmission_ = false;
bool use_screen_reflection_ = false;
bool use_clamp_direct_ = false;
bool use_clamp_indirect_ = false;
public:
DeferredLayer(Instance &inst) : inst_(inst){};
DeferredLayer(Instance &inst) : inst_(inst)
{
float4 data(0.0f);
dummy_black.ensure_2d(RAYTRACE_RADIANCE_FORMAT,
int2(1),
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE,
data);
}
void begin_sync();
void end_sync();
void end_sync(bool is_first_pass, bool is_last_pass);
PassMain::Sub *prepass_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion);
PassMain::Sub *material_add(::Material *blender_mat, GPUMaterial *gpumat);
void render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer,
bool is_first_pass);
bool is_empty() const
{
return closure_count_ != 0;
}
/* Returns the radiance buffer to feed the next layer. */
GPUTexture *render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer,
GPUTexture *radiance_behind_tx);
};
class DeferredPipeline {
friend DeferredLayer;
private:
/* Gbuffer filling passes. We could have an arbitrary number of them but for now we just have
* a hardcoded number of them. */

View File

@@ -30,6 +30,10 @@ void RayTraceModule::init()
ray_tracing_options_ = sce_eevee.ray_tracing_options;
tracing_method_ = RaytraceEEVEE_Method(sce_eevee.ray_tracing_method);
float4 data(0.0f);
radiance_dummy_black_tx_.ensure_2d(
RAYTRACE_RADIANCE_FORMAT, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, data);
}
void RayTraceModule::sync()
@@ -323,16 +327,19 @@ void RayTraceModule::debug_draw(View & /*view*/, GPUFrameBuffer * /*view_fb*/) {
RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
GPUTexture *screen_radiance_back_tx,
GPUTexture *screen_radiance_front_tx,
const float4x4 &screen_radiance_persmat,
eClosureBits active_closures,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view,
bool do_refraction_tracing)
View &render_view)
{
using namespace blender::math;
screen_radiance_front_tx_ = rt_buffer.radiance_feedback_tx.is_valid() ?
rt_buffer.radiance_feedback_tx :
radiance_dummy_black_tx_;
screen_radiance_back_tx_ = screen_radiance_back_tx ? screen_radiance_back_tx :
screen_radiance_front_tx_;
RaytraceEEVEE options = ray_tracing_options_;
bool use_horizon_scan = options.trace_max_roughness < 1.0f;
@@ -386,10 +393,9 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
data_.roughness_mask_bias = data_.roughness_mask_scale * roughness_mask_start;
/* Data for the radiance setup. */
data_.brightness_clamp = (options.sample_clamp > 0.0) ? options.sample_clamp : 1e20;
data_.resolution_scale = resolution_scale;
data_.resolution_bias = int2(inst_.sampling.rng_2d_get(SAMPLING_RAYTRACE_V) * resolution_scale);
data_.radiance_persmat = screen_radiance_persmat;
data_.radiance_persmat = render_view.persmat();
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
@@ -410,26 +416,16 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
inst_.manager->submit(tile_classify_ps_);
}
data_.trace_refraction = do_refraction_tracing;
data_.trace_refraction = screen_radiance_back_tx != nullptr;
for (int i = 0; i < 3; i++) {
result.closures[i] = trace(i,
(closure_count > i),
options,
rt_buffer,
screen_radiance_back_tx,
screen_radiance_front_tx,
screen_radiance_persmat,
main_view,
render_view);
result.closures[i] = trace(i, (closure_count > i), options, rt_buffer, main_view, render_view);
}
if (has_active_closure) {
if (use_horizon_scan) {
DRW_stats_group_start("Horizon Scan");
screen_radiance_front_tx_ = screen_radiance_front_tx;
downsampled_in_radiance_tx_.acquire(tracing_res_horizon, RAYTRACE_RADIANCE_FORMAT, usage_rw);
downsampled_in_normal_tx_.acquire(tracing_res_horizon, GPU_RGB10_A2, usage_rw);
@@ -440,7 +436,7 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
horizon_radiance_denoised_tx_[i].acquire(tracing_res_horizon, GPU_RGBA8, usage_rw);
}
for (int i : IndexRange(3)) {
horizon_scan_output_tx_[i] = result.closures[i].get();
horizon_scan_output_tx_[i] = result.closures[i];
}
horizon_tracing_dispatch_buf_.clear_to_zero();
@@ -465,6 +461,8 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
DRW_stats_group_end();
rt_buffer.history_persmat = render_view.persmat();
return result;
}
@@ -473,9 +471,6 @@ RayTraceResultTexture RayTraceModule::trace(
bool active_layer,
RaytraceEEVEE options,
RayTraceBuffer &rt_buffer,
GPUTexture *screen_radiance_back_tx,
GPUTexture *screen_radiance_front_tx,
const float4x4 &screen_radiance_persmat,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view)
@@ -512,7 +507,6 @@ RayTraceResultTexture RayTraceModule::trace(
data_.thickness = options.screen_trace_thickness;
data_.quality = 1.0f - 0.95f * options.screen_trace_quality;
data_.brightness_clamp = (options.sample_clamp > 0.0) ? options.sample_clamp : 1e20;
float roughness_mask_start = options.trace_max_roughness;
float roughness_mask_fade = 0.2f;
@@ -522,7 +516,7 @@ RayTraceResultTexture RayTraceModule::trace(
data_.resolution_scale = resolution_scale;
data_.resolution_bias = int2(inst_.sampling.rng_2d_get(SAMPLING_RAYTRACE_V) * resolution_scale);
data_.history_persmat = denoise_buf->history_persmat;
data_.radiance_persmat = screen_radiance_persmat;
data_.radiance_persmat = render_view.persmat();
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
data_.skip_denoise = !use_spatial_denoise;
@@ -540,9 +534,6 @@ RayTraceResultTexture RayTraceModule::trace(
ray_time_tx_.acquire(tracing_res, RAYTRACE_RAYTIME_FORMAT);
ray_radiance_tx_.acquire(tracing_res, RAYTRACE_RADIANCE_FORMAT);
screen_radiance_front_tx_ = screen_radiance_front_tx;
screen_radiance_back_tx_ = screen_radiance_back_tx;
inst_.manager->submit(generate_ps_, render_view);
if (tracing_method_ == RAYTRACE_EEVEE_METHOD_SCREEN) {
if (inst_.planar_probes.enabled()) {
@@ -638,6 +629,32 @@ RayTraceResultTexture RayTraceModule::trace(
return result;
}
RayTraceResult RayTraceModule::alloc_only(RayTraceBuffer &rt_buffer)
{
const int2 extent = inst_.film.render_extent_get();
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
RayTraceResult result;
for (int i = 0; i < 3; i++) {
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[i];
denoise_buf->denoised_bilateral_tx.acquire(extent, RAYTRACE_RADIANCE_FORMAT, usage_rw);
result.closures[i] = {denoise_buf->denoised_bilateral_tx};
}
return result;
}
RayTraceResult RayTraceModule::alloc_dummy(RayTraceBuffer &rt_buffer)
{
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
RayTraceResult result;
for (int i = 0; i < 3; i++) {
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[i];
denoise_buf->denoised_bilateral_tx.acquire(int2(1), RAYTRACE_RADIANCE_FORMAT, usage_rw);
result.closures[i] = {denoise_buf->denoised_bilateral_tx};
}
return result;
}
/** \} */
} // namespace blender::eevee

View File

@@ -28,7 +28,7 @@ class Instance;
* \{ */
/**
* Contain persistent buffer that need to be stored per view, per layer.
* Contain persistent buffer that need to be stored per view, per deferred layer.
*/
struct RayTraceBuffer {
/** Set of buffers that need to be allocated for each ray type. */
@@ -54,6 +54,26 @@ struct RayTraceBuffer {
* One for each closure. Not to be mistaken with deferred layer type.
*/
DenoiseBuffer closures[3];
/**
* Radiance feedback of the deferred layer for next sample's reflection or next layer's
* transmission.
*/
Texture radiance_feedback_tx = {"radiance_feedback_tx"};
/**
* Perspective matrix for which the radiance feedback buffer was recorded.
* Can be different from de-noise buffer's history matrix.
*/
float4x4 history_persmat;
GPUTexture *feedback_ensure(bool is_dummy, int2 extent)
{
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
if (radiance_feedback_tx.ensure_2d(GPU_RGBA16F, is_dummy ? int2(1) : extent, usage_rw)) {
radiance_feedback_tx.clear(float4(0.0f));
}
return radiance_feedback_tx;
}
};
/**
@@ -65,18 +85,26 @@ class RayTraceResultTexture {
private:
/** Result is in a temporary texture that needs to be released. */
TextureFromPool *result_ = nullptr;
/** Value of `result_->tx_` that can be referenced in advance. */
GPUTexture *tx_ = nullptr;
/** History buffer to swap the temporary texture that does not need to be released. */
Texture *history_ = nullptr;
public:
RayTraceResultTexture() = default;
RayTraceResultTexture(TextureFromPool &result) : result_(result.ptr()){};
RayTraceResultTexture(TextureFromPool &result) : result_(result.ptr()), tx_(result){};
RayTraceResultTexture(TextureFromPool &result, Texture &history)
: result_(result.ptr()), history_(history.ptr()){};
: result_(result.ptr()), tx_(result), history_(history.ptr()){};
GPUTexture *get()
operator GPUTexture *() const
{
return *result_;
BLI_assert(tx_ != nullptr);
return tx_;
}
GPUTexture **operator&()
{
return &tx_;
}
void release()
@@ -186,6 +214,7 @@ class RayTraceModule {
GPUTexture *screen_radiance_front_tx_ = nullptr;
GPUTexture *screen_radiance_back_tx_ = nullptr;
Texture radiance_dummy_black_tx_ = {"radiance_dummy_black_tx"};
/** Dummy texture when the tracing is disabled. */
TextureFromPool dummy_result_tx_ = {"dummy_result_tx"};
/** Pointer to `inst_.render_buffers.depth_tx` updated before submission. */
@@ -223,13 +252,20 @@ class RayTraceModule {
*/
RayTraceResult render(RayTraceBuffer &rt_buffer,
GPUTexture *screen_radiance_back_tx,
GPUTexture *screen_radiance_front_tx,
const float4x4 &screen_radiance_persmat,
eClosureBits active_closures,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view,
bool do_refraction_tracing);
View &render_view);
/**
* Only allocate the RayTraceResult results buffers to be used by other passes.
*/
RayTraceResult alloc_only(RayTraceBuffer &rt_buffer);
/**
* Only allocate the RayTraceResult results buffers as dummy texture to ensure correct bindings.
*/
RayTraceResult alloc_dummy(RayTraceBuffer &rt_buffer);
void debug_pass_sync();
void debug_draw(View &view, GPUFrameBuffer *view_fb);
@@ -239,9 +275,6 @@ class RayTraceModule {
bool active_layer,
RaytraceEEVEE options,
RayTraceBuffer &rt_buffer,
GPUTexture *screen_radiance_back_tx,
GPUTexture *screen_radiance_front_tx,
const float4x4 &screen_radiance_persmat,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view);

View File

@@ -34,9 +34,6 @@ void SphereProbeModule::begin_sync()
LightProbeModule &light_probes = instance_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
{
const RaytraceEEVEE &options = instance_.scene->eevee.ray_tracing_options;
float probe_brightness_clamp = (options.sample_clamp > 0.0) ? options.sample_clamp : 1e20;
GPUShader *shader = instance_.shaders.static_shader_get(SPHERE_PROBE_REMAP);
PassSimple &pass = remap_ps_;
@@ -50,7 +47,7 @@ void SphereProbeModule::begin_sync()
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));
pass.push_constant("probe_brightness_clamp", probe_brightness_clamp);
pass.bind_resources(instance_.uniform_data);
pass.dispatch(&dispatch_probe_pack_);
}
{
@@ -85,6 +82,7 @@ void SphereProbeModule::begin_sync()
pass.bind_ssbo("reflection_probe_buf", &data_buf_);
instance_.volume_probes.bind_resources(pass);
instance_.sampling.bind_resources(pass);
pass.bind_resources(instance_.uniform_data);
pass.dispatch(&dispatch_probe_select_);
pass.barrier(GPU_BARRIER_UNIFORM);
}

View File

@@ -60,6 +60,13 @@ void Sampling::init(const Scene *scene)
/* Only multiply after to have full the full DoF web pattern for each time steps. */
sample_count_ *= motion_blur_steps_;
auto clamp_value_load = [](float value) { return (value > 0.0) ? value : 1e20; };
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);
clamp_data_.volume_indirect = clamp_value_load(scene->eevee.clamp_volume_indirect);
}
void Sampling::init(const Object &probe_object)

View File

@@ -65,8 +65,10 @@ class Sampling {
SamplingDataBuf data_;
ClampData &clamp_data_;
public:
Sampling(Instance &inst) : inst_(inst){};
Sampling(Instance &inst, ClampData &clamp_data) : inst_(inst), clamp_data_(clamp_data){};
~Sampling(){};
void init(const Scene *scene);

View File

@@ -1622,6 +1622,20 @@ BLI_STATIC_ASSERT_ALIGN(HiZData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Light Clamping
* \{ */
struct ClampData {
float surface_direct;
float surface_indirect;
float volume_direct;
float volume_indirect;
};
BLI_STATIC_ASSERT_ALIGN(ClampData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ray-Tracing
* \{ */
@@ -1685,8 +1699,6 @@ struct RayTraceData {
int horizon_resolution_scale;
/** Determine how fast the sample steps are getting bigger. */
float quality;
/** Maximum brightness during lighting evaluation. */
float brightness_clamp;
/** Maximum roughness for which we will trace a ray. */
float roughness_mask_scale;
float roughness_mask_bias;
@@ -1697,6 +1709,7 @@ struct RayTraceData {
/** Closure being ray-traced. */
int closure_index;
int _pad0;
int _pad1;
};
BLI_STATIC_ASSERT_ALIGN(RayTraceData, 16)
@@ -1821,7 +1834,7 @@ BLI_STATIC_ASSERT_ALIGN(PlanarProbeDisplayData, 16)
struct PipelineInfoData {
float alpha_hash_scale;
bool32_t is_probe_reflection;
bool32_t use_combined_lightprobe_eval;
float _pad1;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(PipelineInfoData, 16)
@@ -1836,6 +1849,7 @@ BLI_STATIC_ASSERT_ALIGN(PipelineInfoData, 16)
struct UniformData {
AOData ao;
CameraData camera;
ClampData clamp;
FilmData film;
HiZData hiz;
RayTraceData raytrace;

View File

@@ -78,11 +78,9 @@ World::~World()
void World::sync()
{
::World *bl_world = inst_.use_studio_light() ? nullptr : inst_.scene->world;
bool has_update = false;
if (bl_world) {
if (inst_.scene->world != nullptr) {
/* Detect world update before overriding it. */
WorldHandle wo_handle = inst_.sync.sync_world();
has_update = wo_handle.recalc != 0;
@@ -91,8 +89,9 @@ void World::sync()
/* Sync volume first since its result can override the surface world. */
sync_volume();
::World *bl_world;
if (inst_.use_studio_light()) {
has_update = lookdev_world_.sync(LookdevParameters(inst_.v3d));
has_update |= lookdev_world_.sync(LookdevParameters(inst_.v3d));
bl_world = lookdev_world_.world_get();
}
else if ((inst_.view_layer->layflag & SCE_LAY_SKY) == 0) {

View File

@@ -48,40 +48,43 @@ void main()
GBufferReader gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel);
vec3 diffuse_color = vec3(0.0);
vec3 diffuse_light = vec3(0.0);
vec3 diffuse_direct = vec3(0.0);
vec3 diffuse_indirect = vec3(0.0);
vec3 specular_color = vec3(0.0);
vec3 specular_light = vec3(0.0);
vec3 specular_direct = vec3(0.0);
vec3 specular_indirect = vec3(0.0);
vec3 out_direct = vec3(0.0);
vec3 out_indirect = vec3(0.0);
vec3 average_normal = vec3(0.0);
out_combined = vec4(0.0, 0.0, 0.0, 0.0);
for (int i = 0; i < GBUFFER_LAYER_MAX && i < gbuf.closure_count; i++) {
ClosureUndetermined cl = gbuffer_closure_get(gbuf, i);
if (cl.type == CLOSURE_NONE_ID) {
continue;
}
int layer_index = gbuffer_closure_get_bin_index(gbuf, i);
vec3 closure_light = load_radiance_direct(texel, layer_index);
vec3 closure_direct_light = load_radiance_direct(texel, layer_index);
vec3 closure_indirect_light = vec3(0.0);
if (!use_combined_lightprobe_eval) {
closure_light += load_radiance_indirect(texel, layer_index);
if (use_split_radiance) {
closure_indirect_light = load_radiance_indirect(texel, layer_index);
}
average_normal += cl.N * reduce_add(cl.color);
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSDF_DIFFUSE_ID:
diffuse_color += cl.color;
diffuse_light += closure_light;
average_normal += cl.N * reduce_add(cl.color);
diffuse_direct += closure_direct_light;
diffuse_indirect += closure_indirect_light;
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
specular_color += cl.color;
specular_light += closure_light;
average_normal += cl.N * reduce_add(cl.color);
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */
specular_direct += closure_direct_light;
specular_indirect += closure_indirect_light;
break;
}
@@ -93,16 +96,36 @@ void main()
cl.color *= cl.color;
}
closure_light *= cl.color;
out_combined.rgb += closure_light;
out_direct += closure_direct_light * cl.color;
out_indirect += closure_indirect_light * cl.color;
}
if (use_radiance_feedback) {
/* Output unmodified radiance for indirect lighting. */
vec3 out_radiance = imageLoad(radiance_feedback_img, texel).rgb;
out_radiance += out_direct + out_indirect;
imageStore(radiance_feedback_img, texel, vec4(out_radiance, 0.0));
}
/* Light clamping. */
float clamp_direct = uniform_buf.clamp.surface_direct;
float clamp_indirect = uniform_buf.clamp.surface_indirect;
out_direct = colorspace_brightness_clamp_max(out_direct, clamp_direct);
out_indirect = colorspace_brightness_clamp_max(out_indirect, clamp_indirect);
/* TODO(fcleù): Shouldn't we clamp these relative the main clamp? */
diffuse_direct = colorspace_brightness_clamp_max(diffuse_direct, clamp_direct);
diffuse_indirect = colorspace_brightness_clamp_max(diffuse_indirect, clamp_indirect);
specular_direct = colorspace_brightness_clamp_max(specular_direct, clamp_direct);
specular_indirect = colorspace_brightness_clamp_max(specular_indirect, clamp_indirect);
/* Light passes. */
if (render_pass_diffuse_light_enabled) {
vec3 diffuse_light = diffuse_direct + diffuse_indirect;
output_renderpass_color(uniform_buf.render_pass.diffuse_color_id, vec4(diffuse_color, 1.0));
output_renderpass_color(uniform_buf.render_pass.diffuse_light_id, vec4(diffuse_light, 1.0));
}
if (render_pass_specular_light_enabled) {
vec3 specular_light = specular_direct + specular_indirect;
output_renderpass_color(uniform_buf.render_pass.specular_color_id, vec4(specular_color, 1.0));
output_renderpass_color(uniform_buf.render_pass.specular_light_id, vec4(specular_light, 1.0));
}
@@ -113,9 +136,7 @@ void main()
output_renderpass_color(uniform_buf.render_pass.normal_id, vec4(average_normal, 1.0));
}
if (any(isnan(out_combined))) {
out_combined = vec4(1.0, 0.0, 1.0, 0.0);
}
out_combined = vec4(out_direct + out_indirect, 0.0);
out_combined = any(isnan(out_combined)) ? vec4(1.0, 0.0, 1.0, 0.0) : out_combined;
out_combined = colorspace_safe_color(out_combined);
}

View File

@@ -91,9 +91,29 @@ void main()
if (use_lightprobe_eval) {
LightProbeSample samp = lightprobe_load(P, Ng, V);
float clamp_indirect = uniform_buf.clamp.surface_indirect;
samp.volume_irradiance = spherical_harmonics_clamp(samp.volume_irradiance, clamp_indirect);
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT && i < gbuf.closure_count; i++) {
ClosureUndetermined cl = gbuffer_closure_get(gbuf, i);
lightprobe_eval(samp, cl, P, V, gbuf.thickness, stack.cl[i].light_shadowed);
vec3 indirect_light = lightprobe_eval(samp, cl, P, V, gbuf.thickness);
if (use_split_indirect) {
int layer_index = gbuffer_closure_get_bin_index(gbuf, i);
/* TODO(fclem): Layered texture. */
if (layer_index == 0) {
imageStore(indirect_radiance_1_img, texel, vec4(indirect_light, 1.0));
}
else if (layer_index == 1) {
imageStore(indirect_radiance_2_img, texel, vec4(indirect_light, 1.0));
}
else if (layer_index == 2) {
imageStore(indirect_radiance_3_img, texel, vec4(indirect_light, 1.0));
}
}
else {
stack.cl[i].light_shadowed += indirect_light;
}
}
}

View File

@@ -12,6 +12,7 @@
#pragma BLENDER_REQUIRE(eevee_subsurface_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
#if CLOSURE_BIN_COUNT != LIGHT_CLOSURE_EVAL_COUNT
# error Closure data count and eval count must match
@@ -64,21 +65,37 @@ void forward_lighting_eval(float thickness, out vec3 radiance, out vec3 transmit
LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V);
float clamp_indirect_sh = uniform_buf.clamp.surface_indirect;
samp.volume_irradiance = spherical_harmonics_clamp(samp.volume_irradiance, clamp_indirect_sh);
/* Combine all radiance. */
radiance = g_emission;
vec3 radiance_direct = vec3(0.0);
vec3 radiance_indirect = vec3(0.0);
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) {
ClosureUndetermined cl = g_closure_get(i);
lightprobe_eval(samp, cl, g_data.P, V, thickness, stack.cl[i].light_shadowed);
if (cl.weight > 1e-5) {
vec3 direct_light = stack.cl[i].light_shadowed;
vec3 indirect_light = lightprobe_eval(samp, cl, g_data.P, V, thickness);
if ((cl.type == CLOSURE_BSDF_TRANSLUCENT_ID ||
cl.type == CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID) &&
(thickness > 0.0))
{
/* We model two transmission event, so the surface color need to be applied twice. */
stack.cl[i].light_shadowed *= cl.color;
cl.color *= cl.color;
}
radiance += stack.cl[i].light_shadowed * cl.color * cl.weight;
cl.color *= cl.weight;
radiance_direct += direct_light * cl.color;
radiance_indirect += indirect_light * cl.color;
}
}
/* Light clamping. */
float clamp_direct = uniform_buf.clamp.surface_direct;
float clamp_indirect = uniform_buf.clamp.surface_indirect;
radiance_direct = colorspace_brightness_clamp_max(radiance_direct, clamp_direct);
radiance_indirect = colorspace_brightness_clamp_max(radiance_indirect, clamp_indirect);
radiance = radiance_direct + radiance_indirect + g_emission;
transmittance = g_transmittance;
}

View File

@@ -122,6 +122,9 @@ void main()
LightProbeSample samp = lightprobe_load(P, Ng, V);
float clamp_indirect = uniform_buf.clamp.surface_indirect;
samp.volume_irradiance = spherical_harmonics_clamp(samp.volume_irradiance, clamp_indirect);
for (int i = 0; i < GBUFFER_LAYER_MAX && i < gbuf.closure_count; i++) {
ClosureUndetermined cl = gbuffer_closure_get(gbuf, i);

View File

@@ -53,7 +53,7 @@ void main()
vec3 ssP_prev = drw_ndc_to_screen(project_point(uniform_buf.raytrace.radiance_persmat, P));
vec4 radiance = texture(in_radiance_tx, ssP_prev.xy);
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.raytrace.brightness_clamp);
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.clamp.surface_indirect);
imageStore(out_radiance_img, texel, radiance);
}

View File

@@ -68,7 +68,7 @@ vec3 lightprobe_spherical_sample_normalized_with_parallax(
{
SphereProbeData probe = reflection_probe_buf[probe_index];
ReflectionProbeLowFreqLight shading_sh = reflection_probes_extract_low_freq(P_sh);
vec3 normalization_factor = reflection_probes_normalization_eval(
float normalization_factor = reflection_probes_normalization_eval(
L, shading_sh, probe.low_freq_light);
L = lightprobe_sphere_parallax(probe, P, L);
return normalization_factor * reflection_probes_sample(L, lod, probe.atlas_coord).rgb;
@@ -139,32 +139,23 @@ vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V
return mix(radiance_cube, radiance_sh, fac);
}
void lightprobe_eval(LightProbeSample samp,
ClosureUndetermined cl,
vec3 P,
vec3 V,
float thickness,
inout vec3 radiance)
vec3 lightprobe_eval(
LightProbeSample samp, ClosureUndetermined cl, vec3 P, vec3 V, float thickness)
{
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
radiance += lightprobe_eval(samp, to_closure_translucent(cl), P, V, thickness);
break;
return lightprobe_eval(samp, to_closure_translucent(cl), P, V, thickness);
case CLOSURE_BSSRDF_BURLEY_ID:
/* TODO: Support translucency in ray tracing first. Otherwise we have a discrepancy. */
return vec3(0.0);
case CLOSURE_BSDF_DIFFUSE_ID:
radiance += lightprobe_eval(samp, to_closure_diffuse(cl), P, V);
break;
return lightprobe_eval(samp, to_closure_diffuse(cl), P, V);
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
radiance += lightprobe_eval(samp, to_closure_reflection(cl), P, V);
break;
return lightprobe_eval(samp, to_closure_reflection(cl), P, V);
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
radiance += lightprobe_eval(samp, to_closure_refraction(cl), P, V, thickness);
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */
break;
return lightprobe_eval(samp, to_closure_refraction(cl), P, V, thickness);
}
return vec3(0.0);
}
#endif /* SPHERE_PROBE */

View File

@@ -13,6 +13,7 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
void main()
{
@@ -60,12 +61,16 @@ void main()
* direction over many rays. */
vec3 Ng = ray.direction;
LightProbeSample samp = lightprobe_load(ray.origin, Ng, V);
/* Clamp SH to have parity with forward evaluation. */
float clamp_indirect = uniform_buf.clamp.surface_indirect;
samp.volume_irradiance = spherical_harmonics_clamp(samp.volume_irradiance, clamp_indirect);
vec3 radiance = lightprobe_eval_direction(
samp, ray.origin, ray.direction, safe_rcp(ray_pdf_inv));
/* Set point really far for correct reprojection of background. */
float hit_time = 1000.0;
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.raytrace.brightness_clamp);
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.clamp.surface_indirect);
imageStore(ray_time_img, texel, vec4(hit_time));
imageStore(ray_radiance_img, texel, vec4(radiance, 0.0));

View File

@@ -87,11 +87,6 @@ void main()
if (hit.valid) {
/* Evaluate radiance at hit-point. */
radiance = textureLod(planar_radiance_tx, vec3(hit.ss_hit_P.xy, planar_id), 0.0).rgb;
/* Transmit twice if thickness is set and ray is longer than thickness. */
// if (thickness > 0.0 && length(ray_data.xyz) > thickness) {
// ray_radiance.rgb *= color;
// }
}
else {
/* Using ray direction as geometric normal to bias the sampling position.
@@ -105,7 +100,7 @@ void main()
hit.time = 10000.0;
}
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.raytrace.brightness_clamp);
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.clamp.surface_indirect);
imageStore(ray_time_img, texel, vec4(hit.time));
imageStore(ray_radiance_img, texel, vec4(radiance, 0.0));

View File

@@ -13,6 +13,7 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
void main()
{
@@ -131,12 +132,16 @@ void main()
vec3 Ng = ray.direction;
/* Fallback to nearest light-probe. */
LightProbeSample samp = lightprobe_load(ray.origin, Ng, V);
/* Clamp SH to have parity with forward evaluation. */
float clamp_indirect = uniform_buf.clamp.surface_indirect;
samp.volume_irradiance = spherical_harmonics_clamp(samp.volume_irradiance, clamp_indirect);
radiance = lightprobe_eval_direction(samp, ray.origin, ray.direction, safe_rcp(ray_pdf_inv));
/* Set point really far for correct reprojection of background. */
hit.time = 10000.0;
}
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.raytrace.brightness_clamp);
radiance = colorspace_brightness_clamp_max(radiance, uniform_buf.clamp.surface_indirect);
imageStore(ray_time_img, texel, vec4(hit.time));
imageStore(ray_radiance_img, texel, vec4(radiance, 0.0));

View File

@@ -40,11 +40,11 @@ ReflectionProbeLowFreqLight reflection_probes_extract_low_freq(SphericalHarmonic
return result;
}
vec3 reflection_probes_normalization_eval(vec3 L,
ReflectionProbeLowFreqLight numerator,
ReflectionProbeLowFreqLight denominator)
float reflection_probes_normalization_eval(vec3 L,
ReflectionProbeLowFreqLight numerator,
ReflectionProbeLowFreqLight denominator)
{
/* TODO(fclem): Adjusting directionality is tricky.
* Needs to be revisited later on. For now only use the ambient term. */
return vec3(numerator.ambient * safe_rcp(denominator.ambient));
return (numerator.ambient * safe_rcp(denominator.ambient));
}

View File

@@ -89,11 +89,12 @@ void main()
radiance.rgb = mix(world_radiance.rgb, radiance.rgb, opacity);
}
radiance = colorspace_brightness_clamp_max(radiance, probe_brightness_clamp);
if (!any(greaterThanEqual(local_texel, ivec2(write_coord.extent)))) {
float clamp_indirect = uniform_buf.clamp.surface_indirect;
vec3 out_radiance = colorspace_brightness_clamp_max(radiance, clamp_indirect);
ivec3 texel = ivec3(local_texel + write_coord.offset, write_coord.layer);
imageStore(atlas_img, texel, vec4(radiance, 1.0));
imageStore(atlas_img, texel, vec4(out_radiance, 1.0));
}
if (extract_sh) {

View File

@@ -26,5 +26,8 @@ void main()
sh = lightprobe_irradiance_sample(probe_center);
}
float clamp_indirect_sh = uniform_buf.clamp.surface_indirect;
sh = spherical_harmonics_clamp(sh, clamp_indirect_sh);
reflection_probe_buf[idx].low_freq_light = reflection_probes_extract_low_freq(sh);
}

View File

@@ -707,3 +707,29 @@ SphericalHarmonicL1 spherical_harmonics_decompress(SphericalHarmonicL1 sh)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clamping
*
* Clamp the total power of the SH function.
* \{ */
SphericalHarmonicL1 spherical_harmonics_clamp(SphericalHarmonicL1 sh, float clamp_value)
{
/* Convert coefficients to per channel column. */
mat4x4 per_channel = transpose(mat4x4(sh.L0.M0, sh.L1.Mn1, sh.L1.M0, sh.L1.Mp1));
/* Maximum per channel. */
vec3 max_L1 = vec3(reduce_max(abs(per_channel[0].yzw)),
reduce_max(abs(per_channel[1].yzw)),
reduce_max(abs(per_channel[2].yzw)));
/* Find maximum of the sh function over all chanels. */
vec3 max_sh = abs(sh.L0.M0.rgb) * 0.282094792 + max_L1 * 0.488602512;
float fac = clamp_value * safe_rcp(reduce_max(max_sh));
if (fac > 1.0) {
return sh;
}
return spherical_harmonics_mul(sh, fac);
}
/** \} */

View File

@@ -21,8 +21,7 @@
#ifdef VOLUME_LIGHTING
vec3 volume_scatter_light_eval(
const bool is_directional, vec3 P, vec3 V, uint l_idx, float s_anisotropy)
vec3 volume_light_eval(const bool is_directional, vec3 P, vec3 V, uint l_idx, float s_anisotropy)
{
LightData light = light_buf[l_idx];
@@ -48,10 +47,24 @@ vec3 volume_scatter_light_eval(
return vec3(0);
}
vec3 Li = volume_light(light, is_directional, lv) * visibility *
volume_shadow(light, is_directional, P, lv, extinction_tx);
vec3 Li = volume_light(light, is_directional, lv) * visibility;
return colorspace_brightness_clamp_max(Li, uniform_buf.volumes.light_clamp);
if (light.tilemap_index != LIGHT_NO_SHADOW) {
Li *= volume_shadow(light, is_directional, P, lv, extinction_tx);
}
return Li;
}
vec3 volume_lightprobe_eval(vec3 P, vec3 V, float s_anisotropy)
{
SphericalHarmonicL1 phase_sh = volume_phase_function_as_sh_L1(V, s_anisotropy);
SphericalHarmonicL1 volume_radiance_sh = lightprobe_irradiance_sample(P);
float clamp_indirect = uniform_buf.clamp.volume_indirect;
volume_radiance_sh = spherical_harmonics_clamp(volume_radiance_sh, clamp_indirect);
return spherical_harmonics_dot(volume_radiance_sh, phase_sh).xyz;
}
#endif
@@ -81,25 +94,35 @@ void main()
float s_anisotropy = phase.x / max(1.0, phase.y);
#ifdef VOLUME_LIGHTING
SphericalHarmonicL1 phase_sh = volume_phase_function_as_sh_L1(V, s_anisotropy);
SphericalHarmonicL1 volume_radiance_sh = lightprobe_irradiance_sample(P);
vec3 direct_radiance = vec3(0.0);
vec3 light_scattering = spherical_harmonics_dot(volume_radiance_sh, phase_sh).xyz;
if (reduce_max(s_scattering) > 0.0) {
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
direct_radiance += volume_light_eval(true, P, V, l_idx, s_anisotropy);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
light_scattering += volume_scatter_light_eval(true, P, V, l_idx, s_anisotropy);
vec2 pixel = ((vec2(froxel.xy) + 0.5) * uniform_buf.volumes.inv_tex_size.xy) *
uniform_buf.volumes.main_view_extent;
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx)
{
direct_radiance += volume_light_eval(false, P, V, l_idx, s_anisotropy);
}
LIGHT_FOREACH_END
}
LIGHT_FOREACH_END
vec2 pixel = ((vec2(froxel.xy) + 0.5) * uniform_buf.volumes.inv_tex_size.xy) *
uniform_buf.volumes.main_view_extent;
vec3 indirect_radiance = volume_lightprobe_eval(P, V, s_anisotropy).xyz;
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
light_scattering += volume_scatter_light_eval(false, P, V, l_idx, s_anisotropy);
}
LIGHT_FOREACH_END
direct_radiance *= s_scattering;
indirect_radiance *= s_scattering;
scattering += light_scattering * s_scattering;
float clamp_direct = uniform_buf.clamp.volume_direct;
float clamp_indirect = uniform_buf.clamp.volume_indirect;
direct_radiance = colorspace_brightness_clamp_max(direct_radiance, clamp_direct);
indirect_radiance = colorspace_brightness_clamp_max(indirect_radiance, clamp_indirect);
scattering += direct_radiance + indirect_radiance;
#endif
if (uniform_buf.volumes.history_opacity > 0.0) {

View File

@@ -61,6 +61,11 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
.image_out(2, DEFERRED_RADIANCE_FORMAT, "direct_radiance_1_img")
.image_out(3, DEFERRED_RADIANCE_FORMAT, "direct_radiance_2_img")
.image_out(4, DEFERRED_RADIANCE_FORMAT, "direct_radiance_3_img")
/* Optimized out if use_split_indirect is false. */
.image_out(5, DEFERRED_RADIANCE_FORMAT, "indirect_radiance_1_img")
.image_out(6, DEFERRED_RADIANCE_FORMAT, "indirect_radiance_2_img")
.image_out(7, DEFERRED_RADIANCE_FORMAT, "indirect_radiance_3_img")
.specialization_constant(Type::BOOL, "use_split_indirect", false)
.specialization_constant(Type::BOOL, "use_lightprobe_eval", false)
.specialization_constant(Type::BOOL, "render_pass_shadow_enabled", true)
.define("SPECIALIZED_SHADOW_PARAMS")
@@ -103,6 +108,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_combine)
.sampler(5, ImageType::FLOAT_2D, "indirect_radiance_1_tx")
.sampler(6, ImageType::FLOAT_2D, "indirect_radiance_2_tx")
.sampler(7, ImageType::FLOAT_2D, "indirect_radiance_3_tx")
.image(5, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "radiance_feedback_img")
.fragment_out(0, Type::VEC4, "out_combined")
.additional_info("eevee_shared",
"eevee_gbuffer_data",
@@ -114,7 +120,8 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_combine)
.specialization_constant(Type::BOOL, "render_pass_diffuse_light_enabled", true)
.specialization_constant(Type::BOOL, "render_pass_specular_light_enabled", true)
.specialization_constant(Type::BOOL, "render_pass_normal_enabled", true)
.specialization_constant(Type::BOOL, "use_combined_lightprobe_eval", false)
.specialization_constant(Type::BOOL, "use_radiance_feedback", false)
.specialization_constant(Type::BOOL, "use_split_radiance", false)
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_deferred_capture_eval)

View File

@@ -174,7 +174,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_world)
IRRADIANCE_GRID_BRICK_SIZE,
IRRADIANCE_GRID_BRICK_SIZE)
.define("IRRADIANCE_GRID_UPLOAD")
.additional_info("eevee_shared")
.additional_info("eevee_shared", "eevee_global_ubo")
.push_constant(Type::INT, "grid_index")
.storage_buf(0, Qualifier::READ, "uint", "bricks_infos_buf[]")
.storage_buf(1, Qualifier::READ, "SphereProbeHarmonic", "harmonic_buf")
@@ -188,7 +188,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
IRRADIANCE_GRID_BRICK_SIZE,
IRRADIANCE_GRID_BRICK_SIZE)
.define("IRRADIANCE_GRID_UPLOAD")
.additional_info("eevee_shared")
.additional_info("eevee_shared", "eevee_global_ubo")
.push_constant(Type::MAT4, "grid_local_to_world")
.push_constant(Type::INT, "grid_index")
.push_constant(Type::INT, "grid_start_index")

View File

@@ -23,13 +23,12 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.push_constant(Type::IVEC4, "probe_coord_packed")
.push_constant(Type::IVEC4, "write_coord_packed")
.push_constant(Type::IVEC4, "world_coord_packed")
.push_constant(Type::FLOAT, "probe_brightness_clamp")
.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]")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "atlas_img")
.compute_source("eevee_reflection_probe_remap_comp.glsl")
.additional_info("eevee_shared")
.additional_info("eevee_shared", "eevee_global_ubo")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_irradiance)
@@ -48,7 +47,10 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select)
"SphereProbeData",
"reflection_probe_buf[SPHERE_PROBE_MAX]")
.push_constant(Type::INT, "reflection_probe_count")
.additional_info("eevee_shared", "eevee_sampling_data", "eevee_volume_probe_data")
.additional_info("eevee_shared",
"eevee_sampling_data",
"eevee_global_ubo",
"eevee_volume_probe_data")
.compute_source("eevee_reflection_probe_select_comp.glsl")
.do_static_compilation(true);

View File

@@ -165,7 +165,6 @@
.screen_trace_quality = 0.25f, \
.screen_trace_thickness = 0.2f, \
.trace_max_roughness = 0.5f, \
.sample_clamp = 10.0f, \
.resolution_scale = 2, \
}
@@ -231,6 +230,8 @@
.motion_blur_max = 32, \
.motion_blur_steps = 1, \
\
.clamp_surface_indirect = 10.0f, \
\
.shadow_cube_size = 512, \
.shadow_cascade_size = 1024, \
.shadow_ray_count = 1, \

View File

@@ -1803,14 +1803,10 @@ typedef struct RaytraceEEVEE {
float trace_max_roughness;
/** Resolution downscale factor. */
int resolution_scale;
/** Maximum intensity a ray can have. */
float sample_clamp;
/** #RaytraceEEVEE_Flag. */
int flag;
/** #RaytraceEEVEE_DenoiseStages. */
int denoise_stages;
char _pad0[4];
} RaytraceEEVEE;
typedef struct SceneEEVEE {
@@ -1881,6 +1877,11 @@ typedef struct SceneEEVEE {
int shadow_step_count;
float shadow_normal_bias;
float clamp_surface_direct;
float clamp_surface_indirect;
float clamp_volume_direct;
float clamp_volume_indirect;
int ray_tracing_method;
struct RaytraceEEVEE ray_tracing_options;

View File

@@ -1947,6 +1947,22 @@ static void rna_SceneEEVEE_gi_cubemap_resolution_update(Main * /*main*/,
FOREACH_SCENE_OBJECT_END;
}
static void rna_SceneEEVEE_clamp_surface_indirect_update(Main * /*main*/,
Scene *scene,
PointerRNA * /*ptr*/)
{
/* Tag all light probes to recalc transform. This signals EEVEE to update the light probes. */
FOREACH_SCENE_OBJECT_BEGIN (scene, ob) {
if (ob->type == OB_LIGHTPROBE) {
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
}
}
FOREACH_SCENE_OBJECT_END;
/* Also tag the world. */
DEG_id_tag_update(&scene->world->id, ID_RECALC_SHADING);
}
static std::optional<std::string> rna_SceneRenderView_path(const PointerRNA *ptr)
{
const SceneRenderView *srv = (SceneRenderView *)ptr->data;
@@ -7742,12 +7758,6 @@ static void rna_def_raytrace_eevee(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "sample_clamp", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop, "Clamp", "Clamp ray intensity to reduce noise (0 to disable)");
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, nullptr);
prop = RNA_def_property(srna, "screen_trace_thickness", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_ui_text(
prop,
@@ -8032,6 +8042,52 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
"enabled for final renders)");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
/* Clamping */
prop = RNA_def_property(srna, "clamp_surface_direct", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Clamp Surface Direct",
"If non-zero, the maximum value for lights contribution on a surface. "
"Higher values will be scaled down to avoid too "
"much noise and slow convergence at the cost of accuracy. "
"Used by light objects");
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, nullptr);
prop = RNA_def_property(srna, "clamp_surface_indirect", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Clamp Surface Indirect",
"If non-zero, the maximum value for indirect lighting on surface. "
"Higher values will be scaled down to avoid too "
"much noise and slow convergence at the cost of accuracy. "
"Used by ray-tracing and light-probes");
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_surface_indirect_update");
prop = RNA_def_property(srna, "clamp_volume_direct", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Clamp Volume Direct",
"If non-zero, the maximum value for lights contribution in volumes. "
"Higher values will be scaled down to avoid too "
"much noise and slow convergence at the cost of accuracy. "
"Used by light objects");
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, nullptr);
prop = RNA_def_property(srna, "clamp_volume_indirect", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(prop,
"Clamp Volume Indirect",
"If non-zero, the maximum value for indirect lighting in volumes. "
"Higher values will be scaled down to avoid too "
"much noise and slow convergence at the cost of accuracy. "
"Used by light-probes");
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, nullptr);
/* Volumetrics */
prop = RNA_def_property(srna, "volumetric_start", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_ui_text(prop, "Start", "Start distance of the volumetric effect");