EEVEE-Next: Ray-tracing Denoise Pipeline

This is a full rewrite of the raytracing denoise pipeline. It uses the
same principle as before but now uses compute shaders for every stages
and a tile base approach. More aggressive filtering is needed since we
are moving towards having no prefiltered screen radiance buffer. Thus
we introduce a temporal denoise and a bilateral denoise stage to the
denoising. These are optionnal and can be disabled.

Note that this patch does not include any tracing part and only samples
the reflection probes. It is focused on denoising only. Tracing will
come in another PR.

The motivation for this is that having hardware raytracing support
means we can't prefilter the radiance in screen space so we have to
have better denoising. Also this means we can have better surface
appearance with support for other BxDF model than GGX. Also GGX support
is improved.

Technically, the new denoising fixes some implementation mistake the
old pipeline did. It separates all 3 stages (spatial, temporal,
bilateral) and use random sampling for all stages hoping to create
a noisy enough (but still stable) output so that the TAA soaks the
remaining noise. However that's not always the case. Depending on the
nature of the scene, the input can be very high frequency and might
create lots of flickering. That why another solution needs to be found
for the higher roughness material as denoising them becomes expensive
and low quality.

Pull Request: https://projects.blender.org/blender/blender/pulls/110117
This commit is contained in:
Clément Foucault 2023-08-03 15:32:06 +02:00 committed by Clément Foucault
parent 36c6bcca1a
commit 17db856686
48 changed files with 2764 additions and 192 deletions

@ -459,6 +459,156 @@ class RENDER_PT_eevee_screen_space_reflections(RenderButtonsPanel, Panel):
col.prop(props, "ssr_firefly_fac")
class RENDER_PT_eevee_next_raytracing(RenderButtonsPanel, Panel):
bl_label = "Raytracing"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.eevee
layout.prop(props, "ray_tracing_method", text="Method")
layout.prop(props, "ray_split_settings", text="Settings", expand=True)
class EeveeRaytracingOptionsPanel(RenderButtonsPanel, Panel):
bl_label = "Reflection"
bl_options = {'DEFAULT_CLOSED'}
bl_parent_id = "RENDER_PT_eevee_next_raytracing"
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw_internal(self, context, props):
layout = self.layout
layout.use_property_split = True
scene = context.scene
eevee = scene.eevee
layout.prop(props, "resolution_scale")
layout.prop(props, "sample_clamp")
class EeveeRaytracingScreenOption(RenderButtonsPanel, Panel):
bl_label = "Screen Tracing"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
use_screen_trace = (context.scene.eevee.ray_tracing_method == 'SCREEN')
return (context.engine in cls.COMPAT_ENGINES) and use_screen_trace
def draw_internal(self, props):
layout = self.layout
layout.use_property_split = True
layout.prop(props, "screen_trace_quality", text="Precision")
layout.prop(props, "screen_trace_thickness", text="Thickness")
class EeveeRaytracingDenoisePanel(RenderButtonsPanel, Panel):
bl_label = "Denoising"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw_header_internal(self, props):
self.layout.prop(props, "use_denoise", text="")
def draw_internal(self, props):
layout = self.layout
layout.use_property_split = True
col = layout.column()
col.active = props.use_denoise
col.prop(props, "denoise_spatial")
col = layout.column()
col.active = props.denoise_spatial
col.prop(props, "denoise_temporal")
col = layout.column()
col.active = props.denoise_spatial and props.denoise_temporal
col.prop(props, "denoise_bilateral")
class RENDER_PT_eevee_next_raytracing_reflection(EeveeRaytracingOptionsPanel):
#NOTE: Label is drawn by draw_header
bl_label = ""
bl_parent_id = "RENDER_PT_eevee_next_raytracing"
def draw_header(self, context):
layout = self.layout
if context.scene.eevee.ray_split_settings == "UNIFIED":
layout.label(text="Reflection & Refraction")
else:
layout.label(text="Reflection")
def draw(self, context):
self.draw_internal(context, context.scene.eevee.reflection_options)
class RENDER_PT_eevee_next_screen_trace_reflection(EeveeRaytracingScreenOption):
bl_parent_id = "RENDER_PT_eevee_next_raytracing_reflection"
def draw(self, context):
self.draw_internal(context.scene.eevee.reflection_options)
class RENDER_PT_eevee_next_denoise_reflection(EeveeRaytracingDenoisePanel):
bl_parent_id = "RENDER_PT_eevee_next_raytracing_reflection"
def draw_header(self, context):
self.draw_header_internal(context.scene.eevee.reflection_options)
def draw(self, context):
self.draw_internal(context.scene.eevee.reflection_options)
class RENDER_PT_eevee_next_raytracing_refraction(EeveeRaytracingOptionsPanel):
bl_label = "Refraction"
bl_parent_id = "RENDER_PT_eevee_next_raytracing"
@classmethod
def poll(cls, context):
return (context.scene.eevee.ray_split_settings == "SPLIT")
def draw(self, context):
self.draw_internal(context, context.scene.eevee.refraction_options)
class RENDER_PT_eevee_next_screen_trace_refraction(EeveeRaytracingScreenOption):
bl_parent_id = "RENDER_PT_eevee_next_raytracing_refraction"
def draw(self, context):
self.draw_internal(context.scene.eevee.refraction_options)
class RENDER_PT_eevee_next_denoise_refraction(EeveeRaytracingDenoisePanel):
bl_parent_id = "RENDER_PT_eevee_next_raytracing_refraction"
def draw_header(self, context):
self.draw_header_internal(context.scene.eevee.refraction_options)
def draw(self, context):
self.draw_internal(context.scene.eevee.refraction_options)
class RENDER_PT_eevee_shadows(RenderButtonsPanel, Panel):
bl_label = "Shadows"
bl_options = {'DEFAULT_CLOSED'}
@ -961,6 +1111,13 @@ classes = (
RENDER_PT_eevee_next_depth_of_field,
RENDER_PT_eevee_subsurface_scattering,
RENDER_PT_eevee_screen_space_reflections,
RENDER_PT_eevee_next_raytracing,
RENDER_PT_eevee_next_raytracing_reflection,
RENDER_PT_eevee_next_raytracing_refraction,
RENDER_PT_eevee_next_screen_trace_reflection,
RENDER_PT_eevee_next_screen_trace_refraction,
RENDER_PT_eevee_next_denoise_reflection,
RENDER_PT_eevee_next_denoise_refraction,
RENDER_PT_eevee_motion_blur,
RENDER_PT_eevee_next_motion_blur,
RENDER_PT_motion_blur_curve,

@ -2454,6 +2454,7 @@ class USERPREF_PT_experimental_debugging(ExperimentalPanel, Panel):
({"property": "show_asset_debug_info"}, None),
({"property": "use_asset_indexing"}, None),
({"property": "use_viewport_debug"}, None),
({"property": "use_eevee_debug"}, None),
),
)

@ -533,5 +533,23 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
*/
{
/* Keep this block, even when empty. */
if (!DNA_struct_elem_find(fd->filesdna, "SceneEEVEE", "RaytraceEEVEE", "reflection_options")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.reflection_options.flag = RAYTRACE_EEVEE_USE_DENOISE;
scene->eevee.reflection_options.denoise_stages = RAYTRACE_EEVEE_DENOISE_SPATIAL |
RAYTRACE_EEVEE_DENOISE_TEMPORAL |
RAYTRACE_EEVEE_DENOISE_BILATERAL;
scene->eevee.reflection_options.screen_trace_quality = 0.25f;
scene->eevee.reflection_options.screen_trace_thickness = 0.2f;
scene->eevee.reflection_options.sample_clamp = 10.0f;
scene->eevee.reflection_options.resolution_scale = 2;
scene->eevee.refraction_options = scene->eevee.reflection_options;
scene->eevee.ray_split_settings = 0;
scene->eevee.ray_tracing_method = RAYTRACE_EEVEE_METHOD_SCREEN;
}
}
}
}

