EEVEE-Next: Irradiance Cache: Initial Implementation #108639

Merged
Clément Foucault merged 86 commits from fclem/blender:eevee-next-irradiance-cache into main 2023-06-23 08:39:52 +02:00
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)
fclem marked this conversation as resolved Outdated

run make format

run `make format`
{
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,
fclem marked this conversation as resolved Outdated

Looks like these could use FunctionRef instead of std::function? That's a bit clearer since these aren't stored more permanently.

Looks like these could use `FunctionRef` instead of `std::function`? That's a bit clearer since these aren't stored more permanently.
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. */
fclem marked this conversation as resolved
Review

These can be combined into one line: Vector<IrradianceBrickPacked> allocated(brick_len);

These can be combined into one line: `Vector<IrradianceBrickPacked> allocated(brick_len);`
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;
fclem marked this conversation as resolved