EEVEE-Next: Irradiance Cache: Initial Implementation

This is a full rewrite of the irradiance volume baking.
The baking is much faster and doesn't scale linearly with the number
of irradiance samples in the volumes.

Ref #105643

Pull Request: https://projects.blender.org/blender/blender/pulls/108639
This commit is contained in:
Clément Foucault
2023-06-23 08:39:46 +02:00
parent cd08a55a68
commit ddd88c00b4
69 changed files with 3820 additions and 168 deletions

View File

@@ -31,6 +31,7 @@ class DATA_PT_context_grease_pencil(DataButtonsPanel, Panel):
elif grease_pencil:
layout.template_ID(space, "pin_id")
class DATA_PT_grease_pencil_layers(DataButtonsPanel, Panel):
bl_label = "Layers"

View File

@@ -19,7 +19,7 @@ class DataButtonsPanel:
class DATA_PT_context_lightprobe(DataButtonsPanel, Panel):
bl_label = ""
bl_options = {'HIDE_HEADER'}
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_RENDER'}
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_RENDER', 'BLENDER_EEVEE_NEXT'}
def draw(self, context):
layout = self.layout
@@ -83,6 +83,42 @@ class DATA_PT_lightprobe(DataButtonsPanel, Panel):
sub.prop(probe, "clip_end", text="End")
class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel):
bl_label = "Probe"
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
probe = context.lightprobe
if probe.type == 'GRID':
col = layout.column()
sub = col.column(align=True)
sub.prop(probe, "grid_resolution_x", text="Resolution X")
sub.prop(probe, "grid_resolution_y", text="Y")
sub.prop(probe, "grid_resolution_z", text="Z")
col.separator()
col.operator("object.lightprobe_cache_bake").subset = "ACTIVE"
col.operator("object.lightprobe_cache_free").subset = "ACTIVE"
col.separator()
col.prop(probe, "grid_bake_samples")
col.prop(probe, "surfel_density")
elif probe.type == 'PLANAR':
# Currently unsupported
pass
else:
# Currently unsupported
pass
class DATA_PT_lightprobe_visibility(DataButtonsPanel, Panel):
bl_label = "Visibility"
bl_parent_id = "DATA_PT_lightprobe"
@@ -169,6 +205,7 @@ class DATA_PT_lightprobe_display(DataButtonsPanel, Panel):
classes = (
DATA_PT_context_lightprobe,
DATA_PT_lightprobe,
DATA_PT_lightprobe_eevee_next,
DATA_PT_lightprobe_visibility,
DATA_PT_lightprobe_parallax,
DATA_PT_lightprobe_display,

View File

@@ -573,6 +573,28 @@ class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel):
col.prop(props, "gi_filter_quality")
class RENDER_PT_eevee_next_indirect_lighting(RenderButtonsPanel, Panel):
bl_label = "Indirect Lighting"
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
layout.use_property_decorate = False # No animation.
scene = context.scene
props = scene.eevee
col = layout.column()
col.operator("object.lightprobe_cache_bake", text="Bake Light Caches", icon='RENDER_STILL').subset = "ALL"
col.operator("object.lightprobe_cache_free", text="Delete Light Caches").subset = "ALL"
class RENDER_PT_eevee_indirect_lighting_display(RenderButtonsPanel, Panel):
bl_label = "Display"
bl_parent_id = "RENDER_PT_eevee_indirect_lighting"
@@ -599,6 +621,28 @@ class RENDER_PT_eevee_indirect_lighting_display(RenderButtonsPanel, Panel):
row.prop(props, "gi_show_irradiance", text="", toggle=True)
class RENDER_PT_eevee_next_indirect_lighting_display(RenderButtonsPanel, Panel):
bl_label = "Display"
bl_parent_id = "RENDER_PT_eevee_next_indirect_lighting"
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
layout.use_property_decorate = False # No animation.
scene = context.scene
props = scene.eevee
row = layout.row(align=True)
row.prop(props, "gi_irradiance_display_size", text="Irradiance Size")
row.prop(props, "gi_show_irradiance", text="", toggle=True)
class RENDER_PT_eevee_film(RenderButtonsPanel, Panel):
bl_label = "Film"
bl_options = {'DEFAULT_CLOSED'}
@@ -908,6 +952,8 @@ classes = (
RENDER_PT_eevee_next_shadows,
RENDER_PT_eevee_indirect_lighting,
RENDER_PT_eevee_indirect_lighting_display,
RENDER_PT_eevee_next_indirect_lighting,
RENDER_PT_eevee_next_indirect_lighting_display,
RENDER_PT_eevee_film,
RENDER_PT_eevee_next_film,

View File

@@ -1503,6 +1503,7 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
EEVEE_lightcache_blend_read_data(reader, sce->eevee.light_cache_data);
}
}
EEVEE_lightcache_info_update(&sce->eevee);
BKE_screen_view3d_shading_blend_read_data(reader, &sce->display.shading);
@@ -1812,6 +1813,7 @@ constexpr IDTypeInfo get_type_info()
IDTypeInfo IDType_ID_SCE = get_type_info();
const char *RE_engine_id_BLENDER_EEVEE = "BLENDER_EEVEE";
const char *RE_engine_id_BLENDER_EEVEE_NEXT = "BLENDER_EEVEE_NEXT";
const char *RE_engine_id_BLENDER_WORKBENCH = "BLENDER_WORKBENCH";
const char *RE_engine_id_BLENDER_WORKBENCH_NEXT = "BLENDER_WORKBENCH_NEXT";
const char *RE_engine_id_CYCLES = "CYCLES";
@@ -3000,7 +3002,8 @@ bool BKE_scene_use_spherical_stereo(Scene *scene)
bool BKE_scene_uses_blender_eevee(const Scene *scene)
{
return STREQ(scene->r.engine, RE_engine_id_BLENDER_EEVEE);
return STREQ(scene->r.engine, RE_engine_id_BLENDER_EEVEE) ||
STREQ(scene->r.engine, RE_engine_id_BLENDER_EEVEE_NEXT);
}
bool BKE_scene_uses_blender_workbench(const Scene *scene)

View File