@ -151,6 +151,7 @@ set(SRC
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_raytrace.cc
engines/eevee_next/eevee_reflection_probes.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
@ -464,6 +465,8 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_ambient_occlusion_lib.glsl
engines/eevee_next/shaders/eevee_ambient_occlusion_pass_comp.glsl
engines/eevee_next/shaders/eevee_attributes_lib.glsl
engines/eevee_next/shaders/eevee_bxdf_lib.glsl
engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl
engines/eevee_next/shaders/eevee_camera_lib.glsl
engines/eevee_next/shaders/eevee_colorspace_lib.glsl
engines/eevee_next/shaders/eevee_cryptomatte_lib.glsl
@ -519,6 +522,15 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl
engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl
engines/eevee_next/shaders/eevee_ray_denoise_temporal_comp.glsl
engines/eevee_next/shaders/eevee_ray_generate_comp.glsl
engines/eevee_next/shaders/eevee_ray_generate_lib.glsl
engines/eevee_next/shaders/eevee_ray_tile_classify_comp.glsl
engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl
engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl
engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl
engines/eevee_next/shaders/eevee_octahedron_lib.glsl
engines/eevee_next/shaders/eevee_ray_types_lib.glsl
engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl

@ -68,8 +68,11 @@
#define SHADOW_VIEW_MAX 64 /* Must match DRW_VIEW_MAX. */
/* Ray-tracing. */
#define RAYTRACE_GROUP_SIZE 16
#define RAYTRACE_MAX_TILES (16384 / RAYTRACE_GROUP_SIZE) * (16384 / RAYTRACE_GROUP_SIZE)
#define RAYTRACE_GROUP_SIZE 8
/* Keep this as a define to avoid shader variations. */
#define RAYTRACE_RADIANCE_FORMAT GPU_R11F_G11F_B10F
#define RAYTRACE_VARIANCE_FORMAT GPU_R16F
#define RAYTRACE_TILEMASK_FORMAT GPU_R8UI
/* Minimum visibility size. */
#define LIGHTPROBE_FILTER_VIS_GROUP_SIZE 16

@ -69,6 +69,7 @@ void Instance::init(const int2 &output_res,
film.init(output_res, output_rect);
ambient_occlusion.init();
velocity.init();
raytracing.init();
depth_of_field.init();
shadows.init();
motion_blur.init();
@ -151,6 +152,7 @@ void Instance::begin_sync()
scene_sync();
depth_of_field.sync();
raytracing.sync();
motion_blur.sync();
hiz_buffer.sync();
main_view.sync();

@ -29,6 +29,7 @@
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_raytrace.hh"
#include "eevee_reflection_probes.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
@ -58,6 +59,7 @@ class Instance {
ShadowModule shadows;
LightModule lights;
AmbientOcclusion ambient_occlusion;
RayTraceModule raytracing;
ReflectionProbeModule reflection_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
@ -113,6 +115,7 @@ class Instance {
shadows(*this),
lights(*this),
ambient_occlusion(*this),
raytracing(*this),
reflection_probes(*this),
velocity(*this),
motion_blur(*this),

@ -405,17 +405,20 @@ void DeferredLayer::begin_sync()
void DeferredLayer::end_sync()
{
if (closure_bits_ & (CLOSURE_DIFFUSE | CLOSURE_REFLECTION)) {
eClosureBits evaluated_closures = CLOSURE_DIFFUSE | CLOSURE_REFLECTION | CLOSURE_REFRACTION;
if (closure_bits_ & evaluated_closures) {
const bool is_last_eval_pass = !(closure_bits_ & CLOSURE_SSS);
eval_light_ps_.init();
/* Use stencil test to reject pixel not written by this layer. */
eval_light_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_STENCIL_NEQUAL |
DRW_STATE_BLEND_CUSTOM);
eval_light_ps_.state_stencil(0x00u, 0x00u, (CLOSURE_DIFFUSE | CLOSURE_REFLECTION));
eval_light_ps_.state_stencil(0x00u, 0x00u, evaluated_closures);
eval_light_ps_.shader_set(inst_.shaders.static_shader_get(DEFERRED_LIGHT));
eval_light_ps_.bind_image("out_diffuse_light_img", &diffuse_light_tx_);
eval_light_ps_.bind_image("out_specular_light_img", &specular_light_tx_);
eval_light_ps_.bind_image("indirect_refraction_img", &indirect_refraction_tx_);
eval_light_ps_.bind_image("indirect_reflection_img", &indirect_reflection_tx_);
eval_light_ps_.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
eval_light_ps_.bind_texture("gbuffer_color_tx", &inst_.gbuffer.color_tx);
eval_light_ps_.push_constant("is_last_eval_pass", is_last_eval_pass);
@ -465,33 +468,46 @@ PassMain::Sub *DeferredLayer::material_add(::Material *blender_mat, GPUMaterial
return pass;
}
void DeferredLayer::render(View &view,
void DeferredLayer::render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
int2 extent)
int2 extent,
RayTraceBuffer &rt_buffer)
{
GPU_framebuffer_bind(prepass_fb);
inst_.manager->submit(prepass_ps_, view);
inst_.manager->submit(prepass_ps_, render_view);
inst_.hiz_buffer.set_dirty();
inst_.shadows.set_view(view);
inst_.irradiance_cache.set_view(view);
inst_.shadows.set_view(render_view);
inst_.irradiance_cache.set_view(render_view);
inst_.gbuffer.acquire(extent, closure_bits_);
GPU_framebuffer_bind(combined_fb);
inst_.manager->submit(gbuffer_ps_, view);
inst_.manager->submit(gbuffer_ps_, render_view);
RayTraceResult refract_result = inst_.raytracing.trace(
rt_buffer, closure_bits_, CLOSURE_REFRACTION, main_view, render_view);
indirect_refraction_tx_ = refract_result.get();
RayTraceResult reflect_result = inst_.raytracing.trace(
rt_buffer, closure_bits_, CLOSURE_REFLECTION, main_view, render_view);
indirect_reflection_tx_ = reflect_result.get();
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
diffuse_light_tx_.acquire(extent, GPU_RGBA16F, usage);
specular_light_tx_.acquire(extent, GPU_RGBA16F, usage);
diffuse_light_tx_.clear(float4(0.0f));
specular_light_tx_.acquire(extent, GPU_RGBA16F, usage);
specular_light_tx_.clear(float4(0.0f));
inst_.manager->submit(eval_light_ps_, view);
inst_.manager->submit(eval_light_ps_, render_view);
refract_result.release();
reflect_result.release();
if (closure_bits_ & CLOSURE_SSS) {
inst_.subsurface.render(view, combined_fb, diffuse_light_tx_);
inst_.subsurface.render(render_view, combined_fb, diffuse_light_tx_);
}
diffuse_light_tx_.release();
@ -542,17 +558,22 @@ PassMain::Sub *DeferredPipeline::material_add(::Material *blender_mat, GPUMateri
}
}
void DeferredPipeline::render(View &view,
void DeferredPipeline::render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
int2 extent)
int2 extent,
RayTraceBuffer &rt_buffer_opaque_layer,
RayTraceBuffer &rt_buffer_refract_layer)
{
DRW_stats_group_start("Deferred.Opaque");
opaque_layer_.render(view, prepass_fb, combined_fb, extent);
opaque_layer_.render(
main_view, render_view, prepass_fb, combined_fb, extent, rt_buffer_opaque_layer);
DRW_stats_group_end();
DRW_stats_group_start("Deferred.Refract");
refraction_layer_.render(view, prepass_fb, combined_fb, extent);
refraction_layer_.render(
main_view, render_view, prepass_fb, combined_fb, extent, rt_buffer_refract_layer);
DRW_stats_group_end();
}
@ -645,6 +666,8 @@ void DeferredProbeLayer::end_sync()
eval_light_ps_.shader_set(inst_.shaders.static_shader_get(DEFERRED_LIGHT_DIFFUSE_ONLY));
eval_light_ps_.bind_image("out_diffuse_light_img", dummy_light_tx_);
eval_light_ps_.bind_image("out_specular_light_img", dummy_light_tx_);
eval_light_ps_.bind_image("indirect_refraction_img", dummy_light_tx_);
eval_light_ps_.bind_image("indirect_reflection_img", dummy_light_tx_);
eval_light_ps_.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
eval_light_ps_.bind_texture("gbuffer_color_tx", &inst_.gbuffer.color_tx);
eval_light_ps_.push_constant("is_last_eval_pass", is_last_eval_pass);

@ -21,6 +21,7 @@
namespace blender::eevee {
class Instance;
struct RayTraceBuffer;
/* -------------------------------------------------------------------- */
/** \name World Background Pipeline
@ -175,6 +176,10 @@ class DeferredLayer {
TextureFromPool diffuse_light_tx_ = {"diffuse_light_accum_tx"};
TextureFromPool specular_light_tx_ = {"specular_light_accum_tx"};
/* Reference to ray-tracing result. */
GPUTexture *indirect_refraction_tx_ = nullptr;
GPUTexture *indirect_reflection_tx_ = nullptr;
public:
DeferredLayer(Instance &inst) : inst_(inst){};
@ -184,7 +189,12 @@ class DeferredLayer {
PassMain::Sub *prepass_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion);
PassMain::Sub *material_add(::Material *blender_mat, GPUMaterial *gpumat);
void render(View &view, Framebuffer &prepass_fb, Framebuffer &combined_fb, int2 extent);
void render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
int2 extent,
RayTraceBuffer &rt_buffer);
};
class DeferredPipeline {
@ -205,7 +215,13 @@ class DeferredPipeline {
PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat, bool has_motion);
PassMain::Sub *material_add(::Material *material, GPUMaterial *gpumat);
void render(View &view, Framebuffer &prepass_fb, Framebuffer &combined_fb, int2 extent);
void render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
int2 extent,
RayTraceBuffer &rt_buffer_opaque_layer,
RayTraceBuffer &rt_buffer_refract_layer);
};
/** \} */

@ -0,0 +1,354 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation.
*/
/** \file
* \ingroup eevee
*
* The raytracing module class handles ray generation, scheduling, tracing and denoising.
*/
#include <fstream>
#include <iostream>
#include "BKE_global.h"
#include "eevee_instance.hh"
#include "eevee_raytrace.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Raytracing
*
* \{ */
void RayTraceModule::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
reflection_options_ = sce_eevee.reflection_options;
refraction_options_ = sce_eevee.refraction_options;
if (sce_eevee.ray_split_settings == 0) {
refraction_options_ = reflection_options_;
}
}
void RayTraceModule::sync()
{
/* Setup. */
{
PassSimple &pass = tile_classify_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(RAY_TILE_CLASSIFY));
pass.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
pass.bind_texture("stencil_tx", &renderbuf_stencil_view_);
pass.bind_image("tile_mask_img", &tile_mask_tx_);
pass.bind_ssbo("ray_dispatch_buf", &ray_dispatch_buf_);
pass.bind_ssbo("denoise_dispatch_buf", &denoise_dispatch_buf_);
pass.bind_ubo("raytrace_buf", &data_);
pass.dispatch(&tile_classify_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_SHADER_STORAGE);
}
{
PassSimple &pass = tile_compact_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(RAY_TILE_COMPACT));
pass.bind_image("tile_mask_img", &tile_mask_tx_);
pass.bind_ssbo("ray_dispatch_buf", &ray_dispatch_buf_);
pass.bind_ssbo("denoise_dispatch_buf", &denoise_dispatch_buf_);
pass.bind_ssbo("ray_tiles_buf", &ray_tiles_buf_);
pass.bind_ssbo("denoise_tiles_buf", &denoise_tiles_buf_);
pass.bind_ubo("raytrace_buf", &data_);
pass.dispatch(&tile_compact_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
for (auto type : IndexRange(2)) {
PassSimple &pass = (type == 0) ? generate_reflect_ps_ : generate_refract_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get((type == 0) ? RAY_GENERATE_REFLECT :
RAY_GENERATE_REFRACT));
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_texture("stencil_tx", &renderbuf_stencil_view_);
pass.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
pass.bind_texture("gbuffer_color_tx", &inst_.gbuffer.color_tx);
pass.bind_image("out_ray_data_img", &ray_data_tx_);
pass.bind_ssbo("tiles_coord_buf", &ray_tiles_buf_);
inst_.sampling.bind_resources(&pass);
pass.dispatch(ray_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_TEXTURE_FETCH |
GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
/* Tracing. */
for (auto type : IndexRange(2)) {
PassSimple &pass = (type == 0) ? trace_reflect_ps_ : trace_refract_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get((type == 0) ? RAY_TRACE_SCREEN_REFLECT :
RAY_TRACE_SCREEN_REFRACT));
pass.bind_ssbo("tiles_coord_buf", &ray_tiles_buf_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_ubo("raytrace_buf", &data_);
inst_.hiz_buffer.bind_resources(&pass);
inst_.sampling.bind_resources(&pass);
inst_.reflection_probes.bind_resources(&pass);
pass.dispatch(ray_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
/* Denoise. */
for (auto type : IndexRange(2)) {
PassSimple &pass = (type == 0) ? denoise_spatial_reflect_ps_ : denoise_spatial_refract_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get((type == 0) ? RAY_DENOISE_SPATIAL_REFLECT :
RAY_DENOISE_SPATIAL_REFRACT));
pass.bind_ssbo("tiles_coord_buf", &denoise_tiles_buf_);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
pass.bind_texture("stencil_tx", &renderbuf_stencil_view_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_image("out_radiance_img", &denoised_spatial_tx_);
pass.bind_image("out_variance_img", &hit_variance_tx_);
pass.bind_image("out_hit_depth_img", &hit_depth_tx_);
pass.bind_image("tile_mask_img", &tile_mask_tx_);
pass.bind_ubo("raytrace_buf", &data_);
inst_.sampling.bind_resources(&pass);
inst_.hiz_buffer.bind_resources(&pass);
pass.dispatch(denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
PassSimple &pass = denoise_temporal_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(RAY_DENOISE_TEMPORAL));
pass.bind_ubo("raytrace_buf", &data_);
pass.bind_texture("radiance_history_tx", &radiance_history_tx_);
pass.bind_texture("variance_history_tx", &variance_history_tx_);
pass.bind_texture("tilemask_history_tx", &tilemask_history_tx_);
pass.bind_image("hit_depth_img", &hit_depth_tx_);
pass.bind_image("in_radiance_img", &denoised_spatial_tx_);
pass.bind_image("out_radiance_img", &denoised_temporal_tx_);
pass.bind_image("in_variance_img", &hit_variance_tx_);
pass.bind_image("out_variance_img", &denoise_variance_tx_);
pass.bind_ssbo("tiles_coord_buf", &denoise_tiles_buf_);
inst_.sampling.bind_resources(&pass);
inst_.hiz_buffer.bind_resources(&pass);
pass.dispatch(denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
for (auto type : IndexRange(2)) {
PassSimple &pass = (type == 0) ? denoise_bilateral_reflect_ps_ : denoise_bilateral_refract_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get((type == 0) ? RAY_DENOISE_BILATERAL_REFLECT :
RAY_DENOISE_BILATERAL_REFRACT));
pass.bind_texture("gbuffer_closure_tx", &inst_.gbuffer.closure_tx);
pass.bind_image("in_radiance_img", &denoised_temporal_tx_);
pass.bind_image("out_radiance_img", &denoised_bilateral_tx_);
pass.bind_image("in_variance_img", &denoise_variance_tx_);
pass.bind_image("tile_mask_img", &tile_mask_tx_);
pass.bind_ssbo("tiles_coord_buf", &denoise_tiles_buf_);
pass.bind_ubo("raytrace_buf", &data_);
inst_.sampling.bind_resources(&pass);
inst_.hiz_buffer.bind_resources(&pass);
pass.dispatch(denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
}
void RayTraceModule::debug_pass_sync() {}
void RayTraceModule::debug_draw(View & /* view */, GPUFrameBuffer * /* view_fb */) {}
RayTraceResult RayTraceModule::trace(RayTraceBuffer &rt_buffer,
eClosureBits active_closures,
eClosureBits raytrace_closure,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view)
{
BLI_assert_msg(count_bits_i(raytrace_closure) == 1,
"Only one closure type can be raytraced at a time.");
BLI_assert_msg(raytrace_closure ==
(raytrace_closure & (CLOSURE_REFLECTION | CLOSURE_REFRACTION)),
"Only reflection and refraction are implemented.");
RaytraceEEVEE options;
PassSimple *generate_ray_ps = nullptr;
PassSimple *trace_ray_ps = nullptr;
PassSimple *denoise_spatial_ps = nullptr;
PassSimple *denoise_bilateral_ps = nullptr;
RayTraceBuffer::DenoiseBuffer *denoise_buf = nullptr;
if (raytrace_closure == CLOSURE_REFLECTION) {
options = reflection_options_;
generate_ray_ps = &generate_reflect_ps_;
trace_ray_ps = &trace_reflect_ps_;
denoise_spatial_ps = &denoise_spatial_reflect_ps_;
denoise_bilateral_ps = &denoise_bilateral_reflect_ps_;
denoise_buf = &rt_buffer.reflection;
}
else if (raytrace_closure == CLOSURE_REFRACTION) {
options = refraction_options_;
generate_ray_ps = &generate_refract_ps_;
trace_ray_ps = &trace_refract_ps_;
denoise_spatial_ps = &denoise_spatial_refract_ps_;
denoise_bilateral_ps = &denoise_bilateral_refract_ps_;
denoise_buf = &rt_buffer.refraction;
}
if ((active_closures & raytrace_closure) == 0) {
/* Early out. Release persistent buffers. Still acquire one dummy resource for validation. */
denoise_buf->denoised_spatial_tx.acquire(int2(1), RAYTRACE_RADIANCE_FORMAT);
denoise_buf->radiance_history_tx.free();
denoise_buf->variance_history_tx.free();
denoise_buf->tilemask_history_tx.free();
return {denoise_buf->denoised_spatial_tx};
}
const int resolution_scale = max_ii(1, power_of_2_max_i(options.resolution_scale));
const int2 extent = inst_.film.render_extent_get();
const int2 tracing_res = math::divide_ceil(extent, int2(resolution_scale));
const int2 dummy_extent(1, 1);
tile_classify_dispatch_size_ = int3(math::divide_ceil(extent, int2(RAYTRACE_GROUP_SIZE)), 1);
const int denoise_tile_count = tile_classify_dispatch_size_.x * tile_classify_dispatch_size_.y;
const int2 tile_mask_extent = tile_classify_dispatch_size_.xy();
const int2 ray_tiles = math::divide_ceil(tracing_res, int2(RAYTRACE_GROUP_SIZE));
const int ray_tile_count = ray_tiles.x * ray_tiles.y;
tile_compact_dispatch_size_ = int3(math::divide_ceil(ray_tiles, int2(RAYTRACE_GROUP_SIZE)), 1);
renderbuf_stencil_view_ = inst_.render_buffers.depth_tx.stencil_view();
renderbuf_depth_view_ = inst_.render_buffers.depth_tx;
bool use_denoise = (options.flag & RAYTRACE_EEVEE_USE_DENOISE);
bool use_spatial_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_SPATIAL) &&
use_denoise;
bool use_temporal_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_TEMPORAL) &&
use_spatial_denoise;
bool use_bilateral_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_BILATERAL) &&
use_temporal_denoise;
DRW_stats_group_start("Raytracing");
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;
data_.max_trace_roughness = 1.0f;
data_.resolution_scale = resolution_scale;
data_.closure_active = raytrace_closure;
data_.resolution_bias = int2(inst_.sampling.rng_2d_get(SAMPLING_RAYTRACE_V) * resolution_scale);
data_.history_persmat = denoise_buf->history_persmat;
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
data_.skip_denoise = !use_spatial_denoise;
data_.push_update();
tile_mask_tx_.acquire(tile_mask_extent, RAYTRACE_TILEMASK_FORMAT);
denoise_tiles_buf_.resize(ceil_to_multiple_u(denoise_tile_count, 512));
ray_tiles_buf_.resize(ceil_to_multiple_u(ray_tile_count, 512));
/* Ray setup. */
inst_.manager->submit(tile_classify_ps_);
inst_.manager->submit(tile_compact_ps_);
{
/* Tracing rays. */
ray_data_tx_.acquire(tracing_res, GPU_RGBA16F);
ray_time_tx_.acquire(tracing_res, GPU_R32F);
ray_radiance_tx_.acquire(tracing_res, RAYTRACE_RADIANCE_FORMAT);
inst_.manager->submit(*generate_ray_ps, render_view);
inst_.manager->submit(*trace_ray_ps, render_view);
}
RayTraceResult result;
/* Spatial denoise pass is required to resolve at least one ray per pixel. */
{
denoise_buf->denoised_spatial_tx.acquire(extent, RAYTRACE_RADIANCE_FORMAT);
hit_variance_tx_.acquire(use_temporal_denoise ? extent : dummy_extent,
RAYTRACE_VARIANCE_FORMAT);
hit_depth_tx_.acquire(use_temporal_denoise ? extent : dummy_extent, GPU_R32F);
denoised_spatial_tx_ = denoise_buf->denoised_spatial_tx;
inst_.manager->submit(*denoise_spatial_ps, render_view);
result = {denoise_buf->denoised_spatial_tx};
}
ray_data_tx_.release();
ray_time_tx_.release();
ray_radiance_tx_.release();
if (use_temporal_denoise) {
denoise_buf->denoised_temporal_tx.acquire(extent, RAYTRACE_RADIANCE_FORMAT);
denoise_variance_tx_.acquire(use_bilateral_denoise ? extent : dummy_extent,
RAYTRACE_VARIANCE_FORMAT);
denoise_buf->variance_history_tx.ensure_2d(RAYTRACE_VARIANCE_FORMAT,
use_bilateral_denoise ? extent : dummy_extent);
denoise_buf->tilemask_history_tx.ensure_2d(RAYTRACE_TILEMASK_FORMAT, tile_mask_extent);
if (denoise_buf->radiance_history_tx.ensure_2d(RAYTRACE_RADIANCE_FORMAT, extent) ||
denoise_buf->valid_history == false)
{
/* If viewport resolution changes, do not try to use history. */
denoise_buf->tilemask_history_tx.clear(uint4(0u));
}
radiance_history_tx_ = denoise_buf->radiance_history_tx;
variance_history_tx_ = denoise_buf->variance_history_tx;
tilemask_history_tx_ = denoise_buf->tilemask_history_tx;
denoised_temporal_tx_ = denoise_buf->denoised_temporal_tx;
inst_.manager->submit(denoise_temporal_ps_, render_view);
/* Swap after last use. */
TextureFromPool::swap(tile_mask_tx_, denoise_buf->tilemask_history_tx);
/* Save view-projection matrix for next reprojection. */
denoise_buf->history_persmat = main_view.persmat();
/* Radiance will be swapped with history in RayTraceResult::release().
* Variance is swapped with history after bilateral denoise.
* It keeps dataflow easier to follow. */
result = {denoise_buf->denoised_temporal_tx, denoise_buf->radiance_history_tx};
/* Not referenced by result anymore. */
denoise_buf->denoised_spatial_tx.release();
}
/* Only use history buffer for the next frame if temporal denoise was used by the current one. */
denoise_buf->valid_history = use_temporal_denoise;
hit_variance_tx_.release();
hit_depth_tx_.release();
if (use_bilateral_denoise) {
denoise_buf->denoised_bilateral_tx.acquire(extent, RAYTRACE_RADIANCE_FORMAT);
denoised_bilateral_tx_ = denoise_buf->denoised_bilateral_tx;
inst_.manager->submit(*denoise_bilateral_ps, render_view);
/* Swap after last use. */
TextureFromPool::swap(denoise_buf->denoised_temporal_tx, denoise_buf->radiance_history_tx);
TextureFromPool::swap(denoise_variance_tx_, denoise_buf->variance_history_tx);
result = {denoise_buf->denoised_bilateral_tx};
/* Not referenced by result anymore. */
denoise_buf->denoised_temporal_tx.release();
}
tile_mask_tx_.release();
denoise_variance_tx_.release();
DRW_stats_group_end();
return result;
}
/** \} */
} // namespace blender::eevee

@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation.
*/
/** \file
* \ingroup eevee
*
* The ray-tracing module class handles ray generation, scheduling, tracing and denoising.
*/
#pragma once
#include "DNA_scene_types.h"
#include "DRW_render.h"
#include "eevee_shader_shared.hh"
namespace blender::eevee {
class Instance;
/* -------------------------------------------------------------------- */
/** \name Raytracing Buffers
*
* Contain persistent data used for temporal denoising. Similar to \class GBuffer but only contains
* persistent data.
* \{ */
/**
* Contain persistent buffer that need to be stored per view.
*/
struct RayTraceBuffer {
/** Set of buffers that need to be allocated for each ray type. */
struct DenoiseBuffer {
/* Persistent history buffers. */
Texture radiance_history_tx = {"radiance_tx"};
Texture variance_history_tx = {"variance_tx"};
/* Map of tiles that were processed inside the history buffer. */
Texture tilemask_history_tx = {"tilemask_tx"};
/** Perspective matrix for which the history buffers were recorded. */
float4x4 history_persmat;
/** True if history buffer was used last frame and can be re-projected. */
bool valid_history = false;
/**
* Textures containing the ray hit radiance denoised (full-res). One of them is result_tx.
* One might become result buffer so it need instantiation by closure type to avoid reuse.
*/
TextureFromPool denoised_spatial_tx = {"denoised_spatial_tx"};
TextureFromPool denoised_temporal_tx = {"denoised_temporal_tx"};
TextureFromPool denoised_bilateral_tx = {"denoised_bilateral_tx"};
};
/**
* One for each closure type. Not to be mistaken with deferred layer type.
* For instance the opaque deferred layer will only used the reflection history buffer.
*/
DenoiseBuffer reflection, refraction;
};
/**
* Contains the result texture.
* The result buffer is usually short lived and is kept in a TextureFromPool managed by the mode.
* This structure contains a reference to it so that it can be freed after use by the caller.
*/
class RayTraceResult {
private:
/** Result is in a temporary texture that needs to be released. */
TextureFromPool *result_ = nullptr;
/** History buffer to swap the tmp texture that does not need to be released. */
Texture *history_ = nullptr;
public:
RayTraceResult() = default;
RayTraceResult(TextureFromPool &result) : result_(result.ptr()){};
RayTraceResult(TextureFromPool &result, Texture &history)
: result_(result.ptr()), history_(history.ptr()){};
GPUTexture *get()
{
return *result_;
}
void release()
{
if (history_) {
/* Swap after last use. */
TextureFromPool::swap(*result_, *history_);
}
/* NOTE: This releases the previous history. */
result_->release();
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Raytracing
* \{ */
class RayTraceModule {
private:
Instance &inst_;
draw::PassSimple tile_classify_ps_ = {"TileClassify"};
draw::PassSimple tile_compact_ps_ = {"TileCompact"};
draw::PassSimple generate_reflect_ps_ = {"RayGenerate.Reflection"};
draw::PassSimple generate_refract_ps_ = {"RayGenerate.Refraction"};
draw::PassSimple trace_reflect_ps_ = {"Trace.Reflection"};
draw::PassSimple trace_refract_ps_ = {"Trace.Refraction"};
draw::PassSimple denoise_spatial_reflect_ps_ = {"DenoiseSpatial.Reflection"};
draw::PassSimple denoise_spatial_refract_ps_ = {"DenoiseSpatial.Refraction"};
draw::PassSimple denoise_temporal_ps_ = {"DenoiseTemporal"};
draw::PassSimple denoise_bilateral_reflect_ps_ = {"DenoiseBilateral.Reflection"};
draw::PassSimple denoise_bilateral_refract_ps_ = {"DenoiseBilateral.Refraction"};
/** Dispatch with enough tiles for the whole screen. */
int3 tile_classify_dispatch_size_ = int3(1);
/** Dispatch with enough tiles for the tile mask. */
int3 tile_compact_dispatch_size_ = int3(1);
/** 2D tile mask to check which unused adjacent tile we need to clear. */
TextureFromPool tile_mask_tx_ = {"tile_mask_tx"};
/** Indirect dispatch rays. Avoid dispatching workgroups that ultimately won't do any tracing. */
DispatchIndirectBuf ray_dispatch_buf_ = {"ray_dispatch_buf_"};
/** Indirect dispatch denoise full-resolution tiles. */
DispatchIndirectBuf denoise_dispatch_buf_ = {"denoise_dispatch_buf_"};
/** Tile buffer that contains tile coordinates. */
RayTraceTileBuf ray_tiles_buf_ = {"ray_tiles_buf_"};
RayTraceTileBuf denoise_tiles_buf_ = {"denoise_tiles_buf_"};
/** Texture containing the ray direction and pdf. */
TextureFromPool ray_data_tx_ = {"ray_data_tx"};
/** Texture containing the ray hit time. */
TextureFromPool ray_time_tx_ = {"ray_data_tx"};
/** Texture containing the ray hit radiance (tracing-res). */
TextureFromPool ray_radiance_tx_ = {"ray_radiance_tx"};
/** Textures containing the ray hit radiance denoised (full-res). One of them is result_tx. */
GPUTexture *denoised_spatial_tx_ = nullptr;
GPUTexture *denoised_temporal_tx_ = nullptr;
GPUTexture *denoised_bilateral_tx_ = nullptr;
/** Ray hit depth for temporal denoising. Output of spatial denoise. */
TextureFromPool hit_depth_tx_ = {"hit_depth_tx_"};
/** Ray hit variance for temporal denoising. Output of spatial denoise. */
TextureFromPool hit_variance_tx_ = {"hit_variance_tx_"};
/** Temporally stable variance for temporal denoising. Output of temporal denoise. */
TextureFromPool denoise_variance_tx_ = {"denoise_variance_tx_"};
/** Persistent texture reference for temporal denoising input. */
GPUTexture *radiance_history_tx_ = nullptr;
GPUTexture *variance_history_tx_ = nullptr;
GPUTexture *tilemask_history_tx_ = nullptr;
/** Dummy texture when the tracing is disabled. */
TextureFromPool dummy_result_tx_ = {"dummy_result_tx"};
/** Pointer to inst_.render_buffers.depth_tx.stencil_view() updated before submission. */
GPUTexture *renderbuf_stencil_view_ = nullptr;
/** Pointer to inst_.render_buffers.depth_tx updated before submission. */
GPUTexture *renderbuf_depth_view_ = nullptr;
/** Copy of the scene options to avoid changing parameters during motion blur. */
RaytraceEEVEE reflection_options_;
RaytraceEEVEE refraction_options_;
RayTraceDataBuf data_;
public:
RayTraceModule(Instance &inst) : inst_(inst){};
void init();
void sync();
/**
* RayTrace the scene and resolve a radiance buffer for the corresponding `closure_bit` into the
* given `out_radiance_tx`.
*
* Should not be conditionally executed as it manages the RayTraceResult.
*
* \arg active_closures is a mask of all active closures in a deferred layer.
* \arg raytrace_closure is type of closure the rays are to be casted for.
* \arg main_view is the un-jittered view.
* \arg render_view is the TAA jittered view.
*/
RayTraceResult trace(RayTraceBuffer &rt_buffer,
eClosureBits active_closures,
eClosureBits raytrace_closure,
View &main_view,
View &render_view);
void debug_pass_sync();
void debug_draw(View &view, GPUFrameBuffer *view_fb);
};
/** \} */
} // namespace blender::eevee

@ -65,9 +65,13 @@ void RenderBuffers::acquire(int2 extent)
eGPUTextureFormat color_format = GPU_RGBA16F;
eGPUTextureFormat float_format = GPU_R16F;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT;
/* Depth and combined are always needed. */
depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8);
depth_tx.ensure_2d(GPU_DEPTH24_STENCIL8, extent, usage | GPU_TEXTURE_USAGE_MIP_SWIZZLE_VIEW);
/* TODO(fclem): depth_tx should ideally be a texture from pool but we need stencil_view
* which is currently unsupported by pool textures. */
// depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8);
combined_tx.acquire(extent, color_format);
eGPUTextureUsage usage_attachment_read_write = GPU_TEXTURE_USAGE_ATTACHMENT |
@ -109,7 +113,9 @@ void RenderBuffers::acquire(int2 extent)
void RenderBuffers::release()
{
depth_tx.release();
/* TODO(fclem): depth_tx should ideally be a texture from pool but we need stencil_view
* which is currently unsupported by pool textures. */
// depth_tx.release();
combined_tx.release();
vector_tx.release();

@ -24,7 +24,7 @@ class RenderBuffers {
public:
UniformBuffer<RenderBuffersInfoData> data;
TextureFromPool depth_tx;
Texture depth_tx;
TextureFromPool combined_tx;
// TextureFromPool mist_tx; /* Derived from depth_tx during accumulation. */

@ -98,7 +98,10 @@ void Sampling::end_sync()
void Sampling::step()
{
{
uint64_t sample_filter = sample_ % interactive_sample_aa_;
uint64_t sample_filter = sample_;
if (interactive_mode()) {
sample_filter = sample_filter % interactive_sample_aa_;
}
/* TODO(fclem) we could use some persistent states to speedup the computation. */
double2 r, offset = {0, 0};
/* Using 2,3 primes as per UE4 Temporal AA presentation.
@ -131,11 +134,15 @@ void Sampling::step()
data_.dimensions[SAMPLING_CURVES_U] = r[0];
}
{
uint64_t sample_raytrace = sample_;
if (interactive_mode()) {
sample_raytrace = sample_raytrace % interactive_sample_raytrace_;
}
/* Using leaped Halton sequence so we can reused the same primes as lens. */
double3 r, offset = {0, 0, 0};
uint64_t leap = 11;
uint3 primes = {5, 4, 7};
BLI_halton_3d(primes, offset, sample_ * leap, r);
BLI_halton_3d(primes, offset, sample_raytrace * leap, r);
data_.dimensions[SAMPLING_SHADOW_U] = r[0];
data_.dimensions[SAMPLING_SHADOW_V] = r[1];
data_.dimensions[SAMPLING_SHADOW_W] = r[2];

@ -32,7 +32,9 @@ class Sampling {
static constexpr uint64_t infinite_sample_count_ = 0xFFFFFFu;
/* During interactive rendering, loop over the first few samples. */
static constexpr uint64_t interactive_sample_aa_ = 8;
static constexpr uint64_t interactive_sample_max_ = interactive_sample_aa_;
static constexpr uint64_t interactive_sample_raytrace_ = 32;
static constexpr uint64_t interactive_sample_max_ = interactive_sample_aa_ *
interactive_sample_raytrace_;
/** 0 based current sample. Might not increase sequentially in viewport. */
uint64_t sample_ = 0;

@ -152,6 +152,28 @@ 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 RAY_DENOISE_SPATIAL_REFLECT:
return "eevee_ray_denoise_spatial_reflect";
case RAY_DENOISE_SPATIAL_REFRACT:
return "eevee_ray_denoise_spatial_refract";
case RAY_DENOISE_TEMPORAL:
return "eevee_ray_denoise_temporal";
case RAY_DENOISE_BILATERAL_REFLECT:
return "eevee_ray_denoise_bilateral_reflect";
case RAY_DENOISE_BILATERAL_REFRACT:
return "eevee_ray_denoise_bilateral_refract";
case RAY_GENERATE_REFLECT:
return "eevee_ray_generate_reflect";
case RAY_GENERATE_REFRACT:
return "eevee_ray_generate_refract";
case RAY_TRACE_SCREEN_REFLECT:
return "eevee_ray_trace_screen_reflect";
case RAY_TRACE_SCREEN_REFRACT:
return "eevee_ray_trace_screen_refract";
case RAY_TILE_CLASSIFY:
return "eevee_ray_tile_classify";
case RAY_TILE_COMPACT:
return "eevee_ray_tile_compact";
case LIGHTPROBE_IRRADIANCE_BOUNDS:
return "eevee_lightprobe_irradiance_bounds";
case LIGHTPROBE_IRRADIANCE_RAY:

@ -75,6 +75,18 @@ enum eShaderType {
MOTION_BLUR_TILE_FLATTEN_RGBA,
MOTION_BLUR_TILE_FLATTEN_RG,
RAY_DENOISE_BILATERAL_REFLECT,
RAY_DENOISE_BILATERAL_REFRACT,
RAY_DENOISE_SPATIAL_REFLECT,
RAY_DENOISE_SPATIAL_REFRACT,
RAY_DENOISE_TEMPORAL,
RAY_GENERATE_REFLECT,
RAY_GENERATE_REFRACT,
RAY_TILE_CLASSIFY,
RAY_TILE_COMPACT,
RAY_TRACE_SCREEN_REFLECT,
RAY_TRACE_SCREEN_REFRACT,
REFLECTION_PROBE_REMAP,
REFLECTION_PROBE_UPDATE_IRRADIANCE,

@ -986,6 +986,31 @@ enum eClosureBits : uint32_t {
CLOSURE_AMBIENT_OCCLUSION = (1u << 12u),
};
struct RayTraceData {
/** ViewProjection matrix used to render the previous frame. */
float4x4 history_persmat;
/** Input resolution. */
int2 full_resolution;
/** Inverse of input resolution to get screen UVs. */
float2 full_resolution_inv;
/** Scale and bias to go from raytrace resolution to input resolution. */
int2 resolution_bias;
int resolution_scale;
/** View space thickness the objects. */
float thickness;
/** 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 max_trace_roughness;
/** If set to true will bypass spatial denoising. */
bool1 skip_denoise;
/** Closure being ray-traced. */
eClosureBits closure_active;
};
BLI_STATIC_ASSERT_ALIGN(RayTraceData, 16)
/** \} */
/* -------------------------------------------------------------------- */
@ -1120,6 +1145,7 @@ using CameraDataBuf = draw::UniformBuffer<CameraData>;
using DepthOfFieldDataBuf = draw::UniformBuffer<DepthOfFieldData>;
using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, true>;
using DrawIndirectBuf = draw::StorageBuffer<DrawCommand, true>;
using DispatchIndirectBuf = draw::StorageBuffer<DispatchCommand>;
using FilmDataBuf = draw::UniformBuffer<FilmData>;
using HiZDataBuf = draw::UniformBuffer<HiZData>;
using IrradianceGridDataBuf = draw::UniformArrayBuffer<IrradianceGridData, IRRADIANCE_GRID_MAX>;
@ -1132,6 +1158,8 @@ using LightCullingZdistBuf = draw::StorageArrayBuffer<float, LIGHT_CHUNK, true>;
using LightDataBuf = draw::StorageArrayBuffer<LightData, LIGHT_CHUNK>;
using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
using RayTraceTileBuf = draw::StorageArrayBuffer<uint, 1024, true>;
using RayTraceDataBuf = draw::UniformBuffer<RayTraceData>;
using ReflectionProbeDataBuf =
draw::UniformArrayBuffer<ReflectionProbeData, REFLECTION_PROBES_MAX>;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;

@ -122,9 +122,16 @@ void ShadingView::render()
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_new_, extent_);
/* TODO: cleanup. */
View main_view_new("MainView", main_view_);
/* TODO(Miguel Pozo): Deferred and forward prepass should happen before the GBuffer pass. */
inst_.pipelines.deferred.render(render_view_new_, prepass_fb_, combined_fb_, extent_);
inst_.pipelines.deferred.render(main_view_new,
render_view_new_,
prepass_fb_,
combined_fb_,
extent_,
rt_buffer_opaque_,
rt_buffer_refract_);
// inst_.lookdev.render_overlay(view_fb_);

@ -42,8 +42,8 @@ class ShadingView {
const float4x4 &face_matrix_;
/** Ray-tracing persistent buffers. Only opaque and refraction can have surface tracing. */
// RaytraceBuffer rt_buffer_opaque_;
// RaytraceBuffer rt_buffer_refract_;
RayTraceBuffer rt_buffer_opaque_;
RayTraceBuffer rt_buffer_refract_;
DepthOfFieldBuffer dof_buffer_;
Framebuffer prepass_fb_;

@ -155,7 +155,8 @@ OcclusionData ambient_occlusion_search(vec3 vP,
for (int i = 0; i < 2; i++) {
Ray ray;
ray.origin = vP;
ray.direction = vec3(dir * radius, 0.0);
ray.direction = vec3(dir, 0.0);
ray.max_time = radius;
ScreenSpaceRay ssray;

@ -0,0 +1,92 @@
/**
* BxDF evaluation functions.
**/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name GGX
*
* \{ */
float bxdf_ggx_D(float NH, float a2)
{
return a2 / (M_PI * sqr((NH * a2 - NH) * NH + 1.0));
}
float bxdf_ggx_smith_G1(float NX, float a2)
{
return 2.0 / (1.0 + sqrt(1.0 + a2 * (1 - NX * NX) / (NX * NX)));
}
float bxdf_ggx_D_opti(float NH, float a2)
{
float tmp = (NH * a2 - NH) * NH + 1.0;
/* Doing RCP and mul a2 at the end. */
return M_PI * tmp * tmp;
}
float bxdf_ggx_smith_G1_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
* this way the (2*NL)*(2*NV) in G = G1(V) * G1(L) gets canceled by the brdf denominator 4*NL*NV
* Rcp is done on the whole G later.
* Note that this is not convenient for the transmission formula. */
/* return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function. */
return NX + sqrt(NX * (NX - NX * a2) + a2);
}
float bsdf_ggx(vec3 N, vec3 L, vec3 V, float roughness)
{
float a2 = sqr(roughness);
vec3 H = normalize(L + V);
float NH = max(dot(N, H), 1e-8);
float NL = max(dot(N, L), 1e-8);
float NV = max(dot(N, V), 1e-8);
/* Doing RCP at the end */
float G = bxdf_ggx_smith_G1_opti(NV, a2) * bxdf_ggx_smith_G1_opti(NL, a2);
float D = bxdf_ggx_D_opti(NH, a2);
/* Denominator is canceled by G1_Smith */
/* bsdf = D * G / (4.0 * NL * NV); /* Reference function. */
/* NL term to fit Cycles. NOTE(fclem): Not sure what it */
return NL * a2 / (D * G);
}
float btdf_ggx(vec3 N, vec3 L, vec3 V, float roughness, float eta)
{
float a2 = sqr(roughness);
vec3 H = normalize(L + V);
float NH = max(dot(N, H), 1e-8);
float NL = max(dot(N, L), 1e-8);
float NV = max(dot(N, V), 1e-8);
float VH = max(dot(V, H), 1e-8);
float LH = max(dot(L, H), 1e-8);
float Ht2 = sqr(eta * LH + VH);
/* Doing RCP at the end */
float G = bxdf_ggx_smith_G1_opti(NV, a2) * bxdf_ggx_smith_G1_opti(NL, a2);
float D = bxdf_ggx_D_opti(NH, a2);
/* btdf = abs(VH*LH) * ior^2 * D * G(V) * G(L) / (Ht2 * NV) */
return abs(VH * LH) * sqr(eta) * 4.0 * a2 / (D * G * (Ht2 * NV));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lambert
*
* Not really a microfacet model but fits this file.
* \{ */
float bsdf_lambert(vec3 N, vec3 L)
{
return saturate(dot(N, L));
}
/** \} */

@ -0,0 +1,219 @@
/**
* Sampling of Normal Distribution Function for various BxDF.
**/
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Microfacet GGX distribution
* \{ */
#define GGX_USE_VISIBLE_NORMAL 1
float sample_pdf_ggx_reflect(float NH, float NV, float VH, float G1, float alpha)
{
float a2 = sqr(alpha);
#if GGX_USE_VISIBLE_NORMAL
return G1 * VH * bxdf_ggx_D(NH, a2) / NV;
#else
return NH * bxdf_ggx_D(NH, a2);
#endif
}
float sample_pdf_ggx_refract(
float NH, float NV, float VH, float LH, float G1, float alpha, float eta)
{
float a2 = sqr(alpha);
float D = bxdf_ggx_D_opti(NH, a2);
float Ht2 = sqr(eta * LH + VH);
return VH * abs(LH) * ((G1 * D) * sqr(eta) * a2 / (D * NV * Ht2));
}
/**
* Returns a tangent space microfacet normal following the GGX distribution.
*
* \param rand: random point on the unit cylinder (result of sample_cylinder).
* \param alpha: roughness parameter.
* \param tV: tangent space view vector.
* \param G1: output G1 factor to be reused.
*/
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt, out float G1)
{
#if GGX_USE_VISIBLE_NORMAL
/* From:
* "A Simpler and Exact Sampling Routine for the GGXDistribution of Visible Normals"
* by Eric Heitz.
* http://jcgt.org/published/0007/04/01/slides.pdf
* View vector is expected to be in tangent space. */
/* Stretch view. */
vec3 Th, Bh, Vh = normalize(vec3(alpha * Vt.xy, Vt.z));
make_orthonormal_basis(Vh, Th, Bh);
/* Sample point with polar coordinates (r, phi). */
float r = sqrt(rand.x);
float x = r * rand.y;
float y = r * rand.z;
float s = 0.5 * (1.0 + Vh.z);
G1 = 1.0 / s;
y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;
float z = sqrt(saturate(1.0 - x * x - y * y));
/* Compute normal. */
vec3 Hh = x * Th + y * Bh + z * Vh;
/* Unstretch. */
vec3 Ht = normalize(vec3(alpha * Hh.xy, saturate(Hh.z)));
/* Microfacet Normal. */
return Ht;
#else
/* Theta is the cone angle. */
float z = sqrt((1.0 - rand.x) / (1.0 + sqr(alpha) * rand.x - rand.x)); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
/* Microfacet Normal */
return vec3(x, y, z);
#endif
}
/**
* Returns a reflected ray direction following the GGX distribution.
*
* \param rand: random point on the unit cylinder (result of sample_cylinder).
* \param alpha: roughness parameter.
* \param V: View vector.
* \param N: Normal vector.
* \param T: Tangent vector.
* \param B: Bitangent vector.
* \return pdf: associated pdf for a reflection ray. 0 if ray is invalid.
*/
vec3 sample_ggx_reflect(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
float G1;
vec3 tV = world_to_tangent(V, N, T, B);
vec3 tH = sample_ggx(rand, alpha, tV, G1);
float NH = saturate(tH.z);
float NV = saturate(tV.z);
float VH = dot(tV, tH);
vec3 H = tangent_to_world(tH, N, T, B);
if (VH > 0.0) {
vec3 L = reflect(-V, H);
pdf = sample_pdf_ggx_reflect(NH, NV, VH, G1, alpha);
return L;
}
pdf = 0.0;
return vec3(1.0, 0.0, 0.0);
}
/**
* Returns a refracted ray direction following the GGX distribution.
*
* \param rand: random point on the unit cylinder (result of sample_cylinder).
* \param alpha: roughness parameter.
* \param V: View vector.
* \param N: Normal vector.
* \param T: Tangent vector.
* \param B: Bitangent vector.
* \return pdf_reflect: associated pdf for a reflection ray.
*/
vec3 sample_ggx_refract(
vec3 rand, float alpha, float ior, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
float G1;
vec3 Vt = world_to_tangent(V, N, T, B);
vec3 Ht = sample_ggx(rand, alpha, Vt, G1);
float NH = saturate(Ht.z);
float NV = saturate(Vt.z);
float VH = dot(Vt, Ht);
vec3 H = tangent_to_world(Ht, N, T, B);
if (VH > 0.0) {
vec3 L = refract(-V, H, 1.0 / ior);
float LH = dot(L, H);
pdf = sample_pdf_ggx_refract(NH, NV, VH, LH, G1, alpha, ior);
return L;
}
pdf = 0.0;
return vec3(1.0, 0.0, 0.0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform Hemisphere
* \{ */
float sample_pdf_uniform_hemisphere()
{
return 0.5 * M_1_PI;
}
vec3 sample_uniform_hemisphere(vec3 rand)
{
float z = rand.x; /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
return vec3(x, y, z);
}
vec3 sample_uniform_hemisphere(vec3 rand, vec3 N, vec3 T, vec3 B, out float pdf)
{
vec3 tH = sample_uniform_hemisphere(rand);
pdf = sample_pdf_uniform_hemisphere();
return tH * mat3(N, T, B);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cosine Hemisphere
* \{ */
float sample_pdf_cosine_hemisphere(float cos_theta)
{
return cos_theta * M_1_PI;
}
vec3 sample_cosine_hemisphere(vec3 rand)
{
float z = sqrt(max(1e-16, rand.x)); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
return vec3(x, y, z);
}
vec3 sample_cosine_hemisphere(vec3 rand, vec3 N, vec3 T, vec3 B, out float pdf)
{
vec3 tH = sample_cosine_hemisphere(rand);
pdf = sample_pdf_cosine_hemisphere(tH.z);
return tH * mat3(N, T, B);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform Cone sampling
* \{ */
vec3 sample_uniform_cone(vec3 rand, float angle)
{
float z = cos(angle * rand.x); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
return vec3(x, y, z);
}
vec3 sample_uniform_cone(vec3 rand, float angle, vec3 N, vec3 T, vec3 B)
{
vec3 tH = sample_uniform_cone(rand, angle);
/* TODO: pdf? */
return tH * mat3(N, T, B);
}
/** \} */

@ -16,7 +16,7 @@ void main()
{
ivec2 texel = ivec2(gl_FragCoord.xy);
float depth = texelFetch(hiz_tx, ivec2(gl_FragCoord.xy), 0).r;
float depth = texelFetch(hiz_tx, texel, 0).r;
vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth);
/* TODO(fclem): High precision derivative. */
@ -38,6 +38,11 @@ void main()
diffuse_data.sss_id = 0u;
float thickness = 0.0;
ClosureRefraction refraction_data;
refraction_data.N = diffuse_data.N;
refraction_data.roughness = gbuffer_1_packed.z;
refraction_data.ior = 0.0; /* Not needed. */
bool is_refraction = gbuffer_is_refraction(gbuffer_1_packed);
if (is_refraction) {
/* Still evaluate the diffuse light so that dithered SSS / Refraction combination still
@ -52,12 +57,14 @@ void main()
}
vec3 diffuse_light = vec3(0.0);
vec3 reflection_light = vec3(0.0);
float shadow = 1.0;
#ifdef DO_REFLECTION_PROBES
reflection_probes_eval(reflection_data, P, V, reflection_light);
vec3 reflection_light = imageLoad(indirect_reflection_img, texel).rgb;
vec3 refraction_light = imageLoad(indirect_refraction_img, texel).rgb;
#else
vec3 reflection_light = vec3(0.0);
vec3 refraction_light = vec3(0.0);
#endif
float shadow = 1.0;
lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light);
@ -72,35 +79,32 @@ void main()
reflection_light,
shadow);
/* Apply color and output lighting to render-passes. */
vec4 color_0_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 0), 0);
vec4 color_1_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 1), 0);
reflection_data.color = gbuffer_color_unpack(color_0_packed);
refraction_data.color = is_refraction ? gbuffer_color_unpack(color_1_packed) : vec3(0.0);
diffuse_data.color = is_refraction ? vec3(0.0) : gbuffer_color_unpack(color_1_packed);
/* Light passes. */
if (rp_buf.diffuse_light_id >= 0) {
imageStore(rp_color_img, ivec3(texel, rp_buf.diffuse_light_id), vec4(diffuse_light, 1.0));
}
if (rp_buf.specular_light_id >= 0) {
vec3 specular_light = reflection_light + refraction_light;
imageStore(rp_color_img, ivec3(texel, rp_buf.specular_light_id), vec4(specular_light, 1.0));
}
if (rp_buf.shadow_id >= 0) {
imageStore(rp_value_img, ivec3(texel, rp_buf.shadow_id), vec4(shadow));
}
if (is_last_eval_pass) {
/* Apply color and output lighting to render-passes. */
vec4 color_0_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 0), 0);
vec4 color_1_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 1), 0);
reflection_data.color = gbuffer_color_unpack(color_0_packed);
diffuse_data.color = gbuffer_color_unpack(color_1_packed);
if (is_refraction) {
diffuse_data.color = vec3(0.0);
}
/* Light passes. */
if (rp_buf.diffuse_light_id >= 0) {
imageStore(rp_color_img, ivec3(texel, rp_buf.diffuse_light_id), vec4(diffuse_light, 1.0));
}
if (rp_buf.specular_light_id >= 0) {
imageStore(
rp_color_img, ivec3(texel, rp_buf.specular_light_id), vec4(reflection_light, 1.0));
}
if (rp_buf.shadow_id >= 0) {
imageStore(rp_value_img, ivec3(texel, rp_buf.shadow_id), vec4(shadow));
}
/** NOTE: AO is done on its own pass. */
diffuse_light *= diffuse_data.color;
reflection_light *= reflection_data.color;
refraction_light *= refraction_data.color;
/* Add radiance to combined pass. */
out_radiance = vec4(diffuse_light + reflection_light, 0.0);
out_radiance = vec4(diffuse_light + reflection_light + refraction_light, 0.0);
out_transmittance = vec4(1.0);
}
else {
@ -110,35 +114,6 @@ void main()
/* Output object ID for sub-surface screen space processing. */
diffuse_radiance.w = gbuffer_object_id_f16_pack(diffuse_data.sss_id);
imageStore(out_diffuse_light_img, texel, diffuse_radiance);
imageStore(out_specular_light_img, texel, vec4(reflection_light, 0.0));
imageStore(out_specular_light_img, texel, vec4(reflection_light + reflection_light, 0.0));
}
/* Apply color and output lighting to render-passes. */
vec4 color_0_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 0), 0);
vec4 color_1_packed = texelFetch(gbuffer_color_tx, ivec3(texel, 1), 0);
reflection_data.color = gbuffer_color_unpack(color_0_packed);
diffuse_data.color = gbuffer_color_unpack(color_1_packed);
if (is_refraction) {
diffuse_data.color = vec3(0.0);
}
/* Light passes. */
if (rp_buf.diffuse_light_id >= 0) {
imageStore(rp_color_img, ivec3(texel, rp_buf.diffuse_light_id), vec4(diffuse_light, 1.0));
}
if (rp_buf.specular_light_id >= 0) {
imageStore(rp_color_img, ivec3(texel, rp_buf.specular_light_id), vec4(reflection_light, 1.0));
}
if (rp_buf.shadow_id >= 0) {
imageStore(rp_value_img, ivec3(texel, rp_buf.shadow_id), vec4(shadow));
}
/* TODO: AO. */
diffuse_light *= diffuse_data.color;
reflection_light *= reflection_data.color;
/* Add radiance to combined pass. */
out_radiance = vec4(diffuse_light + reflection_light, 0.0);
out_transmittance = vec4(1.0);
}

@ -0,0 +1,189 @@
/**
* Bilateral filtering of denoised raytraced radiance.
*
* Dispatched at fullres using a tile list.
*
* Input: Temporaly Stabilized Radiance, Stabilized Variance
* Ouput: Denoised radiance
*
* Following "Stochastic All The Things: Raytracing in Hybrid Real-Time Rendering"
* by Tomasz Stachowiak
* https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
float bilateral_depth_weight(vec3 center_N, vec3 center_P, vec3 sample_P)
{
vec4 center_plane_eq = vec4(center_N, -dot(center_N, center_P));
/* Only compare distance to the center plane formed by the normal. */
float depth_delta = dot(center_plane_eq, vec4(sample_P, 1.0));
/* TODO(fclem): Scene parameter. This is dependent on scene scale. */
const float scale = 10000.0;
float weight = exp2(-scale * sqr(depth_delta));
return weight;
}
float bilateral_spatial_weight(float sigma, vec2 offset_from_center)
{
/* From https://github.com/tranvansang/bilateral-filter/blob/master/fshader.frag */
float fac = -1.0 / square_f(sigma);
/* Take two standard deviation. */
fac *= 2.0;
float weight = exp2(fac * length_squared(offset_from_center));
return weight;
}
float bilateral_normal_weight(vec3 center_N, vec3 sample_N)
{
float facing_ratio = dot(center_N, sample_N);
float weight = saturate(pow8f(facing_ratio));
return weight;
}
/* In order to remove some more fireflies, "tonemap" the color samples during the accumulation. */
vec3 to_accumulation_space(vec3 color)
{
return color / (1.0 + dot(color, vec3(1.0)));
}
vec3 from_accumulation_space(vec3 color)
{
return color / (1.0 - dot(color, vec3(1.0)));
}
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureDiffuse closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 1), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
}
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureRefraction closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 1), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
if (gbuffer_is_refraction(data_in)) {
closure.roughness = data_in.z;
closure.ior = gbuffer_ior_unpack(data_in.w);
}
else {
closure.roughness = 1.0;
closure.ior = 1.1;
}
}
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureReflection closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 0), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
closure.roughness = data_in.z;
}
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel_fullres = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
vec2 center_uv = vec2(texel_fullres) * raytrace_buf.full_resolution_inv;
float center_depth = texelFetch(hiz_tx, texel_fullres, 0).r;
vec3 center_P = get_world_space_from_depth(center_uv, center_depth);
#if defined(RAYTRACE_DIFFUSE)
ClosureDiffuse sample_closure, center_closure;
#elif defined(RAYTRACE_REFRACT)
ClosureRefraction sample_closure, center_closure;
#elif defined(RAYTRACE_REFLECT)
ClosureReflection sample_closure, center_closure;
#else
# error
#endif
gbuffer_load_closure_data(gbuffer_closure_tx, texel_fullres, center_closure);
float roughness = center_closure.roughness;
float variance = imageLoad(in_variance_img, texel_fullres).r;
vec3 in_radiance = imageLoad(in_radiance_img, texel_fullres).rgb;
bool is_background = (center_depth == 0.0);
bool is_smooth = (roughness < 0.05);
bool is_low_variance = (variance < 0.05);
bool is_high_variance = (variance > 0.5);
/* Width of the box filter in pixels. */
float filter_size_factor = saturate(roughness * 8.0);
float filter_size = mix(0.0, 9.0, filter_size_factor);
uint sample_count = uint(mix(1.0, 10.0, filter_size_factor) * (is_high_variance ? 1.5 : 1.0));
if (is_smooth || is_background || is_low_variance) {
/* Early out cases. */
imageStore(out_radiance_img, texel_fullres, vec4(in_radiance, 0.0));
return;
}
vec2 noise = interlieved_gradient_noise(vec2(texel_fullres) + 0.5, vec2(3, 5), vec2(0.0));
noise += sampling_rng_2D_get(SAMPLING_RAYTRACE_W);
vec3 accum_radiance = to_accumulation_space(in_radiance);
float accum_weight = 1.0;
/* We want to resize the blur depending on the roughness and keep the amount of sample low.
* So we do a random sampling around the center point. */
for (uint i = 0u; i < sample_count; i++) {
/* Essentially a box radius overtime. */
vec2 offset_f = (fract(hammersley_2d(i, sample_count) + noise) - 0.5) * filter_size;
ivec2 offset = ivec2(floor(offset_f + 0.5));
ivec2 sample_texel = texel_fullres + offset;
ivec2 sample_tile = sample_texel / RAYTRACE_GROUP_SIZE;
/* Make sure the sample has been processed and do not contain garbage data. */
bool unprocessed_tile = imageLoad(tile_mask_img, sample_tile).r == 0;
if (unprocessed_tile) {
continue;
}
float sample_depth = texelFetch(hiz_tx, sample_texel, 0).r;
vec2 sample_uv = vec2(sample_texel) * raytrace_buf.full_resolution_inv;
vec3 sample_P = get_world_space_from_depth(sample_uv, sample_depth);
/* Background case. */
if (sample_depth == 0.0) {
continue;
}
gbuffer_load_closure_data(gbuffer_closure_tx, sample_texel, sample_closure);
float depth_weight = bilateral_depth_weight(center_closure.N, center_P, sample_P);
float spatial_weight = bilateral_spatial_weight(filter_size, vec2(offset));
float normal_weight = bilateral_normal_weight(center_closure.N, sample_closure.N);
float weight = depth_weight * spatial_weight * normal_weight;
vec3 radiance = imageLoad(in_radiance_img, sample_texel).rgb;
/* Do not gather unprocessed pixels. */
if (all(equal(in_radiance, FLT_11_11_10_MAX))) {
continue;
}
accum_radiance += to_accumulation_space(radiance) * weight;
accum_weight += weight;
}
vec3 out_radiance = accum_radiance * safe_rcp(accum_weight);
out_radiance = from_accumulation_space(out_radiance);
imageStore(out_radiance_img, texel_fullres, vec4(out_radiance, 0.0));
}

@ -0,0 +1,233 @@
/**
* Spatial ray reuse. Denoise raytrace result using ratio estimator.
*
* Input: Ray direction * hit time, Ray radiance, Ray hit depth
* Ouput: Ray radiance reconstructed, Mean Ray hit depth, Radiance Variance
*
* Shader is specialized depending on the type of ray to denoise.
*
* Following "Stochastic All The Things: Raytracing in Hybrid Real-Time Rendering"
* by Tomasz Stachowiak
* https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing
*/
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureDiffuse closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 1), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
}
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureRefraction closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 1), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
if (gbuffer_is_refraction(data_in)) {
closure.roughness = data_in.z;
closure.ior = gbuffer_ior_unpack(data_in.w);
}
else {
closure.roughness = 1.0;
closure.ior = 1.1;
}
}
void gbuffer_load_closure_data(sampler2DArray gbuffer_closure_tx,
ivec2 texel,
out ClosureReflection closure)
{
vec4 data_in = texelFetch(gbuffer_closure_tx, ivec3(texel, 0), 0);
closure.N = gbuffer_normal_unpack(data_in.xy);
closure.roughness = data_in.z;
}
float bxdf_eval(ClosureDiffuse closure, vec3 L, vec3 V)
{
return bsdf_lambert(closure.N, L);
}
float bxdf_eval(ClosureRefraction closure, vec3 L, vec3 V)
{
return btdf_ggx(closure.N, L, V, closure.roughness, closure.ior);
}
float bxdf_eval(ClosureReflection closure, vec3 L, vec3 V)
{
return bsdf_ggx(closure.N, L, V, closure.roughness);
}
void neighbor_tile_mask_bit_set(inout uint tile_mask, ivec2 offset)
{
/* Only valid for a 3x3 neighborhood. */
offset += 1;
uint shift = offset.x + (offset.y << 2u);
tile_mask |= 1u << shift;
}
bool neighbor_tile_mask_bit_get(uint tile_mask, ivec2 offset)
{
offset += 1;
uint shift = offset.x + (offset.y << 2u);
return flag_test(tile_mask, 1u << shift);
}
#if defined(RAYTRACE_DIFFUSE)
# define ClosureT ClosureDiffuse
# define CLOSURE_ACTIVE eClosureBits(CLOSURE_DIFFUSE)
#elif defined(RAYTRACE_REFRACT)
# define ClosureT ClosureRefraction
# define CLOSURE_ACTIVE eClosureBits(CLOSURE_REFRACTION)
#elif defined(RAYTRACE_REFLECT)
# define ClosureT ClosureReflection
# define CLOSURE_ACTIVE eClosureBits(CLOSURE_REFLECTION)
#else
# error
#endif
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel_fullres = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel = (texel_fullres) / raytrace_buf.resolution_scale;
if (raytrace_buf.skip_denoise) {
imageStore(out_radiance_img, texel_fullres, imageLoad(ray_radiance_img, texel));
return;
}
/* Store invalid neighbor tiles to avoid sampling them in the resampling loop. */
uint invalid_neighbor_tile_mask = 0u;
/* Clear neighbor tiles that will not be processed. */
/* TODO(fclem): Optimize this. We don't need to clear the whole ring. */
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
if (x == 0 && y == 0) {
continue;
}
ivec2 tile_coord_neighbor = ivec2(tile_coord) + ivec2(x, y);
if (!in_image_range(tile_coord_neighbor, tile_mask_img)) {
neighbor_tile_mask_bit_set(invalid_neighbor_tile_mask, ivec2(x, y));
continue;
}
bool tile_is_unused = imageLoad(tile_mask_img, tile_coord_neighbor).r == 0;
if (tile_is_unused) {
ivec2 texel_fullres_neighbor = texel_fullres + ivec2(x, y) * int(tile_size);
imageStore(out_radiance_img, texel_fullres_neighbor, vec4(FLT_11_11_10_MAX, 0.0));
imageStore(out_variance_img, texel_fullres_neighbor, vec4(0.0));
imageStore(out_hit_depth_img, texel_fullres_neighbor, vec4(0.0));
neighbor_tile_mask_bit_set(invalid_neighbor_tile_mask, ivec2(x, y));
}
}
}
bool valid_texel = in_texture_range(texel_fullres, stencil_tx);
uint closure_bits = (!valid_texel) ? 0u : texelFetch(stencil_tx, texel_fullres, 0).r;
if (!flag_test(closure_bits, CLOSURE_ACTIVE)) {
imageStore(out_radiance_img, texel_fullres, vec4(FLT_11_11_10_MAX, 0.0));
imageStore(out_variance_img, texel_fullres, vec4(0.0));
imageStore(out_hit_depth_img, texel_fullres, vec4(0.0));
return;
}
vec2 uv = (vec2(texel_fullres) + 0.5) * raytrace_buf.full_resolution_inv;
vec3 V = transform_direction(ViewMatrixInverse, get_view_vector_from_screen_uv(uv));
ClosureT closure;
gbuffer_load_closure_data(gbuffer_closure_tx, texel_fullres, closure);
uint sample_count = 16u;
float filter_size = 9.0;
#if defined(RAYTRACE_REFRACT) || defined(RAYTRACE_REFLECT)
float filter_size_factor = saturate(closure.roughness * 8.0);
sample_count = 1u + uint(15.0 * filter_size_factor + 0.5);
/* NOTE: filter_size should never be greater than twice RAYTRACE_GROUP_SIZE. Otherwise, the
* reconstruction can becomes ill defined since we don't know if further tiles are valids. */
filter_size = 12.0 * sqrt(filter_size_factor);
if (raytrace_buf.resolution_scale > 1) {
/* Filter at least 1 trace pixel to fight the undersampling. */
filter_size = max(filter_size, 3.0);
sample_count = max(sample_count, 5u);
}
/* NOTE: Roughness is squared now. */
closure.roughness = max(1e-3, sqr(closure.roughness));
#endif
vec2 noise = utility_tx_fetch(utility_tx, vec2(texel_fullres), UTIL_BLUE_NOISE_LAYER).ba;
noise += sampling_rng_1D_get(SAMPLING_CLOSURE);
vec3 rgb_moment = vec3(0.0);
vec3 radiance_accum = vec3(0.0);
float weight_accum = 0.0;
float closest_hit_time = 1.0e10;
for (uint i = 0u; i < sample_count; i++) {
vec2 offset_f = (fract(hammersley_2d(i, sample_count) + noise) - 0.5) * filter_size;
ivec2 offset = ivec2(floor(offset_f + 0.5));
ivec2 sample_texel = texel + offset;
/* Reject samples outside of valid neighbor tiles. */
ivec2 sample_tile = ivec2(sample_texel * raytrace_buf.resolution_scale) / int(tile_size);
ivec2 sample_tile_relative = sample_tile - ivec2(tile_coord);
if (neighbor_tile_mask_bit_get(invalid_neighbor_tile_mask, sample_tile_relative)) {
continue;
}
vec4 ray_data = imageLoad(ray_data_img, sample_texel);
float ray_time = imageLoad(ray_time_img, sample_texel).r;
vec4 ray_radiance = imageLoad(ray_radiance_img, sample_texel);
vec3 ray_direction = ray_data.xyz;
float ray_pdf_inv = ray_data.w;
/* Skip invalid pixels. */
if (ray_pdf_inv == 0.0) {
continue;
}
closest_hit_time = min(closest_hit_time, ray_time);
/* Slide 54. */
/* TODO(fclem): Apparently, ratio estimator should be pdf_bsdf / pdf_ray. */
float weight = bxdf_eval(closure, ray_direction, V) * ray_pdf_inv;
radiance_accum += ray_radiance.rgb * weight;
weight_accum += weight;
rgb_moment += sqr(ray_radiance.rgb) * weight;
}
float inv_weight = safe_rcp(weight_accum);
radiance_accum *= inv_weight;
/* Use radiance sum as signal mean. */
vec3 rgb_mean = radiance_accum;
rgb_moment *= inv_weight;
vec3 rgb_variance = abs(rgb_moment - sqr(rgb_mean));
float hit_variance = max_v3(rgb_variance);
float scene_z = get_view_z_from_depth(texelFetch(hiz_tx, texel_fullres, 0).r);
float hit_depth = get_depth_from_view_z(scene_z - closest_hit_time);
imageStore(out_radiance_img, texel_fullres, vec4(radiance_accum, 0.0));
imageStore(out_variance_img, texel_fullres, vec4(hit_variance));
imageStore(out_hit_depth_img, texel_fullres, vec4(hit_depth));
}

@ -0,0 +1,212 @@
/**
* Temporal Reprojection and accumulation of denoised raytraced radiance.
*
* Dispatched at fullres using a tile list.
*
* Input: Spatialy denoised radiance, Variance, Hit depth
* Ouput: Stabilized Radiance, Stabilized Variance
*
* Following "Stochastic All The Things: Raytracing in Hybrid Real-Time Rendering"
* by Tomasz Stachowiak
* https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing
*/
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
struct LocalStatistics {
vec3 mean;
vec3 moment;
vec3 variance;
vec3 deviation;
vec3 clamp_min;
vec3 clamp_max;
};
LocalStatistics local_statistics_get(ivec2 texel, vec3 center_radiance)
{
/* Build Local statistics (slide 46). */
LocalStatistics result;
result.mean = center_radiance;
result.variance = center_radiance;
float weight_accum = 1.0;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
if (x == 0 && y == 0) {
continue;
}
ivec2 neighbor_texel = texel + ivec2(x, y);
if (!in_image_range(neighbor_texel, in_radiance_img)) {
continue;
}
vec3 radiance = imageLoad(in_radiance_img, neighbor_texel).rgb;
/* Exclude unprocessed pixels. */
if (all(equal(radiance, FLT_11_11_10_MAX))) {
continue;
}
/* Weight corners less to avoid box artifacts.
* Same idea as in "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014
* (Slide 32) Simple clamp to min/max of 8 neighbors results in 3x3 box artifacts. */
float weight = (x == y) ? 0.25 : 1.0;
/* Use YCoCg for clamping and accumulation to avoid color shift artifacts. */
vec3 radiance_YCoCg = colorspace_YCoCg_from_scene_linear(radiance.rgb);
result.mean += radiance_YCoCg;
result.moment += square_f(radiance_YCoCg);
weight_accum += 1.0;
}
}
float inv_weight = safe_rcp(weight_accum);
result.mean *= inv_weight;
result.moment *= inv_weight;
result.variance = abs(result.moment - square_f(result.mean));
result.deviation = max(vec3(1e-4), sqrt(result.variance));
result.clamp_min = result.mean - result.deviation;
result.clamp_max = result.mean + result.deviation;
return result;
}
vec4 bilinear_weights_from_subpixel_coord(vec2 co)
{
/* From top left in clockwise order. */
vec4 weights;
weights.x = (1.0 - co.x) * co.y;
weights.y = co.x * co.y;
weights.z = co.x * (1.0 - co.y);
weights.w = (1.0 - co.x) * (1.0 - co.y);
return weights;
}
vec4 radiance_history_fetch(ivec2 texel, float bilinear_weight)
{
/* Out of history view. Return sample without weight. */
if (!in_texture_range(texel, radiance_history_tx)) {
return vec4(0.0);
}
ivec2 history_tile = texel / RAYTRACE_GROUP_SIZE;
/* Fetch previous tilemask to avoid loading invalid data. */
bool is_valid_history = texelFetch(tilemask_history_tx, history_tile, 0).r != 0;
/* Exclude unprocessed pixels. */
if (!is_valid_history) {
return vec4(0.0);
}
vec3 history_radiance = texelFetch(radiance_history_tx, texel, 0).rgb;
/* Exclude unprocessed pixels. */
if (all(equal(history_radiance, FLT_11_11_10_MAX))) {
return vec4(0.0);
}
return vec4(history_radiance * bilinear_weight, bilinear_weight);
}
vec4 radiance_history_sample(vec3 P, LocalStatistics local)
{
vec2 uv = project_point(raytrace_buf.history_persmat, P).xy * 0.5 + 0.5;
/* FIXME(fclem): Find why we need this half pixel offset. */
vec2 texel_co = uv * vec2(textureSize(radiance_history_tx, 0).xy) - 0.5;
vec4 bilinear_weights = bilinear_weights_from_subpixel_coord(fract(texel_co));
ivec2 texel = ivec2(floor(texel_co));
/* Radiance needs to be manually interpolated because any pixel might contain invalid data. */
vec4 history_radiance;
history_radiance = radiance_history_fetch(texel + ivec2(0, 1), bilinear_weights.x);
history_radiance += radiance_history_fetch(texel + ivec2(1, 1), bilinear_weights.y);
history_radiance += radiance_history_fetch(texel + ivec2(1, 0), bilinear_weights.z);
history_radiance += radiance_history_fetch(texel + ivec2(0, 0), bilinear_weights.w);
/* Use YCoCg for clamping and accumulation to avoid color shift artifacts. */
vec4 history_radiance_YCoCg;
history_radiance_YCoCg.rgb = colorspace_YCoCg_from_scene_linear(history_radiance.rgb);
history_radiance_YCoCg.a = history_radiance.a;
/* Weighted contribution (slide 46). */
vec3 dist = abs(history_radiance_YCoCg.rgb - local.mean) / local.deviation;
float weight = exp2(-4.0 * dot(dist, vec3(1.0)));
return history_radiance_YCoCg * weight;
}
vec2 variance_history_sample(vec3 P)
{
vec2 uv = project_point(raytrace_buf.history_persmat, P).xy * 0.5 + 0.5;
if (!in_range_exclusive(uv, vec2(0.0), vec2(1.0))) {
/* Out of history view. Return sample without weight. */
return vec2(0.0);
}
float history_variance = texture(variance_history_tx, uv).r;
ivec2 history_texel = ivec2(floor(uv * vec2(textureSize(variance_history_tx, 0).xy)));
ivec2 history_tile = history_texel / RAYTRACE_GROUP_SIZE;
/* Fetch previous tilemask to avoid loading invalid data. */
bool is_valid_history = texelFetch(tilemask_history_tx, history_tile, 0).r != 0;
if (is_valid_history) {
return vec2(history_variance, 1.0);
}
return vec2(0.0);
}
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel_fullres = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
vec2 uv = (vec2(texel_fullres) + 0.5) * raytrace_buf.full_resolution_inv;
float in_variance = imageLoad(in_variance_img, texel_fullres).r;
vec3 in_radiance = imageLoad(in_radiance_img, texel_fullres).rgb;
if (all(equal(in_radiance, FLT_11_11_10_MAX))) {
/* Early out on pixels that were marked unprocessed by the previous pass. */
imageStore(out_radiance_img, texel_fullres, vec4(FLT_11_11_10_MAX, 0.0));
imageStore(out_variance_img, texel_fullres, vec4(0.0));
return;
}
LocalStatistics local = local_statistics_get(texel_fullres, in_radiance);
/* Radiance. */
/* Surface reprojection. */
/* TODO(fclem): Use per pixel velocity. Is this worth it? */
float scene_depth = texelFetch(hiz_tx, texel_fullres, 0).r;
vec3 P = get_world_space_from_depth(uv, scene_depth);
vec4 history_radiance = radiance_history_sample(P, local);
/* Reflection reprojection. */
float hit_depth = imageLoad(hit_depth_img, texel_fullres).r;
vec3 P_hit = get_world_space_from_depth(uv, hit_depth);
history_radiance += radiance_history_sample(P_hit, local);
/* Finalize accumulation. */
history_radiance *= safe_rcp(history_radiance.w);
/* Clamp resulting history radiance (slide 47). */
history_radiance.rgb = clamp(history_radiance.rgb, local.clamp_min, local.clamp_max);
/* Go back from YCoCg for final blend. */
history_radiance.rgb = colorspace_scene_linear_from_YCoCg(history_radiance.rgb);
/* Blend history with new radiance. */
float mix_fac = (history_radiance.w > 1e-3) ? 0.97 : 0.0;
/* Reduce blend factor to improve low rougness reflections. Use variance instead for speed. */
mix_fac *= mix(0.75, 1.0, saturate(in_variance * 20.0));
vec3 out_radiance = mix(safe_color(in_radiance), safe_color(history_radiance.rgb), mix_fac);
/* This is feedback next frame as radiance_history_tx. */
imageStore(out_radiance_img, texel_fullres, vec4(out_radiance, 0.0));
/* Variance. */
/* Reflection reprojection. */
vec2 history_variance = variance_history_sample(P_hit);
/* Blend history with new variance. */
float mix_variance_fac = (history_variance.y == 0.0) ? 0.0 : 0.90;
float out_variance = mix(in_variance, history_variance.x, mix_variance_fac);
/* This is feedback next frame as variance_history_tx. */
imageStore(out_variance_img, texel_fullres, vec4(out_variance));
}