@@ -10,6 +10,7 @@
#include "CLG_log.h"
#include "DNA_lightprobe_types.h"
#include "DNA_modifier_types.h"
#include "DNA_movieclip_types.h"
@@ -200,7 +201,7 @@ static void versioning_remove_microfacet_sharp_distribution(bNodeTree *ntree)
}
}
void blo_do_versions_400(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
{
if (!MAIN_VERSION_ATLEAST(bmain, 400, 1)) {
LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) {
@@ -273,5 +274,12 @@ void blo_do_versions_400(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
/* Convert anisotropic BSDF node to glossy BSDF. */
/* Keep this block, even when empty. */
if (!DNA_struct_elem_find(fd->filesdna, "LightProbe", "int", "grid_bake_sample_count")) {
LISTBASE_FOREACH (LightProbe *, lightprobe, &bmain->lightprobes) {
lightprobe->grid_bake_samples = 2048;
lightprobe->surfel_density = 1.0f;
}
}
}
}

View File

@@ -149,6 +149,8 @@ set(SRC
engines/eevee_next/eevee_instance.cc
engines/eevee_next/eevee_irradiance_cache.cc
engines/eevee_next/eevee_light.cc
engines/eevee_next/eevee_lightprobe.cc
engines/eevee_next/eevee_lightcache.cc
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
@@ -288,6 +290,7 @@ set(SRC
engines/eevee_next/eevee_instance.hh
engines/eevee_next/eevee_irradiance_cache.hh
engines/eevee_next/eevee_light.hh
engines/eevee_next/eevee_lightcache.hh
engines/eevee_next/eevee_material.hh
engines/eevee_next/eevee_motion_blur.hh
engines/eevee_next/eevee_pipeline.hh
@@ -455,7 +458,6 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_camera_lib.glsl
engines/eevee_next/shaders/eevee_colorspace_lib.glsl
engines/eevee_next/shaders/eevee_cryptomatte_lib.glsl
engines/eevee_next/shaders/eevee_transparency_lib.glsl
engines/eevee_next/shaders/eevee_debug_surfels_vert.glsl
engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl
engines/eevee_next/shaders/eevee_deferred_light_frag.glsl
@@ -474,6 +476,8 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl
engines/eevee_next/shaders/eevee_depth_of_field_tiles_dilate_comp.glsl
engines/eevee_next/shaders/eevee_depth_of_field_tiles_flatten_comp.glsl
engines/eevee_next/shaders/eevee_display_probe_grid_frag.glsl
engines/eevee_next/shaders/eevee_display_probe_grid_vert.glsl
engines/eevee_next/shaders/eevee_film_comp.glsl
engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl
engines/eevee_next/shaders/eevee_film_frag.glsl
@@ -493,6 +497,11 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_light_eval_lib.glsl
engines/eevee_next/shaders/eevee_light_iter_lib.glsl
engines/eevee_next/shaders/eevee_light_lib.glsl
engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl
engines/eevee_next/shaders/eevee_lightprobe_irradiance_bounds_comp.glsl
engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl
engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl
engines/eevee_next/shaders/eevee_lightprobe_lib.glsl
engines/eevee_next/shaders/eevee_ltc_lib.glsl
engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl
@@ -513,6 +522,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_surfels_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl
engines/eevee_next/shaders/eevee_shadow_test.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl
@@ -521,12 +531,19 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl
engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl
engines/eevee_next/shaders/eevee_subsurface_eval_frag.glsl
engines/eevee_next/shaders/eevee_surf_capture_frag.glsl
engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl
engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
engines/eevee_next/shaders/eevee_surf_lib.glsl
engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl
engines/eevee_next/shaders/eevee_surf_world_frag.glsl
engines/eevee_next/shaders/eevee_surfel_light_comp.glsl
engines/eevee_next/shaders/eevee_surfel_list_build_comp.glsl
engines/eevee_next/shaders/eevee_surfel_list_lib.glsl
engines/eevee_next/shaders/eevee_surfel_list_sort_comp.glsl
engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl
engines/eevee_next/shaders/eevee_transparency_lib.glsl
engines/eevee_next/shaders/eevee_velocity_lib.glsl
engines/eevee_next/eevee_defines.hh

View File

@@ -78,7 +78,27 @@ void Camera::sync()
CameraData &data = data_;
if (inst_.drw_view) {
if (inst_.is_baking()) {
/* Any view so that shadows and light culling works during irradiance bake. */
draw::View &view = inst_.irradiance_cache.bake.view_z_;
data.viewmat = view.viewmat();
data.viewinv = view.viewinv();
data.winmat = view.winmat();
data.wininv = view.wininv();
data.persmat = data.winmat * data.viewmat;
data.persinv = math::invert(data.persmat);
data.uv_scale = float2(1.0f);
data.uv_bias = float2(0.0f);
data.type = CAMERA_ORTHO;
/* \note: Follow camera parameters where distances are positive in front of the camera. */
data.clip_near = -view.far_clip();
data.clip_far = -view.near_clip();
data.fisheye_fov = data.fisheye_lens = -1.0f;
data.equirect_bias = float2(0.0f);
data.equirect_scale = float2(0.0f);
}
else if (inst_.drw_view) {
DRW_view_viewmat_get(inst_.drw_view, data.viewmat.ptr(), false);
DRW_view_viewmat_get(inst_.drw_view, data.viewinv.ptr(), true);
DRW_view_winmat_get(inst_.drw_view, data.winmat.ptr(), false);

View File

@@ -71,6 +71,10 @@
#define MOTION_BLUR_GROUP_SIZE 32
#define MOTION_BLUR_DILATE_GROUP_SIZE 512
/* Irradiance Cache. */
/** Maximum number of entities inside the cache. */
#define IRRADIANCE_GRID_MAX 64
/* Depth Of Field. */
#define DOF_TILES_SIZE 8
#define DOF_TILES_FLATTEN_GROUP_SIZE DOF_TILES_SIZE
@@ -86,6 +90,13 @@
#define DOF_GATHER_GROUP_SIZE DOF_TILES_SIZE
#define DOF_RESOLVE_GROUP_SIZE (DOF_TILES_SIZE * 2)
/* IrradianceBake. */
#define SURFEL_GROUP_SIZE 256
#define SURFEL_LIST_GROUP_SIZE 256
#define IRRADIANCE_GRID_GROUP_SIZE 4 /* In each dimension, so 4x4x4 workgroup size. */
#define IRRADIANCE_GRID_BRICK_SIZE 4 /* In each dimension, so 4x4x4 brick size. */
#define IRRADIANCE_BOUNDS_GROUP_SIZE 64
/* Resource bindings. */
/* Textures. */
@@ -96,6 +107,7 @@
#define SHADOW_TILEMAPS_TEX_SLOT 4
#define SHADOW_ATLAS_TEX_SLOT 5
#define SSS_TRANSMITTANCE_TEX_SLOT 6
#define IRRADIANCE_ATLAS_TEX_SLOT 7
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 4
@@ -107,6 +119,8 @@
#define GBUF_COLOR_SLOT 4
/* Uniform Buffers. */
#define IRRADIANCE_GRID_BUF_SLOT 3
#define HIZ_BUF_SLOT 5
/* Only during pre-pass. */
#define VELOCITY_CAMERA_PREV_BUF 3
#define VELOCITY_CAMERA_CURR_BUF 4
@@ -120,9 +134,14 @@
#define LIGHT_BUF_SLOT 1
#define LIGHT_ZBIN_BUF_SLOT 2
#define LIGHT_TILE_BUF_SLOT 3
#define IRRADIANCE_BRICK_BUF_SLOT 4
/* Only during surface capture. */
#define SURFEL_BUF_SLOT 4
/* Only during surface capture. */
#define CAPTURE_BUF_SLOT 5
/* Only during shadow rendering. */
#define SHADOW_PAGE_INFO_SLOT 4
#define SAMPLING_BUF_SLOT 5
#define SAMPLING_BUF_SLOT 6
#define CRYPTOMATTE_BUF_SLOT 7
/* Only during pre-pass. */

View File

@@ -85,8 +85,7 @@ static void eevee_engine_init(void *vedata)
}
}
ved->instance->init(
size, &rect, nullptr, depsgraph, nullptr, camera, nullptr, default_view, v3d, rv3d);
ved->instance->init(size, &rect, nullptr, depsgraph, camera, nullptr, default_view, v3d, rv3d);
}
static void eevee_draw_scene(void *vedata)
@@ -161,7 +160,7 @@ static void eevee_render_to_image(void *vedata,
rcti rect;
RE_GetViewPlane(render, &view_rect, &rect);
instance->init(size, &rect, engine, depsgraph, nullptr, camera_original_ob, layer);
instance->init(size, &rect, engine, depsgraph, camera_original_ob, layer);
instance->render_frame(layer, viewname);
EEVEE_Data *ved = static_cast<EEVEE_Data *>(vedata);

View File

@@ -19,6 +19,7 @@
#include "DNA_modifier_types.h"
#include "RE_pipeline.h"
#include "eevee_engine.h"
#include "eevee_instance.hh"
namespace blender::eevee {
@@ -37,14 +38,12 @@ void Instance::init(const int2 &output_res,
const rcti *output_rect,
RenderEngine *render_,
Depsgraph *depsgraph_,
const LightProbe *light_probe_,
Object *camera_object_,
const RenderLayer *render_layer_,
const DRWView *drw_view_,
const View3D *v3d_,
const RegionView3D *rv3d_)
{
UNUSED_VARS(light_probe_);
render = render_;
depsgraph = depsgraph_;
camera_orig_object = camera_object_;
@@ -73,6 +72,35 @@ void Instance::init(const int2 &output_res,
irradiance_cache.init();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
{
this->depsgraph = depsgraph;
this->manager = manager;
camera_orig_object = nullptr;
render = nullptr;
render_layer = nullptr;
drw_view = nullptr;
v3d = nullptr;
rv3d = nullptr;
is_light_bake = true;
debug_mode = (eDebugMode)G.debug_value;
info = "";
update_eval_members();
sampling.init(scene);
camera.init();
/* Film isn't used but init to avoid side effects in other module. */
rcti empty_rect{0, 0, 0, 0};
film.init(int2(1), &empty_rect);
velocity.init();
depth_of_field.init();
shadows.init();
main_view.init();
irradiance_cache.init();
}
void Instance::set_time(float time)
{
BLI_assert(render);
@@ -107,6 +135,7 @@ void Instance::begin_sync()
shadows.begin_sync();
pipelines.begin_sync();
cryptomatte.begin_sync();
light_probes.begin_sync();
gpencil_engine_enabled = false;
@@ -139,7 +168,8 @@ void Instance::scene_sync()
void Instance::object_sync(Object *ob)
{
const bool is_renderable_type = ELEM(ob->type, OB_CURVES, OB_GPENCIL_LEGACY, OB_MESH, OB_LAMP);
const bool is_renderable_type = ELEM(
ob->type, OB_CURVES, OB_GPENCIL_LEGACY, OB_MESH, OB_LAMP, OB_LIGHTPROBE);
const int ob_visibility = DRW_object_visibility_in_active_context(ob);
const bool partsys_is_visible = (ob_visibility & OB_VISIBLE_PARTICLES) != 0 &&
(ob->type == OB_MESH);
@@ -180,6 +210,9 @@ void Instance::object_sync(Object *ob)
case OB_GPENCIL_LEGACY:
sync.sync_gpencil(ob, ob_handle, res_handle);
break;
case OB_LIGHTPROBE:
light_probes.sync_probe(ob, ob_handle);
break;
default:
break;
}
@@ -209,6 +242,7 @@ void Instance::end_sync()
film.end_sync();
cryptomatte.end_sync();
pipelines.end_sync();
light_probes.end_sync();
}
void Instance::render_sync()
@@ -449,6 +483,79 @@ void Instance::update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view
EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL);
}
void Instance::light_bake_irradiance(
Object &probe,
FunctionRef<void()> context_enable,
FunctionRef<void()> context_disable,
FunctionRef<bool()> stop,
FunctionRef<void(LightProbeGridCacheFrame *, float progress)> result_update)
{
BLI_assert(is_baking());
auto custom_pipeline_wrapper = [&](FunctionRef<void()> callback) {
context_enable();
DRW_custom_pipeline_begin(&draw_engine_eevee_next_type, depsgraph);
callback();
DRW_custom_pipeline_end();
context_disable();
};
auto context_wrapper = [&](FunctionRef<void()> callback) {
context_enable();
callback();
context_disable();
};
irradiance_cache.bake.init(probe);
custom_pipeline_wrapper([&]() {
/* TODO: lightprobe visibility group option. */
manager->begin_sync();
render_sync();
manager->end_sync();
irradiance_cache.bake.surfels_create(probe);
irradiance_cache.bake.surfels_lights_eval();
});
sampling.init(probe);
while (!sampling.finished()) {
context_wrapper([&]() {
/* Batch ray cast by pack of 16. Avoids too much overhead of the update function & context
* switch. */
/* TODO(fclem): Could make the number of iteration depend on the computation time. */
for (int i = 0; i < 16 && !sampling.finished(); i++) {
sampling.step();
irradiance_cache.bake.raylists_build();
irradiance_cache.bake.propagate_light();
irradiance_cache.bake.irradiance_capture();
}
if (sampling.finished()) {
/* TODO(fclem): Dilation, filter etc... */
// irradiance_cache.bake.irradiance_finalize();
}
LightProbeGridCacheFrame *cache_frame;
if (sampling.finished()) {
cache_frame = irradiance_cache.bake.read_result_packed();
}
else {
/* TODO(fclem): Only do this read-back if needed. But it might be tricky to know when. */
cache_frame = irradiance_cache.bake.read_result_unpacked();
}
float progress = sampling.sample_index() / float(sampling.sample_count());
result_update(cache_frame, progress);
});
if (stop()) {
return;
}
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -23,6 +23,7 @@
#include "eevee_hizbuffer.hh"
#include "eevee_irradiance_cache.hh"
#include "eevee_light.hh"
#include "eevee_lightprobe.hh"
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
@@ -65,6 +66,7 @@ class Instance {
RenderBuffers render_buffers;
MainView main_view;
World world;
LightProbeModule light_probes;
IrradianceCache irradiance_cache;
/** Input data. */
@@ -73,6 +75,7 @@ class Instance {
/** Evaluated IDs. */
Scene *scene;
ViewLayer *view_layer;
/** Camera object if rendering through a camera. nullptr otherwise. */
Object *camera_eval_object;
Object *camera_orig_object;
/** Only available when rendering for final render. */
@@ -85,6 +88,8 @@ class Instance {
/** True if the grease pencil engine might be running. */
bool gpencil_engine_enabled;
/** True if the instance is created for light baking. */
bool is_light_bake = false;
/** Info string displayed at the top of the render / viewport. */
std::string info = "";
@@ -111,14 +116,16 @@ class Instance {
render_buffers(*this),
main_view(*this),
world(*this),
light_probes(*this),
irradiance_cache(*this){};
~Instance(){};
/* Render & Viewport. */
/* TODO(fclem): Split for clarity. */
void init(const int2 &output_res,
const rcti *output_rect,
RenderEngine *render,
Depsgraph *depsgraph,
const LightProbe *light_probe_ = nullptr,
Object *camera_object = nullptr,
const RenderLayer *render_layer = nullptr,
const DRWView *drw_view = nullptr,
@@ -129,17 +136,36 @@ class Instance {
void object_sync(Object *ob);
void end_sync();
/* Render. */
void render_sync();
void render_frame(RenderLayer *render_layer, const char *view_name);
void store_metadata(RenderResult *render_result);
/* Viewport. */
void draw_viewport(DefaultFramebufferList *dfbl);
/* Light bake. */
void init_light_bake(Depsgraph *depsgraph, draw::Manager *manager);
void light_bake_irradiance(
Object &probe,
FunctionRef<void()> context_enable,
FunctionRef<void()> context_disable,
FunctionRef<bool()> stop,
FunctionRef<void(LightProbeGridCacheFrame *, float progress)> result_update);
static void update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view_layer);
bool is_viewport() const
{
return render == nullptr;
return render == nullptr && !is_baking();
}
bool is_baking() const
{
return is_light_bake;
}
bool overlays_enabled() const

View File

@@ -2,66 +2,849 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_rand.hh"
#include "DNA_lightprobe_types.h"
#include "BKE_lightprobe.h"
#include "GPU_capabilities.h"
#include "GPU_debug.h"
#include "BLI_math_rotation.hh"
#include "eevee_instance.hh"
#include "eevee_irradiance_cache.hh"
namespace blender::eevee {
void IrradianceCache::generate_random_surfels()
{
const int surfels_len = 256;
debug_surfels.resize(surfels_len);
RandomNumberGenerator rng;
rng.seed(0);
for (DebugSurfel &surfel : debug_surfels) {
float3 random = rng.get_unit_float3();
surfel.position = random * 3.0f;
surfel.normal = random;
surfel.color = float4(rng.get_float(), rng.get_float(), rng.get_float(), 1.0f);
}
debug_surfels.push_update();
}
/* -------------------------------------------------------------------- */
/** \name Interface
* \{ */
void IrradianceCache::init()
{
if (debug_surfels_sh_ == nullptr) {
debug_surfels_sh_ = inst_.shaders.static_shader_get(DEBUG_SURFELS);
display_grids_enabled_ = DRW_state_draw_support() &&
(inst_.scene->eevee.flag & SCE_EEVEE_SHOW_IRRADIANCE);
/* TODO option. */
int atlas_byte_size = 1024 * 1024 * 16;
/* This might become an option in the future. */
bool use_l2_band = false;
int sh_coef_len = use_l2_band ? 9 : 4;
int texel_byte_size = 8; /* Assumes GPU_RGBA16F. */
int3 atlas_extent(IRRADIANCE_GRID_BRICK_SIZE);
atlas_extent.z *= sh_coef_len;
int atlas_col_count = 256;
atlas_extent.x *= atlas_col_count;
/* Determine the row count depending on the scene settings. */
int row_byte_size = atlas_extent.x * atlas_extent.y * atlas_extent.z * texel_byte_size;
int atlas_row_count = divide_ceil_u(atlas_byte_size, row_byte_size);
atlas_extent.y *= atlas_row_count;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_ATTACHMENT;
do_full_update_ = irradiance_atlas_tx_.ensure_3d(GPU_RGBA16F, atlas_extent, usage);
if (do_full_update_) {
/* Delete all references to existing bricks. */
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
grid.bricks.clear();
}
brick_pool_.clear();
/* Fill with all the available bricks. */
for (auto i : IndexRange(atlas_row_count * atlas_col_count)) {
if (i == 0) {
/* Reserve one brick for the world. */
world_brick_index_ = 0;
}
else {
IrradianceBrick brick;
brick.atlas_coord = uint2(i % atlas_col_count, i / atlas_col_count) *
IRRADIANCE_GRID_BRICK_SIZE;
brick_pool_.append(irradiance_brick_pack(brick));
}
}
if (irradiance_atlas_tx_.is_valid()) {
/* Clear the pool to avoid any interpolation to undefined values. */
irradiance_atlas_tx_.clear(float4(0.0f));
}
}
/* TODO: Remove this. */
generate_random_surfels();
if (irradiance_atlas_tx_.is_valid() == false) {
inst_.info = "Irradiance Atlas texture could not be created";
}
}
void IrradianceCache::sync()
{
debug_pass_sync();
}
void IrradianceCache::debug_pass_sync()
{
if (inst_.debug_mode == eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS) {
debug_surfels_ps_.init();
debug_surfels_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_DEPTH_LESS_EQUAL);
debug_surfels_ps_.shader_set(debug_surfels_sh_);
debug_surfels_ps_.bind_ssbo("surfels_buf", debug_surfels);
debug_surfels_ps_.push_constant("surfel_radius", 0.25f);
debug_surfels_ps_.draw_procedural(GPU_PRIM_TRI_STRIP, debug_surfels.size(), 4);
if (inst_.is_baking()) {
bake.sync();
}
}
void IrradianceCache::debug_draw(View &view, GPUFrameBuffer *view_fb)
Vector<IrradianceBrickPacked> IrradianceCache::bricks_alloc(int brick_len)
{
if (inst_.debug_mode == eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS) {
inst_.info = "Debug Mode: Irradiance Cache Surfels";
GPU_framebuffer_bind(view_fb);
if (brick_pool_.size() < brick_len) {
/* Fail allocation. Not enough brick in the atlas. */
return {};
}
Vector<IrradianceBrickPacked> allocated(brick_len);
/* Copy bricks to return vector. */
allocated.as_mutable_span().copy_from(brick_pool_.as_span().take_back(brick_len));
/* Remove bricks from the pool. */
brick_pool_.resize(brick_pool_.size() - brick_len);
return allocated;
}
void IrradianceCache::bricks_free(Vector<IrradianceBrickPacked> &bricks)
{
brick_pool_.extend(bricks.as_span());
bricks.clear();
}
void IrradianceCache::set_view(View & /*view*/)
{
Vector<IrradianceGrid *> grid_updates;
Vector<IrradianceGrid *> grid_loaded;
/* First allocate the needed bricks and populate the brick buffer. */
bricks_infos_buf_.clear();
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
LightProbeGridCacheFrame *cache = grid.cache ? grid.cache->grid_static_cache : nullptr;
if (cache == nullptr) {
continue;
}
if (cache->baking.L0 == nullptr && cache->irradiance.L0 == nullptr) {
/* No data. */
continue;
}
int3 grid_size = int3(cache->size);
if (grid_size.x <= 0 || grid_size.y <= 0 || grid_size.z <= 0) {
inst_.info = "Error: Malformed irradiance grid data";
continue;
}
/* TODO frustum cull and only load visible grids. */
/* Note that we reserve 1 slot for the world irradiance. */
if (grid_loaded.size() >= IRRADIANCE_GRID_MAX - 1) {
inst_.info = "Error: Too many grid visible";
continue;
}
if (grid.bricks.is_empty()) {
int3 grid_size_in_bricks = math::divide_ceil(grid_size,
int3(IRRADIANCE_GRID_BRICK_SIZE - 1));
int brick_len = grid_size_in_bricks.x * grid_size_in_bricks.y * grid_size_in_bricks.z;
grid.bricks = bricks_alloc(brick_len);
if (grid.bricks.is_empty()) {
inst_.info = "Error: Irradiance grid allocation failed";
continue;
}
grid_updates.append(&grid);
}
grid.brick_offset = bricks_infos_buf_.size();
bricks_infos_buf_.extend(grid.bricks);
if (grid_size.x <= 0 || grid_size.y <= 0 || grid_size.z <= 0) {
inst_.info = "Error: Malformed irradiance grid data";
continue;
}
float4x4 grid_to_world = grid.object_to_world * math::from_location<float4x4>(float3(-1.0f)) *
math::from_scale<float4x4>(float3(2.0f / float3(grid_size))) *
math::from_location<float4x4>(float3(0.0f));
grid.world_to_grid_transposed = float3x4(math::transpose(math::invert(grid_to_world)));
grid.grid_size = grid_size;
grid_loaded.append(&grid);
}
/* Then create brick & grid infos UBOs content. */
{
/* Stable sorting of grids. */
std::sort(grid_loaded.begin(),
grid_loaded.end(),
[](const IrradianceGrid *a, const IrradianceGrid *b) {
float volume_a = math::determinant(float3x3(a->world_to_grid_transposed));
float volume_b = math::determinant(float3x3(b->world_to_grid_transposed));
if (volume_a != volume_b) {
/* Smallest first. */
return volume_a > volume_b;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
return a->world_to_grid_transposed[0][0] < b->world_to_grid_transposed[0][0] ||
a->world_to_grid_transposed[0][1] < b->world_to_grid_transposed[0][1] ||
a->world_to_grid_transposed[0][2] < b->world_to_grid_transposed[0][2];
});
/* Insert grids in UBO in sorted order. */
int grids_len = 0;
for (IrradianceGrid *grid : grid_loaded) {
grid->grid_index = grids_len;
grids_infos_buf_[grids_len++] = *grid;
}
/* Insert world grid last. */
IrradianceGridData grid;
grid.world_to_grid_transposed = float3x4::identity();
grid.grid_size = int3(1);
grid.brick_offset = bricks_infos_buf_.size();
grids_infos_buf_[grids_len++] = grid;
bricks_infos_buf_.append(world_brick_index_);
if (grids_len < IRRADIANCE_GRID_MAX) {
/* Tag last grid as invalid to stop the iteration. */
grids_infos_buf_[grids_len].grid_size = int3(-1);
}
bricks_infos_buf_.push_update();
grids_infos_buf_.push_update();
}
/* Upload data for each grid that need to be inserted in the atlas. */
for (IrradianceGrid *grid : grid_updates) {
LightProbeGridCacheFrame *cache = grid->cache->grid_static_cache;
/* Staging textures are recreated for each light grid to avoid increasing VRAM usage. */
draw::Texture irradiance_a_tx = {"irradiance_a_tx"};
draw::Texture irradiance_b_tx = {"irradiance_b_tx"};
draw::Texture irradiance_c_tx = {"irradiance_c_tx"};
draw::Texture irradiance_d_tx = {"irradiance_d_tx"};
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
int3 grid_size = int3(cache->size);
if (cache->baking.L0) {
irradiance_a_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L0);
irradiance_b_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_a);
irradiance_c_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_b);
irradiance_d_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_c);
}
else if (cache->irradiance.L0) {
irradiance_a_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L0);
irradiance_b_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_a);
irradiance_c_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_b);
irradiance_d_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_c);
}
else {
continue;
}
if (irradiance_a_tx.is_valid() == false) {
inst_.info = "Error: Could not allocate irradiance staging texture";
/* Avoid undefined behavior with uninitialized values. Still load a clear texture. */
float4 zero(0.0f);
irradiance_a_tx.ensure_3d(GPU_RGB16F, int3(1), usage, zero);
irradiance_b_tx.ensure_3d(GPU_RGB16F, int3(1), usage, zero);
irradiance_c_tx.ensure_3d(GPU_RGB16F, int3(1), usage, zero);
irradiance_d_tx.ensure_3d(GPU_RGB16F, int3(1), usage, zero);
}
grid_upload_ps_.init();
grid_upload_ps_.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_LOAD));
grid_upload_ps_.push_constant("grid_index", grid->grid_index);
grid_upload_ps_.bind_ubo("grids_infos_buf", &grids_infos_buf_);
grid_upload_ps_.bind_ssbo("bricks_infos_buf", &bricks_infos_buf_);
grid_upload_ps_.bind_texture("irradiance_a_tx", &irradiance_a_tx);
grid_upload_ps_.bind_texture("irradiance_b_tx", &irradiance_b_tx);
grid_upload_ps_.bind_texture("irradiance_c_tx", &irradiance_c_tx);
grid_upload_ps_.bind_texture("irradiance_d_tx", &irradiance_d_tx);
grid_upload_ps_.bind_image("irradiance_atlas_img", &irradiance_atlas_tx_);
/* Note that we take into account the padding border of each brick. */
int3 grid_size_in_bricks = math::divide_ceil(grid_size, int3(IRRADIANCE_GRID_BRICK_SIZE - 1));
grid_upload_ps_.dispatch(grid_size_in_bricks);
inst_.manager->submit(grid_upload_ps_);
irradiance_a_tx.free();
irradiance_b_tx.free();
irradiance_c_tx.free();
irradiance_d_tx.free();
}
do_full_update_ = false;
}
void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!inst_.is_baking()) {
debug_pass_draw(view, view_fb);
display_pass_draw(view, view_fb);
}
}
void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
switch (inst_.debug_mode) {
case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL:
inst_.info = "Debug Mode: Surfels Normal";
break;
case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE:
inst_.info = "Debug Mode: Surfels Irradiance";
break;
default:
/* Nothing to display. */
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
if (grid.cache == nullptr) {
continue;
}
LightProbeGridCacheFrame *cache = grid.cache->grid_static_cache;
if (cache->surfels == nullptr || cache->surfels_len == 0) {
continue;
}
debug_surfels_ps_.init();
debug_surfels_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_DEPTH_LESS_EQUAL);
display_grids_ps_.framebuffer_set(&view_fb);
debug_surfels_ps_.shader_set(inst_.shaders.static_shader_get(DEBUG_SURFELS));
debug_surfels_ps_.push_constant("surfel_radius", 1.5f / 4.0f);
debug_surfels_ps_.push_constant("debug_mode", static_cast<int>(inst_.debug_mode));
debug_surfels_buf_.resize(cache->surfels_len);
/* TODO(fclem): Cleanup: Could have a function in draw::StorageArrayBuffer that takes an input
* data. */
Span<Surfel> grid_surfels(static_cast<Surfel *>(cache->surfels), cache->surfels_len);
MutableSpan<Surfel>(debug_surfels_buf_.data(), cache->surfels_len).copy_from(grid_surfels);
debug_surfels_buf_.push_update();
debug_surfels_ps_.bind_ssbo("surfels_buf", debug_surfels_buf_);
debug_surfels_ps_.draw_procedural(GPU_PRIM_TRI_STRIP, cache->surfels_len, 4);
inst_.manager->submit(debug_surfels_ps_, view);
}
}
void IrradianceCache::display_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!display_grids_enabled_) {
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
if (grid.cache == nullptr) {
continue;
}
LightProbeGridCacheFrame *cache = grid.cache->grid_static_cache;
if (cache == nullptr) {
continue;
}
/* Display texture. Updated for each individual light grid to avoid increasing VRAM usage. */
draw::Texture irradiance_a_tx = {"irradiance_a_tx"};
draw::Texture irradiance_b_tx = {"irradiance_b_tx"};
draw::Texture irradiance_c_tx = {"irradiance_c_tx"};
draw::Texture irradiance_d_tx = {"irradiance_d_tx"};
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
int3 grid_size = int3(cache->size);
if (cache->baking.L0) {
irradiance_a_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L0);
irradiance_b_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_a);
irradiance_c_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_b);
irradiance_d_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L1_c);
}
else if (cache->irradiance.L0) {
irradiance_a_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L0);
irradiance_b_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_a);
irradiance_c_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_b);
irradiance_d_tx.ensure_3d(GPU_RGB16F, grid_size, usage, (float *)cache->irradiance.L1_c);
}
else {
continue;
}
display_grids_ps_.init();
display_grids_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_CULL_BACK);
display_grids_ps_.framebuffer_set(&view_fb);
display_grids_ps_.shader_set(inst_.shaders.static_shader_get(DISPLAY_PROBE_GRID));
display_grids_ps_.push_constant("sphere_radius", inst_.scene->eevee.gi_irradiance_draw_size);
display_grids_ps_.push_constant("grid_resolution", grid_size);
display_grids_ps_.push_constant("grid_to_world", grid.object_to_world);
display_grids_ps_.push_constant("world_to_grid", grid.world_to_object);
display_grids_ps_.bind_texture("irradiance_a_tx", &irradiance_a_tx);
display_grids_ps_.bind_texture("irradiance_b_tx", &irradiance_b_tx);
display_grids_ps_.bind_texture("irradiance_c_tx", &irradiance_c_tx);
display_grids_ps_.bind_texture("irradiance_d_tx", &irradiance_d_tx);
int sample_count = int(BKE_lightprobe_grid_cache_frame_sample_count(cache));
int triangle_count = sample_count * 2;
display_grids_ps_.draw_procedural(GPU_PRIM_TRIS, 1, triangle_count * 3);
inst_.manager->submit(display_grids_ps_, view);
irradiance_a_tx.free();
irradiance_b_tx.free();
irradiance_c_tx.free();
irradiance_d_tx.free();
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Baking
* \{ */
void IrradianceBake::init(const Object &probe_object)
{
const ::LightProbe *lightprobe = static_cast<::LightProbe *>(probe_object.data);
surfel_density_ = lightprobe->surfel_density;
}
void IrradianceBake::sync()
{
{
PassSimple &pass = surfel_light_eval_ps_;
pass.init();
/* Apply lights contribution to scene surfel representation. */
pass.shader_set(inst_.shaders.static_shader_get(SURFEL_LIGHT));
pass.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
pass.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
inst_.lights.bind_resources(&pass);
inst_.shadows.bind_resources(&pass);
/* Sync with the surfel creation stage. */
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
pass.dispatch(&dispatch_per_surfel_);
}
{
PassSimple &pass = surfel_ray_build_ps_;
pass.init();
{
PassSimple::Sub &sub = pass.sub("ListBuild");
sub.shader_set(inst_.shaders.static_shader_get(SURFEL_LIST_BUILD));
sub.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
sub.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
sub.bind_ssbo("list_start_buf", &list_start_buf_);
sub.bind_ssbo("list_info_buf", &list_info_buf_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
sub.dispatch(&dispatch_per_surfel_);
}
{
PassSimple::Sub &sub = pass.sub("ListSort");
sub.shader_set(inst_.shaders.static_shader_get(SURFEL_LIST_SORT));
sub.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
sub.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
sub.bind_ssbo("list_start_buf", &list_start_buf_);
sub.bind_ssbo("list_info_buf", &list_info_buf_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
sub.dispatch(&dispatch_per_list_);
}
}
{
PassSimple &pass = surfel_light_propagate_ps_;
pass.init();
{
PassSimple::Sub &sub = pass.sub("RayEval");
sub.shader_set(inst_.shaders.static_shader_get(SURFEL_RAY));
sub.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
sub.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
sub.push_constant("radiance_src", &radiance_src_);
sub.push_constant("radiance_dst", &radiance_dst_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
sub.dispatch(&dispatch_per_surfel_);
}
}
{
PassSimple &pass = irradiance_capture_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_RAY));
pass.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
pass.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
pass.bind_ssbo("list_start_buf", &list_start_buf_);
pass.bind_ssbo("list_info_buf", &list_info_buf_);
pass.push_constant("radiance_src", &radiance_src_);
pass.bind_image("irradiance_L0_img", &irradiance_L0_tx_);
pass.bind_image("irradiance_L1_a_img", &irradiance_L1_a_tx_);
pass.bind_image("irradiance_L1_b_img", &irradiance_L1_b_tx_);
pass.bind_image("irradiance_L1_c_img", &irradiance_L1_c_tx_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.dispatch(&dispatch_per_grid_sample_);
}
}
void IrradianceBake::surfel_raster_views_sync(const float3 &scene_min, const float3 &scene_max)
{
using namespace blender::math;
grid_pixel_extent_ = max(int3(1), int3(surfel_density_ * (scene_max - scene_min)));
grid_pixel_extent_ = min(grid_pixel_extent_, int3(16384));
/* We could use multi-view rendering here to avoid multiple submissions but it is unlikely to
* make any difference. The bottleneck is still the light propagation loop. */
auto sync_view = [&](View &view, CartesianBasis basis) {
float3 extent_min = transform_point(invert(basis), scene_min);
float3 extent_max = transform_point(invert(basis), scene_max);
float4x4 winmat = projection::orthographic(
extent_min.x, extent_max.x, extent_min.y, extent_max.y, -extent_min.z, -extent_max.z);
float4x4 viewinv = from_rotation<float4x4>(to_quaternion<float>(basis));
view.visibility_test(false);
view.sync(invert(viewinv), winmat);
};
sync_view(view_x_, basis_x_);
sync_view(view_y_, basis_y_);
sync_view(view_z_, basis_z_);
}
void IrradianceBake::surfels_create(const Object &probe_object)
{
/**
* We rasterize the scene along the 3 axes. Each generated fragment will write a surface element
* so raster grid density need to match the desired surfel density. We do a first pass to know
* how much surfel to allocate then render again to create the surfels.
*/
using namespace blender::math;
const ::LightProbe *lightprobe = static_cast<::LightProbe *>(probe_object.data);
int3 grid_resolution = int3(&lightprobe->grid_resolution_x);
float4x4 grid_local_to_world = invert(float4x4(probe_object.world_to_object));
dispatch_per_grid_sample_ = math::divide_ceil(grid_resolution, int3(IRRADIANCE_GRID_GROUP_SIZE));
capture_info_buf_.irradiance_grid_size = grid_resolution;
capture_info_buf_.irradiance_grid_local_to_world = grid_local_to_world;
capture_info_buf_.irradiance_grid_world_to_local_rotation = float4x4(
(invert(normalize(float3x3(grid_local_to_world)))));
eGPUTextureUsage texture_usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_HOST_READ | GPU_TEXTURE_USAGE_ATTACHMENT;
/* 32bit float is needed here otherwise we loose too much energy from rounding error during the
* accumulation when the sample count is above 500. */
irradiance_L0_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage);
irradiance_L1_a_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage);
irradiance_L1_b_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage);
irradiance_L1_c_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage);
irradiance_L0_tx_.clear(float4(0.0f));
irradiance_L1_a_tx_.clear(float4(0.0f));
irradiance_L1_b_tx_.clear(float4(0.0f));
irradiance_L1_c_tx_.clear(float4(0.0f));
DRW_stats_group_start("IrradianceBake.SceneBounds");
{
draw::Manager &manager = *inst_.manager;
PassSimple &pass = irradiance_bounds_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_BOUNDS));
pass.bind_ssbo("capture_info_buf", &capture_info_buf_);
pass.bind_ssbo("bounds_buf", &manager.bounds_buf.current());
pass.push_constant("resource_len", int(manager.resource_handle_count()));
pass.dispatch(
int3(divide_ceil_u(manager.resource_handle_count(), IRRADIANCE_BOUNDS_GROUP_SIZE), 1, 1));
}
/* Raster the scene to query the number of surfel needed. */
capture_info_buf_.do_surfel_count = false;
capture_info_buf_.do_surfel_output = false;
int neg_flt_max = int(0xFF7FFFFFu ^ 0x7FFFFFFFu); /* floatBitsToOrderedInt(-FLT_MAX) */
int pos_flt_max = 0x7F7FFFFF; /* floatBitsToOrderedInt(FLT_MAX) */
capture_info_buf_.scene_bound_x_min = pos_flt_max;
capture_info_buf_.scene_bound_y_min = pos_flt_max;
capture_info_buf_.scene_bound_z_min = pos_flt_max;
capture_info_buf_.scene_bound_x_max = neg_flt_max;
capture_info_buf_.scene_bound_y_max = neg_flt_max;
capture_info_buf_.scene_bound_z_max = neg_flt_max;
capture_info_buf_.push_update();
inst_.manager->submit(irradiance_bounds_ps_);
GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE);
capture_info_buf_.read();
auto ordered_int_bits_to_float = [](int32_t int_value) -> float {
int32_t float_bits = (int_value < 0) ? (int_value ^ 0x7FFFFFFF) : int_value;
return *reinterpret_cast<float *>(&float_bits);
};
float3 scene_min = float3(ordered_int_bits_to_float(capture_info_buf_.scene_bound_x_min),
ordered_int_bits_to_float(capture_info_buf_.scene_bound_y_min),
ordered_int_bits_to_float(capture_info_buf_.scene_bound_z_min));
float3 scene_max = float3(ordered_int_bits_to_float(capture_info_buf_.scene_bound_x_max),
ordered_int_bits_to_float(capture_info_buf_.scene_bound_y_max),
ordered_int_bits_to_float(capture_info_buf_.scene_bound_z_max));
/* To avoid loosing any surface to the clipping planes, add some padding. */
float epsilon = 1.0f / surfel_density_;
scene_min -= epsilon;
scene_max += epsilon;
surfel_raster_views_sync(scene_min, scene_max);
scene_bound_sphere_ = float4(midpoint(scene_max, scene_min),
distance(scene_max, scene_min) / 2.0f);
DRW_stats_group_end();
/* WORKAROUND: Sync camera with correct bounds for light culling. */
inst_.camera.sync();
DRW_stats_group_start("IrradianceBake.SurfelsCount");
/* Raster the scene to query the number of surfel needed. */
capture_info_buf_.do_surfel_count = true;
capture_info_buf_.do_surfel_output = false;
capture_info_buf_.surfel_len = 0u;
capture_info_buf_.push_update();
empty_raster_fb_.ensure(transform_point(invert(basis_x_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_x_);
empty_raster_fb_.ensure(transform_point(invert(basis_y_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_y_);
empty_raster_fb_.ensure(transform_point(invert(basis_z_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_z_);
DRW_stats_group_end();
/* Allocate surfel pool. */
GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE);
capture_info_buf_.read();
if (capture_info_buf_.surfel_len == 0) {
/* No surfel to allocated. */
return;
}
/* TODO(fclem): Check for GL limit and abort if the surfel cache doesn't fit the GPU memory. */
surfels_buf_.resize(capture_info_buf_.surfel_len);
surfels_buf_.clear_to_zero();
dispatch_per_surfel_.x = divide_ceil_u(surfels_buf_.size(), SURFEL_GROUP_SIZE);
DRW_stats_group_start("IrradianceBake.SurfelsCreate");
/* Raster the scene to generate the surfels. */
capture_info_buf_.do_surfel_count = true;
capture_info_buf_.do_surfel_output = true;
capture_info_buf_.surfel_len = 0u;
capture_info_buf_.push_update();
empty_raster_fb_.ensure(transform_point(invert(basis_x_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_x_);
empty_raster_fb_.ensure(transform_point(invert(basis_y_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_y_);
empty_raster_fb_.ensure(transform_point(invert(basis_z_), grid_pixel_extent_).xy());
inst_.pipelines.capture.render(view_z_);
/* Sync with any other following pass using the surfel buffer. */
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
/* Read back so that following push_update will contain correct surfel count. */
capture_info_buf_.read();
DRW_stats_group_end();
}
void IrradianceBake::surfels_lights_eval()
{
/* Use the last setup view. This should work since the view is orthographic. */
/* TODO(fclem): Remove this. It is only present to avoid crash inside `shadows.set_view` */
inst_.render_buffers.acquire(int2(1));
inst_.lights.set_view(view_z_, grid_pixel_extent_.xy());
inst_.shadows.set_view(view_z_);
inst_.render_buffers.release();
inst_.manager->submit(surfel_light_eval_ps_, view_z_);
}
void IrradianceBake::raylists_build()
{
using namespace blender::math;
float2 rand_uv = inst_.sampling.rng_2d_get(eSamplingDimension::SAMPLING_LENS_U);
const float3 ray_direction = inst_.sampling.sample_sphere(rand_uv);
const float3 up = ray_direction;
const float3 forward = cross(up, normalize(orthogonal(up)));
const float4x4 viewinv = from_orthonormal_axes<float4x4>(float3(0.0f), forward, up);
const float4x4 viewmat = invert(viewinv);
/* Compute projection bounds. */
float2 min, max;
min = max = transform_point(viewmat, scene_bound_sphere_.xyz()).xy();
min -= scene_bound_sphere_.w;
max += scene_bound_sphere_.w;
/* This avoid light leaking by making sure that for one surface there will always be at least 1
* surfel capture inside a ray list. Since the surface with the maximum distance (after
* projection) between adjacent surfels is a slope that goes through 3 corners of a cube,
* the distance the grid needs to cover is the diagonal of a cube face.
*
* The lower the number the more surfels it clumps together in the same surfel-list.
* Biasing the grid_density like that will create many invalid link between coplanar surfels.
* These are dealt with during the list sorting pass.
*
* This has a side effect of inflating shadows and emissive surfaces.
*
* We add an extra epsilon just in case. We really need this step to be leak free. */
const float max_distance_between_neighbor_surfels_inv = M_SQRT1_2 - 1e-4;
/* Surfel list per unit distance. */
const float ray_grid_density = surfel_density_ * max_distance_between_neighbor_surfels_inv;
/* Surfel list size in unit distance. */
const float pixel_size = 1.0f / ray_grid_density;
list_info_buf_.ray_grid_size = math::max(int2(1), int2(ray_grid_density * (max - min)));
/* Add a 2 pixels margin to have empty lists for irradiance grid samples to fall into (as they
* are not considered by the scene bounds). The first pixel margin is because we are jittering
* the grid position. */
list_info_buf_.ray_grid_size += int2(4);
min -= pixel_size * 2.0f;
max += pixel_size * 2.0f;
/* Randomize grid center to avoid uneven inflating of corners in some directions. */
const float2 aa_rand = inst_.sampling.rng_2d_get(eSamplingDimension::SAMPLING_FILTER_U);
/* Offset in surfel list "pixel". */
const float2 aa_offset = (aa_rand - 0.5f) * 0.499f;
min += pixel_size * aa_offset;
list_info_buf_.list_max = list_info_buf_.ray_grid_size.x * list_info_buf_.ray_grid_size.y;
list_info_buf_.push_update();
/* NOTE: Z values do not really matter since we are not doing any rasterization. */
const float4x4 winmat = projection::orthographic<float>(min.x, max.x, min.y, max.y, 0, 1);
ray_view_.sync(viewmat, winmat);
dispatch_per_list_.x = divide_ceil_u(list_info_buf_.list_max, SURFEL_LIST_GROUP_SIZE);
list_start_buf_.resize(ceil_to_multiple_u(list_info_buf_.list_max, 4));
GPU_storagebuf_clear(list_start_buf_, -1);
inst_.manager->submit(surfel_ray_build_ps_, ray_view_);
}
void IrradianceBake::propagate_light()
{
/* NOTE: Subtract 1 because after `sampling.step()`. */
capture_info_buf_.sample_index = inst_.sampling.sample_index() - 1;
capture_info_buf_.sample_count = inst_.sampling.sample_count();
capture_info_buf_.push_update();
inst_.manager->submit(surfel_light_propagate_ps_, ray_view_);
std::swap(radiance_src_, radiance_dst_);
}
void IrradianceBake::irradiance_capture()
{
inst_.manager->submit(irradiance_capture_ps_, ray_view_);
}
void IrradianceBake::read_surfels(LightProbeGridCacheFrame *cache_frame)
{
if (!ELEM(inst_.debug_mode,
eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL,
eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE))
{
return;
}
GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE);
capture_info_buf_.read();
surfels_buf_.read();
cache_frame->surfels_len = capture_info_buf_.surfel_len;
cache_frame->surfels = MEM_malloc_arrayN(cache_frame->surfels_len, sizeof(Surfel), __func__);
MutableSpan<Surfel> surfels_dst((Surfel *)cache_frame->surfels, cache_frame->surfels_len);
Span<Surfel> surfels_src(surfels_buf_.data(), cache_frame->surfels_len);
surfels_dst.copy_from(surfels_src);
}
LightProbeGridCacheFrame *IrradianceBake::read_result_unpacked()
{
LightProbeGridCacheFrame *cache_frame = BKE_lightprobe_grid_cache_frame_create();
read_surfels(cache_frame);
cache_frame->size[0] = irradiance_L0_tx_.width();
cache_frame->size[1] = irradiance_L0_tx_.height();
cache_frame->size[2] = irradiance_L0_tx_.depth();
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
cache_frame->baking.L0 = (float(*)[4])irradiance_L0_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_a = (float(*)[4])irradiance_L1_a_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_b = (float(*)[4])irradiance_L1_b_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_c = (float(*)[4])irradiance_L1_c_tx_.read<float4>(GPU_DATA_FLOAT);
/* TODO(fclem): Connectivity. */
// cache_frame->connectivity.bitmask = connectivity_tx_.read<uint8_t>(GPU_DATA_FLOAT);
return cache_frame;
}
LightProbeGridCacheFrame *IrradianceBake::read_result_packed()
{
LightProbeGridCacheFrame *cache_frame = BKE_lightprobe_grid_cache_frame_create();
read_surfels(cache_frame);
cache_frame->size[0] = irradiance_L0_tx_.width();
cache_frame->size[1] = irradiance_L0_tx_.height();
cache_frame->size[2] = irradiance_L0_tx_.depth();
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
cache_frame->baking.L0 = (float(*)[4])irradiance_L0_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_a = (float(*)[4])irradiance_L1_a_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_b = (float(*)[4])irradiance_L1_b_tx_.read<float4>(GPU_DATA_FLOAT);
cache_frame->baking.L1_c = (float(*)[4])irradiance_L1_c_tx_.read<float4>(GPU_DATA_FLOAT);
int64_t sample_count = irradiance_L0_tx_.width() * irradiance_L0_tx_.height() *
irradiance_L0_tx_.depth();
size_t coefficient_texture_size = sizeof(*cache_frame->irradiance.L0) * sample_count;
cache_frame->irradiance.L0 = (float(*)[3])MEM_mallocN(coefficient_texture_size, __func__);
cache_frame->irradiance.L1_a = (float(*)[3])MEM_mallocN(coefficient_texture_size, __func__);
cache_frame->irradiance.L1_b = (float(*)[3])MEM_mallocN(coefficient_texture_size, __func__);
cache_frame->irradiance.L1_c = (float(*)[3])MEM_mallocN(coefficient_texture_size, __func__);
for (auto i : IndexRange(sample_count)) {
copy_v3_v3(cache_frame->irradiance.L0[i], cache_frame->baking.L0[i]);
copy_v3_v3(cache_frame->irradiance.L1_a[i], cache_frame->baking.L1_a[i]);
copy_v3_v3(cache_frame->irradiance.L1_b[i], cache_frame->baking.L1_b[i]);
copy_v3_v3(cache_frame->irradiance.L1_c[i], cache_frame->baking.L1_c[i]);
}
MEM_SAFE_FREE(cache_frame->baking.L0);
MEM_SAFE_FREE(cache_frame->baking.L1_a);
MEM_SAFE_FREE(cache_frame->baking.L1_b);
MEM_SAFE_FREE(cache_frame->baking.L1_c);
// cache_frame->visibility.L0 = irradiance_only_L0_tx_.read<uint8_t>(GPU_DATA_UBYTE);
// cache_frame->visibility.L1_a = irradiance_only_L1_a_tx_.read<uint8_t>(GPU_DATA_UBYTE);
// cache_frame->visibility.L1_b = irradiance_only_L1_b_tx_.read<uint8_t>(GPU_DATA_UBYTE);
// cache_frame->visibility.L1_c = irradiance_only_L1_c_tx_.read<uint8_t>(GPU_DATA_UBYTE);
/* TODO(fclem): Connectivity. */
// cache_frame->connectivity.bitmask = connectivity_tx_.read<uint8_t>(GPU_DATA_FLOAT);
return cache_frame;
}
/** \} */
} // namespace blender::eevee

View File

@@ -2,34 +2,185 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "DNA_lightprobe_types.h"
#include "BLI_math_quaternion_types.hh"
#include "eevee_lightprobe.hh"
#include "eevee_shader_shared.hh"
namespace blender::eevee {
class Instance;
class CapturePipeline;
class ShadowModule;
class Camera;
/**
* Baking related pass and data. Not used at runtime.
*/
class IrradianceBake {
friend CapturePipeline;
friend ShadowModule;
friend Camera;
class IrradianceCache {
private:
Instance &inst_;
DebugSurfelBuf debug_surfels;
PassSimple debug_surfels_ps_ = {"IrradianceCache.Debug"};
GPUShader *debug_surfels_sh_ = nullptr;
/** Light cache being baked. */
LightCache *light_cache_ = nullptr;
/** Surface elements that represent the scene. */
SurfelBuf surfels_buf_;
/** Capture state. */
CaptureInfoBuf capture_info_buf_;
/** Framebuffer. */
Framebuffer empty_raster_fb_ = {"empty_raster_fb_"};
/** Evaluate light object contribution and store result to surfel. */
PassSimple surfel_light_eval_ps_ = {"LightEval"};
/** Create linked list of surfel to emulated raycast. */
PassSimple surfel_ray_build_ps_ = {"RayBuild"};
/** Propagate light from surfel to surfel. */
PassSimple surfel_light_propagate_ps_ = {"LightPropagate"};
/** Capture surfel lighting to irradiance samples. */
PassSimple irradiance_capture_ps_ = {"IrradianceCapture"};
/** Compute scene bounding box. */
PassSimple irradiance_bounds_ps_ = {"IrradianceBounds"};
/** Index of source and destination radiance in radiance double-buffer. */
int radiance_src_ = 0, radiance_dst_ = 1;
/* TODO: Remove this. */
void generate_random_surfels();
/**
* Basis orientation for each baking projection.
* Note that this is the view orientation. The projection matrix will take the negative Z axis
* as forward and Y as up. */
math::CartesianBasis basis_x_ = {
math::AxisSigned::Y_POS, math::AxisSigned::Z_POS, math::AxisSigned::X_POS};
math::CartesianBasis basis_y_ = {
math::AxisSigned::X_POS, math::AxisSigned::Z_POS, math::AxisSigned::Y_NEG};
math::CartesianBasis basis_z_ = {
math::AxisSigned::X_POS, math::AxisSigned::Y_POS, math::AxisSigned::Z_POS};
/** Views for each baking projection. */
View view_x_ = {"BakingViewX"};
View view_y_ = {"BakingViewY"};
View view_z_ = {"BakingViewZ"};
/** Pixel resolution in each of the projection axes. Match the target surfel density. */
int3 grid_pixel_extent_ = int3(0);
/** Information for surfel list building. */
SurfelListInfoBuf list_info_buf_ = {"list_info_buf_"};
/** List array containing list start surfel index. Cleared to -1. */
StorageArrayBuffer<int, 16, true> list_start_buf_ = {"list_start_buf_"};
/* Dispatch size for per surfel workload. */
int3 dispatch_per_surfel_ = int3(1);
/* Dispatch size for per surfel list workload. */
int3 dispatch_per_list_ = int3(1);
/* Dispatch size for per grid sample workload. */
int3 dispatch_per_grid_sample_ = int3(1);
/** View used to flatten the surfels into surfel lists representing rays. */
View ray_view_ = {"RayProjectionView"};
/** Irradiance textures for baking. Only represents one grid in there. */
Texture irradiance_L0_tx_ = {"irradiance_L0_tx_"};
Texture irradiance_L1_a_tx_ = {"irradiance_L1_a_tx_"};
Texture irradiance_L1_b_tx_ = {"irradiance_L1_b_tx_"};
Texture irradiance_L1_c_tx_ = {"irradiance_L1_c_tx_"};
/* Bounding sphere of the scene being baked. In world space. */
float4 scene_bound_sphere_;
/* Surfel per unit distance. */
float surfel_density_ = 1.0f;
public:
IrradianceCache(Instance &inst) : inst_(inst){};
IrradianceBake(Instance &inst) : inst_(inst){};
void init(const Object &probe_object);
void sync();
/** Create the views used to rasterize the scene into surfel representation. */
void surfel_raster_views_sync(const float3 &scene_min, const float3 &scene_max);
/** Create a surfel representation of the scene from the probe using the capture pipeline. */
void surfels_create(const Object &probe_object);
/** Evaluate direct lighting (and also clear the surfels radiance). */
void surfels_lights_eval();
/** Create a surfel lists to emulate ray-casts for the current sample random direction. */
void raylists_build();
/** Propagate light from surfel to surfel in a random direction over the sphere. */
void propagate_light();
/** Store surfel irradiance inside the irradiance grid samples. */
void irradiance_capture();
/** Read grid unpacked irradiance back to CPU and returns as a #LightProbeGridCacheFrame. */
LightProbeGridCacheFrame *read_result_unpacked();
/** Read grid packed irradiance back to CPU and returns as a #LightProbeGridCacheFrame. */
LightProbeGridCacheFrame *read_result_packed();
private:
/** Read surfel data back to CPU into \a cache_frame . */
void read_surfels(LightProbeGridCacheFrame *cache_frame);
};
/**
* Runtime container of diffuse indirect lighting.
* Also have debug and baking components.
*/
class IrradianceCache {
public:
IrradianceBake bake;
private:
Instance &inst_;
/** Atlas 3D texture containing all loaded grid data. */
Texture irradiance_atlas_tx_ = {"irradiance_atlas_tx_"};
/** Reserved atlas brick for world irradiance. */
int world_brick_index_ = 0;
/** Data structure used to index irradiance cache pages inside the atlas. */
IrradianceGridDataBuf grids_infos_buf_ = {"grids_infos_buf_"};
IrradianceBrickBuf bricks_infos_buf_ = {"bricks_infos_buf_"};
/** Pool of atlas regions to allocate to different grids. */
Vector<IrradianceBrickPacked> brick_pool_;
/** Stream data into the irradiance atlas texture. */
PassSimple grid_upload_ps_ = {"IrradianceCache.Upload"};
/** If true, will trigger the reupload of all grid data instead of just streaming new ones. */
bool do_full_update_ = true;
/** Display surfel debug data. */
PassSimple debug_surfels_ps_ = {"IrradianceCache.Debug"};
/** Debug surfel elements copied from the light cache. */
draw::StorageArrayBuffer<Surfel> debug_surfels_buf_;
/** Display grid cache data. */
bool display_grids_enabled_ = false;
PassSimple display_grids_ps_ = {"IrradianceCache.Display Grids"};
public:
IrradianceCache(Instance &inst) : bake(inst), inst_(inst){};
~IrradianceCache(){};
void init();
void sync();
void set_view(View &view);
void viewport_draw(View &view, GPUFrameBuffer *view_fb);
void debug_pass_sync();
void debug_draw(View &view, GPUFrameBuffer *view_fb);
Vector<IrradianceBrickPacked> bricks_alloc(int brick_len);
void bricks_free(Vector<IrradianceBrickPacked> &bricks);
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_ubo(IRRADIANCE_GRID_BUF_SLOT, &grids_infos_buf_);
pass->bind_ssbo(IRRADIANCE_BRICK_BUF_SLOT, &bricks_infos_buf_);
pass->bind_texture(IRRADIANCE_ATLAS_TEX_SLOT, &irradiance_atlas_tx_);
}
private:
void debug_pass_draw(View &view, GPUFrameBuffer *view_fb);
void display_pass_draw(View &view, GPUFrameBuffer *view_fb);
};
} // namespace blender::eevee

View File

@@ -0,0 +1,346 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Contains everything about light baking.
*/
#include "DRW_render.h"
#include "BKE_global.h"
#include "BKE_lightprobe.h"
#include "DNA_lightprobe_types.h"
#include "BLI_threads.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "PIL_time.h"
#include "GPU_capabilities.h"
#include "GPU_context.h"
#include "WM_api.h"
#include "WM_types.h"
#include "wm_window.h"
#include "eevee_engine.h"
#include "eevee_instance.hh"
#include "eevee_lightcache.hh"
/* -------------------------------------------------------------------- */
/** \name Light Probe Baking
* \{ */
namespace blender::eevee {
class LightBake {
private:
Depsgraph *depsgraph_;
/** Scene frame to evaluate the depsgraph at. */
int frame_;
/** Milliseconds. Delay the start of the baking to not slowdown interactions (TODO: remove). */
int delay_ms_;
/**
* If running in parallel (in a separate thread), use this context.
* Created on main thread but first bound in worker thread.
*/
void *gl_context_ = nullptr;
/** Context associated to `gl_context_`. Created in the worker thread. */
GPUContext *gpu_context_ = nullptr;
/** Baking instance. Created and freed in the worker thread. */
Instance *instance_ = nullptr;
/** Manager used for command submission. Created and freed in the worker thread. */
draw::Manager *manager_ = nullptr;
/** Lightprobe original objects to bake. */
Vector<Object *> original_probes_;
/** Frame to copy to original objects during update. This is needed to avoid race conditions. */
Vector<LightProbeGridCacheFrame *> bake_result_;
std::mutex result_mutex_;
public:
LightBake(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Span<Object *> probes,
bool run_as_job,
int frame,
int delay_ms = 0)
: depsgraph_(DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER)),
frame_(frame),
delay_ms_(delay_ms),
original_probes_(probes)
{
BLI_assert(BLI_thread_is_main());
bake_result_.resize(probes.size());
bake_result_.fill(nullptr);
if (run_as_job && !GPU_use_main_context_workaround()) {
/* This needs to happen in main thread. */
gl_context_ = WM_system_gpu_context_create();
wm_window_reset_drawable();
}
}
~LightBake()
{
BLI_assert(BLI_thread_is_main());
DEG_graph_free(depsgraph_);
}
/**
* Called from main thread.
* Copy result to original scene data.
* Note that since this is in the main thread, the viewport cannot be using the light cache.
* So there is no race condition here.
*/
void update()
{
BLI_assert(BLI_thread_is_main());
for (auto i : bake_result_.index_range()) {
if (bake_result_[i] == nullptr) {
continue;
}
Object *orig_ob = original_probes_[i];
{
std::scoped_lock lock(result_mutex_);
LightProbeObjectCache *cache = orig_ob->lightprobe_cache;
/* Delete any existing cache. */
if (cache->grid_static_cache != nullptr) {
BKE_lightprobe_grid_cache_frame_free(cache->grid_static_cache);
}
/* Pass ownership to original object. */
cache->grid_static_cache = bake_result_[i];
bake_result_[i] = nullptr;
}
/* Propagate the cache to evaluated object. */
DEG_id_tag_update(&orig_ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
/**
* Called from worker thread.
*/
void run(bool *stop = nullptr, bool *do_update = nullptr, float *progress = nullptr)
{
DEG_graph_relations_update(depsgraph_);
DEG_evaluate_on_framechange(depsgraph_, frame_);
if (delay_ms_ > 0) {
PIL_sleep_ms(delay_ms_);
}
context_enable();
manager_ = new draw::Manager();
instance_ = new eevee::Instance();
instance_->init_light_bake(depsgraph_, manager_);
context_disable();
for (auto i : original_probes_.index_range()) {
Object *eval_ob = DEG_get_evaluated_object(depsgraph_, original_probes_[i]);
instance_->light_bake_irradiance(
*eval_ob,
[this]() { context_enable(); },
[this]() { context_disable(); },
[&]() { return (G.is_break == true) || ((stop != nullptr) ? *stop : false); },
[&](LightProbeGridCacheFrame *cache_frame, float grid_progress) {
{
std::scoped_lock lock(result_mutex_);
/* Delete any existing cache that wasn't transferred to the original object. */
if (bake_result_[i] != nullptr) {
BKE_lightprobe_grid_cache_frame_free(bake_result_[i]);
}
bake_result_[i] = cache_frame;
}
if (do_update) {
*do_update = true;
}
if (progress) {
*progress = (i + grid_progress) / original_probes_.size();
}
});
if ((G.is_break == true) || ((stop != nullptr && *stop == true))) {
break;
}
}
delete_resources();
}
private:
void context_enable(bool render_begin = true)
{
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
GPU_context_main_lock();
DRW_gpu_context_enable();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_enable();
}
else {
/* Worker thread case. */
DRW_system_gpu_render_context_enable(gl_context_);
if (gpu_context_ == nullptr) {
/* Create GPUContext in worker thread as it needs the correct gl context bound (which can
* only be bound in worker thread because of some GL driver requirements). */
gpu_context_ = GPU_context_create(nullptr, gl_context_);
}
DRW_blender_gpu_render_context_enable(gpu_context_);
}
if (render_begin) {
GPU_render_begin();
}
}
void context_disable()
{
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
DRW_gpu_context_disable();
GPU_render_end();
GPU_context_main_unlock();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_disable();
GPU_render_end();
}
else {
/* Worker thread case. */
DRW_blender_gpu_render_context_disable(gpu_context_);
GPU_render_end();
DRW_system_gpu_render_context_disable(gl_context_);
}
}
/**
* Delete the engine instance and the optional contexts.
* This needs to run on the worker thread because the OpenGL context can only be ever bound to a
* single thread (because of some driver implementation), and the resources (textures,
* buffers,...) need to be freed with the right context bound.
*/
void delete_resources()
{
/* Bind context without GPU_render_begin(). */
context_enable(false);
/* Free GPU data (Textures, Framebuffers, etc...). */
delete instance_;
delete manager_;
/* Delete / unbind the GL & GPU context. Assumes it is currently bound. */
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
DRW_gpu_context_disable();
GPU_context_main_unlock();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_disable();
}
else {
/* Worker thread case. */
if (gpu_context_ != nullptr) {
GPU_context_discard(gpu_context_);
}
DRW_system_gpu_render_context_disable(gl_context_);
WM_system_gpu_context_dispose(gl_context_);
}
}
};
} // namespace blender::eevee
using namespace blender::eevee;
/* -------------------------------------------------------------------- */
/** \name Light Bake Job
* \{ */
wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm,
wmWindow *win,
Main *bmain,
ViewLayer *view_layer,
Scene *scene,
blender::Vector<Object *> original_probes,
int delay_ms,
int frame)
{
/* Do not bake if there is a render going on. */
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER)) {
return nullptr;
}
/* Stop existing baking job. */
WM_jobs_stop(wm, nullptr, (void *)EEVEE_NEXT_lightbake_job);
wmJob *wm_job = WM_jobs_get(wm,
win,
scene,
"Bake Lighting",
WM_JOB_EXCL_RENDER | WM_JOB_PRIORITY | WM_JOB_PROGRESS,
WM_JOB_TYPE_LIGHT_BAKE);
LightBake *bake = new LightBake(
bmain, view_layer, scene, std::move(original_probes), true, frame, delay_ms);
WM_jobs_customdata_set(wm_job, bake, EEVEE_NEXT_lightbake_job_data_free);
WM_jobs_timer(wm_job, 0.4, NC_SCENE | NA_EDITED, 0);
WM_jobs_callbacks(wm_job,
EEVEE_NEXT_lightbake_job,
nullptr,
EEVEE_NEXT_lightbake_update,
EEVEE_NEXT_lightbake_update);
G.is_break = false;
return wm_job;
}
void *EEVEE_NEXT_lightbake_job_data_alloc(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
blender::Vector<Object *> original_probes,
int frame)
{
LightBake *bake = new LightBake(
bmain, view_layer, scene, std::move(original_probes), false, frame);
/* TODO(fclem): Can remove this cast once we remove the previous EEVEE light cache. */
return reinterpret_cast<void *>(bake);
}
void EEVEE_NEXT_lightbake_job_data_free(void *job_data)
{
delete static_cast<LightBake *>(job_data);
}
void EEVEE_NEXT_lightbake_update(void *job_data)
{
static_cast<LightBake *>(job_data)->update();
}
void EEVEE_NEXT_lightbake_job(void *job_data, bool *stop, bool *do_update, float *progress)
{
static_cast<LightBake *>(job_data)->run(stop, do_update, progress);
}
/** \} */

View File

@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "BLI_vector.hh"
struct wmWindowManager;
struct wmWindow;
struct Main;
struct ViewLayer;
struct Scene;
struct Object;
struct wmJob;
/** Opaque type hiding eevee::LightBake. */
struct EEVEE_NEXT_LightBake;
/* -------------------------------------------------------------------- */
/** \name Light Bake Job
* \{ */
/**
* Create the job description.
* This is called for async (modal) bake operator.
* The actual work will be done by `EEVEE_NEXT_lightbake_job()`.
* IMPORTANT: Must run on the main thread because of potential GPUContext creation.
*/
wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm,
wmWindow *win,
Main *bmain,
ViewLayer *view_layer,
Scene *scene,
blender::Vector<Object *> original_probes,
int delay_ms,
int frame);
/**
* Allocate dependency graph and job description (EEVEE_NEXT_LightBake).
* Dependency graph evaluation does *not* happen here. It is delayed until
* `EEVEE_NEXT_lightbake_job` runs.
* IMPORTANT: Must run on the main thread because of potential GPUContext creation.
* Return `EEVEE_NEXT_LightBake *` but cast to `void *` because of compatibility with existing
* EEVEE function.
*/
void *EEVEE_NEXT_lightbake_job_data_alloc(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
blender::Vector<Object *> original_probes,
int frame);
/**
* Free the job data.
* NOTE: Does not free the GPUContext. This is the responsibility of `EEVEE_NEXT_lightbake_job()`
*/
void EEVEE_NEXT_lightbake_job_data_free(void *job_data /* EEVEE_NEXT_LightBake */);
/**
* Callback for updating original scene light cache with bake result.
* Run by the job system for each update step and the finish step.
* This is called manually by `EEVEE_NEXT_lightbake_job()` if not run from a job.
*/
void EEVEE_NEXT_lightbake_update(void *job_data /* EEVEE_NEXT_LightBake */);
/**
* Do the full light baking for all samples.
* Will call `EEVEE_NEXT_lightbake_update()` on finish.
*/
void EEVEE_NEXT_lightbake_job(void *job_data /* EEVEE_NEXT_LightBake */,
bool *stop,
bool *do_update,
float *progress);
/** \} */

View File

@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
*/
#include "DNA_lightprobe_types.h"
#include "WM_api.h"
#include "eevee_instance.hh"
#include "eevee_lightprobe.hh"
#include "draw_debug.hh"
namespace blender::eevee {
void LightProbeModule::begin_sync()
{
auto_bake_enabled_ = inst_.is_viewport() &&
(inst_.scene->eevee.flag & SCE_EEVEE_GI_AUTOBAKE) != 0;
}
void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
{
IrradianceGrid &grid = grid_map_.lookup_or_add_default(handle.object_key);
grid.used = true;
if (handle.recalc != 0 || grid.initialized == false) {
grid.initialized = true;
grid.updated = true;
grid.object_to_world = float4x4(ob->object_to_world);
grid.world_to_object = float4x4(
math::normalize(math::transpose(float3x3(grid.object_to_world))));
grid.cache = ob->lightprobe_cache;
/* Force reupload. */
inst_.irradiance_cache.bricks_free(grid.bricks);
}
}
void LightProbeModule::sync_cube(ObjectHandle &handle)
{
ReflectionCube &cube = cube_map_.lookup_or_add_default(handle.object_key);
cube.used = true;
if (handle.recalc != 0 || cube.initialized == false) {
cube.initialized = true;
cube_update_ = true;
}
}
void LightProbeModule::sync_probe(const Object *ob, ObjectHandle &handle)
{
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
switch (lightprobe->type) {
case LIGHTPROBE_TYPE_CUBE:
sync_cube(handle);
return;
case LIGHTPROBE_TYPE_PLANAR:
/* TODO(fclem): Remove support? Add support? */
return;
case LIGHTPROBE_TYPE_GRID:
sync_grid(ob, handle);
return;
}
BLI_assert_unreachable();
}
void LightProbeModule::end_sync()
{
{
/* Check for deleted or updated grid. */
grid_update_ = false;
auto it_end = grid_map_.items().end();
for (auto it = grid_map_.items().begin(); it != it_end; ++it) {
IrradianceGrid &grid = (*it).value;
if (grid.updated) {
grid.updated = false;
grid_update_ = true;
}
if (!grid.used) {
inst_.irradiance_cache.bricks_free(grid.bricks);
grid_map_.remove(it);
grid_update_ = true;
continue;
}
/* Untag for next sync. */
grid.used = false;
}
}
{
/* Check for deleted or updated cube. */
cube_update_ = false;
auto it_end = cube_map_.items().end();
for (auto it = cube_map_.items().begin(); it != it_end; ++it) {
ReflectionCube &cube = (*it).value;
if (cube.updated) {
cube.updated = false;
cube_update_ = true;
}
if (!cube.used) {
cube_map_.remove(it);
cube_update_ = true;
continue;
}
/* Untag for next sync. */
cube.used = false;
}
}
#if 0 /* TODO make this work with new per object light cache. */
/* If light-cache auto-update is enable we tag the relevant part
* of the cache to update and fire up a baking job. */
if (auto_bake_enabled_ && (grid_update_ || cube_update_)) {
Scene *original_scene = DEG_get_input_scene(inst_.depsgraph);
LightCache *light_cache = original_scene->eevee.light_cache_data;
if (light_cache != nullptr) {
if (grid_update_) {
light_cache->flag |= LIGHTCACHE_UPDATE_GRID;
}
/* TODO(fclem): Reflection Cubemap should capture albedo + normal and be
* relit at runtime. So no dependency like in the old system. */
if (cube_update_) {
light_cache->flag |= LIGHTCACHE_UPDATE_CUBE;
}
/* Tag the lightcache to auto update. */
light_cache->flag |= LIGHTCACHE_UPDATE_AUTO;
/* Use a notifier to trigger the operator after drawing. */
/* TODO(fclem): Avoid usage of global DRW. */
WM_event_add_notifier(DRW_context_state_get()->evil_C, NC_LIGHTPROBE, original_scene);
}
}
#endif
}
} // namespace blender::eevee

View File

@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
*/
#pragma once
#include "BLI_map.hh"
#include "eevee_sync.hh"
namespace blender::eevee {
class Instance;
class IrradianceCache;
struct LightProbe {
bool used = false;
bool initialized = false;
bool updated = false;
};
struct IrradianceGrid : public LightProbe, IrradianceGridData {
/** Copy of the transform matrix. */
float4x4 object_to_world;
/** Precomputed inverse transform with normalized axes. No position. Used for rotating SH. */
float4x4 world_to_object;
/**
* Reference to the light-cache data.
* Do not try to dereference it before LightProbeModule::end_sync() as the grid could
* already have been freed (along with its cache). It is only safe to dereference after the
* pruning have been done.
*/
const struct LightProbeObjectCache *cache = nullptr;
/** List of associated atlas bricks that are used by this grid. */
Vector<IrradianceBrickPacked> bricks;
/** Index of the grid inside the grid UBO. */
int grid_index;
};
struct ReflectionCube : public LightProbe {
};
class LightProbeModule {
friend class IrradianceCache;
private:
Instance &inst_;
/** Light Probe map to detect deletion and store associated data. */
Map<ObjectKey, IrradianceGrid> grid_map_;
Map<ObjectKey, ReflectionCube> cube_map_;
/** True if a grid update was detected. It will trigger a bake if auto bake is enabled. */
bool grid_update_;
/** True if a grid update was detected. It will trigger a bake if auto bake is enabled. */
bool cube_update_;
/** True if the auto bake feature is enabled & available in this context. */
bool auto_bake_enabled_;
public:
LightProbeModule(Instance &inst) : inst_(inst){};
~LightProbeModule(){};
void begin_sync();
void sync_cube(ObjectHandle &handle);
void sync_grid(const Object *ob, ObjectHandle &handle);
void sync_probe(const Object *ob, ObjectHandle &handle);
void end_sync();
};
} // namespace blender::eevee

View File

@@ -164,9 +164,11 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
blender_mat->nodetree :
default_surface_ntree_.nodetree_get(blender_mat);
bool use_deferred_compilation = inst_.is_viewport();
MaterialPass matpass = MaterialPass();
matpass.gpumat = inst_.shaders.material_shader_get(
blender_mat, ntree, pipeline_type, geometry_type, true);
blender_mat, ntree, pipeline_type, geometry_type, use_deferred_compilation);
switch (GPU_material_status(matpass.gpumat)) {
case GPU_MAT_SUCCESS:
@@ -240,9 +242,20 @@ Material &MaterialModule::material_sync(Object *ob,
Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() {
Material mat;
/* Order is important for transparent. */
mat.prepass = material_pass_get(ob, blender_mat, prepass_pipe, geometry_type);
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
if (inst_.is_baking()) {
mat.prepass = MaterialPass();
/* TODO(fclem): Still need the shading pass for correct attribute extraction. Would be better
* to avoid this shader compilation in another context. */
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
mat.capture = material_pass_get(ob, blender_mat, MAT_PIPE_CAPTURE, geometry_type);
}
else {
/* Order is important for transparent. */
mat.prepass = material_pass_get(ob, blender_mat, prepass_pipe, geometry_type);
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
mat.capture = MaterialPass();
}
if (blender_mat->blend_shadow == MA_BS_NONE) {
mat.shadow = MaterialPass();
}
@@ -252,6 +265,12 @@ Material &MaterialModule::material_sync(Object *ob,
mat.is_alpha_blend_transparent = (blender_mat->blend_method == MA_BM_BLEND) &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);
if (inst_.is_baking()) {
/* WORKAROUND(fclem): This is to request the shadow for the surfels. This will well
* over-request the number of shadow tiles. A better way would be to request from the surfels
* directly. */
mat.is_alpha_blend_transparent = true;
}
return mat;
});

View File

@@ -34,6 +34,7 @@ enum eMaterialPipeline {
MAT_PIPE_FORWARD_PREPASS_VELOCITY,
MAT_PIPE_VOLUME,
MAT_PIPE_SHADOW,
MAT_PIPE_CAPTURE,
};
enum eMaterialGeometry {
@@ -48,16 +49,16 @@ static inline void material_type_from_shader_uuid(uint64_t shader_uuid,
eMaterialPipeline &pipeline_type,
eMaterialGeometry &geometry_type)
{
const uint64_t geometry_mask = ((1u << 3u) - 1u);
const uint64_t pipeline_mask = ((1u << 3u) - 1u);
const uint64_t geometry_mask = ((1u << 4u) - 1u);
const uint64_t pipeline_mask = ((1u << 4u) - 1u);
geometry_type = static_cast<eMaterialGeometry>(shader_uuid & geometry_mask);
pipeline_type = static_cast<eMaterialPipeline>((shader_uuid >> 3u) & pipeline_mask);
pipeline_type = static_cast<eMaterialPipeline>((shader_uuid >> 4u) & pipeline_mask);
}
static inline uint64_t shader_uuid_from_material_type(eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type)
{
return geometry_type | (pipeline_type << 3);
return geometry_type | (pipeline_type << 4);
}
ENUM_OPERATORS(eClosureBits, CLOSURE_AMBIENT_OCCLUSION)
@@ -213,7 +214,7 @@ struct MaterialPass {
struct Material {
bool is_alpha_blend_transparent;
MaterialPass shadow, shading, prepass;
MaterialPass shadow, shading, prepass, capture;
};
struct MaterialArray {

View File

@@ -254,6 +254,7 @@ void ForwardPipeline::render(View &view,
// }
inst_.shadows.set_view(view);
inst_.irradiance_cache.set_view(view);
GPU_framebuffer_bind(combined_fb);
inst_.manager->submit(opaque_ps_, view);
@@ -370,6 +371,7 @@ void DeferredLayer::end_sync()
inst_.shadows.bind_resources(&eval_light_ps_);
inst_.sampling.bind_resources(&eval_light_ps_);
inst_.hiz_buffer.bind_resources(&eval_light_ps_);
inst_.irradiance_cache.bind_resources(&eval_light_ps_);
eval_light_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
eval_light_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
@@ -412,6 +414,7 @@ void DeferredLayer::render(View &view,
inst_.hiz_buffer.set_dirty();
inst_.shadows.set_view(view);
inst_.irradiance_cache.set_view(view);
inst_.gbuffer.acquire(extent, closure_bits_);
@@ -494,4 +497,37 @@ void DeferredPipeline::render(View &view,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Pipeline
*
* \{ */
void CapturePipeline::sync()
{
surface_ps_.init();
/* Surfel output is done using a SSBO, so no need for a fragment shader output color or depth. */
/* WORKAROUND: Avoid rasterizer discard, but the shaders actually use no fragment output. */
surface_ps_.state_set(DRW_STATE_WRITE_STENCIL);
surface_ps_.framebuffer_set(&inst_.irradiance_cache.bake.empty_raster_fb_);
surface_ps_.bind_ssbo(SURFEL_BUF_SLOT, &inst_.irradiance_cache.bake.surfels_buf_);
surface_ps_.bind_ssbo(CAPTURE_BUF_SLOT, &inst_.irradiance_cache.bake.capture_info_buf_);
surface_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
/* TODO(fclem): Remove. There should be no view dependent behavior during capture. */
surface_ps_.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
}
PassMain::Sub *CapturePipeline::surface_material_add(GPUMaterial *gpumat)
{
return &surface_ps_.sub(GPU_material_get_name(gpumat));
}
void CapturePipeline::render(View &view)
{
inst_.manager->submit(surface_ps_, view);
}
/** \} */
} // namespace blender::eevee

View File

@@ -182,6 +182,28 @@ class DeferredPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Pipeline
*
* \{ */
class CapturePipeline {
private:
Instance &inst_;
PassMain surface_ps_ = {"Capture.Surface"};
public:
CapturePipeline(Instance &inst) : inst_(inst){};
PassMain::Sub *surface_material_add(GPUMaterial *gpumat);
void sync();
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utility texture
*
@@ -269,17 +291,20 @@ class PipelineModule {
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
CapturePipeline capture;
UtilityTexture utility_tx;
public:
PipelineModule(Instance &inst) : world(inst), deferred(inst), forward(inst), shadow(inst){};
PipelineModule(Instance &inst)
: world(inst), deferred(inst), forward(inst), shadow(inst), capture(inst){};
void begin_sync()
{
deferred.begin_sync();
forward.sync();
shadow.sync();
capture.sync();
}
void end_sync()
@@ -322,6 +347,8 @@ class PipelineModule {
return nullptr;
case MAT_PIPE_SHADOW:
return shadow.surface_material_add(gpumat);
case MAT_PIPE_CAPTURE:
return capture.surface_material_add(gpumat);
}
return nullptr;
}

View File

@@ -10,6 +10,9 @@
#include "BLI_rand.h"
#include "BLI_math_base.hh"
#include "BLI_math_base_safe.h"
#include "eevee_instance.hh"
#include "eevee_sampling.hh"
@@ -53,6 +56,15 @@ void Sampling::init(const Scene *scene)
sample_count_ *= motion_blur_steps_;
}
void Sampling::init(const Object &probe_object)
{
BLI_assert(inst_.is_baking());
const ::LightProbe *lightprobe = static_cast<::LightProbe *>(probe_object.data);
sample_count_ = max_ii(1, lightprobe->grid_bake_samples);
sample_ = 0;
}
void Sampling::end_sync()
{
if (reset_) {
@@ -174,6 +186,22 @@ float2 Sampling::sample_disk(const float2 &rand)
return sqrtf(rand.x) * float2(cosf(omega), sinf(omega));
}
float3 Sampling::sample_hemisphere(const float2 &rand)
{
const float omega = rand.y * 2.0f * M_PI;
const float cos_theta = rand.x;
const float sin_theta = safe_sqrtf(1.0f - square_f(cos_theta));
return float3(sin_theta * float2(cosf(omega), sinf(omega)), cos_theta);
}
float3 Sampling::sample_sphere(const float2 &rand)
{
const float omega = rand.y * 2.0f * M_PI;
const float cos_theta = rand.x * 2.0f - 1.0f;
const float sin_theta = safe_sqrtf(1.0f - square_f(cos_theta));
return float3(sin_theta * float2(cosf(omega), sinf(omega)), cos_theta);
}
float2 Sampling::sample_spiral(const float2 &rand)
{
/* Fibonacci spiral. */

View File

@@ -66,6 +66,7 @@ class Sampling {
~Sampling(){};
void init(const Scene *scene);
void init(const Object &probe_object);
void end_sync();
void step();
@@ -123,11 +124,18 @@ class Sampling {
return interactive_mode_;
}
/* Target sample count. */
uint64_t sample_count() const
{
return sample_count_;
}
/* 0 based current sample. Might not increase sequentially in viewport. */
uint64_t sample_index() const
{
return sample_;
}
/* Return true if we are starting a new motion blur step. We need to run sync again since
* depsgraph was updated by MotionBlur::step(). */
bool do_render_sync() const
@@ -152,6 +160,20 @@ class Sampling {
*/
static float2 sample_disk(const float2 &rand);
/**
* Uniform hemisphere distribution.
* \a rand is 2 random float in the [0..1] range.
* Returns point on a Z positive hemisphere of radius 1 and centered on the origin.
*/
static float3 sample_hemisphere(const float2 &rand);
/**
* Uniform sphere distribution.
* \a rand is 2 random float in the [0..1] range.
* Returns point on the sphere of radius 1 and centered on the origin.
*/
static float3 sample_sphere(const float2 &rand);
/**
* Uniform disc distribution using Fibonacci spiral sampling.
* \a rand is 2 random float in the [0..1] range.

View File

@@ -102,6 +102,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_motion_blur_tiles_flatten_viewport";
case DEBUG_SURFELS:
return "eevee_debug_surfels";
case DISPLAY_PROBE_GRID:
return "eevee_display_probe_grid";
case DOF_BOKEH_LUT:
return "eevee_depth_of_field_bokeh_lut";
case DOF_DOWNSAMPLE:
@@ -146,6 +148,12 @@ 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 LIGHTPROBE_IRRADIANCE_BOUNDS:
return "eevee_lightprobe_irradiance_bounds";
case LIGHTPROBE_IRRADIANCE_RAY:
return "eevee_lightprobe_irradiance_ray";
case LIGHTPROBE_IRRADIANCE_LOAD:
return "eevee_lightprobe_irradiance_load";
case SHADOW_CLIPMAP_CLEAR:
return "eevee_shadow_clipmap_clear";
case SHADOW_DEBUG:
@@ -170,10 +178,20 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_shadow_tag_update";
case SHADOW_TILEMAP_TAG_USAGE_OPAQUE:
return "eevee_shadow_tag_usage_opaque";
case SHADOW_TILEMAP_TAG_USAGE_SURFELS:
return "eevee_shadow_tag_usage_surfels";
case SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT:
return "eevee_shadow_tag_usage_transparent";
case SUBSURFACE_EVAL:
return "eevee_subsurface_eval";
case SURFEL_LIGHT:
return "eevee_surfel_light";
case SURFEL_LIST_BUILD:
return "eevee_surfel_list_build";
case SURFEL_LIST_SORT:
return "eevee_surfel_list_sort";
case SURFEL_RAY:
return "eevee_surfel_ray";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";
@@ -411,7 +429,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
info.additional_info("eevee_geom_curves");
break;
case MAT_GEOM_MESH:
default:
info.additional_info("eevee_geom_mesh");
break;
}
@@ -436,6 +453,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
case MAT_PIPE_SHADOW:
info.additional_info("eevee_surf_shadow");
break;
case MAT_PIPE_CAPTURE:
info.additional_info("eevee_surf_capture");
break;
case MAT_PIPE_DEFERRED:
info.additional_info("eevee_surf_deferred");
break;
@@ -443,7 +463,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
info.additional_info("eevee_surf_forward");
break;
default:
BLI_assert(0);
BLI_assert_unreachable();
break;
}
break;

View File

@@ -34,6 +34,8 @@ enum eShaderType {
DEBUG_SURFELS,
DISPLAY_PROBE_GRID,
DOF_BOKEH_LUT,
DOF_DOWNSAMPLE,
DOF_FILTER,
@@ -61,6 +63,10 @@ enum eShaderType {
LIGHT_CULLING_TILE,
LIGHT_CULLING_ZBIN,
LIGHTPROBE_IRRADIANCE_BOUNDS,
LIGHTPROBE_IRRADIANCE_RAY,
LIGHTPROBE_IRRADIANCE_LOAD,
MOTION_BLUR_GATHER,
MOTION_BLUR_TILE_DILATE,
MOTION_BLUR_TILE_FLATTEN_RENDER,
@@ -78,10 +84,16 @@ enum eShaderType {
SHADOW_TILEMAP_INIT,
SHADOW_TILEMAP_TAG_UPDATE,
SHADOW_TILEMAP_TAG_USAGE_OPAQUE,
SHADOW_TILEMAP_TAG_USAGE_SURFELS,
SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT,
SUBSURFACE_EVAL,
SURFEL_LIGHT,
SURFEL_LIST_BUILD,
SURFEL_LIST_SORT,
SURFEL_RAY,
MAX_SHADER_TYPE,
};

View File

@@ -53,7 +53,8 @@ enum eDebugMode : uint32_t {
/**
* Display IrradianceCache surfels.
*/
DEBUG_IRRADIANCE_CACHE_SURFELS = 3u,
DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL = 3u,
DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE = 4u,
/**
* Show tiles depending on their status.
*/
@@ -838,17 +839,109 @@ static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug
/** \name Irradiance Cache
* \{ */
struct DebugSurfel {
packed_float3 position;
int _pad0;
packed_float3 normal;
int _pad1;
float4 color;
struct SurfelRadiance {
float4 front;
float4 back;
};
BLI_STATIC_ASSERT_ALIGN(DebugSurfel, 16)
BLI_STATIC_ASSERT_ALIGN(SurfelRadiance, 16)
struct Surfel {
/** World position of the surfel. */
packed_float3 position;
/** Previous surfel index in the ray link-list. Only valid after sorting. */
int prev;
/** World orientation of the surface. */
packed_float3 normal;
/** Next surfel index in the ray link-list. */
int next;
/** Surface albedo to apply to incoming radiance. */
packed_float3 albedo_front;
/** Distance along the ray direction for sorting. */
float ray_distance;
/** Surface albedo to apply to incoming radiance. */
packed_float3 albedo_back;
int _pad3;
/** Surface radiance: Emission + Direct Lighting. */
SurfelRadiance radiance_direct;
/** Surface radiance: Indirect Lighting. Double buffered to avoid race conditions. */
SurfelRadiance radiance_indirect[2];
};
BLI_STATIC_ASSERT_ALIGN(Surfel, 16)
struct CaptureInfoData {
/** Number of surfels inside the surfel buffer or the needed len. */
packed_int3 irradiance_grid_size;
/** True if the surface shader needs to write the surfel data. */
bool1 do_surfel_output;
/** True if the surface shader needs to increment the surfel_len. */
bool1 do_surfel_count;
/** Number of surfels inside the surfel buffer or the needed len. */
uint surfel_len;
/** Total number of a ray for light transportation. */
float sample_count;
/** 0 based sample index. */
float sample_index;
/** Transform of the lightprobe object. */
float4x4 irradiance_grid_local_to_world;
/** Transform vectors from world space to local space. Does not have location component. */
/** TODO(fclem): This could be a float3x4 or a float3x3 if padded correctly. */
float4x4 irradiance_grid_world_to_local_rotation;
/** Scene bounds. Stored as min & max and as int for atomic operations. */
int scene_bound_x_min;
int scene_bound_y_min;
int scene_bound_z_min;
int scene_bound_x_max;
int scene_bound_y_max;
int scene_bound_z_max;
int _pad0;
int _pad1;
int _pad2;
};
BLI_STATIC_ASSERT_ALIGN(CaptureInfoData, 16)
struct SurfelListInfoData {
/** Size of the grid used to project the surfels into linked lists. */
int2 ray_grid_size;
/** Maximum number of list. Is equal to `ray_grid_size.x * ray_grid_size.y`. */
int list_max;
int _pad0;
};
BLI_STATIC_ASSERT_ALIGN(SurfelListInfoData, 16)
struct IrradianceGridData {
/** World to non-normalized local grid space [0..size-1]. Stored transposed for compactness. */
float3x4 world_to_grid_transposed;
/** Number of bricks for this grid. */
packed_int3 grid_size;
/** Index in brick descriptor list of the first brick of this grid. */
int brick_offset;
};
BLI_STATIC_ASSERT_ALIGN(IrradianceGridData, 16)
struct IrradianceBrick {
/* Offset in pixel to the start of the data inside the atlas texture. */
uint2 atlas_coord;
};
/** \note Stored packed as a uint. */
#define IrradianceBrickPacked uint
static inline IrradianceBrickPacked irradiance_brick_pack(IrradianceBrick brick)
{
uint2 data = (uint2(brick.atlas_coord) & 0xFFFFu) << uint2(0u, 16u);
IrradianceBrickPacked brick_packed = data.x | data.y;
return brick_packed;
}
static inline IrradianceBrick irradiance_brick_unpack(IrradianceBrickPacked brick_packed)
{
IrradianceBrick brick;
brick.atlas_coord = (uint2(brick_packed) >> uint2(0u, 16u)) & uint2(0xFFFFu);
return brick;
}
/** \} */
@@ -959,8 +1052,9 @@ using DepthOfFieldDataBuf = draw::UniformBuffer<DepthOfFieldData>;
using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, true>;
using DrawIndirectBuf = draw::StorageBuffer<DrawCommand, true>;
using FilmDataBuf = draw::UniformBuffer<FilmData>;
using DebugSurfelBuf = draw::StorageArrayBuffer<DebugSurfel, 64>;
using HiZDataBuf = draw::UniformBuffer<HiZData>;
using IrradianceGridDataBuf = draw::UniformArrayBuffer<IrradianceGridData, IRRADIANCE_GRID_MAX>;
using IrradianceBrickBuf = draw::StorageVectorBuffer<IrradianceBrickPacked, 16>;
using LightCullingDataBuf = draw::StorageBuffer<LightCullingData>;
using LightCullingKeyBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
using LightCullingTileBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
@@ -978,6 +1072,10 @@ using ShadowTileMapDataBuf = draw::StorageVectorBuffer<ShadowTileMapData, SHADOW
using ShadowTileMapClipBuf = draw::StorageArrayBuffer<ShadowTileMapClip, SHADOW_MAX_TILEMAP, true>;
using ShadowTileDataBuf = draw::StorageArrayBuffer<ShadowTileDataPacked, SHADOW_MAX_TILE, true>;
using SubsurfaceDataBuf = draw::UniformBuffer<SubsurfaceData>;
using SurfelBuf = draw::StorageArrayBuffer<Surfel, 64>;
using SurfelRadianceBuf = draw::StorageArrayBuffer<SurfelRadiance, 64>;
using CaptureInfoBuf = draw::StorageBuffer<CaptureInfoData>;
using SurfelListInfoBuf = draw::StorageBuffer<SurfelListInfoData>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;

View File

@@ -708,6 +708,32 @@ void ShadowModule::begin_sync()
PassMain &pass = tilemap_usage_ps_;
pass.init();
if (inst_.is_baking()) {
SurfelBuf &surfels_buf = inst_.irradiance_cache.bake.surfels_buf_;
CaptureInfoBuf &capture_info_buf = inst_.irradiance_cache.bake.capture_info_buf_;
float surfel_coverage_area = inst_.irradiance_cache.bake.surfel_density_;
/* Directional shadows. */
float texel_size = ShadowDirectional::tile_size_get(0) / float(SHADOW_PAGE_RES);
int directional_level = std::max(0, int(std::ceil(log2(surfel_coverage_area / texel_size))));
/* Punctual shadows. */
float projection_ratio = tilemap_pixel_radius() / (surfel_coverage_area / 2.0);
PassMain::Sub &sub = pass.sub("Surfels");
sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_USAGE_SURFELS));
sub.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data);
sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data);
sub.bind_ssbo("surfel_buf", &surfels_buf);
sub.bind_ssbo("capture_info_buf", &capture_info_buf);
sub.push_constant("directional_level", directional_level);
sub.push_constant("tilemap_projection_ratio", projection_ratio);
inst_.lights.bind_resources(&sub);
sub.dispatch(&inst_.irradiance_cache.bake.dispatch_per_surfel_);
/* Skip opaque and transparent tagging for light baking. */
return;
}
{
/** Use depth buffer to tag needed shadow pages for opaque geometry. */
PassMain::Sub &sub = pass.sub("Opaque");
@@ -768,7 +794,7 @@ void ShadowModule::sync_object(const ObjectHandle &handle,
curr_casters_.append(resource_handle.raw);
}
if (is_alpha_blend) {
if (is_alpha_blend && !inst_.is_baking()) {
tilemap_usage_transparent_ps_->draw(box_batch_, resource_handle);
}
}

View File

@@ -131,6 +131,7 @@ void SyncModule::sync_mesh(Object *ob,
geometry_call(material.shading.sub_pass, geom, res_handle);
geometry_call(material.prepass.sub_pass, geom, res_handle);
geometry_call(material.shadow.sub_pass, geom, res_handle);
geometry_call(material.capture.sub_pass, geom, res_handle);
is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr;
is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent;

View File

@@ -134,8 +134,7 @@ void ShadingView::render()
inst_.lights.debug_draw(render_view_new_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_new_, combined_fb_);
inst_.shadows.debug_draw(render_view_new_, combined_fb_);
inst_.irradiance_cache.debug_draw(render_view_new_, combined_fb_);
inst_.irradiance_cache.viewport_draw(render_view_new_, combined_fb_);
GPUTexture *combined_final_tx = render_postfx(rbufs.combined_tx);

View File

@@ -1,21 +1,26 @@
void main()
{
DebugSurfel surfel = surfels_buf[surfel_index];
out_color = surfel.color;
Surfel surfel = surfels_buf[surfel_index];
vec3 radiance = vec3(0.0);
radiance += gl_FrontFacing ? surfel.radiance_direct.front.rgb : surfel.radiance_direct.back.rgb;
radiance += gl_FrontFacing ? surfel.radiance_indirect[1].front.rgb :
surfel.radiance_indirect[1].back.rgb;
switch (eDebugMode(debug_mode)) {
default:
case DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL:
out_color = vec4(pow(surfel.normal * 0.5 + 0.5, vec3(2.2)), 0.0);
break;
case DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE:
out_color = vec4(radiance, 0.0);
break;
}
/* Display surfels as circles. */
if (distance(P, surfel.position) > surfel_radius) {
discard;
return;
}
/* Display backfacing surfels with a transparent checkerboard grid. */
if (!gl_FrontFacing) {
ivec2 grid_uv = ivec2(gl_FragCoord.xy) / 5;
if ((grid_uv.x + grid_uv.y) % 2 == 0) {
discard;
return;
}
}
}

View File

@@ -1,10 +1,21 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(common_debug_draw_lib.glsl)
void main()
{
surfel_index = gl_InstanceID;
DebugSurfel surfel = surfels_buf[surfel_index];
Surfel surfel = surfels_buf[surfel_index];
#if 0 /* Debug surfel lists. TODO allow in release build with a dedicated shader. */
if (gl_VertexID == 0 && surfel.next > -1) {
Surfel surfel_next = surfels_buf[surfel.next];
vec4 line_color = (surfel.prev == -1) ? vec4(1.0, 1.0, 0.0, 1.0) :
(surfel_next.next == -1) ? vec4(0.0, 1.0, 1.0, 1.0) :
vec4(0.0, 1.0, 0.0, 1.0);
drw_debug_line(surfel_next.position, surfel.position, line_color);
}
#endif
vec3 lP;
@@ -35,4 +46,5 @@ void main()
P = (model_matrix * vec4(lP, 1)).xyz;
gl_Position = point_world_to_ndc(P);
gl_Position.z -= 2.5e-5;
}

View File

@@ -9,6 +9,7 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
void main()
{
@@ -53,6 +54,8 @@ void main()
vec3 reflection_light = vec3(0.0);
float shadow = 1.0;
lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light);
light_eval(diffuse_data,
reflection_data,
P,

View File

@@ -0,0 +1,28 @@
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
void main()
{
float dist_sqr = dot(lP, lP);
/* Discard outside the circle. */
if (dist_sqr > 1.0) {
discard;
return;
}
SphericalHarmonicL1 sh;
sh.L0.M0 = texelFetch(irradiance_a_tx, cell, 0);
sh.L1.Mn1 = texelFetch(irradiance_b_tx, cell, 0);
sh.L1.M0 = texelFetch(irradiance_c_tx, cell, 0);
sh.L1.Mp1 = texelFetch(irradiance_d_tx, cell, 0);
vec3 vN = vec3(lP, sqrt(max(0.0, 1.0 - dist_sqr)));
vec3 N = normal_view_to_world(vN);
vec3 lN = transform_direction(world_to_grid, N);
vec3 irradiance = spherical_harmonics_evaluate_lambert_non_linear(lN, sh);
out_color = vec4(irradiance, 0.0);
}

View File

@@ -0,0 +1,34 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
void main()
{
/* Constant array moved inside function scope.
* Minimises local register allocation in MSL. */
const vec2 pos[6] = vec2[6](vec2(-1.0, -1.0),
vec2(1.0, -1.0),
vec2(-1.0, 1.0),
vec2(1.0, -1.0),
vec2(1.0, 1.0),
vec2(-1.0, 1.0));
lP = pos[gl_VertexID % 6];
int cell_index = gl_VertexID / 6;
ivec3 grid_res = grid_resolution;
cell = ivec3(cell_index / (grid_res.z * grid_res.y),
(cell_index / grid_res.z) % grid_res.y,
cell_index % grid_res.z);
vec3 ws_cell_pos = lightprobe_irradiance_grid_sample_position(grid_to_world, grid_res, cell);
vec3 vs_offset = vec3(lP, 0.0) * sphere_radius;
vec3 vP = (ViewMatrix * vec4(ws_cell_pos, 1.0)).xyz + vs_offset;
gl_Position = ProjectionMatrix * vec4(vP, 1.0);
/* Small bias to let the icon draw without zfighting. */
gl_Position.z += 0.0001;
}

View File

@@ -22,7 +22,7 @@ void light_eval_ex(ClosureDiffuse diffuse,
vec3 P,
vec3 Ng,
vec3 V,
float vP_z,
float vP_z, /* TODO(fclem): Remove, is unused. */
float thickness,
vec4 ltc_mat,
uint l_idx,
@@ -115,7 +115,11 @@ void light_eval(ClosureDiffuse diffuse,
}
LIGHT_FOREACH_END
#ifdef GPU_FRAGMENT_SHADER
vec2 px = gl_FragCoord.xy;
#else
vec2 px = vec2(0.0);
#endif
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, px, vP_z, l_idx) {
light_eval_ex(diffuse,
reflection,

View File

@@ -0,0 +1,79 @@
/**
* The resources expected to be defined are:
* - grids_infos_buf
* - bricks_infos_buf
* - irradiance_atlas_tx
*/
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
/**
* Return sample coordinates of the first SH coef in unormalized texture space.
*/
vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, vec3 lP)
{
ivec3 brick_coord = ivec3((lP - 0.5) / float(IRRADIANCE_GRID_BRICK_SIZE - 1));
/* Avoid sampling adjacent bricks. */
brick_coord = max(brick_coord, ivec3(0));
/* Avoid sampling adjacent bricks. */
lP = max(lP, vec3(0.5));
/* Local position inside the brick (still in grid sample spacing unit). */
vec3 brick_lP = lP - vec3(brick_coord) * float(IRRADIANCE_GRID_BRICK_SIZE - 1);
int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord);
IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]);
vec3 output_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP;
return output_coord;
}
vec4 textureUnormalizedCoord(sampler3D tx, vec3 co)
{
return texture(tx, co / vec3(textureSize(tx, 0)));
}
SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P)
{
vec3 lP;
int grid_index;
for (grid_index = 0; grid_index < IRRADIANCE_GRID_MAX; grid_index++) {
/* Last grid is tagged as invalid to stop the iteration. */
if (grids_infos_buf[grid_index].grid_size.x == -1) {
/* Sample the last grid instead. */
grid_index -= 1;
break;
}
/* If sample fall inside the grid, step out of the loop. */
if (lightprobe_irradiance_grid_local_coord(grids_infos_buf[grid_index], P, lP)) {
break;
}
}
vec3 atlas_coord = lightprobe_irradiance_grid_atlas_coord(grids_infos_buf[grid_index], lP);
SphericalHarmonicL1 sh;
sh.L0.M0 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.Mn1 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.M0 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.Mp1 = textureUnormalizedCoord(atlas_tx, atlas_coord);
return sh;
}
void lightprobe_eval(ClosureDiffuse diffuse,
ClosureReflection reflection,
vec3 P,
vec3 Ng,
vec3 V,
inout vec3 out_diffuse,
inout vec3 out_specular)
{
SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample(irradiance_atlas_tx, P);
out_diffuse += spherical_harmonics_evaluate_lambert_non_linear(diffuse.N, irradiance);
}

View File

@@ -0,0 +1,48 @@
/**
* Surface Capture: Output surface parameters to diverse storage.
*
* The resources expected to be defined are:
* - capture_info_buf
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
void main()
{
uint index = gl_GlobalInvocationID.x;
if (index >= resource_len) {
return;
}
ObjectBounds bounds = bounds_buf[index];
/* Bounds are not correct as culling is disabled for these. */
if (bounds._inner_sphere_radius <= 0.0) {
return;
}
IsectBox box = isect_data_setup(bounds.bounding_corners[0].xyz,
bounds.bounding_corners[1].xyz,
bounds.bounding_corners[2].xyz,
bounds.bounding_corners[3].xyz);
vec3 local_min = vec3(FLT_MAX);
vec3 local_max = vec3(-FLT_MAX);
for (int i = 0; i < 8; i++) {
local_min = min(local_min, box.corners[i].xyz);
local_max = max(local_max, box.corners[i].xyz);
}
atomicMin(capture_info_buf.scene_bound_x_min, floatBitsToOrderedInt(local_min.x));
atomicMax(capture_info_buf.scene_bound_x_max, floatBitsToOrderedInt(local_max.x));
atomicMin(capture_info_buf.scene_bound_y_min, floatBitsToOrderedInt(local_min.y));
atomicMax(capture_info_buf.scene_bound_y_max, floatBitsToOrderedInt(local_max.y));
atomicMin(capture_info_buf.scene_bound_z_min, floatBitsToOrderedInt(local_min.z));
atomicMax(capture_info_buf.scene_bound_z_max, floatBitsToOrderedInt(local_max.z));
}

View File

@@ -0,0 +1,51 @@
/**
* Load an input lightgrid cache texture into the atlas.
*
* Each thread group will load a brick worth of data and add the needed padding texels.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer)
{
imageStore(irradiance_atlas_img,
ivec3(atlas_coord, layer * IRRADIANCE_GRID_BRICK_SIZE) + ivec3(gl_LocalInvocationID),
sh_coefficient);
}
void main()
{
int brick_index = lightprobe_irradiance_grid_brick_index_get(grids_infos_buf[grid_index],
ivec3(gl_WorkGroupID));
/* Brick coordinate in the source grid. */
ivec3 brick_coord = ivec3(gl_WorkGroupID);
/* Add padding border to allow bilinear filtering. */
ivec3 texel_coord = brick_coord * (IRRADIANCE_GRID_BRICK_SIZE - 1) + ivec3(gl_LocalInvocationID);
ivec3 input_coord = min(texel_coord, textureSize(irradiance_a_tx, 0) - 1);
/* Brick coordinate in the destination atlas. */
IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]);
ivec2 output_coord = ivec2(brick.atlas_coord);
SphericalHarmonicL1 sh;
sh.L0.M0 = texelFetch(irradiance_a_tx, input_coord, 0);
sh.L1.Mn1 = texelFetch(irradiance_b_tx, input_coord, 0);
sh.L1.M0 = texelFetch(irradiance_c_tx, input_coord, 0);
sh.L1.Mp1 = texelFetch(irradiance_d_tx, input_coord, 0);
/* Rotate Spherical Harmonic into world space. */
mat3 world_to_grid_transposed = mat3(grids_infos_buf[grid_index].world_to_grid_transposed);
mat3 rotation = normalize(world_to_grid_transposed);
spherical_harmonics_L1_rotate(rotation, sh.L1);
atlas_store(sh.L0.M0, output_coord, 0);
atlas_store(sh.L1.Mn1, output_coord, 1);
atlas_store(sh.L1.M0, output_coord, 2);
atlas_store(sh.L1.Mp1, output_coord, 3);
}

View File

@@ -0,0 +1,115 @@
/**
* For every irradiance probe sample, compute the incomming radiance from both side.
* This is the same as the surfel ray but we do not actually transport the light, we only capture
* the irradiance as spherical harmonic coefficients.
*
* Dispatched as 1 thread per irradiance probe sample.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surfel_list_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
void irradiance_capture(vec3 L, vec3 irradiance, inout SphericalHarmonicL1 sh)
{
vec3 lL = transform_direction(capture_info_buf.irradiance_grid_world_to_local_rotation, L);
/* Spherical harmonics need to be weighted by sphere area. */
irradiance *= 4.0 * M_PI;
spherical_harmonics_encode_signal_sample(lL, vec4(irradiance, 1.0), sh);
}
void irradiance_capture(Surfel surfel, vec3 P, inout SphericalHarmonicL1 sh)
{
vec3 L = safe_normalize(surfel.position - P);
bool facing = dot(-L, surfel.normal) > 0.0;
SurfelRadiance surfel_radiance_indirect = surfel.radiance_indirect[radiance_src];
vec3 irradiance = vec3(0.0);
irradiance += facing ? surfel.radiance_direct.front.rgb : surfel.radiance_direct.back.rgb;
/* NOTE: The indirect radiance is already normalized and this is wanted, because we are not
* integrating the same signal and we would have the SH lagging behind the surfel integration
* otherwise. */
irradiance += facing ? surfel_radiance_indirect.front.rgb : surfel_radiance_indirect.back.rgb;
irradiance_capture(L, irradiance, sh);
}
void main()
{
ivec3 grid_coord = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(grid_coord, capture_info_buf.irradiance_grid_size))) {
return;
}
vec3 P = lightprobe_irradiance_grid_sample_position(
capture_info_buf.irradiance_grid_local_to_world,
capture_info_buf.irradiance_grid_size,
grid_coord);
/* Project to get ray linked list. */
float irradiance_sample_ray_distance;
int list_index = surfel_list_index_get(P, irradiance_sample_ray_distance);
/* Walk the ray to get which surfels the irradiance sample is between. */
int surfel_prev = -1;
int surfel_next = list_start_buf[list_index];
for (; surfel_next > -1; surfel_next = surfel_buf[surfel_next].next) {
/* Reminder: List is sorted with highest value first. */
if (surfel_buf[surfel_next].ray_distance < irradiance_sample_ray_distance) {
break;
}
surfel_prev = surfel_next;
}
vec3 sky_L = cameraVec(P);
SphericalHarmonicL1 sh;
sh.L0.M0 = imageLoad(irradiance_L0_img, grid_coord);
sh.L1.Mn1 = imageLoad(irradiance_L1_a_img, grid_coord);
sh.L1.M0 = imageLoad(irradiance_L1_b_img, grid_coord);
sh.L1.Mp1 = imageLoad(irradiance_L1_c_img, grid_coord);
/* Un-normalize for accumulation. */
float weight_captured = capture_info_buf.sample_index * 2.0;
sh.L0.M0 *= weight_captured;
sh.L1.Mn1 *= weight_captured;
sh.L1.M0 *= weight_captured;
sh.L1.Mp1 *= weight_captured;
if (surfel_next > -1) {
Surfel surfel = surfel_buf[surfel_next];
irradiance_capture(surfel, P, sh);
}
else {
/* TODO(fclem): Sky radiance. */
irradiance_capture(sky_L, vec3(0.0), sh);
}
if (surfel_prev > -1) {
Surfel surfel = surfel_buf[surfel_prev];
irradiance_capture(surfel, P, sh);
}
else {
/* TODO(fclem): Sky radiance. */
irradiance_capture(-sky_L, vec3(0.0), sh);
}
/* Normalize for storage. We accumulated 2 samples. */
weight_captured += 2.0;
sh.L0.M0 /= weight_captured;
sh.L1.Mn1 /= weight_captured;
sh.L1.M0 /= weight_captured;
sh.L1.Mp1 /= weight_captured;
imageStore(irradiance_L0_img, grid_coord, sh.L0.M0);
imageStore(irradiance_L1_a_img, grid_coord, sh.L1.Mn1);
imageStore(irradiance_L1_b_img, grid_coord, sh.L1.M0);
imageStore(irradiance_L1_c_img, grid_coord, sh.L1.Mp1);
}

View File

@@ -0,0 +1,37 @@
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
vec3 lightprobe_irradiance_grid_sample_position(mat4 grid_local_to_world,
ivec3 grid_res,
ivec3 cell_coord)
{
vec3 ls_cell_pos = (vec3(cell_coord) + vec3(0.5)) / vec3(grid_res);
ls_cell_pos = ls_cell_pos * 2.0 - 1.0;
vec3 ws_cell_pos = (grid_local_to_world * vec4(ls_cell_pos, 1.0)).xyz;
return ws_cell_pos;
}
/**
* Return true if sample position is valid.
* \a r_lP is the local position in grid units [0..grid_size).
*/
bool lightprobe_irradiance_grid_local_coord(IrradianceGridData grid_data, vec3 P, out vec3 r_lP)
{
/* Position in cell units. */
/* NOTE: The vector-matrix multiplication swapped on purpose to cancel the matrix transpose. */
vec3 lP = (vec4(P, 1.0) * grid_data.world_to_grid_transposed).xyz;
r_lP = clamp(lP, vec3(0.0), vec3(grid_data.grid_size) - 1e-5);
/* Sample is valid if position wasn't clamped. */
return all(equal(lP, r_lP));
}
int lightprobe_irradiance_grid_brick_index_get(IrradianceGridData grid_data, ivec3 brick_coord)
{
int3 grid_size_in_bricks = divide_ceil(grid_data.grid_size,
int3(IRRADIANCE_GRID_BRICK_SIZE - 1));
int brick_index = grid_data.brick_offset;
brick_index += brick_coord.x;
brick_index += brick_coord.y * grid_size_in_bricks.x;
brick_index += brick_coord.z * grid_size_in_bricks.x * grid_size_in_bricks.y;
return brick_index;
}

View File

@@ -0,0 +1,71 @@
/* Directive for resetting the line numbering so the failing tests lines can be printed.
* This conflict with the shader compiler error logging scheme.
* Comment out for correct compilation error line. */
#line 5
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl)
#define TEST(a, b) if (true)
void main()
{
TEST(eevee_lightprobe, IrradianceBrickIndex)
{
float near = 0.5, far = 1.0;
mat4 pers_mat = projection_perspective(-near, near, -near, near, near, far);
mat4 normal_mat = invert(transpose(pers_mat));
LightData light;
light.clip_near = floatBitsToInt(near);
light.clip_far = floatBitsToInt(far);
light.influence_radius_max = far;
light.type = LIGHT_SPOT;
light.normal_mat_packed.x = normal_mat[3][2];
light.normal_mat_packed.y = normal_mat[3][3];
vec2 atlas_size = vec2(SHADOW_TILEMAP_RES);
{
/* Simulate a "2D" plane crossing the frustum diagonaly. */
vec3 lP0 = vec3(-1.0, 0.0, -1.0);
vec3 lP1 = vec3(0.5, 0.0, -0.5);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
float expect = 1.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), expect, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), expect * 2.0, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), expect * 4.0, 1e-4);
}
{
/* Simulate a "2D" plane crossing the near plane at the center diagonaly. */
vec3 lP0 = vec3(-1.0, 0.0, -1.0);
vec3 lP1 = vec3(0.0, 0.0, -0.5);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
float expect = 2.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), expect, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), expect * 2.0, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), expect * 4.0, 1e-4);
}
{
/* Simulate a "2D" plane parallel to near clip plane. */
vec3 lP0 = vec3(-1.0, 0.0, -0.75);
vec3 lP1 = vec3(0.0, 0.0, -0.75);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), 0.0, 1e-4);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), 0.0, 1e-4);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), 0.0, 1e-4);
}
}
}