@ -0,0 +1,77 @@
/**
* Generate Ray direction along with other data that are then used
* by the next pass to trace the rays.
*/
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_generate_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel_fullres = texel * raytrace_buf.resolution_scale + raytrace_buf.resolution_bias;
bool valid_texel = in_texture_range(texel_fullres, stencil_tx);
uint closure_bits = (!valid_texel) ? 0u : texelFetch(stencil_tx, texel_fullres, 0).r;
#if defined(RAYTRACE_REFLECT)
ClosureReflection closure;
eClosureBits closure_active = CLOSURE_REFLECTION;
const int gbuf_layer = 0;
#elif defined(RAYTRACE_REFRACT)
ClosureRefraction closure;
eClosureBits closure_active = CLOSURE_REFRACTION;
const int gbuf_layer = 1;
#endif
if (!flag_test(closure_bits, closure_active)) {
imageStore(out_ray_data_img, texel, vec4(0.0));
return;
}
vec2 uv = (vec2(texel_fullres) + 0.5) / vec2(textureSize(stencil_tx, 0).xy);
vec3 V = transform_direction(ViewMatrixInverse, get_view_vector_from_screen_uv(uv));
vec2 noise = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).rg;
/* Load GBuffer data. */
vec4 gbuffer_packed = texelFetch(gbuffer_closure_tx, ivec3(texel_fullres, gbuf_layer), 0);
closure.N = gbuffer_normal_unpack(gbuffer_packed.xy);
#if defined(RAYTRACE_REFLECT)
closure.roughness = gbuffer_packed.z;
#elif defined(RAYTRACE_REFRACT)
if (gbuffer_is_refraction(gbuffer_packed)) {
closure.roughness = gbuffer_packed.z;
closure.ior = gbuffer_ior_unpack(gbuffer_packed.w);
}
else {
/* Avoid producing incorrect ray directions. */
closure.ior = 1.1;
closure.roughness = 0.0;
}
#endif
float pdf;
vec3 ray_direction = ray_generate_direction(noise.xy, closure, V, pdf);
#if defined(RAYTRACE_REFRACT)
if (gbuffer_is_refraction(gbuffer_packed) && closure_active != CLOSURE_REFRACTION) {
/* Discard incorect rays. */
pdf = 0.0;
}
#endif
/* Store inverse pdf to speedup denoising.
* Limit to the smallest non-0 value that the format can encode.
* Strangely it does not correspond to the IEEE spec. */
float inv_pdf = (pdf == 0.0) ? 0.0 : max(6e-8, 1.0 / pdf);
imageStore(out_ray_data_img, texel, vec4(ray_direction, inv_pdf));
}

@ -0,0 +1,69 @@
/**
* Ray generation routines for each BSDF types.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
/* Could maybe become parameters. */
#define RAY_BIAS_REFLECTION 0.02
#define RAY_BIAS_REFRACTION 0.02
#define RAY_BIAS_DIFFUSE 0.02
/* Returns viewspace ray. */
vec3 ray_generate_direction(vec2 noise, ClosureReflection reflection, vec3 V, out float pdf)
{
vec2 noise_offset = sampling_rng_2D_get(SAMPLING_RAYTRACE_U);
vec3 Xi = sample_cylinder(fract(noise_offset + noise));
/* Bias the rays so we never get really high energy rays almost parallel to the surface. */
Xi.x = Xi.x * (1.0 - RAY_BIAS_REFLECTION) + RAY_BIAS_REFLECTION;
float roughness_sqr = max(1e-3, sqr(reflection.roughness));
/* Gives *perfect* reflection for very small roughness. */
if (reflection.roughness < 0.0016) {
Xi = vec3(0.0);
}
vec3 T, B, N = reflection.N;
make_orthonormal_basis(N, T, B);
return sample_ggx_reflect(Xi, roughness_sqr, V, N, T, B, pdf);
}
/* Returns viewspace ray. */
vec3 ray_generate_direction(vec2 noise, ClosureRefraction refraction, vec3 V, out float pdf)
{
vec2 noise_offset = sampling_rng_2D_get(SAMPLING_RAYTRACE_U);
vec3 Xi = sample_cylinder(fract(noise_offset + noise));
/* Bias the rays so we never get really high energy rays almost parallel to the surface. */
Xi.x = Xi.x * (1.0 - RAY_BIAS_REFRACTION) + RAY_BIAS_REFRACTION;
float roughness_sqr = max(1e-3, sqr(refraction.roughness));
/* Gives *perfect* refraction for very small roughness. */
if (refraction.roughness < 0.0016) {
Xi = vec3(0.0);
}
vec3 T, B, N = refraction.N;
make_orthonormal_basis(N, T, B);
return sample_ggx_refract(Xi, roughness_sqr, refraction.ior, V, N, T, B, pdf);
}
/* Returns viewspace ray. */
vec3 ray_generate_direction(vec2 noise, ClosureDiffuse diffuse, vec3 V, out float pdf)
{
vec2 noise_offset = sampling_rng_2D_get(SAMPLING_RAYTRACE_U);
vec3 Xi = sample_cylinder(fract(noise_offset + noise));
/* Bias the rays so we never get really high energy rays almost parallel to the surface. */
Xi.x = Xi.x * (1.0 - RAY_BIAS_DIFFUSE) + RAY_BIAS_DIFFUSE;
vec3 T, B, N = diffuse.N;
make_orthonormal_basis(N, T, B);
return sample_cosine_hemisphere(Xi, N, T, B, pdf);
}