View File

@@ -24,6 +24,22 @@ void shadow_tag_usage_tile(LightData light, ivec2 tile_co, int lod, int tilemap_
atomicOr(tiles_buf[tile_index], uint(SHADOW_IS_USED));
}
void shadow_tag_usage_tilemap_directional_at_level(uint l_idx, vec3 P, int level)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
vec3 lP = shadow_world_to_local(light, P);
level = clamp(level, light.clipmap_lod_min, light.clipmap_lod_max);
ShadowCoordinates coord = shadow_directional_coordinates_at_level(light, lP, level);
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
}
void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radius)
{
LightData light = light_buf[l_idx];
@@ -34,7 +50,7 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
vec3 lP = shadow_world_to_local(light, P);
if (radius == 0) {
if (radius == 0.0) {
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
}
@@ -46,9 +62,9 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
for (int level = min_level; level <= max_level; level++) {
ShadowCoordinates coord_min = shadow_directional_coordinates_at_level(
light, lP - vec3(radius, radius, 0), level);
light, lP - vec3(radius, radius, 0.0), level);
ShadowCoordinates coord_max = shadow_directional_coordinates_at_level(
light, lP + vec3(radius, radius, 0), level);
light, lP + vec3(radius, radius, 0.0), level);
for (int x = coord_min.tile_coord.x; x <= coord_max.tile_coord.x; x++) {
for (int y = coord_min.tile_coord.y; y <= coord_max.tile_coord.y; y++) {
@@ -59,7 +75,7 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
}
}
void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, vec3 V, float dist_to_cam, float radius)
void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, float dist_to_cam, float radius)
{
LightData light = light_buf[l_idx];
@@ -159,7 +175,7 @@ void shadow_tag_usage(vec3 vP, vec3 P, vec3 V, float radius, float dist_to_cam,
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
shadow_tag_usage_tilemap_punctual(l_idx, P, V, dist_to_cam, radius);
shadow_tag_usage_tilemap_punctual(l_idx, P, dist_to_cam, radius);
}
LIGHT_FOREACH_END
}
@@ -170,3 +186,21 @@ void shadow_tag_usage(vec3 vP, vec3 P, vec2 pixel)
shadow_tag_usage(vP, P, vec3(0), 0, dist_to_cam, pixel);
}
void shadow_tag_usage_surfel(Surfel surfel, int directional_lvl)
{
vec3 P = surfel.position;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
shadow_tag_usage_tilemap_directional_at_level(l_idx, P, directional_lvl);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx)
{
/* Set distance to camera to 1 to avoid changing footprint_ratio. */
float dist_to_cam = 1.0;
shadow_tag_usage_tilemap_punctual(l_idx, P, dist_to_cam, 0);
}
LIGHT_FOREACH_END
}