@ -0,0 +1,67 @@
/**
* This pass load Gbuffer data and output a mask of tiles to process.
* This mask is then processed by the compaction phase.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
shared uint tile_contains_glossy_rays;
/* Returns a blend factor between different irradiance fetching method for reflections. */
float ray_glossy_factor(float roughness)
{
/* TODO */
return 1.0;
}
void main()
{
if (all(equal(gl_LocalInvocationID, uvec3(0)))) {
/* Clear num_groups_x to 0 so that we can use it as counter in the compaction phase.
* Note that these writes are subject to race condition, but we write the same value
* from all workgroups. */
denoise_dispatch_buf.num_groups_x = 0u;
denoise_dispatch_buf.num_groups_y = 1u;
denoise_dispatch_buf.num_groups_z = 1u;
ray_dispatch_buf.num_groups_x = 0u;
ray_dispatch_buf.num_groups_y = 1u;
ray_dispatch_buf.num_groups_z = 1u;
/* Init shared variables. */
tile_contains_glossy_rays = 0;
}
barrier();
ivec2 texel = min(ivec2(gl_GlobalInvocationID.xy), textureSize(stencil_tx, 0) - 1);
eClosureBits closure_bits = eClosureBits(texelFetch(stencil_tx, texel, 0).r);
if (flag_test(closure_bits, raytrace_buf.closure_active)) {
int gbuffer_layer = raytrace_buf.closure_active == CLOSURE_REFRACTION ? 1 : 0;
vec4 gbuffer_packed = texelFetch(gbuffer_closure_tx, ivec3(texel, gbuffer_layer), 0);
float roughness = gbuffer_packed.z;
if (ray_glossy_factor(roughness) > 0.0) {
/* We don't care about race condition here. */
tile_contains_glossy_rays = 1;
}
}
barrier();
if (all(equal(gl_LocalInvocationID, uvec3(0)))) {
ivec2 tile_co = ivec2(gl_WorkGroupID.xy);
uint tile_mask = 0u;
if (tile_contains_glossy_rays > 0) {
tile_mask = 1u;
}
imageStore(tile_mask_img, tile_co, uvec4(tile_mask));
}
}

@ -0,0 +1,39 @@
/**
* This pass scans the tile mask generated by the classify step and output indirect dispatch args.
*
* Dispatched as one thread for each trace resolution tile.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
void main()
{
ivec2 tile = ivec2(gl_GlobalInvocationID.xy);
bool tracing_tile_is_used = false;
for (int x = 0; x < raytrace_buf.resolution_scale; x++) {
for (int y = 0; y < raytrace_buf.resolution_scale; y++) {
ivec2 full_res_tile = tile * raytrace_buf.resolution_scale + ivec2(x, y);
if (any(greaterThanEqual(full_res_tile, imageSize(tile_mask_img)))) {
continue;
}
bool denoise_tile_is_used = imageLoad(tile_mask_img, full_res_tile).r != 0u;
if (denoise_tile_is_used) {
/* Dispatch full resolution denoise tile. */
uint tile_index = atomicAdd(denoise_dispatch_buf.num_groups_x, 1u);
denoise_tiles_buf[tile_index] = packUvec2x16(uvec2(full_res_tile));
tracing_tile_is_used = true;
}
}
}
if (tracing_tile_is_used) {
/* Dispatch trace resolution tracing tile. */
uint tile_index = atomicAdd(ray_dispatch_buf.num_groups_x, 1u);
ray_tiles_buf[tile_index] = packUvec2x16(uvec2(tile));
}
}

@ -0,0 +1,103 @@
/**
* Use screen space tracing against depth buffer to find intersection with the scene.
*/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel_fullres = texel * raytrace_buf.resolution_scale + raytrace_buf.resolution_bias;
float depth = texelFetch(hiz_tx, texel_fullres, 0).r;
vec2 uv = (vec2(texel_fullres) + 0.5) * raytrace_buf.full_resolution_inv;
vec4 ray_data = imageLoad(ray_data_img, texel);
float ray_pdf_inv = ray_data.w;
if (ray_pdf_inv == 0.0) {
/* Invalid ray or pixels without ray. Do not trace. */
imageStore(ray_time_img, texel, vec4(0.0));
imageStore(ray_radiance_img, texel, vec4(0.0));
return;
}
Ray ray;
ray.origin = get_world_space_from_depth(uv, depth);
ray.direction = ray_data.xyz;
vec3 radiance = vec3(0.0);
float noise_offset = sampling_rng_1D_get(SAMPLING_RAYTRACE_W);
float rand_trace = interlieved_gradient_noise(vec2(texel), 5.0, noise_offset);
#if defined(RAYTRACE_REFLECT)
const bool discard_backface = true;
const bool allow_self_intersection = false;
#elif defined(RAYTRACE_REFRACT)
const bool discard_backface = false;
const bool allow_self_intersection = true;
#endif
/* TODO(fclem): Take IOR into account in the roughness LOD bias. */
/* TODO(fclem): pdf to roughness mapping is a crude approximation. Find something better. */
float roughness = saturate(sample_pdf_uniform_hemisphere() / ray_pdf_inv);
bool hit = false;
float hit_time = 0.0;
/* Transform the ray into viewspace. */
Ray ray_view;
ray_view.origin = transform_point(drw_view.viewmat, ray.origin);
ray_view.direction = transform_direction(drw_view.viewmat, ray.direction);
/* Extend the ray to cover the whole view. */
ray_view.direction *= 1e6;
/* FIXME. This should be 1e6 and replace the line above. But the ray clipping
* (raytrace_clip_ray_to_near_plane) is not taking it into account. */
ray_view.max_time = 1.0;
hit = raytrace_screen(raytrace_buf,
hiz_buf,
hiz_tx,
rand_trace,
roughness,
discard_backface,
allow_self_intersection,
ray_view);
if (hit) {
/* Evaluate radiance at hitpoint. */
// vec2 hit_uv = get_uvs_from_view(ray.origin + ray.direction);
// radiance = textureLod(radiance_tx, hit_uv, 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;
// }
ReflectionProbeData world_probe = reflection_probe_buf[0];
radiance = reflection_probes_sample(ray.direction, 0.0, world_probe).rgb;
hit_time = length(ray_view.direction);
}
else {
/* Fallback to nearest lightprobe. */
// radiance = lightprobe_cubemap_eval(ray.origin, ray.direction, roughness, rand_probe);
ReflectionProbeData world_probe = reflection_probe_buf[0];
radiance = reflection_probes_sample(ray.direction, 0.0, world_probe).rgb;
hit_time = 10000.0;
}
float luma = max(1e-8, max_v3(radiance));
radiance *= 1.0 - max(0.0, luma - raytrace_buf.brightness_clamp) / luma;
imageStore(ray_time_img, texel, vec4(hit_time));
imageStore(ray_radiance_img, texel, vec4(radiance, 0.0));
}

@ -0,0 +1,132 @@
/**
* Screen-space raytracing routine.
*
* Based on "Efficient GPU Screen-Space Ray Tracing"
* by Morgan McGuire & Michael Mara
* https://jcgt.org/published/0003/04/04/paper.pdf
*
* Many modifications were made for our own usage.
*/
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
/* Inputs expected to be in viewspace. */
void raytrace_clip_ray_to_near_plane(inout Ray ray)
{
float near_dist = get_view_z_from_depth(0.0);
if ((ray.origin.z + ray.direction.z) > near_dist) {
ray.direction *= abs((near_dist - ray.origin.z) / ray.direction.z);
}
}
/**
* Raytrace against the given hizbuffer heightfield.
*
* \param stride_rand: Random number in [0..1] range. Offset along the ray to avoid banding
* artifact when steps are too large.
* \param roughness: Determine how lower depth mipmaps are used to make the tracing faster. Lower
* roughness will use lower mipmaps.
* \param discard_backface: If true, raytrace will return false if we hit a surface from behind.
* \param allow_self_intersection: If false, raytrace will return false if the ray is not covering
* at least one pixel.
* \param ray: Viewspace ray. Direction premultiplied by maximum length.
*
* \return True if there is a valid intersection.
*/
#ifdef METAL_AMD_RAYTRACE_WORKAROUND
__attribute__((noinline))
#endif
bool raytrace_screen(RayTraceData rt_data,
HiZData hiz_data,
sampler2D hiz_tx,
float stride_rand,
float roughness,
const bool discard_backface,
const bool allow_self_intersection,
inout Ray ray)
{
/* Clip to near plane for perspective view where there is a singularity at the camera origin. */
if (ProjectionMatrix[3][3] == 0.0) {
raytrace_clip_ray_to_near_plane(ray);
}
/* NOTE: The 2.0 factor here is because we are applying it in. */
ScreenSpaceRay ssray = raytrace_screenspace_ray_create(
ray, 2.0 * rt_data.full_resolution_inv, rt_data.thickness);
/* Avoid no iteration. */
if (!allow_self_intersection && ssray.max_time < 1.1) {
/* Still output the clipped ray. */
vec3 hit_ssP = ssray.origin.xyz + ssray.direction.xyz * ssray.max_time;
vec3 hit_P = get_world_space_from_depth(hit_ssP.xy, saturate(hit_ssP.z));
ray.direction = hit_P - ray.origin;
return false;
}
ssray.max_time = max(1.1, ssray.max_time);
float prev_delta = 0.0, prev_time = 0.0;
float depth_sample = get_depth_from_view_z(ray.origin.z);
float delta = depth_sample - ssray.origin.z;
float lod_fac = saturate(fast_sqrt(roughness) * 2.0 - 0.4);
/* Cross at least one pixel. */
float t = 1.001, time = 1.001;
bool hit = false;
#ifdef METAL_AMD_RAYTRACE_WORKAROUND
bool hit_failsafe = true;
#endif
const int max_steps = 255;
for (int iter = 1; !hit && (time < ssray.max_time) && (iter < max_steps); iter++) {
float stride = 1.0 + float(iter) * rt_data.quality;
float lod = log2(stride) * lod_fac;
prev_time = time;
prev_delta = delta;
time = min(t + stride * stride_rand, ssray.max_time);
t += stride;
vec4 ss_p = ssray.origin + ssray.direction * time;
depth_sample = textureLod(hiz_tx, ss_p.xy * hiz_data.uv_scale, floor(lod)).r;
delta = depth_sample - ss_p.z;
/* Check if the ray is below the surface ... */
hit = (delta < 0.0);
/* ... and above it with the added thickness. */
hit = hit && (delta > ss_p.z - ss_p.w || abs(delta) < abs(ssray.direction.z * stride * 2.0));
#ifdef METAL_AMD_RAYTRACE_WORKAROUND
/* For workaround, perform discard backface and background check only within
* the iteration where the first successful ray intersection is registered.
* We flag failures to discard ray hits later. */
bool hit_valid = !(discard_backface && prev_delta < 0.0) && (depth_sample != 1.0);
if (hit && !hit_valid) {
hit_failsafe = false;
}
#endif
}
#ifndef METAL_AMD_RAYTRACE_WORKAROUND
/* Discard backface hits. */
hit = hit && !(discard_backface && prev_delta < 0.0);
/* Reject hit if background. */
hit = hit && (depth_sample != 1.0);
#endif
/* Refine hit using intersection between the sampled heightfield and the ray.
* This simplifies nicely to this single line. */
time = mix(prev_time, time, saturate(prev_delta / (prev_delta - delta)));
vec3 hit_ssP = ssray.origin.xyz + ssray.direction.xyz * time;
/* Set ray to where tracing ended. */
vec3 hit_P = get_world_space_from_depth(hit_ssP.xy, saturate(hit_ssP.z));
ray.direction = hit_P - ray.origin;
#ifdef METAL_AMD_RAYTRACE_WORKAROUND
/* Check failed ray flag to discard bad hits. */
if (!hit_failsafe) {
return false;
}
#endif
return hit;
}

@ -3,17 +3,18 @@
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
/**
* Screen-Space Raytracing functions.
* General purpose 3D ray.
*/
struct Ray {
vec3 origin;
/* Ray direction premultiplied by its maximum length. */
vec3 direction;
float max_time;
};
/* Screenspace ray ([0..1] "uv" range) where direction is normalize to be as small as one
* full-resolution pixel. The ray is also clipped to all frustum sides.
* Z component is device normalized Z (aka. depth buffer value).
* W component is device normalized Z + Thickness.
*/
struct ScreenSpaceRay {
vec4 origin;
@ -56,7 +57,23 @@ ScreenSpaceRay raytrace_screenspace_ray_create(Ray ray, vec2 pixel_size)
{
ScreenSpaceRay ssray;
ssray.origin.xyz = project_point(ProjectionMatrix, ray.origin);
ssray.direction.xyz = project_point(ProjectionMatrix, ray.origin + ray.direction);
ssray.direction.xyz = project_point(ProjectionMatrix, ray.origin + ray.direction * ray.max_time);
raytrace_screenspace_ray_finalize(ssray, pixel_size);
return ssray;
}
ScreenSpaceRay raytrace_screenspace_ray_create(Ray ray, vec2 pixel_size, float thickness)
{
ScreenSpaceRay ssray;
ssray.origin.xyz = project_point(ProjectionMatrix, ray.origin);
ssray.direction.xyz = project_point(ProjectionMatrix, ray.origin + ray.direction * ray.max_time);
/* Interpolate thickness in screen space.
* Calculate thickness further away to avoid near plane clipping issues. */
ssray.origin.w = get_depth_from_view_z(ray.origin.z - thickness);
ssray.direction.w = get_depth_from_view_z(ray.origin.z + ray.direction.z - thickness);
ssray.origin.w = ssray.origin.w * 2.0 - 1.0;
ssray.direction.w = ssray.direction.w * 2.0 - 1.0;
raytrace_screenspace_ray_finalize(ssray, pixel_size);
return ssray;

@ -1,5 +1,6 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
vec4 reflection_probe_eval(ClosureReflection reflection,
@ -28,7 +29,7 @@ vec4 reflection_probe_eval(ClosureReflection reflection,
vec3 T, B;
make_orthonormal_basis(reflection.N, T, B);
float pdf;
vec3 H = sample_ggx(Xi, roughness, V, reflection.N, T, B, pdf);
vec3 H = sample_ggx_reflect(Xi, roughness, V, reflection.N, T, B, pdf);
vec3 L = -reflect(V, H);
float NL = dot(reflection.N, L);

@ -5,7 +5,6 @@
**/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Sampling data.
@ -51,6 +50,12 @@ float interlieved_gradient_noise(vec2 pixel, float seed, float offset)
return fract(offset + 52.9829189 * fract(0.06711056 * pixel.x + 0.00583715 * pixel.y));
}
vec2 interlieved_gradient_noise(vec2 pixel, vec2 seed, vec2 offset)
{
return vec2(interlieved_gradient_noise(pixel, seed.x, offset.x),
interlieved_gradient_noise(pixel, seed.y, offset.y));
}
/* From: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html */
float van_der_corput_radical_inverse(uint bits)
{
@ -75,6 +80,14 @@ vec2 hammersley_2d(float i, float sample_count)
return rand;
}
vec2 hammersley_2d(uint i, uint sample_count)
{
vec2 rand;
rand.x = float(i) / float(sample_count);
rand.y = van_der_corput_radical_inverse(i);
return rand;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -83,11 +96,19 @@ vec2 hammersley_2d(float i, float sample_count)
* Functions mapping input random numbers to sampling shapes (i.e: hemisphere).
* \{ */
/* Given 2 random number in [0..1] range, return a random unit disk sample. */
vec2 sample_disk(vec2 noise)
/* Given 1 random number in [0..1] range, return a random unit circle sample. */
vec2 sample_circle(float rand)
{
float angle = noise.x * M_2PI;
return vec2(cos(angle), sin(angle)) * sqrt(noise.y);
float phi = (rand - 0.5) * M_2PI;
float cos_phi = cos(phi);
float sin_phi = sqrt(1.0 - sqr(cos_phi)) * sign(phi);
return vec2(cos_phi, sin_phi);
}
/* Given 2 random number in [0..1] range, return a random unit disk sample. */
vec2 sample_disk(vec2 rand)
{
return sample_circle(rand.y) * sqrt(rand.x);
}
/* This transform a 2d random sample (in [0..1] range) to a sample located on a cylinder of the
@ -95,11 +116,7 @@ vec2 sample_disk(vec2 noise)
* normally precomputed. */
vec3 sample_cylinder(vec2 rand)
{
float theta = rand.x;
float phi = (rand.y - 0.5) * M_2PI;
float cos_phi = cos(phi);
float sin_phi = sqrt(1.0 - sqr(cos_phi)) * sign(phi);
return vec3(theta, cos_phi, sin_phi);
return vec3(rand.x, sample_circle(rand.y));
}
vec3 sample_sphere(vec2 rand)
@ -111,86 +128,3 @@ vec3 sample_sphere(vec2 rand)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Microfacet GGX distribution
* \{ */
#define USE_VISIBLE_NORMAL 1
float D_ggx_opti(float NH, float a2)
{
float tmp = (NH * a2 - NH) * NH + 1.0;
return M_PI * tmp * tmp; /* Doing RCP and mul a2 at the end */
}
float G1_Smith_GGX_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
* this way the (2*NL)*(2*NV) in G = G1(V) * G1(L) gets canceled by the brdf denominator 4*NL*NV
* Rcp is done on the whole G later
* Note that this is not convenient for the transmission formula */
return NX + sqrt(NX * (NX - NX * a2) + a2);
/* return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function */
}
float pdf_ggx_reflect(float NH, float NV, float VH, float alpha)
{
float a2 = sqr(alpha);
#if USE_VISIBLE_NORMAL
float D = a2 / D_ggx_opti(NH, a2);
float G1 = NV * 2.0 / G1_Smith_GGX_opti(NV, a2);
return G1 * VH * D / NV;
#else
return NH * a2 / D_ggx_opti(NH, a2);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt)
{
#if USE_VISIBLE_NORMAL
/* From:
* "A Simpler and Exact Sampling Routine for the GGXDistribution of Visible Normals"
* by Eric Heitz.
* http://jcgt.org/published/0007/04/01/slides.pdf
* View vector is expected to be in tangent space. */
/* Stretch view. */
vec3 Th, Bh, Vh = normalize(vec3(alpha * Vt.xy, Vt.z));
make_orthonormal_basis(Vh, Th, Bh);
/* Sample point with polar coordinates (r, phi). */
float r = sqrt(rand.x);
float x = r * rand.y;
float y = r * rand.z;
float s = 0.5 * (1.0 + Vh.z);
y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;
float z = sqrt(saturate(1.0 - x * x - y * y));
/* Compute normal. */
vec3 Hh = x * Th + y * Bh + z * Vh;
/* Unstretch. */
vec3 Ht = normalize(vec3(alpha * Hh.xy, saturate(Hh.z)));
/* Microfacet Normal. */
return Ht;
#else
/* Theta is the cone angle. */
float z = sqrt((1.0 - rand.x) / (1.0 + sqr(alpha) * rand.x - rand.x)); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
/* Microfacet Normal */
return vec3(x, y, z);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
vec3 Vt = world_to_tangent(V, N, T, B);
vec3 Ht = sample_ggx(rand, alpha, Vt);
float NH = saturate(Ht.z);
float NV = saturate(Vt.z);
float VH = saturate(dot(Vt, Ht));
pdf = pdf_ggx_reflect(NH, NV, VH, alpha);
return tangent_to_world(Ht, N, T, B);
}
/** \} */

@ -34,6 +34,16 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light_base)
.fragment_source("eevee_deferred_light_frag.glsl")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "gbuffer_closure_tx")
.sampler(1, ImageType::FLOAT_2D_ARRAY, "gbuffer_color_tx")
.image(4,
RAYTRACE_RADIANCE_FORMAT,
Qualifier::READ,
ImageType::FLOAT_2D,
"indirect_reflection_img")
.image(5,
RAYTRACE_RADIANCE_FORMAT,
Qualifier::READ,
ImageType::FLOAT_2D,
"indirect_refraction_img")
.additional_info("eevee_shared",
"eevee_utility_texture",
"eevee_sampling_data",

@ -0,0 +1,125 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
#define EEVEE_RAYTRACE_CLOSURE_VARIATION(name, ...) \
GPU_SHADER_CREATE_INFO(name##_reflect) \
.do_static_compilation(true) \
.define("RAYTRACE_REFLECT") \
.additional_info(#name); \
GPU_SHADER_CREATE_INFO(name##_refract) \
.do_static_compilation(true) \
.define("RAYTRACE_REFRACT") \
.additional_info(#name);
/* -------------------------------------------------------------------- */
/** \name Ray tracing Pipeline
* \{ */
GPU_SHADER_CREATE_INFO(eevee_ray_tile_classify)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared")
.typedef_source("draw_shader_shared.h")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "gbuffer_closure_tx")
.sampler(1, ImageType::UINT_2D, "stencil_tx")
.image(0, RAYTRACE_TILEMASK_FORMAT, Qualifier::WRITE, ImageType::UINT_2D, "tile_mask_img")
.storage_buf(0, Qualifier::WRITE, "DispatchCommand", "ray_dispatch_buf")
.storage_buf(1, Qualifier::WRITE, "DispatchCommand", "denoise_dispatch_buf")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_tile_classify_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_ray_tile_compact)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared")
.typedef_source("draw_shader_shared.h")
.image(0, RAYTRACE_TILEMASK_FORMAT, Qualifier::READ, ImageType::UINT_2D, "tile_mask_img")
.storage_buf(0, Qualifier::READ_WRITE, "DispatchCommand", "ray_dispatch_buf")
.storage_buf(1, Qualifier::READ_WRITE, "DispatchCommand", "denoise_dispatch_buf")
.storage_buf(2, Qualifier::WRITE, "uint", "ray_tiles_buf[]")
.storage_buf(3, Qualifier::WRITE, "uint", "denoise_tiles_buf[]")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_tile_compact_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_ray_generate)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_sampling_data", "draw_view", "eevee_utility_texture")
.sampler(0, ImageType::UINT_2D, "stencil_tx")
.sampler(1, ImageType::FLOAT_2D_ARRAY, "gbuffer_closure_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_ray_data_img")
.storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_generate_comp.glsl");
EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_generate)
GPU_SHADER_CREATE_INFO(eevee_ray_trace_screen)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared",
"eevee_sampling_data",
"draw_view",
"eevee_hiz_data",
"eevee_reflection_probe_data")
.image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img")
.image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img")
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img")
.storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_trace_screen_comp.glsl");
EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_trace_screen)
GPU_SHADER_CREATE_INFO(eevee_ray_denoise_spatial)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared",
"eevee_sampling_data",
"draw_view",
"eevee_hiz_data",
"eevee_utility_texture")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "gbuffer_closure_tx")
.sampler(1, ImageType::UINT_2D, "stencil_tx")
.image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img")
.image(1, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_time_img")
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "ray_radiance_img")
.image(3, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_radiance_img")
.image(4, RAYTRACE_VARIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_variance_img")
.image(5, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_hit_depth_img")
.image(6, RAYTRACE_TILEMASK_FORMAT, Qualifier::READ, ImageType::UINT_2D, "tile_mask_img")
.storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_denoise_spatial_comp.glsl");
EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_denoise_spatial)
GPU_SHADER_CREATE_INFO(eevee_ray_denoise_temporal)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared", "draw_view", "eevee_hiz_data")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.sampler(0, ImageType::FLOAT_2D, "radiance_history_tx")
.sampler(1, ImageType::FLOAT_2D, "variance_history_tx")
.sampler(2, ImageType::UINT_2D, "tilemask_history_tx")
.image(0, GPU_R32F, Qualifier::READ, ImageType::FLOAT_2D, "hit_depth_img")
.image(1, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "in_radiance_img")
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_radiance_img")
.image(3, RAYTRACE_VARIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "in_variance_img")
.image(4, RAYTRACE_VARIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_variance_img")
.storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]")
.compute_source("eevee_ray_denoise_temporal_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_ray_denoise_bilateral)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_sampling_data", "draw_view", "eevee_hiz_data")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "gbuffer_closure_tx")
.image(1, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "in_radiance_img")
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_radiance_img")
.image(3, RAYTRACE_VARIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "in_variance_img")
.image(6, RAYTRACE_TILEMASK_FORMAT, Qualifier::READ, ImageType::UINT_2D, "tile_mask_img")
.storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]")
.uniform_buf(1, "RayTraceData", "raytrace_buf")
.compute_source("eevee_ray_denoise_bilateral_comp.glsl");
EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_denoise_bilateral)
/** \} */