View File

@@ -0,0 +1,21 @@
/**
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass iterates the surfels buffer and tag all tiles that are needed for light shadowing as
* needed.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tag_usage_lib.glsl)
void main()
{
int index = int(gl_GlobalInvocationID.x);
if (index >= capture_info_buf.surfel_len) {
return;
}
Surfel surfel = surfel_buf[index];
shadow_tag_usage_surfel(surfel, directional_level);
}

View File

@@ -1,4 +1,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Spherical Harmonics Functions
*
@@ -64,21 +67,21 @@ float spherical_harmonics_L2_Mp2(vec3 v)
* \{ */
struct SphericalHarmonicBandL0 {
vec3 M0;
vec4 M0;
};
struct SphericalHarmonicBandL1 {
vec3 Mn1;
vec3 M0;
vec3 Mp1;
vec4 Mn1;
vec4 M0;
vec4 Mp1;
};
struct SphericalHarmonicBandL2 {
vec3 Mn2;
vec3 Mn1;
vec3 M0;
vec3 Mp1;
vec3 Mp2;
vec4 Mn2;
vec4 Mn1;
vec4 M0;
vec4 Mp1;
vec4 Mp2;
};
struct SphericalHarmonicL0 {
@@ -102,17 +105,18 @@ struct SphericalHarmonicL2 {
/** \name Encode
*
* Decompose an input signal into spherical harmonic coefficients.
* Note that `amplitude` need to be scaled by solid angle.
* \{ */
void spherical_harmonics_L0_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicBandL0 r_L0)
{
r_L0.M0 += spherical_harmonics_L0_M0(direction) * amplitude;
}
void spherical_harmonics_L1_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicBandL1 r_L1)
{
r_L1.Mn1 += spherical_harmonics_L1_Mn1(direction) * amplitude;
@@ -121,7 +125,7 @@ void spherical_harmonics_L1_encode_signal_sample(vec3 direction,
}
void spherical_harmonics_L2_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicBandL2 r_L2)
{
r_L2.Mn2 += spherical_harmonics_L2_Mn2(direction) * amplitude;
@@ -132,14 +136,14 @@ void spherical_harmonics_L2_encode_signal_sample(vec3 direction,
}
void spherical_harmonics_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicL0 sh)
{
spherical_harmonics_L0_encode_signal_sample(direction, amplitude, sh.L0);
}
void spherical_harmonics_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicL1 sh)
{
spherical_harmonics_L0_encode_signal_sample(direction, amplitude, sh.L0);
@@ -147,7 +151,7 @@ void spherical_harmonics_encode_signal_sample(vec3 direction,
}
void spherical_harmonics_encode_signal_sample(vec3 direction,
vec3 amplitude,
vec4 amplitude,
inout SphericalHarmonicL2 sh)
{
spherical_harmonics_L0_encode_signal_sample(direction, amplitude, sh.L0);
@@ -163,19 +167,19 @@ void spherical_harmonics_encode_signal_sample(vec3 direction,
* Evaluate an encoded signal in a given unit vector direction.
* \{ */
vec3 spherical_harmonics_L0_evaluate(vec3 direction, SphericalHarmonicBandL0 L0)
vec4 spherical_harmonics_L0_evaluate(vec3 direction, SphericalHarmonicBandL0 L0)
{
return spherical_harmonics_L0_M0(direction) * L0.M0;
}
vec3 spherical_harmonics_L1_evaluate(vec3 direction, SphericalHarmonicBandL1 L1)
vec4 spherical_harmonics_L1_evaluate(vec3 direction, SphericalHarmonicBandL1 L1)
{
return spherical_harmonics_L1_Mn1(direction) * L1.Mn1 +
spherical_harmonics_L1_M0(direction) * L1.M0 +
spherical_harmonics_L1_Mp1(direction) * L1.Mp1;
}
vec3 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2)
vec4 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2)
{
return spherical_harmonics_L2_Mn2(direction) * L2.Mn2 +
spherical_harmonics_L2_Mn1(direction) * L2.Mn1 +
@@ -186,6 +190,40 @@ vec3 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rotation
* \{ */
void spherical_harmonics_L0_rotate(mat3x3 rotation, inout SphericalHarmonicBandL0 L0)
{
/* L0 band being a constant function (i.e: there is no directionallity) there is nothing to
* rotate. This is a no-op. */
}
void spherical_harmonics_L1_rotate(mat3x3 rotation, inout SphericalHarmonicBandL1 L1)
{
/* Convert L1 coefficients to per channel column.
* Note the component shuffle to match blender coordinate system. */
mat4x3 per_channel = transpose(mat3x4(L1.Mp1, L1.Mn1, -L1.M0));
/* Rotate each channel. */
per_channel[0] = rotation * per_channel[0];
per_channel[1] = rotation * per_channel[1];
per_channel[2] = rotation * per_channel[2];
/* Convert back to L1 coefficients to per channel column.
* Note the component shuffle to match blender coordinate system. */
mat3x4 per_coef = transpose(per_channel);
L1.Mn1 = per_coef[1];
L1.M0 = -per_coef[2];
L1.Mp1 = per_coef[0];
}
void spherical_harmonics_L2_rotate(mat3x3 rotation, inout SphericalHarmonicBandL2 L2)
{
/* TODO */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Evaluation
* \{ */
@@ -198,18 +236,95 @@ vec3 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2)
*/
vec3 spherical_harmonics_evaluate_lambert(vec3 N, SphericalHarmonicL0 sh)
{
return spherical_harmonics_L0_evaluate(N, sh.L0);
vec3 radiance = spherical_harmonics_L0_evaluate(N, sh.L0).rgb;
return radiance;
}
vec3 spherical_harmonics_evaluate_lambert(vec3 N, SphericalHarmonicL1 sh)
{
return spherical_harmonics_L0_evaluate(N, sh.L0) +
spherical_harmonics_L1_evaluate(N, sh.L1) * (2.0 / 3.0);
vec3 radiance = spherical_harmonics_L0_evaluate(N, sh.L0).rgb +
spherical_harmonics_L1_evaluate(N, sh.L1).rgb * (2.0 / 3.0);
return radiance;
}
vec3 spherical_harmonics_evaluate_lambert(vec3 N, SphericalHarmonicL2 sh)
{
return spherical_harmonics_L0_evaluate(N, sh.L0) +
spherical_harmonics_L1_evaluate(N, sh.L1) * (2.0 / 3.0) +
spherical_harmonics_L2_evaluate(N, sh.L2) * (1.0 / 4.0);
vec3 radiance = spherical_harmonics_L0_evaluate(N, sh.L0).rgb +
spherical_harmonics_L1_evaluate(N, sh.L1).rgb * (2.0 / 3.0) +
spherical_harmonics_L2_evaluate(N, sh.L2).rgb * (1.0 / 4.0);
return radiance;
}
/**
* Use non-linear reconstruction method to avoid negative lobe artifacts.
* See this reference for more explanation:
* https://grahamhazel.com/blog/2017/12/22/converting-sh-radiance-to-irradiance/
*/
float spherical_harmonics_evaluate_non_linear(vec3 N, float R0, vec3 R1)
{
/* No idea why this is needed. */
R1 /= 2.0;
float R1_len;
vec3 R1_dir = safe_normalize_and_get_length(R1, R1_len);
float rcp_R0 = safe_rcp(R0);
float q = (1.0 + dot(R1_dir, N)) / 2.0;
float p = 1.0 + 2.0 * R1_len * rcp_R0;
float a = (1.0 - R1_len * rcp_R0) * safe_rcp(1.0 + R1_len * rcp_R0);
return R0 * (a + (1.0 - a) * (p + 1.0) * pow(q, p));
}
vec3 spherical_harmonics_evaluate_lambert_non_linear(vec3 N, SphericalHarmonicL1 sh)
{
/* Shuffling based on spherical_harmonics_L1_* functions. */
vec3 R1_r = vec3(-sh.L1.Mp1.r, -sh.L1.Mn1.r, sh.L1.M0.r);
vec3 R1_g = vec3(-sh.L1.Mp1.g, -sh.L1.Mn1.g, sh.L1.M0.g);
vec3 R1_b = vec3(-sh.L1.Mp1.b, -sh.L1.Mn1.b, sh.L1.M0.b);
vec3 radiance = vec3(spherical_harmonics_evaluate_non_linear(N, sh.L0.M0.r, R1_r),
spherical_harmonics_evaluate_non_linear(N, sh.L0.M0.g, R1_g),
spherical_harmonics_evaluate_non_linear(N, sh.L0.M0.b, R1_b));
/* Return lambertian radiance. So divide by PI. */
return radiance / M_PI;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load/Store
*
* This section define the compression scheme of spherical harmonic data.
* \{ */
SphericalHarmonicL1 spherical_harmonics_unpack(vec4 L0_L1_a,
vec4 L0_L1_b,
vec4 L0_L1_c,
vec4 L0_L1_vis)
{
SphericalHarmonicL1 sh;
sh.L0.M0.xyz = L0_L1_a.xyz;
sh.L1.Mn1.xyz = L0_L1_b.xyz;
sh.L1.M0.xyz = L0_L1_c.xyz;
sh.L1.Mp1.xyz = vec3(L0_L1_a.w, L0_L1_b.w, L0_L1_c.w);
sh.L0.M0.w = L0_L1_vis.x;
sh.L1.Mn1.w = L0_L1_vis.y;
sh.L1.M0.w = L0_L1_vis.z;
sh.L1.Mp1.w = L0_L1_vis.w;
return sh;
}
void spherical_harmonics_pack(SphericalHarmonicL1 sh,
out vec4 L0_L1_a,
out vec4 L0_L1_b,
out vec4 L0_L1_c,
out vec4 L0_L1_vis)
{
L0_L1_a.xyz = sh.L0.M0.xyz;
L0_L1_b.xyz = sh.L1.Mn1.xyz;
L0_L1_c.xyz = sh.L1.M0.xyz;
L0_L1_a.w = sh.L1.Mp1.x;
L0_L1_b.w = sh.L1.Mp1.y;
L0_L1_c.w = sh.L1.Mp1.z;
L0_L1_vis = vec4(sh.L0.M0.w, sh.L1.Mn1.w, sh.L1.M0.w, sh.L1.Mp1.w);
}
/** \} */

View File

@@ -0,0 +1,56 @@
/**
* Surface Capture: Output surface parameters to diverse storage.
*
* This is a separate shader to allow custom closure behavior and avoid putting more complexity
* into other surface shaders.
*/
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_hair_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
vec4 closure_to_rgba(Closure cl)
{
return vec4(0.0);
}
void main()
{
init_globals();
/* TODO(fclem): Remove random sampling for capture and accumulate color. */
g_closure_rand = 0.5;
nodetree_surface();
g_diffuse_data.color *= g_diffuse_data.weight;
g_reflection_data.color *= g_reflection_data.weight;
g_refraction_data.color *= g_refraction_data.weight;
vec3 albedo = g_diffuse_data.color + g_reflection_data.color;
/* ----- Surfel output ----- */
if (capture_info_buf.do_surfel_count) {
/* Generate a surfel only once. This check allow cases where no axis is dominant. */
bool is_surface_view_aligned = dominant_axis(g_data.Ng) == dominant_axis(cameraForward);
if (is_surface_view_aligned) {
uint surfel_id = atomicAdd(capture_info_buf.surfel_len, 1u);
if (capture_info_buf.do_surfel_output) {
surfel_buf[surfel_id].position = g_data.P;
surfel_buf[surfel_id].normal = gl_FrontFacing ? g_data.Ng : -g_data.Ng;
surfel_buf[surfel_id].albedo_front = albedo;
surfel_buf[surfel_id].radiance_direct.front.rgb = g_emission;
/* TODO(fclem): 2nd surface evaluation. */
surfel_buf[surfel_id].albedo_back = albedo;
surfel_buf[surfel_id].radiance_direct.back.rgb = g_emission;
}
}
}
}

View File

@@ -0,0 +1,89 @@
/**
* Apply lights contribution to scene surfel representation.
*/
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
void light_eval_surfel(
ClosureDiffuse diffuse, vec3 P, vec3 Ng, float thickness, inout vec3 out_diffuse)
{
/* Dummy closure. Not used. */
ClosureReflection reflection;
reflection.N = vec3(1.0, 0.0, 0.0);
reflection.roughness = 0.0;
vec3 out_specular = vec3(0.0);
/* Dummy ltc mat parameters. Not used since we have no reflections. */
vec4 ltc_mat_dummy = utility_tx_sample(utility_tx, vec2(0.0), UTIL_LTC_MAT_LAYER);
vec3 V = Ng;
float vP_z = 0.0;
float out_shadow_unused;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
light_eval_ex(diffuse,
reflection,
true,
P,
Ng,
V,
vP_z,
thickness,
ltc_mat_dummy,
l_idx,
out_diffuse,
out_specular,
out_shadow_unused);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx)
{
light_eval_ex(diffuse,
reflection,
false,
P,
Ng,
V,
vP_z,
thickness,
ltc_mat_dummy,
l_idx,
out_diffuse,
out_specular,
out_shadow_unused);
}
LIGHT_FOREACH_END
}
void main()
{
int index = int(gl_GlobalInvocationID.x);
if (index >= capture_info_buf.surfel_len) {
return;
}
Surfel surfel = surfel_buf[index];
ClosureDiffuse diffuse_data;
diffuse_data.N = surfel.normal;
/* TODO: These could be saved inside the surfel. */
diffuse_data.sss_radius = vec3(0.0);
diffuse_data.sss_id = 0u;
float thickness = 0.0;
vec3 diffuse_light = vec3(0.0);
vec3 reflection_light = vec3(0.0);
light_eval_surfel(diffuse_data, surfel.position, surfel.normal, thickness, diffuse_light);
surfel_buf[index].radiance_direct.front.rgb += diffuse_light * surfel.albedo_front;
diffuse_data.N = -surfel.normal;
diffuse_light = vec3(0.0);
reflection_light = vec3(0.0);
light_eval_surfel(diffuse_data, surfel.position, -surfel.normal, thickness, diffuse_light);
surfel_buf[index].radiance_direct.back.rgb += diffuse_light * surfel.albedo_back;
}

View File

@@ -0,0 +1,32 @@
/**
* Takes scene surfel representation and build list of surfels aligning in a given direction.
*
* The lists head are allocated to fit the surfel granularity.
*
* Due to alignment the link and list head are split into several int arrays to avoid too much
* memory waste.
*
* Dispatch 1 thread per surfel.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surfel_list_lib.glsl)
void main()
{
int surfel_index = int(gl_GlobalInvocationID.x);
if (surfel_index >= capture_info_buf.surfel_len) {
return;
}
float ray_distance;
int list_index = surfel_list_index_get(surfel_buf[surfel_index].position, ray_distance);
/* Do separate assignement to avoid reference to buffer in arguments which is tricky to cross
* compile. */
surfel_buf[surfel_index].ray_distance = ray_distance;
/* NOTE: We only need to init the `list_start_buf` to -1 for the whole list to be valid since
* every surfel will load its `next` value from the list head. */
surfel_buf[surfel_index].next = atomicExchange(list_start_buf[list_index], surfel_index);
}

View File

@@ -0,0 +1,19 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
/**
* Return the coresponding list index in the `list_start_buf` for a given world position.
* It will clamp any coordinate outside valid bounds to nearest list.
* Also return the surfel sorting value as `r_ray_distance`.
*/
int surfel_list_index_get(vec3 P, out float r_ray_distance)
{
vec4 hP = point_world_to_ndc(P);
r_ray_distance = -hP.z;
vec2 ssP = hP.xy * 0.5 + 0.5;
ivec2 ray_coord_on_grid = ivec2(ssP * vec2(list_info_buf.ray_grid_size));
ray_coord_on_grid = clamp(ray_coord_on_grid, ivec2(0), list_info_buf.ray_grid_size - 1);
int list_index = ray_coord_on_grid.y * list_info_buf.ray_grid_size.x + ray_coord_on_grid.x;
return list_index;
}

View File

@@ -0,0 +1,162 @@
/**
* Sort a buffer of surfel list by distance along a direction.
* The resulting surfel lists are then the equivalent of a series of ray cast in the same
* direction. The fact that the surfels are sorted gives proper occlusion.
*
* Sort by increasing `ray_distance`. Start of list is smallest value.
*
* Dispatched as 1 thread per list.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
/**
* A doubly-linked list implementation.
* IMPORTANT: It is not general purpose as it only cover the cases needed by this shader.
*/
struct List {
int first, last;
};
/* Return the split list after link_index. */
List list_split_after(inout List original, int link_index)
{
int next_link = surfel_buf[link_index].next;
int last_link = original.last;
original.last = link_index;
List split;
split.first = next_link;
split.last = last_link;
surfel_buf[link_index].next = -1;
surfel_buf[next_link].prev = -1;
return split;
}
void list_add_tail(inout List list, int link_index)
{
surfel_buf[link_index].next = -1;
surfel_buf[link_index].prev = list.last;
surfel_buf[list.last].next = link_index;
list.last = link_index;
}
void list_insert_link_before(inout List list, int next_link, int new_link)
{
if (list.first == next_link) {
/* At beginning of list. */
list.first = new_link;
}
int prev_link = surfel_buf[next_link].prev;
surfel_buf[new_link].next = next_link;
surfel_buf[new_link].prev = prev_link;
surfel_buf[next_link].prev = new_link;
if (prev_link != -1) {
surfel_buf[prev_link].next = new_link;
}
}
/**
* Return true if link from `surfel[a]` to `surfel[b]` is valid.
* WARNING: this function is not commutative : `f(a, b) != f(b, a)`
*/
bool is_valid_surfel_link(int a, int b)
{
vec3 link_vector = normalize(surfel_buf[b].position - surfel_buf[a].position);
float link_angle_cos = dot(surfel_buf[a].normal, link_vector);
bool is_coplanar = abs(link_angle_cos) < 1.0e-3;
return !is_coplanar;
}
void main()
{
int list_index = int(gl_GlobalInvocationID.x);
if (list_index >= list_info_buf.list_max) {
return;
}
int list_start = list_start_buf[list_index];
if (list_start == -1) {
/* Empty list. */
return;
}
/* Create Surfel.prev pointers. */
int prev_id = -1;
for (int i = list_start; i > -1; i = surfel_buf[i].next) {
surfel_buf[i].prev = prev_id;
prev_id = i;
}
List sorted_list;
sorted_list.first = list_start;
sorted_list.last = prev_id;
if (sorted_list.first == sorted_list.last) {
/* Only one item. Nothing to sort. */
return;
}
/* Using insertion sort as it is easier to implement. */
List unsorted_list = list_split_after(sorted_list, sorted_list.first);
/* Mutable foreach. */
for (int i = unsorted_list.first, next; i > -1; i = next) {
next = surfel_buf[i].next;
bool insert = false;
for (int j = sorted_list.first; j > -1; j = surfel_buf[j].next) {
if (surfel_buf[j].ray_distance < surfel_buf[i].ray_distance) {
list_insert_link_before(sorted_list, j, i);
insert = true;
break;
}
}
if (insert == false) {
list_add_tail(sorted_list, i);
}
}
/* Update list start for irradiance sample capture. */
list_start_buf[list_index] = sorted_list.first;
/* Now that we have a sorted list, try to avoid connection from coplanar surfels.
* For that we disconnect them and link them to the first non-coplanar surfel.
* Note that this changes the list to a tree, which doesn't affect the rest of the algorithm.
*
* This is a really important step since it allows to clump more surfels into one ray list and
* avoid light leaking through surfaces. If we don't disconnect coplanar surfels, we loose many
* good rays by evaluating null radiance transfer between the coplanar surfels for rays that
* are not directly perpendicular to the surface. */
/* Mutable foreach. */
for (int i = sorted_list.first, next; i > -1; i = next) {
next = surfel_buf[i].next;
int valid_next = surfel_buf[i].next;
int valid_prev = surfel_buf[i].prev;
/* Search the list for the first valid next and previous surfel. */
while (valid_next > -1) {
if (is_valid_surfel_link(i, valid_next)) {
break;
}
valid_next = surfel_buf[valid_next].next;
}
while (valid_prev > -1) {
if (is_valid_surfel_link(i, valid_prev)) {
break;
}
valid_prev = surfel_buf[valid_prev].prev;
}
surfel_buf[i].next = valid_next;
surfel_buf[i].prev = valid_prev;
}
}

View File

@@ -0,0 +1,100 @@
/**
* For every surfel, compute the incomming radiance from both side.
* For that, walk the ray surfel linked-list and gather the light from the neighbor surfels.
* This shader is dispatched for a random ray in a uniform hemisphere as we evaluate the
* radiance in both directions.
*
* Dispatched as 1 thread per surfel.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
void radiance_transfer(inout Surfel surfel, vec3 in_radiance, vec3 L)
{
float NL = dot(surfel.normal, L);
/* Lambertian BSDF. Albedo applied later depending on which side of the surfel was hit. */
float bsdf = M_1_PI;
/* From "Global Illumination using Parallel Global Ray-Bundles"
* Eq. 3: Outgoing light */
vec3 out_radiance = (M_TAU / capture_info_buf.sample_count) * bsdf * in_radiance * abs(NL);
SurfelRadiance surfel_radiance_indirect = surfel.radiance_indirect[radiance_dst];
bool front_facing = (NL > 0.0);
if (front_facing) {
/* Store radiance normalized for spherical harmonic accumulation and for visualization. */
surfel_radiance_indirect.front.rgb *= surfel_radiance_indirect.front.w;
surfel_radiance_indirect.front += vec4(out_radiance * surfel.albedo_front,
1.0 / capture_info_buf.sample_count);
surfel_radiance_indirect.front.rgb /= surfel_radiance_indirect.front.w;
}
else {
/* Store radiance normalized for spherical harmonic accumulation and for visualization. */
surfel_radiance_indirect.back.rgb *= surfel_radiance_indirect.back.w;
surfel_radiance_indirect.back += vec4(out_radiance * surfel.albedo_back,
1.0 / capture_info_buf.sample_count);
surfel_radiance_indirect.back.rgb /= surfel_radiance_indirect.back.w;
}
surfel.radiance_indirect[radiance_dst] = surfel_radiance_indirect;
}
void radiance_transfer_surfel(inout Surfel receiver, Surfel sender)
{
vec3 L = safe_normalize(sender.position - receiver.position);
bool front_facing = dot(-L, sender.normal) > 0.0;
vec3 radiance;
SurfelRadiance sender_radiance_indirect = sender.radiance_indirect[radiance_src];
if (front_facing) {
radiance = sender.radiance_direct.front.rgb;
radiance += sender_radiance_indirect.front.rgb * sender_radiance_indirect.front.w;
}
else {
radiance = sender.radiance_direct.back.rgb;
radiance += sender_radiance_indirect.back.rgb * sender_radiance_indirect.back.w;
}
radiance_transfer(receiver, radiance, L);
}
void radiance_transfer_world(inout Surfel receiver, vec3 sky_L)
{
/* TODO(fclem): Sky radiance. */
vec3 radiance = vec3(0.0);
radiance_transfer(receiver, radiance, -sky_L);
}
void main()
{
int surfel_index = int(gl_GlobalInvocationID.x);
if (surfel_index >= int(capture_info_buf.surfel_len)) {
return;
}
Surfel surfel = surfel_buf[surfel_index];
vec3 sky_L = cameraVec(surfel.position);
if (surfel.next > -1) {
Surfel surfel_next = surfel_buf[surfel.next];
radiance_transfer_surfel(surfel, surfel_next);
}
else {
radiance_transfer_world(surfel, sky_L);
}
if (surfel.prev > -1) {
Surfel surfel_prev = surfel_buf[surfel.prev];
radiance_transfer_surfel(surfel, surfel_prev);
}
else {
radiance_transfer_world(surfel, -sky_L);
}
surfel_buf[surfel_index] = surfel;
}

View File

@@ -37,6 +37,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
.additional_info("eevee_shared",
"eevee_utility_texture",
"eevee_light_data",
"eevee_lightprobe_data",
"eevee_shadow_data",
"eevee_deferred_base",
"eevee_transmittance_data",

View File

@@ -7,7 +7,7 @@
GPU_SHADER_CREATE_INFO(eevee_hiz_data)
.sampler(HIZ_TEX_SLOT, ImageType::FLOAT_2D, "hiz_tx")
.uniform_buf(5, "HiZData", "hiz_buf");
.uniform_buf(HIZ_BUF_SLOT, "HiZData", "hiz_buf");
GPU_SHADER_CREATE_INFO(eevee_hiz_update)
.do_static_compilation(true)

View File

@@ -5,16 +5,144 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
GPU_SHADER_INTERFACE_INFO(eeve_debug_surfel_iface, "")
/* -------------------------------------------------------------------- */
/** \name Display
* \{ */
GPU_SHADER_INTERFACE_INFO(eevee_debug_surfel_iface, "")
.smooth(Type::VEC3, "P")
.flat(Type::INT, "surfel_index");
GPU_SHADER_CREATE_INFO(eevee_debug_surfels)
.additional_info("eevee_shared", "draw_view")
.vertex_source("eevee_debug_surfels_vert.glsl")
.vertex_out(eeve_debug_surfel_iface)
.vertex_out(eevee_debug_surfel_iface)
.fragment_source("eevee_debug_surfels_frag.glsl")
.fragment_out(0, Type::VEC4, "out_color")
.storage_buf(0, Qualifier::READ, "DebugSurfel", "surfels_buf[]")
.storage_buf(0, Qualifier::READ, "Surfel", "surfels_buf[]")
.push_constant(Type::FLOAT, "surfel_radius")
.push_constant(Type::INT, "debug_mode")
.do_static_compilation(true);
GPU_SHADER_INTERFACE_INFO(eevee_display_probe_grid_iface, "")
.smooth(Type::VEC2, "lP")
.flat(Type::IVEC3, "cell");
GPU_SHADER_CREATE_INFO(eevee_display_probe_grid)
.additional_info("eevee_shared", "draw_view")
.vertex_source("eevee_display_probe_grid_vert.glsl")
.vertex_out(eevee_display_probe_grid_iface)
.fragment_source("eevee_display_probe_grid_frag.glsl")
.fragment_out(0, Type::VEC4, "out_color")
.push_constant(Type::FLOAT, "sphere_radius")
.push_constant(Type::IVEC3, "grid_resolution")
.push_constant(Type::MAT4, "grid_to_world")
.push_constant(Type::MAT4, "world_to_grid")
.sampler(0, ImageType::FLOAT_3D, "irradiance_a_tx")
.sampler(1, ImageType::FLOAT_3D, "irradiance_b_tx")
.sampler(2, ImageType::FLOAT_3D, "irradiance_c_tx")
.sampler(3, ImageType::FLOAT_3D, "irradiance_d_tx")
.do_static_compilation(true);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Baking
* \{ */
GPU_SHADER_CREATE_INFO(eevee_surfel_common)
.storage_buf(SURFEL_BUF_SLOT, Qualifier::READ_WRITE, "Surfel", "surfel_buf[]")
.storage_buf(CAPTURE_BUF_SLOT, Qualifier::READ, "CaptureInfoData", "capture_info_buf");
GPU_SHADER_CREATE_INFO(eevee_surfel_light)
.local_group_size(SURFEL_GROUP_SIZE)
.additional_info("eevee_shared",
"draw_view",
"eevee_utility_texture",
"eevee_surfel_common",
"eevee_light_data",
"eevee_shadow_data")
.compute_source("eevee_surfel_light_comp.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_surfel_list_build)
.local_group_size(SURFEL_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.storage_buf(0, Qualifier::READ_WRITE, "int", "list_start_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, "SurfelListInfoData", "list_info_buf")
.compute_source("eevee_surfel_list_build_comp.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_surfel_list_sort)
.local_group_size(SURFEL_LIST_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.storage_buf(0, Qualifier::READ_WRITE, "int", "list_start_buf[]")
.storage_buf(6, Qualifier::READ, "SurfelListInfoData", "list_info_buf")
.compute_source("eevee_surfel_list_sort_comp.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_surfel_ray)
.local_group_size(SURFEL_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.push_constant(Type::INT, "radiance_src")
.push_constant(Type::INT, "radiance_dst")
.compute_source("eevee_surfel_ray_comp.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_bounds)
.do_static_compilation(true)
.local_group_size(IRRADIANCE_BOUNDS_GROUP_SIZE)
.storage_buf(0, Qualifier::READ_WRITE, "CaptureInfoData", "capture_info_buf")
.storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]")
.push_constant(Type::INT, "resource_len")
.typedef_source("draw_shader_shared.h")
.additional_info("eevee_shared")
.compute_source("eevee_lightprobe_irradiance_bounds_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_ray)
.local_group_size(IRRADIANCE_GRID_GROUP_SIZE,
IRRADIANCE_GRID_GROUP_SIZE,
IRRADIANCE_GRID_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.push_constant(Type::INT, "radiance_src")
.storage_buf(0, Qualifier::READ, "int", "list_start_buf[]")
.storage_buf(6, Qualifier::READ, "SurfelListInfoData", "list_info_buf")
.image(0, GPU_RGBA32F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_L0_img")
.image(1, GPU_RGBA32F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_L1_a_img")
.image(2, GPU_RGBA32F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_L1_b_img")
.image(3, GPU_RGBA32F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_L1_c_img")
.compute_source("eevee_lightprobe_irradiance_ray_comp.glsl")
.do_static_compilation(true);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Runtime
* \{ */
GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
.local_group_size(IRRADIANCE_GRID_BRICK_SIZE,
IRRADIANCE_GRID_BRICK_SIZE,
IRRADIANCE_GRID_BRICK_SIZE)
.additional_info("eevee_shared")
.push_constant(Type::INT, "grid_index")
.uniform_buf(0, "IrradianceGridData", "grids_infos_buf[IRRADIANCE_GRID_MAX]")
.storage_buf(0, Qualifier::READ, "uint", "bricks_infos_buf[]")
.sampler(0, ImageType::FLOAT_3D, "irradiance_a_tx")
.sampler(1, ImageType::FLOAT_3D, "irradiance_b_tx")
.sampler(2, ImageType::FLOAT_3D, "irradiance_c_tx")
.sampler(3, ImageType::FLOAT_3D, "irradiance_d_tx")
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img")
.compute_source("eevee_lightprobe_irradiance_load_comp.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_lightprobe_data)
.uniform_buf(IRRADIANCE_GRID_BUF_SLOT,
"IrradianceGridData",
"grids_infos_buf[IRRADIANCE_GRID_MAX]")
/* NOTE: Use uint instead of IrradianceBrickPacked because Metal needs to know the exact
* type.*/
.storage_buf(IRRADIANCE_BRICK_BUF_SLOT, Qualifier::READ, "uint", "bricks_infos_buf[]")
.sampler(IRRADIANCE_ATLAS_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx");
/** \} */

View File

@@ -131,6 +131,14 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward)
// "eevee_transmittance_data",
);
GPU_SHADER_CREATE_INFO(eevee_surf_capture)
.vertex_out(eevee_surf_iface)
.define("MAT_CAPTURE")
.storage_buf(SURFEL_BUF_SLOT, Qualifier::WRITE, "Surfel", "surfel_buf[]")
.storage_buf(CAPTURE_BUF_SLOT, Qualifier::READ_WRITE, "CaptureInfoData", "capture_info_buf")
.fragment_source("eevee_surf_capture_frag.glsl")
.additional_info("eevee_camera", "eevee_utility_texture");
GPU_SHADER_CREATE_INFO(eevee_surf_depth)
.vertex_out(eevee_surf_iface)
.fragment_source("eevee_surf_depth_frag.glsl")
@@ -216,6 +224,7 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub).define("EEVEE_MATERIAL_STUBS");
EEVEE_MAT_GEOM_VARIATIONS(name##_depth, "eevee_surf_depth", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_deferred, "eevee_surf_deferred", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_capture, "eevee_surf_capture", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_shadow, "eevee_surf_shadow", __VA_ARGS__)
EEVEE_MAT_PIPE_VARIATIONS(eevee_surface, "eevee_material_stub")

View File

@@ -67,6 +67,21 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_opaque)
.additional_info("eevee_shared", "draw_view", "draw_view_culling", "eevee_light_data")
.compute_source("eevee_shadow_tag_usage_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_surfels)
.do_static_compilation(true)
.local_group_size(SURFEL_GROUP_SIZE)
.storage_buf(6, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
/* ShadowTileDataPacked is uint. But MSL translation need the real type. */
.storage_buf(7, Qualifier::READ_WRITE, "uint", "tiles_buf[]")
.push_constant(Type::INT, "directional_level")
.push_constant(Type::FLOAT, "tilemap_projection_ratio")
.additional_info("eevee_shared",
"draw_view",
"draw_view_culling",
"eevee_light_data",
"eevee_surfel_common")
.compute_source("eevee_shadow_tag_usage_surfels_comp.glsl");
GPU_SHADER_INTERFACE_INFO(eevee_shadow_tag_transparent_iface, "interp")
.smooth(Type::VEC3, "P")
.smooth(Type::VEC3, "vP")

View File

@@ -177,8 +177,11 @@ class UniformCommon : public DataBuffer<T, len, false>, NonMovable, NonCopyable
#endif
public:
UniformCommon()
UniformCommon(const char *name = nullptr)
{
if (name) {
name_ = name;
}
ubo_ = GPU_uniformbuf_create_ex(sizeof(T) * len, nullptr, name_);
}
@@ -277,7 +280,7 @@ template<
/* bool device_only = false */>
class UniformArrayBuffer : public detail::UniformCommon<T, len, false> {
public:
UniformArrayBuffer()
UniformArrayBuffer(const char *name = nullptr) : detail::UniformCommon<T, len, false>(name)
{
/* TODO(@fclem): We should map memory instead. */
this->data_ = (T *)MEM_mallocN_aligned(len * sizeof(T), 16, this->name_);
@@ -296,7 +299,7 @@ template<
/* bool device_only = false */>
class UniformBuffer : public T, public detail::UniformCommon<T, 1, false> {
public:
UniformBuffer()
UniformBuffer(const char *name = nullptr) : detail::UniformCommon<T, 1, false>(name)
{
/* TODO(@fclem): How could we map this? */
this->data_ = static_cast<T *>(this);
@@ -368,6 +371,11 @@ class StorageArrayBuffer : public detail::StorageCommon<T, len, device_only> {
return this->len_;
}
MutableSpan<T> as_span() const
{
return {this->data_, this->len_};
}
static void swap(StorageArrayBuffer &a, StorageArrayBuffer &b)
{
SWAP(T *, a.data_, b.data_);
@@ -423,6 +431,14 @@ class StorageVectorBuffer : public StorageArrayBuffer<T, len, false> {
new (ptr) T(std::forward<ForwardT>(value)...);
}
void extend(const Span<T> &values)
{
/* TODO(fclem): Optimize to a single memcpy. */
for (auto v : values) {
this->append(v);
}
}
int64_t size() const
{
return item_len_;

View File

@@ -839,6 +839,11 @@ void DRW_custom_pipeline(DrawEngineType *draw_engine_type,
struct Depsgraph *depsgraph,
void (*callback)(void *vedata, void *user_data),
void *user_data);
/**
* Same as `DRW_custom_pipeline` but allow better code-flow than a callback.
*/
void DRW_custom_pipeline_begin(DrawEngineType *draw_engine_type, struct Depsgraph *depsgraph);
void DRW_custom_pipeline_end(void);
/**
* Used when the render engine want to redo another cache populate inside the same render frame.

View File

@@ -2100,10 +2100,7 @@ void DRW_render_object_iter(
drw_task_graph_deinit();
}
void DRW_custom_pipeline(DrawEngineType *draw_engine_type,
Depsgraph *depsgraph,
void (*callback)(void *vedata, void *user_data),
void *user_data)
void DRW_custom_pipeline_begin(DrawEngineType *draw_engine_type, Depsgraph *depsgraph)
{
Scene *scene = DEG_get_evaluated_scene(depsgraph);
ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph);
@@ -2130,11 +2127,11 @@ void DRW_custom_pipeline(DrawEngineType *draw_engine_type,
DRW_volume_init(DST.vmempool);
DRW_smoke_init(DST.vmempool);
ViewportEngineData *data = DRW_view_data_engine_data_get_ensure(DST.view_data_active,
draw_engine_type);
DRW_view_data_engine_data_get_ensure(DST.view_data_active, draw_engine_type);
}
/* Execute the callback */
callback(data, user_data);
void DRW_custom_pipeline_end()
{
DST.buffer_finish_called = false;
DRW_smoke_exit(DST.vmempool);
@@ -2153,6 +2150,21 @@ void DRW_custom_pipeline(DrawEngineType *draw_engine_type,
drw_manager_exit(&DST);
}
void DRW_custom_pipeline(DrawEngineType *draw_engine_type,
struct Depsgraph *depsgraph,
void (*callback)(void *vedata, void *user_data),
void *user_data)
{
DRW_custom_pipeline_begin(draw_engine_type, depsgraph);
ViewportEngineData *data = DRW_view_data_engine_data_get_ensure(DST.view_data_active,
draw_engine_type);
/* Execute the callback. */
callback(data, user_data);
DRW_custom_pipeline_end();
}
void DRW_cache_restart(void)
{
DRW_smoke_exit(DST.vmempool);

View File

@@ -179,6 +179,14 @@ class Manager {
acquired_textures.append(texture);
}
/**
* Return the number of resource handles allocated.
*/
uint resource_handle_count() const
{
return resource_len_;
}
/** TODO(fclem): The following should become private at some point. */
void begin_sync();
void end_sync();

View File

@@ -400,3 +400,12 @@ void drw_print_value(mat4 value)
drw_print(" ", value[3]);
drw_print(")");
}
void drw_print_value(mat3 value)
{
drw_print("mat3x3(");
drw_print(" ", value[0]);
drw_print(" ", value[1]);
drw_print(" ", value[2]);
drw_print(")");
}

View File

@@ -1146,4 +1146,113 @@ static void test_eevee_shadow_page_mask()
}
DRAW_TEST(eevee_shadow_page_mask)
static void test_eevee_surfel_list()
{
StorageArrayBuffer<int> list_start_buf = {"list_start_buf"};
StorageVectorBuffer<Surfel> surfel_buf = {"surfel_buf"};
CaptureInfoBuf capture_info_buf = {"capture_info_buf"};
SurfelListInfoBuf list_info_buf = {"list_info_buf"};
/**
* Simulate surfels on a 2x2 projection grid covering [0..2] on the Z axis.
*/
{
Surfel surfel;
/* NOTE: Expected link assumes linear increasing processing order [0->5]. But this is
* multithreaded and we can't know the execution order in advance. */
/* 0: Project to (1, 0) = list 1. Unsorted Next = -1; Next = -1; Prev = 3. */
surfel.position = {1.1f, 0.1f, 0.1f};
surfel_buf.append(surfel);
/* 1: Project to (1, 0) = list 1. Unsorted Next = 0; Next = 2; Prev = -1. */
surfel.position = {1.1f, 0.2f, 0.5f};
surfel_buf.append(surfel);
/* 2: Project to (1, 0) = list 1. Unsorted Next = 1; Next = 3; Prev = 1. */
surfel.position = {1.1f, 0.3f, 0.3f};
surfel_buf.append(surfel);
/* 3: Project to (1, 0) = list 1. Unsorted Next = 2; Next = 0; Prev = 2. */
surfel.position = {1.2f, 0.4f, 0.2f};
surfel_buf.append(surfel);
/* 4: Project to (1, 1) = list 3. Unsorted Next = -1; Next = -1; Prev = -1. */
surfel.position = {1.0f, 1.0f, 0.5f};
surfel_buf.append(surfel);
/* 5: Project to (0, 1) = list 2. Unsorted Next = -1; Next = -1; Prev = -1. */
surfel.position = {0.1f, 1.1f, 0.5f};
surfel_buf.append(surfel);
surfel_buf.push_update();
}
{
capture_info_buf.surfel_len = surfel_buf.size();
capture_info_buf.push_update();
}
{
list_info_buf.ray_grid_size = int2(2);
list_info_buf.list_max = list_info_buf.ray_grid_size.x * list_info_buf.ray_grid_size.y;
list_info_buf.push_update();
}
{
list_start_buf.resize(ceil_to_multiple_u(list_info_buf.list_max, 4u));
list_start_buf.push_update();
GPU_storagebuf_clear(list_start_buf, -1);
}
/* Top-down view. */
View view = {"RayProjectionView"};
view.sync(float4x4::identity(), math::projection::orthographic<float>(0, 2, 0, 2, 0, 1));
GPUShader *sh_build = GPU_shader_create_from_info_name("eevee_surfel_list_build");
GPUShader *sh_sort = GPU_shader_create_from_info_name("eevee_surfel_list_sort");
PassSimple pass("Build_and_Sort");
pass.shader_set(sh_build);
pass.bind_ssbo("list_start_buf", list_start_buf);
pass.bind_ssbo("surfel_buf", surfel_buf);
pass.bind_ssbo("capture_info_buf", capture_info_buf);
pass.bind_ssbo("list_info_buf", list_info_buf);
pass.dispatch(int3(1, 1, 1));
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.shader_set(sh_sort);
pass.bind_ssbo("list_start_buf", list_start_buf);
pass.bind_ssbo("surfel_buf", surfel_buf);
pass.bind_ssbo("list_info_buf", list_info_buf);
pass.dispatch(int3(1, 1, 1));
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
Manager manager;
manager.submit(pass, view);
list_start_buf.read();
surfel_buf.read();
/* NOTE: All of these are unstable by definition (atomic + multithread).
* But should be consistent since we only dispatch one thread-group. */
/* Expect last added surfel index. It is the list start index before sorting. */
Vector<int> expect_list_start = {-1, 3, 5, 4};
/* Expect surfel list. */
Vector<int> expect_link_next = {-1, +2, +3, +0, -1, -1};
Vector<int> expect_link_prev = {+3, -1, +1, +2, -1, -1};
Vector<int> link_next, link_prev;
for (auto &surfel : Span<Surfel>(surfel_buf.data(), surfel_buf.size())) {
link_next.append(surfel.next);
link_prev.append(surfel.prev);
}
#if 0 /* Useful for debugging */
// Span<int>(list_start_buf.data(), expect_list_start.size()).print_as_lines("list_start");
// link_next.as_span().print_as_lines("link_next");
// link_prev.as_span().print_as_lines("link_prev");
#endif
EXPECT_EQ_ARRAY(list_start_buf.data(), expect_list_start.data(), expect_list_start.size());
EXPECT_EQ_ARRAY(link_next.data(), expect_link_next.data(), expect_link_next.size());
EXPECT_EQ_ARRAY(link_prev.data(), expect_link_prev.data(), expect_link_prev.size());
GPU_shader_free(sh_build);
GPU_shader_free(sh_sort);
DRW_shaders_free();
}
DRAW_TEST(eevee_surfel_list)
} // namespace blender::draw

View File

@@ -37,6 +37,7 @@ set(SRC
)
set(LIB
bf_draw
)
if(WITH_HEADLESS)

View File

@@ -92,6 +92,7 @@
#include "RE_pipeline.h"
#include "engines/eevee/eevee_lightcache.h"
#include "engines/eevee_next/eevee_lightcache.hh"
#include "render_intern.hh" /* own include */
@@ -1420,7 +1421,9 @@ static int light_cache_bake_exec(bContext *C, wmOperator *op)
bool stop = false, do_update;
float progress; /* Not actually used. */
/* Do the job. */
EEVEE_lightbake_job(rj, &stop, &do_update, &progress);
/* Free baking data. Result is already stored in the scene data. */
EEVEE_lightbake_job_data_free(rj);
/* No redraw needed, we leave state as we entered it. */
@@ -1514,7 +1517,8 @@ void SCENE_OT_light_cache_bake(wmOperatorType *ot)
/* NOTE: New version destined to replace the old lightcache bake operator. */
static void lightprobe_cache_bake_start(bContext *C, wmOperator *op)
static blender::Vector<Object *> lightprobe_cache_irradiance_volume_subset_get(bContext *C,
wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
@@ -1524,10 +1528,13 @@ static void lightprobe_cache_bake_start(bContext *C, wmOperator *op)
static_cast<LightProbe *>(ob->data)->type == LIGHTPROBE_TYPE_GRID;
};
auto irradiance_volume_setup = [](Object *ob) {
blender::Vector<Object *> probes;
auto irradiance_volume_setup = [&](Object *ob) {
BKE_lightprobe_cache_free(ob);
BKE_lightprobe_cache_create(ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
probes.append(ob);
};
int subset = RNA_enum_get(op->ptr, "subset");
@@ -1576,21 +1583,37 @@ static void lightprobe_cache_bake_start(bContext *C, wmOperator *op)
BLI_assert_unreachable();
break;
}
return probes;
}
static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
int delay = RNA_int_get(op->ptr, "delay");
lightprobe_cache_bake_start(C, op);
blender::Vector<Object *> probes = lightprobe_cache_irradiance_volume_subset_get(C, op);
if (probes.is_empty()) {
return OPERATOR_CANCELLED;
}
wmJob *wm_job = EEVEE_NEXT_lightbake_job_create(
wm, win, bmain, view_layer, scene, probes, scene->r.cfra, delay);
WM_event_add_modal_handler(C, op);
/* store actual owner of job, so modal operator could check for it,
/* Store actual owner of job, so modal operator could check for it,
* the reason of this is that active scene could change when rendering
* several layers from compositor #31800. */
op->customdata = scene;
WM_jobs_start(wm, wm_job);
WM_cursor_wait(false);
return OPERATOR_RUNNING_MODAL;
@@ -1625,7 +1648,18 @@ static void lightprobe_cache_bake_cancel(bContext *C, wmOperator *op)
/* Executes blocking bake. */
static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op)
{
lightprobe_cache_bake_start(C, op);
ViewLayer *view_layer = CTX_data_view_layer(C);
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
blender::Vector<Object *> probes = lightprobe_cache_irradiance_volume_subset_get(C, op);
/* TODO: abort if selected engine is not eevee. */
void *rj = EEVEE_NEXT_lightbake_job_data_alloc(bmain, view_layer, scene, probes, scene->r.cfra);
/* Do the job. */
EEVEE_NEXT_lightbake_job(rj, nullptr, nullptr, nullptr);
/* Free baking data. Result is already stored in the scene data. */
EEVEE_NEXT_lightbake_job_data_free(rj);
return OPERATOR_FINISHED;
}
@@ -1651,7 +1685,7 @@ void OBJECT_OT_lightprobe_cache_bake(wmOperatorType *ot)
/* identifiers */
ot->name = "Bake Light Cache";
ot->idname = "OBJECT_OT_lightprobe_cache_bake";
ot->description = "Bake the active view layer lighting";
ot->description = "Bake irradiance volume light cache";
/* api callbacks */
ot->invoke = lightprobe_cache_bake_invoke;
@@ -1726,30 +1760,24 @@ void SCENE_OT_light_cache_free(wmOperatorType *ot)
/* NOTE: New version destined to replace the old lightcache bake operator. */
static bool lightprobe_cache_free_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
return object && object->lightprobe_cache != nullptr;
}
static int lightprobe_cache_free_exec(bContext *C, wmOperator * /*op*/)
static int lightprobe_cache_free_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *object = CTX_data_active_object(C);
/* Kill potential bake job first (see #57011). */
wmWindowManager *wm = CTX_wm_manager(C);
WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_LIGHT_BAKE);
if (object->lightprobe_cache == nullptr) {
return OPERATOR_CANCELLED;
blender::Vector<Object *> probes = lightprobe_cache_irradiance_volume_subset_get(C, op);
for (Object *object : probes) {
if (object->lightprobe_cache == nullptr) {
continue;
}
BKE_lightprobe_cache_free(object);
DEG_id_tag_update(&object->id, ID_RECALC_COPY_ON_WRITE);
}
BKE_lightprobe_cache_free(object);
DEG_id_tag_update(&object->id, ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, scene);
return OPERATOR_FINISHED;
@@ -1757,14 +1785,40 @@ static int lightprobe_cache_free_exec(bContext *C, wmOperator * /*op*/)
void OBJECT_OT_lightprobe_cache_free(wmOperatorType *ot)
{
static const EnumPropertyItem lightprobe_subset_items[] = {
{LIGHTCACHE_SUBSET_ALL,
"ALL",
0,
"All Light Probes",
"Delete all light probes' baked lighting data"},
{LIGHTCACHE_SUBSET_SELECTED,
"SELECTED",
0,
"Selected Only",
"Only delete selected light probes' baked lighting data"},
{LIGHTCACHE_SUBSET_ACTIVE,
"ACTIVE",
0,
"Active Only",
"Only delete the active light probe's baked lighting data"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Delete Light Cache";
ot->idname = "OBJECT_OT_lightprobe_cache_free";
ot->description = "Delete cached indirect lighting";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* api callbacks */
ot->exec = lightprobe_cache_free_exec;
ot->poll = lightprobe_cache_free_poll;
ot->prop = RNA_def_enum(ot->srna,
"subset",
lightprobe_subset_items,
LIGHTCACHE_SUBSET_SELECTED,
"Subset",
"Subset of probes to update");
}
/** \} */

View File

@@ -49,6 +49,7 @@
# ifndef GPU_METAL
# define float2 vec2
# define float3 vec3
# define float3x4 mat3x4
# define float4 vec4
# define float4x4 mat4
# define int2 ivec2
@@ -74,6 +75,7 @@
# include "BLI_math_vector_types.hh"
using blender::float2;
using blender::float3;
using blender::float3x4;
using blender::float4;
using blender::float4x4;
using blender::int2;
@@ -94,6 +96,7 @@ typedef float float2[2];
typedef float float3[3];
typedef float float4[4];
typedef float float4x4[4][4];
typedef float float3x4[3][4];
typedef int int2[2];
typedef int int3[2];
typedef int int4[4];

View File

@@ -20,6 +20,8 @@
.grid_resolution_x = 4, \
.grid_resolution_y = 4, \
.grid_resolution_z = 4, \
.grid_bake_samples = 2048, \
.surfel_density = 1.0f, \
.distinf = 2.5f, \
.distpar = 2.5f, \
.falloff = 0.2f, \

View File

@@ -55,6 +55,12 @@ typedef struct LightProbe {
int grid_resolution_x;
int grid_resolution_y;
int grid_resolution_z;
/** Irradiance grid: number of directions to evaluate light transfer in. */
int grid_bake_samples;
/** Surface element density for scene surface cache. In surfel per unit distance. */
float surfel_density;
char _pad1[4];
/** Object to use as a parallax origin. */

View File

@@ -1792,6 +1792,7 @@ 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;
@@ -1843,7 +1844,6 @@ typedef struct SceneEEVEE {
int shadow_cube_size;
int shadow_cascade_size;
int shadow_pool_size;
char _pad[4];
struct LightCache *light_cache DNA_DEPRECATED;
struct LightCache *light_cache_data;

View File

@@ -156,6 +156,19 @@ static void rna_def_lightprobe(BlenderRNA *brna)
prop, "Resolution Z", "Number of samples along the z axis of the volume");
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "grid_bake_samples", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(
prop, "Bake Samples", "Number of ray directions to evaluate when baking");
RNA_def_property_range(prop, 1, INT_MAX);
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "surfel_density", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(prop,
"Surfel Density",
"Number of surfels per unit distance (higher values improve quality)");
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "visibility_buffer_bias", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "vis_bias");
RNA_def_property_range(prop, 0.001f, 9999.0f);

View File

@@ -7502,7 +7502,8 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
prop = RNA_def_property(srna, "gi_irradiance_display_size", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "gi_irradiance_draw_size");
RNA_def_property_range(prop, 0.05f, 10.0f);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.01f, 1.0f, 1, 3);
RNA_def_property_ui_text(prop,
"Irradiance Display Size",
"Size of the irradiance sample spheres to debug captured light");