@ -580,6 +580,12 @@ class Texture : NonCopyable {
return &tx_;
}
/** WORKAROUND: used when needing a ref to the Texture and not the GPUTexture. */
Texture *ptr()
{
return this;
}
Texture &operator=(Texture &&a)
{
if (this != std::addressof(a)) {
@ -1003,6 +1009,12 @@ class TextureFromPool : public Texture, NonMovable {
Texture::swap(a, b);
}
/** WORKAROUND: used when needing a ref to the Texture and not the GPUTexture. */
TextureFromPool *ptr()
{
return this;
}
/** Remove methods that are forbidden with this type of textures. */
bool ensure_1d(int, int, eGPUTextureFormat, eGPUTextureUsage, float *) = delete;
bool ensure_1d_array(int, int, int, eGPUTextureFormat, eGPUTextureUsage, float *) = delete;

@ -144,6 +144,13 @@ class View {
return data_[view_id].wininv;
}
/* Compute and return the perspective matrix. */
const float4x4 persmat(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].winmat * data_[view_id].viewmat;
}
int visibility_word_per_draw() const
{
return (view_len_ == 1) ? 0 : divide_ceil_u(view_len_, 32);

@ -648,6 +648,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_info.hh

@ -61,6 +61,18 @@ float square_f(float v)
{
return v * v;
}
vec2 square_f(vec2 v)
{
return v * v;
}
vec3 square_f(vec3 v)
{
return v * v;
}
vec4 square_f(vec4 v)
{
return v * v;
}
int cube_i(int v)
{

@ -13,6 +13,9 @@
# define UINT_MAX 0xFFFFFFFFu
#endif
#define NAN_FLT uintBitsToFloat(0x7FC00000u)
#define FLT_11_MAX uintBitsToFloat(0x477E0000)
#define FLT_10_MAX uintBitsToFloat(0x477C0000)
#define FLT_11_11_10_MAX vec3(FLT_11_MAX, FLT_11_MAX, FLT_10_MAX)
#define UNPACK2(a) (a)[0], (a)[1]
#define UNPACK3(a) (a)[0], (a)[1], (a)[2]

@ -156,6 +156,18 @@
.viewport_aa = SCE_DISPLAY_AA_FXAA, \
}
#define _DNA_DEFAULT_RaytraceEEVEE \
{ \
.flag = RAYTRACE_EEVEE_USE_DENOISE, \
.denoise_stages = RAYTRACE_EEVEE_DENOISE_SPATIAL | \
RAYTRACE_EEVEE_DENOISE_TEMPORAL | \
RAYTRACE_EEVEE_DENOISE_BILATERAL, \
.screen_trace_quality = 0.25f, \
.screen_trace_thickness = 0.2f, \
.sample_clamp = 10.0f, \
.resolution_scale = 2, \
}
#define _DNA_DEFAULT_PhysicsSettings \
{ \
.gravity = {0.0f, 0.0f, -9.81f}, \
@ -216,6 +228,12 @@
\
.shadow_cube_size = 512, \
.shadow_cascade_size = 1024, \
\
.ray_split_settings = 0, \
.ray_tracing_method = RAYTRACE_EEVEE_METHOD_SCREEN, \
\
.reflection_options = _DNA_DEFAULT_RaytraceEEVEE, \
.refraction_options = _DNA_DEFAULT_RaytraceEEVEE, \
\
.light_cache_data = NULL, \
.light_threshold = 0.01f, \

@ -1809,6 +1809,24 @@ typedef struct SceneDisplay {
View3DShading shading;
} SceneDisplay;
/**
* Raytracing parameters.
*/
typedef struct RaytraceEEVEE {
/** Higher values will take lower strides and have less blurry intersections. */
float screen_trace_quality;
/** Thickness in world space each surface will have during screen space tracing. */
float screen_trace_thickness;
/** Resolution downscale factor. */
int resolution_scale;
/** Maximum intensity a ray can have. */
float sample_clamp;
/** #RaytraceEEVEE_Flag. */
int flag;
/** #RaytraceEEVEE_DenoiseStages. */
int denoise_stages;
} RaytraceEEVEE;
typedef struct SceneEEVEE {
int flag;
int gi_diffuse_bounces;
@ -1817,7 +1835,6 @@ typedef struct SceneEEVEE {
float gi_irradiance_smoothing;
float gi_glossy_clamp;
float gi_filter_quality;
char _pad0[4];
float gi_cubemap_draw_size;
float gi_irradiance_draw_size;
@ -1870,6 +1887,13 @@ typedef struct SceneEEVEE {
int shadow_cascade_size;
int shadow_pool_size;
int ray_split_settings;
int ray_tracing_method;
char _pad0[4];
struct RaytraceEEVEE reflection_options;
struct RaytraceEEVEE refraction_options;
struct LightCache *light_cache DNA_DEPRECATED;
struct LightCache *light_cache_data;
/* Need a 128 byte string for some translations of some messages. */
@ -2801,8 +2825,26 @@ enum {
SCE_EEVEE_DOF_HQ_SLIGHT_FOCUS = (1 << 22),
SCE_EEVEE_DOF_JITTER = (1 << 23),
SCE_EEVEE_SHADOW_ENABLED = (1 << 24),
SCE_EEVEE_RAYTRACE_OPTIONS_SPLIT = (1 << 25),
};
typedef enum RaytraceEEVEE_Flag {
RAYTRACE_EEVEE_USE_DENOISE = (1 << 0),
} RaytraceEEVEE_Flag;
typedef enum RaytraceEEVEE_DenoiseStages {
RAYTRACE_EEVEE_DENOISE_SPATIAL = (1 << 0),
RAYTRACE_EEVEE_DENOISE_TEMPORAL = (1 << 1),
RAYTRACE_EEVEE_DENOISE_BILATERAL = (1 << 2),
} RaytraceEEVEE_DenoiseStages;
typedef enum RaytraceEEVEE_Method {
RAYTRACE_EEVEE_METHOD_NONE = 0,
RAYTRACE_EEVEE_METHOD_SCREEN = 1,
/* TODO(fclem): Hardware raytracing. */
// RAYTRACE_EEVEE_METHOD_HARDWARE = 2,
} RaytraceEEVEE_Method;
/** #SceneEEVEE::shadow_method */
enum {
SHADOW_ESM = 1,

@ -663,6 +663,7 @@ typedef struct UserDef_Experimental {
char use_undo_legacy;
char no_override_auto_resync;
char use_cycles_debug;
char use_eevee_debug;
char show_asset_debug_info;
char no_asset_indexing;
char use_viewport_debug;
@ -685,7 +686,6 @@ typedef struct UserDef_Experimental {
char use_node_panels;
char use_rotation_socket;
char use_node_group_operators;
char _pad[1];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;

@ -7378,6 +7378,77 @@ static void rna_def_scene_display(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Shading Settings", "Shading settings for OpenGL render engine");
}
static void rna_def_raytrace_eevee(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
static const EnumPropertyItem pixel_rate_items[] = {
{1, "1", 0, "1 rpp", "1 ray per pixel"},
{2, "2", 0, "1/4 rpp", "1 ray for every 4 pixels"},
{4, "4", 0, "1/16 rpp", "1 ray for every 16 pixels"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "RaytraceEEVEE", nullptr);
prop = RNA_def_property(srna, "resolution_scale", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, pixel_rate_items);
RNA_def_property_ui_text(prop, "Resolution", "Number of ray per pixel");
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, "use_denoise", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", RAYTRACE_EEVEE_USE_DENOISE);
RNA_def_property_ui_text(
prop, "Denoise", "Enable noise reduction techniques for raytraced effects");
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, "denoise_spatial", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "denoise_stages", RAYTRACE_EEVEE_DENOISE_SPATIAL);
RNA_def_property_ui_text(prop, "Spatial Reuse", "Reuse neighbor pixels' rays");
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, "denoise_temporal", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "denoise_stages", RAYTRACE_EEVEE_DENOISE_TEMPORAL);
RNA_def_property_ui_text(
prop, "Temporal Accumulation", "Accumulate samples by reprojecting last tracing results");
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, "denoise_bilateral", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "denoise_stages", RAYTRACE_EEVEE_DENOISE_BILATERAL);
RNA_def_property_ui_text(
prop, "Bilateral Filter", "Blur the resolved radiance using a bilateral filter");
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,
"Screen-Trace Thickness",
"Surface thickness used to detect intersection when using screen-tracing");
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 5, 3);
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_quality", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_ui_text(
prop, "Screen-Trace Precision", "Precision of the screen space ray-tracing");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
}
static void rna_def_scene_eevee(BlenderRNA *brna)
{
StructRNA *srna;
@ -7432,6 +7503,22 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem ray_split_settings_items[] = {
{0, "UNIFIED", 0, "Unified", "All ray types use the same settings"},
{1, "SPLIT", 0, "Split", "Settings are individual to each ray type"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem ray_tracing_method_items[] = {
{RAYTRACE_EEVEE_METHOD_NONE, "NONE", 0, "None", "No intersection with scene geometry"},
{RAYTRACE_EEVEE_METHOD_SCREEN,
"SCREEN",
0,
"Screen-Trace",
"Raytrace against the depth buffer"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "SceneEEVEE", nullptr);
RNA_def_struct_path_func(srna, "rna_SceneEEVEE_path");
RNA_def_struct_ui_text(srna, "Scene Display", "Scene display settings for 3D viewport");
@ -7611,6 +7698,17 @@ static void rna_def_scene_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, "ray_split_settings", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, ray_split_settings_items);
RNA_def_property_ui_text(prop, "Options Split", "Split settings per ray type");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "ray_tracing_method", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, ray_tracing_method_items);
RNA_def_property_ui_text(
prop, "Tracing Method", "Select the tracing method used to find scene-ray intersections");
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");
@ -7950,6 +8048,16 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_range(prop, 0.0f, 50.0f);
RNA_def_property_ui_range(prop, 0.0f, 10.0f, 1, 2);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
prop = RNA_def_property(srna, "reflection_options", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "RaytraceEEVEE");
RNA_def_property_ui_text(
prop, "Reflection Trace Options", "Eevee settings for the tracing reflections");
prop = RNA_def_property(srna, "refraction_options", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "RaytraceEEVEE");
RNA_def_property_ui_text(
prop, "Reflection Trace Options", "Eevee settings for the tracing reflections");
}
static void rna_def_scene_gpencil(BlenderRNA *brna)
@ -8482,6 +8590,7 @@ void RNA_def_scene(BlenderRNA *brna)
rna_def_selected_uv_element(brna);
rna_def_display_safe_areas(brna);
rna_def_scene_display(brna);
rna_def_raytrace_eevee(brna);
rna_def_scene_eevee(brna);
rna_def_view_layer_aov(brna);
rna_def_view_layer_lightgroup(brna);

@ -593,7 +593,8 @@ static void rna_UserDef_subdivision_update(Main *bmain, Scene *scene, PointerRNA
Object *ob;
for (ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next)) {
ob = static_cast<Object *>(ob->id.next))
{
if (BKE_object_get_last_subsurf_modifier(ob) != nullptr) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
@ -632,7 +633,8 @@ static void rna_UserDef_weight_color_update(Main *bmain, Scene *scene, PointerRN
Object *ob;
for (ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next)) {
ob = static_cast<Object *>(ob->id.next))
{
if (ob->mode & OB_MODE_WEIGHT_PAINT) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
@ -6670,6 +6672,11 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Cycles Debug", "Enable Cycles debugging options for developers");
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "use_eevee_debug", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "use_eevee_debug", 1);
RNA_def_property_ui_text(prop, "EEVEE Debug", "Enable EEVEE debugging options for developers");
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "use_sculpt_tools_tilt", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "use_sculpt_tools_tilt", 1);
RNA_def_property_ui_text(