Refactor: EEVEE-Next: Light-probe management and structure

This cleanup and centralize lightprobe object management
into the LightProbeModule as it was always intended.
The other modules are kept for data / rendering management.

A few logic were simplified along the way.

Rename a lot of defines and classes for more consistency.

No functional change expected.

Pull Request: https://projects.blender.org/blender/blender/pulls/117941
This commit is contained in:
Clément Foucault
2024-02-08 19:48:28 +01:00
parent 30f8dfc474
commit 63a4c03b09
39 changed files with 985 additions and 1193 deletions

View File

@@ -110,7 +110,7 @@ void Camera::sync()
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_;
draw::View &view = inst_.volume_probes.bake.view_z_;
data.viewmat = view.viewmat();
data.viewinv = view.viewinv();
data.winmat = view.winmat();

View File

@@ -30,16 +30,24 @@
#define CULLING_TILE_GROUP_SIZE 256
/* Reflection Probes. */
#define REFLECTION_PROBES_MAX 128
#define REFLECTION_PROBE_GROUP_SIZE 16
#define REFLECTION_PROBE_SELECT_GROUP_SIZE 64
#define SPHERE_PROBE_GROUP_SIZE 16
#define SPHERE_PROBE_SELECT_GROUP_SIZE 64
/* Number of additional pixels on the border of an octahedral map to reserve for fixing seams.
* Border size requires depends on the max number of mipmap levels. */
#define REFLECTION_PROBE_MIPMAP_LEVELS 5
#define REFLECTION_PROBE_SH_GROUP_SIZE 512
#define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64
#define SPHERE_PROBE_MIPMAP_LEVELS 5
#define SPHERE_PROBE_SH_GROUP_SIZE 512
#define SPHERE_PROBE_SH_SAMPLES_PER_GROUP 64
/**
* Limited by the UBO size limit (16384 bytes / sizeof(SphereProbeData)).
*/
#define SPHERE_PROBE_MAX 128
#define PLANAR_PROBES_MAX 16
/**
* Limited by the performance impact it can cause.
* Limited by the max layer count supported by a hardware (256).
* Limited by the UBO size limit (16384 bytes / sizeof(PlanarProbeData)).
*/
#define PLANAR_PROBE_MAX 16
/**
* IMPORTANT: Some data packing are tweaked for these values.
@@ -170,8 +178,8 @@
/* Only during surface shading (forward and deferred eval). */
#define SHADOW_TILEMAPS_TEX_SLOT 4
#define SHADOW_ATLAS_TEX_SLOT 5
#define IRRADIANCE_ATLAS_TEX_SLOT 6
#define REFLECTION_PROBE_TEX_SLOT 7
#define VOLUME_PROBE_TEX_SLOT 6
#define SPHERE_PROBE_TEX_SLOT 7
#define VOLUME_SCATTERING_TEX_SLOT 8
#define VOLUME_TRANSMITTANCE_TEX_SLOT 9
/* Currently only used by ray-tracing, but might become used by forward too. */
@@ -202,7 +210,7 @@
#define UNIFORM_BUF_SLOT 1
/* Only during surface shading (forward and deferred eval). */
#define IRRADIANCE_GRID_BUF_SLOT 2
#define REFLECTION_PROBE_BUF_SLOT 3
#define SPHERE_PROBE_BUF_SLOT 3
#define PLANAR_PROBE_BUF_SLOT 4
/* Only during pre-pass. */
#define VELOCITY_CAMERA_PREV_BUF 2

View File

@@ -18,8 +18,8 @@ namespace blender::eevee {
void HiZBuffer::sync()
{
int2 render_extent = inst_.film.render_extent_get();
int2 probe_extent = int2(inst_.sphere_probes.probe_render_extent());
/* Padding to avoid complexity during down-sampling and screen tracing. */
int2 probe_extent = int2(inst_.reflection_probes.probe_render_extent());
int2 hiz_extent = math::ceil_to_multiple(math::max(render_extent, probe_extent),
int2(1u << (HIZ_MIP_COUNT - 1)));
int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE));

View File

@@ -90,10 +90,11 @@ void Instance::init(const int2 &output_res,
shadows.init();
motion_blur.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(visible_rect);
}
@@ -124,10 +125,11 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
depth_of_field.init();
shadows.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(&empty_rect);
}
@@ -173,8 +175,7 @@ void Instance::begin_sync()
volume.begin_sync();
pipelines.begin_sync();
cryptomatte.begin_sync();
reflection_probes.begin_sync();
planar_probes.begin_sync();
sphere_probes.begin_sync();
light_probes.begin_sync();
gpencil_engine_enabled = false;
@@ -188,7 +189,7 @@ void Instance::begin_sync()
film.sync();
render_buffers.sync();
ambient_occlusion.sync();
irradiance_cache.sync();
volume_probes.sync();
lookdev.sync();
use_surfaces = (view_layer->layflag & SCE_LAY_SOLID) != 0;
@@ -267,7 +268,7 @@ void Instance::object_sync(Object *ob)
sync.sync_gpencil(ob, ob_handle, res_handle);
break;
case OB_LIGHTPROBE:
sync.sync_light_probe(ob, ob_handle);
light_probes.sync_probe(ob, ob_handle);
break;
default:
break;
@@ -303,7 +304,7 @@ void Instance::end_sync()
cryptomatte.end_sync();
pipelines.end_sync();
light_probes.end_sync();
reflection_probes.end_sync();
sphere_probes.end_sync();
planar_probes.end_sync();
uniform_data.push_update();
@@ -344,7 +345,7 @@ void Instance::render_sync()
bool Instance::do_reflection_probe_sync() const
{
if (!reflection_probes.update_probes_this_sample_) {
if (!sphere_probes.update_probes_this_sample_) {
return false;
}
if (materials.queued_shaders_count > 0) {
@@ -478,12 +479,6 @@ void Instance::render_read_result(RenderLayer *render_layer, const char *view_na
void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
{
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when there
* are other light probes in the scene. */
if (DEG_id_type_any_exists(this->depsgraph, ID_LP)) {
reflection_probes.update_probes_next_sample_ = true;
planar_probes.update_probes_ = true;
}
while (!sampling.finished()) {
this->render_sample();
@@ -635,7 +630,7 @@ void Instance::light_bake_irradiance(
context_disable();
};
irradiance_cache.bake.init(probe);
volume_probes.bake.init(probe);
custom_pipeline_wrapper([&]() {
manager->begin_sync();
@@ -646,19 +641,19 @@ void Instance::light_bake_irradiance(
capture_view.render_world();
irradiance_cache.bake.surfels_create(probe);
volume_probes.bake.surfels_create(probe);
if (irradiance_cache.bake.should_break()) {
if (volume_probes.bake.should_break()) {
return;
}
irradiance_cache.bake.surfels_lights_eval();
volume_probes.bake.surfels_lights_eval();
irradiance_cache.bake.clusters_build();
irradiance_cache.bake.irradiance_offset();
volume_probes.bake.clusters_build();
volume_probes.bake.irradiance_offset();
});
if (irradiance_cache.bake.should_break()) {
if (volume_probes.bake.should_break()) {
return;
}
@@ -673,9 +668,9 @@ void Instance::light_bake_irradiance(
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();
volume_probes.bake.raylists_build();
volume_probes.bake.propagate_light();
volume_probes.bake.irradiance_capture();
}
if (sampling.finished()) {
@@ -685,11 +680,11 @@ void Instance::light_bake_irradiance(
LightProbeGridCacheFrame *cache_frame;
if (sampling.finished()) {
cache_frame = irradiance_cache.bake.read_result_packed();
cache_frame = volume_probes.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();
cache_frame = volume_probes.bake.read_result_unpacked();
}
float progress = sampling.sample_index() / float(sampling.sample_count());

View File

@@ -86,8 +86,6 @@ class Instance {
LightModule lights;
AmbientOcclusion ambient_occlusion;
RayTraceModule raytracing;
ReflectionProbeModule reflection_probes;
PlanarProbeModule planar_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@@ -103,8 +101,10 @@ class Instance {
World world;
LookdevView lookdev_view;
LookdevModule lookdev;
SphereProbeModule sphere_probes;
PlanarProbeModule planar_probes;
VolumeProbeModule volume_probes;
LightProbeModule light_probes;
IrradianceCache irradiance_cache;
VolumeModule volume;
/** Input data. */
@@ -149,8 +149,6 @@ class Instance {
lights(*this),
ambient_occlusion(*this, uniform_data.data.ao),
raytracing(*this, uniform_data.data.raytrace),
reflection_probes(*this),
planar_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),
@@ -165,8 +163,10 @@ class Instance {
world(*this),
lookdev_view(*this),
lookdev(*this),
sphere_probes(*this),
planar_probes(*this),
volume_probes(*this),
light_probes(*this),
irradiance_cache(*this),
volume(*this, uniform_data.data.volumes){};
~Instance(){};

View File

@@ -21,7 +21,7 @@ namespace blender::eevee {
/** \name Interface
* \{ */
void IrradianceCache::init()
void VolumeProbeModule::init()
{
display_grids_enabled_ = DRW_state_draw_support();
@@ -48,7 +48,7 @@ void IrradianceCache::init()
if (do_full_update_) {
/* Delete all references to existing bricks. */
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
grid.bricks.clear();
}
brick_pool_.clear();
@@ -71,7 +71,7 @@ void IrradianceCache::init()
irradiance_atlas_tx_.clear(float4(0.0f));
}
inst_.reflection_probes.do_world_update_irradiance_set(true);
inst_.sphere_probes.tag_world_irradiance_for_update();
}
if (irradiance_atlas_tx_.is_valid() == false) {
@@ -79,14 +79,14 @@ void IrradianceCache::init()
}
}
void IrradianceCache::sync()
void VolumeProbeModule::sync()
{
if (inst_.is_baking()) {
bake.sync();
}
}
Vector<IrradianceBrickPacked> IrradianceCache::bricks_alloc(int brick_len)
Vector<IrradianceBrickPacked> VolumeProbeModule::bricks_alloc(int brick_len)
{
if (brick_pool_.size() < brick_len) {
/* Fail allocation. Not enough brick in the atlas. */
@@ -101,20 +101,20 @@ Vector<IrradianceBrickPacked> IrradianceCache::bricks_alloc(int brick_len)
return allocated;
}
void IrradianceCache::bricks_free(Vector<IrradianceBrickPacked> &bricks)
void VolumeProbeModule::bricks_free(Vector<IrradianceBrickPacked> &bricks)
{
brick_pool_.extend(bricks.as_span());
bricks.clear();
}
void IrradianceCache::set_view(View & /*view*/)
void VolumeProbeModule::set_view(View & /*view*/)
{
Vector<IrradianceGrid *> grid_loaded;
Vector<VolumeProbe *> grid_loaded;
bool any_update = false;
/* First allocate the needed bricks and populate the brick buffer. */
bricks_infos_buf_.clear();
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
LightProbeGridCacheFrame *cache = grid.cache ? grid.cache->grid_static_cache : nullptr;
if (cache == nullptr) {
continue;
@@ -182,7 +182,7 @@ void IrradianceCache::set_view(View & /*view*/)
* before tagging update. But this is a bit too complex and update is quite cheap. So we update
* everything if there is any update on any grid. */
if (any_update) {
for (IrradianceGrid *grid : grid_loaded) {
for (VolumeProbe *grid : grid_loaded) {
grid->do_update = true;
}
}
@@ -190,44 +190,43 @@ void IrradianceCache::set_view(View & /*view*/)
/* 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->object_to_world));
float volume_b = math::determinant(float3x3(b->object_to_world));
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. */
float3 _a = a->object_to_world.location();
float3 _b = b->object_to_world.location();
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
std::sort(
grid_loaded.begin(), grid_loaded.end(), [](const VolumeProbe *a, const VolumeProbe *b) {
float volume_a = math::determinant(float3x3(a->object_to_world));
float volume_b = math::determinant(float3x3(b->object_to_world));
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. */
float3 _a = a->object_to_world.location();
float3 _b = b->object_to_world.location();
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
/* Insert grids in UBO in sorted order. */
int grids_len = 0;
for (IrradianceGrid *grid : grid_loaded) {
for (VolumeProbe *grid : grid_loaded) {
grid->grid_index = grids_len;
grids_infos_buf_[grids_len++] = *grid;
}
/* Insert world grid last. */
IrradianceGridData grid;
VolumeProbeData grid;
grid.world_to_grid_transposed = float3x4::identity();
grid.grid_size = int3(1);
grid.brick_offset = bricks_infos_buf_.size();
@@ -253,7 +252,7 @@ void IrradianceCache::set_view(View & /*view*/)
for (auto it = grid_loaded.rbegin(); it != grid_loaded.rend(); ++it) {
grid_start_index--;
IrradianceGrid *grid = *it;
VolumeProbe *grid = *it;
if (!grid->do_update) {
continue;
}
@@ -391,7 +390,7 @@ void IrradianceCache::set_view(View & /*view*/)
do_update_world_ = false;
}
void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!inst_.is_baking()) {
debug_pass_draw(view, view_fb);
@@ -399,7 +398,7 @@ void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb)
}
}
void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
switch (inst_.debug_mode) {
case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL:
@@ -425,7 +424,7 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (const VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
if (grid.cache == nullptr) {
continue;
}
@@ -527,13 +526,13 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
}
}
void IrradianceCache::display_pass_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::display_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!display_grids_enabled_) {
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (const VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
if (!grid.viewport_display || grid.viewport_display_size == 0.0f || !grid.cache ||
!grid.cache->grid_static_cache)
{
@@ -701,7 +700,7 @@ void IrradianceBake::sync()
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.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.sphere_probes);
sub.push_constant("radiance_src", &radiance_src_);
sub.push_constant("radiance_dst", &radiance_dst_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
@@ -714,7 +713,7 @@ void IrradianceBake::sync()
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_resources(inst_.reflection_probes);
pass.bind_resources(inst_.sphere_probes);
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_);
@@ -825,10 +824,9 @@ void IrradianceBake::surfels_create(const Object &probe_object)
capture_info_buf_.capture_indirect = capture_indirect_;
capture_info_buf_.capture_emission = capture_emission_;
ReflectionProbeModule &reflections = inst_.reflection_probes;
ReflectionProbeAtlasCoordinate atlas_coord = reflections.world_atlas_coord_get();
ReflectionProbeCoordinate coord = atlas_coord.as_sampling_coord(reflections.atlas_extent());
capture_info_buf_.world_atlas_coord = coord;
LightProbeModule &light_probes = inst_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
capture_info_buf_.world_atlas_coord = world_data.atlas_coord;
dispatch_per_grid_sample_ = math::divide_ceil(grid_resolution, int3(IRRADIANCE_GRID_GROUP_SIZE));
capture_info_buf_.irradiance_grid_size = grid_resolution;

View File

@@ -24,7 +24,7 @@ class Instance;
class CapturePipeline;
class ShadowModule;
class Camera;
class ReflectionProbeModule;
class SphereProbeModule;
/**
* Baking related pass and data. Not used at runtime.
@@ -186,7 +186,7 @@ class IrradianceBake {
* Runtime container of diffuse indirect lighting.
* Also have debug and baking components.
*/
class IrradianceCache {
class VolumeProbeModule {
public:
IrradianceBake bake;
@@ -202,27 +202,27 @@ class IrradianceCache {
/** 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_"};
VolumeProbeDataBuf 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"};
PassSimple grid_upload_ps_ = {"VolumeProbeModule.Upload"};
/** If true, will trigger the reupload of all grid data instead of just streaming new ones. */
bool do_full_update_ = true;
/** Display debug data. */
PassSimple debug_ps_ = {"IrradianceCache.Debug"};
PassSimple debug_ps_ = {"VolumeProbeModule.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"};
PassSimple display_grids_ps_ = {"VolumeProbeModule.Display Grids"};
public:
IrradianceCache(Instance &inst) : bake(inst), inst_(inst){};
~IrradianceCache(){};
VolumeProbeModule(Instance &inst) : bake(inst), inst_(inst){};
~VolumeProbeModule(){};
void init();
void sync();
@@ -236,14 +236,14 @@ class IrradianceCache {
{
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_);
pass.bind_texture(VOLUME_PROBE_TEX_SLOT, &irradiance_atlas_tx_);
}
private:
void debug_pass_draw(View &view, GPUFrameBuffer *view_fb);
void display_pass_draw(View &view, GPUFrameBuffer *view_fb);
friend class ReflectionProbeModule;
friend class SphereProbeModule;
};
} // namespace blender::eevee

View File

@@ -6,7 +6,8 @@
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#include "DNA_lightprobe_types.h"
@@ -17,17 +18,64 @@
#include "draw_debug.hh"
#include <iostream>
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Light-Probe Module
* \{ */
LightProbeModule::LightProbeModule(Instance &inst) : inst_(inst)
{
/* Initialize the world probe. */
world_sphere_.clipping_distances = float2(1.0f, 10.0f);
world_sphere_.world_to_probe_transposed = float3x4::identity();
world_sphere_.influence_shape = SHAPE_ELIPSOID;
world_sphere_.parallax_shape = SHAPE_ELIPSOID;
/* Full influence. */
world_sphere_.influence_scale = 0.0f;
world_sphere_.influence_bias = 1.0f;
world_sphere_.parallax_distance = 1e10f;
/* In any case, the world must always be up to valid and used for render. */
world_sphere_.use_for_render = true;
}
static eLightProbeResolution resolution_to_probe_resolution_enum(int resolution)
{
switch (resolution) {
case 64:
return LIGHT_PROBE_RESOLUTION_64;
case 128:
return LIGHT_PROBE_RESOLUTION_128;
case 256:
return LIGHT_PROBE_RESOLUTION_256;
case 512:
return LIGHT_PROBE_RESOLUTION_512;
case 1024:
return LIGHT_PROBE_RESOLUTION_1024;
default:
/* Default to maximum resolution because the old max was 4K for Legacy-EEVEE. */
case 2048:
return LIGHT_PROBE_RESOLUTION_2048;
}
}
void LightProbeModule::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
sphere_object_resolution_ = resolution_to_probe_resolution_enum(sce_eevee.gi_cubemap_resolution);
}
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)
void LightProbeModule::sync_volume(const Object *ob, ObjectHandle &handle)
{
IrradianceGrid &grid = grid_map_.lookup_or_add_default(handle.object_key);
VolumeProbe &grid = volume_map_.lookup_or_add_default(handle.object_key);
grid.used = true;
if (handle.recalc != 0 || grid.initialized == false) {
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
@@ -53,17 +101,78 @@ void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
grid.viewport_display_size = lightprobe->data_display_size;
/* Force reupload. */
inst_.irradiance_cache.bricks_free(grid.bricks);
inst_.volume_probes.bricks_free(grid.bricks);
}
}
void LightProbeModule::sync_cube(ObjectHandle &handle)
void LightProbeModule::sync_sphere(const Object *ob, ObjectHandle &handle)
{
ReflectionCube &cube = cube_map_.lookup_or_add_default(handle.object_key);
SphereProbe &cube = sphere_map_.lookup_or_add_default(handle.object_key);
cube.used = true;
if (handle.recalc != 0 || cube.initialized == false) {
const ::LightProbe &light_probe = *(::LightProbe *)ob->data;
cube.initialized = true;
cube_update_ = true;
cube.updated = true;
cube.do_render = true;
SphereProbeModule &probe_module = inst_.sphere_probes;
eLightProbeResolution probe_resolution = sphere_object_resolution_;
int subdivision_lvl = probe_module.subdivision_level_get(probe_resolution);
if (cube.atlas_coord.subdivision_lvl != subdivision_lvl) {
cube.atlas_coord.free();
cube.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &cube_data = *static_cast<SphereProbeData *>(&cube);
/* Update gpu data sampling coordinates. */
cube_data.atlas_coord = cube.atlas_coord.as_sampling_coord(probe_module.max_resolution_);
/* Coordinates have changed. Area might contain random data. Do not use for rendering. */
cube.use_for_render = false;
}
bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
float influence_distance = light_probe.distinf;
float influence_falloff = light_probe.falloff;
float parallax_distance = light_probe.distpar;
parallax_distance = use_custom_parallax ? max_ff(parallax_distance, influence_distance) :
influence_distance;
auto to_eevee_shape = [](int bl_shape_type) {
return (bl_shape_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : SHAPE_ELIPSOID;
};
cube.influence_shape = to_eevee_shape(light_probe.attenuation_type);
cube.parallax_shape = to_eevee_shape(light_probe.parallax_type);
float4x4 object_to_world = math::scale(float4x4(ob->object_to_world),
float3(influence_distance));
cube.location = object_to_world.location();
cube.volume = math::abs(math::determinant(object_to_world));
cube.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
cube.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
cube.influence_bias = cube.influence_scale;
cube.parallax_distance = parallax_distance / influence_distance;
cube.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
cube.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
cube.viewport_display_size = light_probe.data_display_size;
}
}
void LightProbeModule::sync_planar(const Object *ob, ObjectHandle &handle)
{
PlanarProbe &plane = planar_map_.lookup_or_add_default(handle.object_key);
plane.used = true;
if (handle.recalc != 0 || plane.initialized == false) {
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
plane.initialized = true;
plane.updated = true;
plane.plane_to_world = float4x4(ob->object_to_world);
plane.plane_to_world.z_axis() = math::normalize(plane.plane_to_world.z_axis()) *
light_probe->distinf;
plane.world_to_plane = math::invert(plane.plane_to_world);
plane.clipping_offset = light_probe->clipsta;
plane.viewport_display = (light_probe->flag & LIGHTPROBE_FLAG_SHOW_DATA) != 0;
}
}
@@ -72,84 +181,185 @@ void LightProbeModule::sync_probe(const Object *ob, ObjectHandle &handle)
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
switch (lightprobe->type) {
case LIGHTPROBE_TYPE_SPHERE:
sync_cube(handle);
sync_sphere(ob, handle);
return;
case LIGHTPROBE_TYPE_PLANE:
/* TODO(fclem): Remove support? Add support? */
sync_planar(ob, handle);
return;
case LIGHTPROBE_TYPE_VOLUME:
sync_grid(ob, handle);
sync_volume(ob, handle);
return;
}
BLI_assert_unreachable();
}
void LightProbeModule::end_sync()
void LightProbeModule::sync_world(const ::World *world, bool has_update)
{
{
/* 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;
}
const eLightProbeResolution probe_resolution = static_cast<eLightProbeResolution>(
world->probe_resolution);
SphereProbeModule &sph_module = inst_.sphere_probes;
int subdivision_lvl = sph_module.subdivision_level_get(probe_resolution);
if (subdivision_lvl != world_sphere_.atlas_coord.subdivision_lvl) {
world_sphere_.atlas_coord.free();
world_sphere_.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&world_sphere_);
world_data.atlas_coord = world_sphere_.atlas_coord.as_sampling_coord(
sph_module.max_resolution_);
has_update = true;
}
#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 Cube-map 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);
}
if (has_update) {
world_sphere_.do_render = true;
sph_module.tag_world_irradiance_for_update();
}
#endif
}
void LightProbeModule::end_sync()
{
/* Check for deleted or updated grid. */
volume_update_ = false;
volume_map_.remove_if([&](const Map<ObjectKey, VolumeProbe>::MutableItem &item) {
VolumeProbe &grid = item.value;
bool remove_grid = !grid.used;
if (grid.updated || remove_grid) {
volume_update_ = true;
}
grid.updated = false;
grid.used = false;
return remove_grid;
});
/* Check for deleted or updated cube. */
sphere_update_ = false;
sphere_map_.remove_if([&](const Map<ObjectKey, SphereProbe>::MutableItem &item) {
SphereProbe &cube = item.value;
bool remove_cube = !cube.used;
if (cube.updated || remove_cube) {
sphere_update_ = true;
}
cube.updated = false;
cube.used = false;
return remove_cube;
});
/* Check for deleted or updated plane. */
planar_update_ = false;
planar_map_.remove_if([&](const Map<ObjectKey, PlanarProbe>::MutableItem &item) {
PlanarProbe &plane = item.value;
bool remove_plane = !plane.used;
if (plane.updated || remove_plane) {
planar_update_ = true;
}
plane.updated = false;
plane.used = false;
return remove_plane;
});
}
SphereProbeAtlasCoord LightProbeModule::find_empty_atlas_region(int subdivision_level) const
{
int layer_count = sphere_layer_count();
SphereProbeAtlasCoord::LocationFinder location_finder(layer_count, subdivision_level);
location_finder.mark_space_used(world_sphere_.atlas_coord);
for (const SphereProbe &probe : sphere_map_.values()) {
location_finder.mark_space_used(probe.atlas_coord);
}
return location_finder.first_free_spot();
}
int LightProbeModule::sphere_layer_count() const
{
int max_layer = world_sphere_.atlas_coord.atlas_layer;
for (const SphereProbe &probe : sphere_map_.values()) {
max_layer = max_ii(max_layer, probe.atlas_coord.atlas_layer);
}
int layer_count = max_layer + 1;
return layer_count;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
SphereProbeAtlasCoord::LocationFinder::LocationFinder(int allocated_layer_count,
int subdivision_level)
{
subdivision_level_ = subdivision_level;
areas_per_dimension_ = 1 << subdivision_level_;
areas_per_layer_ = square_i(areas_per_dimension_);
/* Always add an additional layer to make sure that there is always a free area.
* If this area is chosen the atlas will grow. */
int area_len = (allocated_layer_count + 1) * areas_per_layer_;
areas_occupancy_.resize(area_len, false);
}
void SphereProbeAtlasCoord::LocationFinder::mark_space_used(const SphereProbeAtlasCoord &coord)
{
if (coord.atlas_layer == -1) {
/* Coordinate not allocated yet. */
return;
}
/* The input probe data can be stored in a different subdivision level and should tag all areas
* of the target subdivision level. Shift right if subdivision is higher, left if lower. */
const int shift_right = max_ii(coord.subdivision_lvl - subdivision_level_, 0);
const int shift_left = max_ii(subdivision_level_ - coord.subdivision_lvl, 0);
const int2 pos_in_location_finder = (coord.area_location() >> shift_right) << shift_left;
/* Tag all areas this probe overlaps. */
const int layer_offset = coord.atlas_layer * areas_per_layer_;
const int areas_overlapped_per_dim = 1 << shift_left;
for (const int y : IndexRange(areas_overlapped_per_dim)) {
for (const int x : IndexRange(areas_overlapped_per_dim)) {
const int2 pos = pos_in_location_finder + int2(x, y);
const int area_index = pos.x + pos.y * areas_per_dimension_;
areas_occupancy_[area_index + layer_offset].set();
}
}
}
SphereProbeAtlasCoord SphereProbeAtlasCoord::LocationFinder::first_free_spot() const
{
SphereProbeAtlasCoord result;
result.subdivision_lvl = subdivision_level_;
for (int index : areas_occupancy_.index_range()) {
if (!areas_occupancy_[index]) {
result.atlas_layer = index / areas_per_layer_;
result.area_index = index % areas_per_layer_;
return result;
}
}
/* There should always be a free area. See constructor. */
BLI_assert_unreachable();
return result;
}
void SphereProbeAtlasCoord::LocationFinder::print_debug() const
{
std::ostream &os = std::cout;
int layer = 0, row = 0, column = 0;
os << "subdivision " << subdivision_level_ << "\n";
for (bool spot_taken : areas_occupancy_) {
if (row == 0 && column == 0) {
os << "layer " << layer << "\n";
}
os << (spot_taken ? 'X' : '-');
column++;
if (column == areas_per_dimension_) {
os << "\n";
column = 0;
row++;
}
if (row == areas_per_dimension_) {
row = 0;
layer++;
}
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -6,11 +6,13 @@
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#pragma once
#include "BLI_bit_vector.hh"
#include "BLI_map.hh"
#include "eevee_sync.hh"
@@ -18,15 +20,130 @@
namespace blender::eevee {
class Instance;
class IrradianceCache;
class VolumeProbeModule;
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
struct SphereProbeAtlasCoord {
/** On which layer of the texture array is this reflection probe stored. */
int atlas_layer = -1;
/** Gives the extent of this probe relative to the atlas size. */
int subdivision_lvl = -1;
/** Area index within the layer with the according subdivision level. */
int area_index = -1;
/** Release the current atlas space held by this probe. */
void free()
{
atlas_layer = -1;
}
/* Return the area extent in pixel. */
int area_extent(int atlas_extent) const
{
return atlas_extent >> subdivision_lvl;
}
/* Coordinate of the area in [0..area_count_per_dimension[ range. */
int2 area_location() const
{
const int area_count_per_dimension = 1 << subdivision_lvl;
return int2(area_index % area_count_per_dimension, area_index / area_count_per_dimension);
}
/* Coordinate of the bottom left corner of the area in [0..atlas_extent[ range. */
int2 area_offset(int atlas_extent) const
{
return area_location() * area_extent(atlas_extent);
}
SphereProbeUvArea as_sampling_coord(int atlas_extent) const
{
/**
* We want to cover the last mip exactly at the pixel center to reduce padding texels and
* interpolation artifacts.
* This is a diagram of a 2px^2 map with `c` being the texels corners and `x` the pixels
* centers.
*
* c-------c-------c
* | | |
* | x | x | <
* | | | |
* c-------c-------c | sampling area
* | | | |
* | x | x | <
* | | |
* c-------c-------c
* ^-------^
* sampling area
*/
/* Max level only need half a pixel of padding around the sampling area. */
const int mip_max_lvl_padding = 1;
const int mip_min_lvl_padding = mip_max_lvl_padding << SPHERE_PROBE_MIPMAP_LEVELS;
/* Extent and offset in mip 0 texels. */
const int sampling_area_extent = area_extent(atlas_extent) - mip_min_lvl_padding;
const int2 sampling_area_offset = area_offset(atlas_extent) + mip_min_lvl_padding / 2;
/* Convert to atlas UVs. */
SphereProbeUvArea coord;
coord.scale = sampling_area_extent / float(atlas_extent);
coord.offset = float2(sampling_area_offset) / float(atlas_extent);
coord.layer = atlas_layer;
return coord;
}
SphereProbePixelArea as_write_coord(int atlas_extent, int mip_lvl) const
{
SphereProbePixelArea coord;
coord.extent = atlas_extent >> (subdivision_lvl + mip_lvl);
coord.offset = (area_location() * coord.extent) >> mip_lvl;
coord.layer = atlas_layer;
return coord;
}
/**
* Utility class to find a location in the probe atlas that can be used to store a new probe in
* a specified subdivision level.
*
* The allocation space is subdivided in target subdivision level and is multi layered.
* A layer has `(2 ^ subdivision_lvl) ^ 2` areas.
*
* All allocated probe areas are then process and the candidate areas containing allocated probes
* are marked as occupied. The location finder then return the first available area.
*/
class LocationFinder {
BitVector<> areas_occupancy_;
int subdivision_level_;
/* Area count for the given subdivision level. */
int areas_per_dimension_;
int areas_per_layer_;
public:
LocationFinder(int allocated_layer_count, int subdivision_level);
/* Mark space to be occupied by the given probe_data. */
void mark_space_used(const SphereProbeAtlasCoord &coord);
SphereProbeAtlasCoord first_free_spot() const;
void print_debug() const;
};
};
/** \} */
struct LightProbe {
bool used = false;
bool initialized = false;
/* NOTE: Might be not needed if depsgraph updates work as intended. */
bool updated = false;
/** Display debug visuals in the viewport. */
bool viewport_display = false;
float viewport_display_size = 0.0f;
};
struct IrradianceGrid : public LightProbe, IrradianceGridData {
struct VolumeProbe : public LightProbe, VolumeProbeData {
/** Copy of the transform matrix. */
float4x4 object_to_world;
/** Precomputed inverse transform with normalized axes. No position. Used for rotating SH. */
@@ -51,41 +168,104 @@ struct IrradianceGrid : public LightProbe, IrradianceGridData {
float dilation_threshold;
float dilation_radius;
float intensity;
/** Display irradiance samples in the viewport. */
bool viewport_display;
float viewport_display_size;
};
struct ReflectionCube : public LightProbe {};
struct SphereProbe : public LightProbe, SphereProbeData {
/** Used to sort the probes by priority. */
float volume;
/** True if the area in the atlas needs to be updated. */
bool do_render = true;
/** False if the area in the atlas contains undefined data. */
bool use_for_render = false;
/** Far and near clipping distances for rendering. */
float2 clipping_distances;
/** Atlas region this probe is rendered at (or will be rendered at). */
SphereProbeAtlasCoord atlas_coord;
};
struct PlanarProbe : public LightProbe, PlanarProbeData {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
public:
/**
* Update the PlanarProbeData part of the struct.
* `view` is the view we want to render this probe with.
*/
void set_view(const draw::View &view, int layer_id);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
* \note Only works after `set_view` was called.
*/
float4 reflection_clip_plane_get()
{
return float4(-normal, math::dot(normal, plane_to_world.location()) - clipping_offset);
}
private:
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get()
{
return plane_to_world * math::from_scale<float4x4>(float3(1, 1, -1)) * world_to_plane;
}
};
class LightProbeModule {
friend class IrradianceCache;
friend class IrradianceBake;
friend class VolumeProbeModule;
friend class PlanarProbeModule;
friend class SphereProbeModule;
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_;
Map<ObjectKey, VolumeProbe> volume_map_;
Map<ObjectKey, SphereProbe> sphere_map_;
Map<ObjectKey, PlanarProbe> planar_map_;
/* World probe is stored separately. */
SphereProbe world_sphere_;
/** True if a light-probe update was detected. */
bool volume_update_;
bool sphere_update_;
bool planar_update_;
/** True if the auto bake feature is enabled & available in this context. */
bool auto_bake_enabled_;
eLightProbeResolution sphere_object_resolution_ = LIGHT_PROBE_RESOLUTION_64;
public:
LightProbeModule(Instance &inst) : inst_(inst){};
LightProbeModule(Instance &inst);
~LightProbeModule(){};
void init();
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 sync_world(const ::World *world, bool has_update);
void end_sync();
private:
void sync_sphere(const Object *ob, ObjectHandle &handle);
void sync_volume(const Object *ob, ObjectHandle &handle);
void sync_planar(const Object *ob, ObjectHandle &handle);
/** Get the number of atlas layers needed to store light probe spheres. */
int sphere_layer_count() const;
/** Returns coordinates of an area in the atlas for a probe with the given subdivision level. */
SphereProbeAtlasCoord find_empty_atlas_region(int subdivision_level) const;
};
} // namespace blender::eevee

View File

@@ -235,8 +235,8 @@ void LookdevModule::sync_pass(PassSimple &pass,
pass.bind_image("aov_value_img", dummy_aov_value_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.volume.result);
pass.bind_resources(inst_.cryptomatte);

View File

@@ -295,8 +295,8 @@ void ForwardPipeline::sync()
opaque_ps_.bind_resources(inst_.volume.result);
opaque_ps_.bind_resources(inst_.sampling);
opaque_ps_.bind_resources(inst_.hiz_buffer.front);
opaque_ps_.bind_resources(inst_.irradiance_cache);
opaque_ps_.bind_resources(inst_.reflection_probes);
opaque_ps_.bind_resources(inst_.volume_probes);
opaque_ps_.bind_resources(inst_.sphere_probes);
}
opaque_single_sided_ps_ = &opaque_ps_.sub("SingleSided");
@@ -323,8 +323,8 @@ void ForwardPipeline::sync()
sub.bind_resources(inst_.volume.result);
sub.bind_resources(inst_.sampling);
sub.bind_resources(inst_.hiz_buffer.front);
sub.bind_resources(inst_.irradiance_cache);
sub.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.volume_probes);
sub.bind_resources(inst_.sphere_probes);
}
}
@@ -410,7 +410,7 @@ void ForwardPipeline::render(View &view, Framebuffer &prepass_fb, Framebuffer &c
inst_.hiz_buffer.set_dirty();
inst_.shadows.set_view(view, inst_.render_buffers.depth_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
if (has_opaque_) {
combined_fb.bind();
@@ -463,8 +463,8 @@ void DeferredLayerBase::gbuffer_pass_sync(Instance &inst)
* Non-NPR shaders will override these resource bindings. */
gbuffer_ps_.bind_resources(inst.lights);
gbuffer_ps_.bind_resources(inst.shadows);
gbuffer_ps_.bind_resources(inst.reflection_probes);
gbuffer_ps_.bind_resources(inst.irradiance_cache);
gbuffer_ps_.bind_resources(inst.sphere_probes);
gbuffer_ps_.bind_resources(inst.volume_probes);
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL;
@@ -591,8 +591,8 @@ void DeferredLayer::end_sync()
sub.bind_resources(inst_.shadows);
sub.bind_resources(inst_.sampling);
sub.bind_resources(inst_.hiz_buffer.front);
sub.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.irradiance_cache);
sub.bind_resources(inst_.sphere_probes);
sub.bind_resources(inst_.volume_probes);
sub.state_stencil(0xFFu, i + 1, 0xFFu);
sub.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@@ -719,7 +719,7 @@ void DeferredLayer::render(View &main_view,
/* Update for lighting pass or AO node. */
inst_.hiz_buffer.update();
inst_.irradiance_cache.set_view(render_view);
inst_.volume_probes.set_view(render_view);
inst_.shadows.set_view(render_view, inst_.render_buffers.depth_tx);
if (/* FIXME(fclem): Vulkan doesn't implement load / store config yet. */
@@ -1206,7 +1206,7 @@ void DeferredProbeLayer::end_sync()
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.volume_probes);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@@ -1254,7 +1254,7 @@ void DeferredProbeLayer::render(View &view,
inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx);
inst_.lights.set_view(view, extent);
inst_.shadows.set_view(view, inst_.render_buffers.depth_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
/* Update for lighting pass. */
inst_.hiz_buffer.update();
@@ -1347,8 +1347,8 @@ void PlanarProbePipeline::begin_sync()
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.volume_probes);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@@ -1408,7 +1408,7 @@ void PlanarProbePipeline::render(View &view,
inst_.hiz_buffer.set_source(&depth_layer_tx, 0);
inst_.lights.set_view(view, extent);
inst_.shadows.set_view(view, depth_layer_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
/* Update for lighting pass. */
inst_.hiz_buffer.update();
@@ -1448,10 +1448,10 @@ void CapturePipeline::sync()
/* 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_.framebuffer_set(&inst_.volume_probes.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_ssbo(SURFEL_BUF_SLOT, &inst_.volume_probes.bake.surfels_buf_);
surface_ps_.bind_ssbo(CAPTURE_BUF_SLOT, &inst_.volume_probes.bake.capture_info_buf_);
surface_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
/* TODO(fclem): Remove. Bind to get the camera data,

View File

@@ -13,18 +13,6 @@ using namespace blender::math;
/** \name Planar Probe
* \{ */
void PlanarProbe::sync(const float4x4 &world_to_object,
float clipping_offset,
float influence_distance,
bool viewport_display)
{
this->plane_to_world = float4x4(world_to_object);
this->plane_to_world.z_axis() = normalize(this->plane_to_world.z_axis()) * influence_distance;
this->world_to_plane = invert(this->plane_to_world);
this->clipping_offset = clipping_offset;
this->viewport_display = viewport_display;
}
void PlanarProbe::set_view(const draw::View &view, int layer_id)
{
this->viewmat = view.viewmat() * reflection_matrix_get();
@@ -40,16 +28,6 @@ void PlanarProbe::set_view(const draw::View &view, int layer_id)
this->layer_id = layer_id;
}
float4x4 PlanarProbe::reflection_matrix_get()
{
return plane_to_world * from_scale<float4x4>(float3(1, 1, -1)) * world_to_plane;
}
float4 PlanarProbe::reflection_clip_plane_get()
{
return float4(-normal, dot(normal, plane_to_world.location()) - clipping_offset);
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -58,47 +36,33 @@ float4 PlanarProbe::reflection_clip_plane_get()
void PlanarProbeModule::init()
{
update_probes_ = !probes_.is_empty();
/* This triggers the compilation of clipped shader only if we can detect lightprobe planes. */
if (inst_.is_viewport()) {
/* This check needs to happen upfront before sync, so we use the previous sync result. */
update_probes_ = !inst_.light_probes.planar_map_.is_empty();
}
else {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_ = DEG_id_type_any_exists(inst_.depsgraph, ID_LP);
}
do_display_draw_ = false;
}
void PlanarProbeModule::begin_sync()
{
for (PlanarProbe &probe : probes_.values()) {
probe.is_probe_used = false;
}
}
void PlanarProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
{
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
if (light_probe->type != LIGHTPROBE_TYPE_PLANE) {
return;
}
PlanarProbe &probe = find_or_insert(ob_handle);
probe.sync(float4x4(ob->object_to_world),
light_probe->clipsta,
light_probe->distinf,
light_probe->flag & LIGHTPROBE_FLAG_SHOW_DATA);
probe.is_probe_used = true;
}
void PlanarProbeModule::end_sync()
{
probes_.remove_if([](const PlanarProbes::Item &item) { return !item.value.is_probe_used; });
/* When first planar probes are enabled it can happen that the first sample is off. */
if (!update_probes_ && !probes_.is_empty()) {
if (!update_probes_ && !inst_.light_probes.planar_map_.is_empty()) {
DRW_viewport_request_redraw();
}
}
void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_extent)
{
GBuffer &gbuf = instance_.gbuffer;
GBuffer &gbuf = inst_.gbuffer;
const int64_t num_probes = probes_.size();
const int64_t num_probes = inst_.light_probes.planar_map_.size();
/* TODO resolution percentage. */
int2 extent = main_view_extent;
@@ -119,12 +83,12 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
int resource_index = 0;
int display_index = 0;
for (PlanarProbe &probe : probes_.values()) {
if (resource_index == PLANAR_PROBES_MAX) {
for (PlanarProbe &probe : inst_.light_probes.planar_map_.values()) {
if (resource_index == PLANAR_PROBE_MAX) {
break;
}
PlanarProbeResources &res = resources_[resource_index];
PlanarResources &res = resources_[resource_index];
/* TODO Cull out of view planars. */
@@ -137,8 +101,8 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
world_clip_buf_.push_update();
gbuf.acquire(extent,
instance_.pipelines.deferred.closure_layer_count(),
instance_.pipelines.deferred.normal_layer_count());
inst_.pipelines.deferred.closure_layer_count(),
inst_.pipelines.deferred.normal_layer_count());
res.combined_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(radiance_tx_, resource_index));
@@ -150,7 +114,7 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(1), 0));
instance_.pipelines.planar.render(
inst_.pipelines.planar.render(
res.view, depth_tx_.layer_view(resource_index), res.gbuffer_fb, res.combined_fb, extent);
if (do_display_draw_ && probe.viewport_display) {
@@ -162,7 +126,7 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
gbuf.release();
if (resource_index < PLANAR_PROBES_MAX) {
if (resource_index < PLANAR_PROBE_MAX) {
/* Tag the end of the array. */
probe_planar_buf_[resource_index].layer_id = -1;
}
@@ -175,12 +139,6 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
}
}
PlanarProbe &PlanarProbeModule::find_or_insert(ObjectHandle &ob_handle)
{
PlanarProbe &planar_probe = probes_.lookup_or_add_default(ob_handle.object_key.hash());
return planar_probe;
}
void PlanarProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!do_display_draw_) {
@@ -191,12 +149,12 @@ void PlanarProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_PLANAR));
viewport_display_ps_.shader_set(inst_.shaders.static_shader_get(DISPLAY_PROBE_PLANAR));
bind_resources(viewport_display_ps_);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
instance_.manager->submit(viewport_display_ps_, view);
inst_.manager->submit(viewport_display_ps_, view);
}
/** \} */

View File

@@ -22,91 +22,43 @@ class Instance;
class HiZBuffer;
struct ObjectHandle;
/* -------------------------------------------------------------------- */
/** \name Planar Probe
* \{ */
struct PlanarProbe : ProbePlanarData {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
/* Pruning flag. */
bool is_probe_used = false;
/** Display a debug plane in the viewport. */
bool viewport_display = false;
public:
void sync(const float4x4 &world_to_object,
float clipping_offset,
float influence_distance,
bool viewport_display);
/**
* Update the ProbePlanarData part of the struct.
* `view` is the view we want to render this probe with.
*/
void set_view(const draw::View &view, int layer_id);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
* \note Only works after `set_view` was called.
*/
float4 reflection_clip_plane_get();
private:
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get();
};
struct PlanarProbeResources : NonCopyable {
Framebuffer combined_fb = {"planar.combined_fb"};
Framebuffer gbuffer_fb = {"planar.gbuffer_fb"};
draw::View view = {"planar.view"};
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
class PlanarProbeModule {
using PlanarProbes = Map<uint64_t, PlanarProbe>;
friend class Instance;
friend class HiZBuffer;
friend class PlanarProbePipeline;
private:
Instance &instance_;
Instance &inst_;
PlanarProbes probes_;
std::array<PlanarProbeResources, PLANAR_PROBES_MAX> resources_;
struct PlanarResources : NonCopyable {
Framebuffer combined_fb = {"planar.combined_fb"};
Framebuffer gbuffer_fb = {"planar.gbuffer_fb"};
draw::View view = {"planar.view"};
};
std::array<PlanarResources, PLANAR_PROBE_MAX> resources_;
Texture radiance_tx_ = {"planar.radiance_tx"};
Texture depth_tx_ = {"planar.depth_tx"};
ClipPlaneBuf world_clip_buf_ = {"world_clip_buf"};
ProbePlanarDataBuf probe_planar_buf_ = {"probe_planar_buf"};
PlanarProbeDataBuf probe_planar_buf_ = {"probe_planar_buf"};
bool update_probes_ = false;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
ProbePlanarDisplayDataBuf display_data_buf_;
PlanarProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"PlanarProbeModule.Viewport Display"};
public:
PlanarProbeModule(Instance &instance) : instance_(instance) {}
PlanarProbeModule(Instance &instance) : inst_(instance) {}
void init();
void begin_sync();
void sync_object(Object *ob, ObjectHandle &ob_handle);
void end_sync();
void set_view(const draw::View &main_view, int2 main_view_extent);
@@ -126,13 +78,6 @@ class PlanarProbeModule {
{
return update_probes_;
}
private:
PlanarProbe &find_or_insert(ObjectHandle &ob_handle);
friend class Instance;
friend class HiZBuffer;
friend class PlanarProbePipeline;
};
/** \} */

View File

@@ -106,8 +106,8 @@ void RayTraceModule::sync()
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.planar_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
/* TODO(@fclem): Use another dispatch with only tiles that touches planar captures. */
pass.dispatch(raytrace_tracing_dispatch_buf_);
@@ -134,8 +134,8 @@ void RayTraceModule::sync()
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@@ -150,8 +150,8 @@ void RayTraceModule::sync()
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.sampling);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@@ -269,8 +269,8 @@ void RayTraceModule::sync()
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.dispatch(horizon_denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}

View File

@@ -2,512 +2,214 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_bit_vector.hh"
#include "eevee_instance.hh"
#include "eevee_reflection_probes.hh"
#include <iostream>
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name ProbeLocationFinder
* \{ */
/**
* Utility class to find a location in the probes_tx_ that can be used to store a new probe in
* a specified subdivision level.
*/
class ProbeLocationFinder {
BitVector<> taken_spots_;
int probes_per_dimension_;
int probes_per_layer_;
int subdivision_level_;
public:
ProbeLocationFinder(int num_layers, int subdivision_level)
{
subdivision_level_ = subdivision_level;
probes_per_dimension_ = 1 << subdivision_level_;
probes_per_layer_ = probes_per_dimension_ * probes_per_dimension_;
int num_spots = num_layers * probes_per_layer_;
taken_spots_.resize(num_spots, false);
}
void print_debug() const
{
std::ostream &os = std::cout;
int layer = 0;
int row = 0;
int column = 0;
os << "subdivision " << subdivision_level_ << "\n";
for (bool spot_taken : taken_spots_) {
if (row == 0 && column == 0) {
os << "layer " << layer << "\n";
}
os << (spot_taken ? '1' : '0');
column++;
if (column == probes_per_dimension_) {
os << "\n";
column = 0;
row++;
}
if (row == probes_per_dimension_) {
row = 0;
layer++;
}
}
}
/**
* Mark space to be occupied by the given probe_data.
*
* The input probe data can be stored in a different subdivision level and should be converted to
* the subdivision level what we are looking for.
*/
void mark_space_used(const ReflectionProbeAtlasCoordinate &coord)
{
const int shift_right = max_ii(coord.layer_subdivision - subdivision_level_, 0);
const int shift_left = max_ii(subdivision_level_ - coord.layer_subdivision, 0);
const int spots_per_dimension = 1 << shift_left;
const int probes_per_dimension_in_probe_data = 1 << coord.layer_subdivision;
const int2 pos_in_probe_data = int2(coord.area_index % probes_per_dimension_in_probe_data,
coord.area_index / probes_per_dimension_in_probe_data);
const int2 pos_in_location_finder = int2((pos_in_probe_data.x >> shift_right) << shift_left,
(pos_in_probe_data.y >> shift_right) << shift_left);
const int layer_offset = coord.layer * probes_per_layer_;
for (const int y : IndexRange(spots_per_dimension)) {
for (const int x : IndexRange(spots_per_dimension)) {
const int2 pos = pos_in_location_finder + int2(x, y);
const int area_index = pos.x + pos.y * probes_per_dimension_;
taken_spots_[area_index + layer_offset].set();
}
}
}
/**
* Get the first free spot.
*
* .x contains the layer the first free spot was detected.
* .y contains the area_index to use.
*
* Asserts when no free spot is found. ProbeLocationFinder should always be initialized with an
* additional layer to make sure that there is always a free spot.
*/
ReflectionProbeAtlasCoordinate first_free_spot() const
{
ReflectionProbeAtlasCoordinate result;
result.layer_subdivision = subdivision_level_;
for (int index : taken_spots_.index_range()) {
if (!taken_spots_[index]) {
result.layer = index / probes_per_layer_;
result.area_index = index % probes_per_layer_;
return result;
}
}
BLI_assert_unreachable();
return result;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
eLightProbeResolution ReflectionProbeModule::reflection_probe_resolution() const
{
switch (instance_.scene->eevee.gi_cubemap_resolution) {
case 64:
return LIGHT_PROBE_RESOLUTION_64;
case 128:
return LIGHT_PROBE_RESOLUTION_128;
case 256:
return LIGHT_PROBE_RESOLUTION_256;
case 512:
return LIGHT_PROBE_RESOLUTION_512;
case 1024:
return LIGHT_PROBE_RESOLUTION_1024;
default:
return LIGHT_PROBE_RESOLUTION_2048;
}
return LIGHT_PROBE_RESOLUTION_2048;
}
int ReflectionProbeModule::probe_render_extent() const
int SphereProbeModule::probe_render_extent() const
{
return instance_.scene->eevee.gi_cubemap_resolution / 2;
}
void ReflectionProbeModule::init()
void SphereProbeModule::init()
{
if (!is_initialized) {
is_initialized = true;
/* Initialize the world probe. */
ReflectionProbe world_probe = {};
world_probe.type = ReflectionProbe::Type::WORLD;
world_probe.is_probe_used = true;
world_probe.do_render = true;
world_probe.clipping_distances = float2(1.0f, 10.0f);
world_probe.world_to_probe_transposed = float3x4::identity();
world_probe.influence_shape = SHAPE_ELIPSOID;
world_probe.parallax_shape = SHAPE_ELIPSOID;
/* Full influence. */
world_probe.influence_scale = 0.0f;
world_probe.influence_bias = 1.0f;
world_probe.parallax_distance = 1e10f;
probes_.add(world_object_key_, world_probe);
probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
1,
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr,
REFLECTION_PROBE_MIPMAP_LEVELS);
GPU_texture_mipmap_mode(probes_tx_, true, true);
probes_tx_.clear(float4(0.0f));
if (!instance_.is_viewport()) {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_next_sample_ = DEG_id_type_any_exists(instance_.depsgraph, ID_LP);
}
do_display_draw_ = false;
}
void SphereProbeModule::begin_sync()
{
update_probes_this_sample_ = update_probes_next_sample_;
LightProbeModule &light_probes = instance_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
{
const RaytraceEEVEE &options = instance_.scene->eevee.ray_tracing_options;
float probe_brightness_clamp = (options.sample_clamp > 0.0) ? options.sample_clamp : 1e20;
PassSimple &pass = remap_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP));
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_REMAP));
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_texture("atlas_tx", &probes_tx_);
pass.bind_image("atlas_img", &probes_tx_);
pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_sampling_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
pass.push_constant("mip_level", &probe_mip_level_);
pass.push_constant("probe_brightness_clamp", probe_brightness_clamp);
pass.dispatch(&dispatch_probe_pack_);
}
{
PassSimple &pass = update_irradiance_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_UPDATE_IRRADIANCE));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_sampling_coord_));
pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_);
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_UPDATE_IRRADIANCE));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
pass.bind_image("irradiance_atlas_img", &instance_.volume_probes.irradiance_atlas_tx_);
pass.bind_texture("reflection_probes_tx", &probes_tx_);
pass.dispatch(int2(1, 1));
}
do_display_draw_ = false;
}
void ReflectionProbeModule::begin_sync()
{
for (ReflectionProbe &reflection_probe : probes_.values()) {
if (reflection_probe.type == ReflectionProbe::Type::PROBE) {
reflection_probe.is_probe_used = false;
}
}
update_probes_this_sample_ = false;
if (update_probes_next_sample_) {
update_probes_this_sample_ = true;
}
{
PassSimple &pass = select_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_SELECT));
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SELECT));
pass.push_constant("reflection_probe_count", &reflection_probe_count_);
pass.bind_ssbo("reflection_probe_buf", &data_buf_);
instance_.irradiance_cache.bind_resources(pass);
instance_.volume_probes.bind_resources(pass);
instance_.sampling.bind_resources(pass);
pass.dispatch(&dispatch_probe_select_);
pass.barrier(GPU_BARRIER_UNIFORM);
}
}
int ReflectionProbeModule::needed_layers_get() const
bool SphereProbeModule::ensure_atlas()
{
int max_layer = 0;
for (const ReflectionProbe &probe : probes_.values()) {
max_layer = max_ii(max_layer, probe.atlas_coord.layer);
/* Make sure the atlas is always initialized even if there is nothing to render to it to fullfil
* the resource bindings. */
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ;
if (probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
instance_.light_probes.sphere_layer_count(),
usage,
nullptr,
SPHERE_PROBE_MIPMAP_LEVELS))
{
/* TODO(fclem): Clearing means that we need to render all probes again.
* If existing data exists, copy it using `CopyImageSubData`. */
probes_tx_.clear(float4(0.0f));
GPU_texture_mipmap_mode(probes_tx_, true, true);
/* Avoid undefined pixel data. Update all mips. */
GPU_texture_update_mipmap_chain(probes_tx_);
return true;
}
return max_layer + 1;
return false;
}
static int layer_subdivision_for(const int max_resolution,
const eLightProbeResolution probe_resolution)
void SphereProbeModule::end_sync()
{
int i_probe_resolution = int(probe_resolution);
return max_ii(int(log2(max_resolution)) - i_probe_resolution, 0);
}
void ReflectionProbeModule::sync_world(::World *world)
{
ReflectionProbe &probe = probes_.lookup(world_object_key_);
eLightProbeResolution resolution = static_cast<eLightProbeResolution>(world->probe_resolution);
int layer_subdivision = layer_subdivision_for(max_resolution_, resolution);
if (layer_subdivision != probe.atlas_coord.layer_subdivision) {
probe.atlas_coord = find_empty_atlas_region(layer_subdivision);
do_world_update_set(true);
}
world_sampling_coord_ = probe.atlas_coord.as_sampling_coord(atlas_extent());
}
void ReflectionProbeModule::sync_world_lookdev()
{
ReflectionProbe &probe = probes_.lookup(world_object_key_);
const eLightProbeResolution resolution = reflection_probe_resolution();
int layer_subdivision = layer_subdivision_for(max_resolution_, resolution);
if (layer_subdivision != probe.atlas_coord.layer_subdivision) {
probe.atlas_coord = find_empty_atlas_region(layer_subdivision);
}
world_sampling_coord_ = probe.atlas_coord.as_sampling_coord(atlas_extent());
do_world_update_set(true);
}
void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
{
const ::LightProbe &light_probe = *(::LightProbe *)ob->data;
if (light_probe.type != LIGHTPROBE_TYPE_SPHERE) {
return;
}
ReflectionProbe &probe = probes_.lookup_or_add_cb(ob_handle.object_key.hash(), [&]() {
ReflectionProbe probe = {};
probe.do_render = true;
probe.type = ReflectionProbe::Type::PROBE;
return probe;
});
probe.do_render |= (ob_handle.recalc != 0);
probe.is_probe_used = true;
const bool probe_sync_active = instance_.do_reflection_probe_sync();
if (!probe_sync_active && probe.do_render) {
update_probes_next_sample_ = true;
}
/* Only update data when rerendering the probes to reduce flickering. */
if (!probe_sync_active) {
return;
}
probe.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
int subdivision = layer_subdivision_for(max_resolution_, reflection_probe_resolution());
if (probe.atlas_coord.layer_subdivision != subdivision) {
probe.atlas_coord = find_empty_atlas_region(subdivision);
}
bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
float parallax_distance = use_custom_parallax ?
max_ff(light_probe.distpar, light_probe.distinf) :
light_probe.distinf;
float influence_distance = light_probe.distinf;
float influence_falloff = light_probe.falloff;
probe.influence_shape = (light_probe.attenuation_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID :
SHAPE_ELIPSOID;
probe.parallax_shape = (light_probe.parallax_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID :
SHAPE_ELIPSOID;
float4x4 object_to_world = math::scale(float4x4(ob->object_to_world),
float3(influence_distance));
probe.location = object_to_world.location();
probe.volume = math::abs(math::determinant(object_to_world));
probe.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
probe.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
probe.influence_bias = probe.influence_scale;
probe.parallax_distance = parallax_distance / influence_distance;
probe.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
probe.viewport_display_size = light_probe.data_display_size;
}
ReflectionProbeAtlasCoordinate ReflectionProbeModule::find_empty_atlas_region(
int subdivision_level) const
{
ProbeLocationFinder location_finder(needed_layers_get() + 1, subdivision_level);
for (const ReflectionProbe &probe : probes_.values()) {
if (probe.atlas_coord.layer != -1) {
location_finder.mark_space_used(probe.atlas_coord);
}
}
return location_finder.first_free_spot();
}
void ReflectionProbeModule::end_sync()
{
const bool probes_removed = remove_unused_probes();
const bool world_updated = do_world_update_get();
const bool only_world = has_only_world_probe();
const int number_layers_needed = needed_layers_get();
const int current_layers = probes_tx_.depth();
const bool resize_layers = current_layers < number_layers_needed;
const bool rerender_all_probes = resize_layers || world_updated;
if (rerender_all_probes) {
for (ReflectionProbe &probe : probes_.values()) {
const bool world_updated = instance_.light_probes.world_sphere_.do_render;
const bool atlas_resized = ensure_atlas();
/* Detect if we need to render probe objects. */
update_probes_next_sample_ = false;
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (atlas_resized || world_updated) {
/* Last minute tagging. */
probe.do_render = true;
}
}
const bool do_update = instance_.do_reflection_probe_sync() || (only_world && world_updated);
if (!do_update) {
/* World has changed this sample, but probe update isn't initialized this sample. */
if (world_updated && !only_world) {
if (probe.do_render) {
/* Tag the next redraw to warm up the probe pipeline.
* Keep doing this until there is no update.
* This avoids stuttering when moving a lightprobe. */
update_probes_next_sample_ = true;
}
if (update_probes_next_sample_ && !update_probes_this_sample_) {
DRW_viewport_request_redraw();
}
if (!update_probes_next_sample_ && probes_removed) {
data_buf_.push_update();
}
return;
}
if (resize_layers) {
probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
number_layers_needed,
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr,
9999);
GPU_texture_mipmap_mode(probes_tx_, true, true);
probes_tx_.clear(float4(0.0f));
/* If we cannot render probes this redraw make sure we request another redraw. */
if (update_probes_next_sample_ && (instance_.do_reflection_probe_sync() == false)) {
DRW_viewport_request_redraw();
}
}
/* Check reset probe updating as we will rendering probes. */
if (update_probes_this_sample_ || only_world) {
update_probes_next_sample_ = false;
void SphereProbeModule::ensure_cubemap_render_target(int resolution)
{
if (cubemap_tx_.ensure_cube(
GPU_RGBA16F, resolution, GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ))
{
GPU_texture_mipmap_mode(cubemap_tx_, false, true);
}
data_buf_.push_update();
/* TODO(fclem): dealocate it. */
}
bool ReflectionProbeModule::remove_unused_probes()
SphereProbeModule::UpdateInfo SphereProbeModule::update_info_from_probe(const SphereProbe &probe)
{
const int64_t removed_count = probes_.remove_if(
[](const ReflectionProbes::Item &item) { return !item.value.is_probe_used; });
return removed_count > 0;
}
bool ReflectionProbeModule::do_world_update_get() const
{
const ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
return world_probe.do_render;
}
void ReflectionProbeModule::do_world_update_set(bool value)
{
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
world_probe.do_render = value;
do_world_update_irradiance_set(value);
}
void ReflectionProbeModule::do_world_update_irradiance_set(bool value)
{
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
world_probe.do_world_irradiance_update = value;
}
bool ReflectionProbeModule::has_only_world_probe() const
{
return probes_.size() == 1;
}
std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
const ReflectionProbe::Type probe_type)
{
const bool do_probe_sync = instance_.do_reflection_probe_sync();
const bool only_world = has_only_world_probe();
const int max_shift = int(log2(max_resolution_));
for (ReflectionProbe &probe : probes_.values()) {
if (!probe.do_render && !probe.do_world_irradiance_update) {
continue;
}
if (probe.type != probe_type) {
continue;
}
/* Do not update this probe during this sample. */
if (probe.type == ReflectionProbe::Type::WORLD && !only_world && !do_probe_sync) {
continue;
}
if (probe.type == ReflectionProbe::Type::PROBE && !do_probe_sync) {
continue;
}
ReflectionProbeUpdateInfo info = {};
info.probe_type = probe.type;
info.atlas_coord = probe.atlas_coord;
info.resolution = 1 << (max_shift - probe.atlas_coord.layer_subdivision - 1);
info.clipping_distances = probe.clipping_distances;
info.probe_pos = probe.location;
info.do_render = probe.do_render;
info.do_world_irradiance_update = probe.do_world_irradiance_update;
SphereProbeModule::UpdateInfo info = {};
info.atlas_coord = probe.atlas_coord;
info.resolution = 1 << (max_shift - probe.atlas_coord.subdivision_lvl - 1);
info.clipping_distances = probe.clipping_distances;
info.probe_pos = probe.location;
info.do_render = probe.do_render;
info.do_world_irradiance_update = false;
return info;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::world_update_info_pop()
{
SphereProbe &world_probe = instance_.light_probes.world_sphere_;
if (!world_probe.do_render && !do_world_irradiance_update) {
return std::nullopt;
}
SphereProbeModule::UpdateInfo info = update_info_from_probe(world_probe);
info.do_world_irradiance_update = do_world_irradiance_update;
world_probe.do_render = false;
do_world_irradiance_update = false;
ensure_cubemap_render_target(info.resolution);
return info;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::probe_update_info_pop()
{
if (!instance_.do_reflection_probe_sync()) {
/* Do not update probes during this sample as we did not sync the draw::Passes. */
return std::nullopt;
}
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (!probe.do_render) {
continue;
}
SphereProbeModule::UpdateInfo info = update_info_from_probe(probe);
probe.do_render = false;
probe.do_world_irradiance_update = false;
if (cubemap_tx_.ensure_cube(GPU_RGBA16F,
info.resolution,
GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ))
{
GPU_texture_mipmap_mode(cubemap_tx_, false, true);
}
probe.use_for_render = true;
ensure_cubemap_render_target(info.resolution);
return info;
}
return std::nullopt;
}
void ReflectionProbeModule::remap_to_octahedral_projection(
const ReflectionProbeAtlasCoordinate &atlas_coord)
void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord)
{
int resolution = max_resolution_ >> atlas_coord.layer_subdivision;
int resolution = max_resolution_ >> atlas_coord.subdivision_lvl;
/* Update shader parameters that change per dispatch. */
probe_sampling_coord_ = atlas_coord.as_sampling_coord(atlas_extent());
probe_write_coord_ = atlas_coord.as_write_coord(atlas_extent(), 0);
probe_mip_level_ = atlas_coord.layer_subdivision;
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, REFLECTION_PROBE_GROUP_SIZE)), 1);
probe_sampling_coord_ = atlas_coord.as_sampling_coord(max_resolution_);
probe_write_coord_ = atlas_coord.as_write_coord(max_resolution_, 0);
probe_mip_level_ = atlas_coord.subdivision_lvl;
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, SPHERE_PROBE_GROUP_SIZE)), 1);
instance_.manager->submit(remap_ps_);
}
void ReflectionProbeModule::update_world_irradiance()
void SphereProbeModule::update_world_irradiance()
{
instance_.manager->submit(update_irradiance_ps_);
}
void ReflectionProbeModule::update_probes_texture_mipmaps()
void SphereProbeModule::update_probes_texture_mipmaps()
{
GPU_texture_update_mipmap_chain(probes_tx_);
}
void ReflectionProbeModule::set_view(View & /*view*/)
void SphereProbeModule::set_view(View & /*view*/)
{
Vector<ReflectionProbe *> probe_active;
for (auto &probe : probes_.values()) {
Vector<SphereProbe *> probe_active;
for (auto &probe : instance_.light_probes.sphere_map_.values()) {
/* Last slot is reserved for the world probe. */
if (reflection_probe_count_ >= REFLECTION_PROBES_MAX - 1) {
if (reflection_probe_count_ >= SPHERE_PROBE_MAX - 1) {
break;
}
probe.prepare_for_upload(atlas_extent());
/* World is always considered active and added last. */
if (probe.type == ReflectionProbe::Type::WORLD) {
if (!probe.use_for_render) {
continue;
}
/* TODO(fclem): Culling. */
@@ -515,32 +217,29 @@ void ReflectionProbeModule::set_view(View & /*view*/)
}
/* Stable sorting of probes. */
std::sort(probe_active.begin(),
probe_active.end(),
[](const ReflectionProbe *a, const ReflectionProbe *b) {
if (a->volume != b->volume) {
/* Smallest first. */
return a->volume < b->volume;
}
/* 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. */
float3 _a = a->location;
float3 _b = b->location;
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
std::sort(
probe_active.begin(), probe_active.end(), [](const SphereProbe *a, const SphereProbe *b) {
if (a->volume != b->volume) {
/* Smallest first. */
return a->volume < b->volume;
}
/* 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. */
float3 _a = a->location;
float3 _b = b->location;
if (_a.x != _b.x) {
return _a.x < _b.x;
}
if (_a.y != _b.y) {
return _a.y < _b.y;
}
if (_a.z != _b.z) {
return _a.z < _b.z;
}
/* Fallback to memory address, since there's no good alternative. */
return a < b;
});
/* Push all sorted data to the UBO. */
int probe_id = 0;
@@ -548,9 +247,9 @@ void ReflectionProbeModule::set_view(View & /*view*/)
data_buf_[probe_id++] = *probe;
}
/* Add world probe at the end. */
data_buf_[probe_id++] = probes_.lookup(world_object_key_);
data_buf_[probe_id++] = instance_.light_probes.world_sphere_;
/* Tag the end of the array. */
if (probe_id < REFLECTION_PROBES_MAX) {
if (probe_id < SPHERE_PROBE_MAX) {
data_buf_[probe_id].atlas_coord.layer = -1;
}
data_buf_.push_update();
@@ -574,16 +273,11 @@ void ReflectionProbeModule::set_view(View & /*view*/)
/* Add one for world probe. */
reflection_probe_count_ = probe_active.size() + 1;
dispatch_probe_select_.x = divide_ceil_u(reflection_probe_count_,
REFLECTION_PROBE_SELECT_GROUP_SIZE);
SPHERE_PROBE_SELECT_GROUP_SIZE);
instance_.manager->submit(select_ps_);
}
ReflectionProbeAtlasCoordinate ReflectionProbeModule::world_atlas_coord_get() const
{
return probes_.lookup(world_object_key_).atlas_coord;
}
void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
void SphereProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!do_display_draw_) {
return;
@@ -594,7 +288,7 @@ void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_REFLECTION));
bind_resources(viewport_display_ps_);
viewport_display_ps_.bind_resources(*this);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
@@ -603,37 +297,4 @@ void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging
*
* \{ */
void ReflectionProbeModule::debug_print() const
{
std::ostream &os = std::cout;
for (const ReflectionProbe &probe : probes_.values()) {
switch (probe.type) {
case ReflectionProbe::Type::WORLD: {
os << "WORLD";
os << " do_render: " << probe.do_render;
os << "\n";
break;
}
case ReflectionProbe::Type::PROBE: {
os << "PROBE";
os << " do_render: " << probe.do_render;
os << " is_used: " << probe.is_probe_used;
os << "\n";
break;
}
}
os << " - layer: " << probe.atlas_coord.layer;
os << " subdivision: " << probe.atlas_coord.layer_subdivision;
os << " area: " << probe.atlas_coord.area_index;
os << "\n";
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -8,6 +8,7 @@
#pragma once
#include "eevee_lightprobe.hh"
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
@@ -22,144 +23,17 @@ class Instance;
struct ObjectHandle;
struct WorldHandle;
class CaptureView;
struct ReflectionProbeUpdateInfo;
/* -------------------------------------------------------------------- */
/** \name Reflection Probe
* \{ */
struct ReflectionProbeAtlasCoordinate {
/** On which layer of the texture array is this reflection probe stored. */
int layer = -1;
/**
* Subdivision of the layer. 0 = no subdivision and resolution would be
* ReflectionProbeModule::MAX_RESOLUTION.
*/
int layer_subdivision = -1;
/**
* Which area of the subdivided layer is the reflection probe located.
*
* A layer has (2^layer_subdivision)^2 areas.
*/
int area_index = -1;
/* Return the area extent in pixel. */
int area_extent(int atlas_extent) const
{
return atlas_extent >> layer_subdivision;
}
/* Coordinate of the area in [0..area_count_per_dimension[ range. */
int2 area_location() const
{
const int area_count_per_dimension = 1 << layer_subdivision;
return int2(area_index % area_count_per_dimension, area_index / area_count_per_dimension);
}
/* Coordinate of the bottom left corner of the area in [0..atlas_extent[ range. */
int2 area_offset(int atlas_extent) const
{
return area_location() * area_extent(atlas_extent);
}
ReflectionProbeCoordinate as_sampling_coord(int atlas_extent) const
{
/**
* We want to cover the last mip exactly at the pixel center to reduce padding texels and
* interpolation artifacts.
* This is a diagram of a 2px^2 map with `c` being the texels corners and `x` the pixels
* centers.
*
* c-------c-------c
* | | |
* | x | x | <
* | | | |
* c-------c-------c | sampling area
* | | | |
* | x | x | <
* | | |
* c-------c-------c
* ^-------^
* sampling area
*/
/* First level only need half a pixel of padding around the sampling area. */
const int mip_max_lvl_padding = 1;
const int mip_min_lvl_padding = mip_max_lvl_padding << REFLECTION_PROBE_MIPMAP_LEVELS;
/* Extent and offset in mip 0 texels. */
const int sampling_area_extent = area_extent(atlas_extent) - mip_min_lvl_padding;
const int2 sampling_area_offset = area_offset(atlas_extent) + mip_min_lvl_padding / 2;
/* Convert to atlas UVs. */
ReflectionProbeCoordinate coord;
coord.scale = sampling_area_extent / float(atlas_extent);
coord.offset = float2(sampling_area_offset) / float(atlas_extent);
coord.layer = layer;
return coord;
}
ReflectionProbeWriteCoordinate as_write_coord(int atlas_extent, int mip_lvl) const
{
ReflectionProbeWriteCoordinate coord;
coord.extent = atlas_extent >> (layer_subdivision + mip_lvl);
coord.offset = (area_location() * coord.extent) >> mip_lvl;
coord.layer = layer;
return coord;
}
};
struct ReflectionProbe : ReflectionProbeData {
public:
enum class Type {
WORLD,
PROBE,
} type;
/* Used to sort the probes by priority. */
float volume;
/* Should the area in the probes_tx_ be updated? */
bool do_render = false;
bool do_world_irradiance_update = false;
/**
* Probes that aren't used during a draw can be cleared.
*
* Only valid when type == Type::Probe.
*/
bool is_probe_used = false;
/**
* Far and near clipping distances for rendering
*/
float2 clipping_distances;
/** Display debug spheres in the viewport. */
bool viewport_display;
float viewport_display_size;
ReflectionProbeAtlasCoordinate atlas_coord;
void prepare_for_upload(int atlas_extent)
{
/* Compute LOD factor. */
const int probe_resolution = atlas_coord.area_extent(atlas_extent);
const float bias = 0.0;
const float lod_factor = bias + 0.5 * log2f(square_i(probe_resolution));
this->lod_factor = lod_factor;
/* Compute sampling offset and scale. */
static_cast<ReflectionProbeData *>(this)->atlas_coord = atlas_coord.as_sampling_coord(
atlas_extent);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
class ReflectionProbeModule {
using ReflectionProbes = Map<uint64_t, ReflectionProbe>;
class SphereProbeModule {
friend LightProbeModule;
/* Capture View requires access to the cube-maps texture for frame-buffer configuration. */
friend class CaptureView;
/* Instance requires access to #update_probes_this_sample_ */
friend class Instance;
private:
/**
@@ -169,17 +43,15 @@ class ReflectionProbeModule {
*/
static constexpr int max_resolution_ = 2048;
static constexpr uint64_t world_object_key_ = 0;
bool is_initialized = false;
Instance &instance_;
ReflectionProbeDataBuf data_buf_;
ReflectionProbes probes_;
SphereProbeDataBuf data_buf_;
/** Probes texture stored in octahedral mapping. */
Texture probes_tx_ = {"Probes"};
/** Copy the rendered cube-map to the atlas texture. */
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
/** Extract irradiance information from the world. */
PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"};
PassSimple select_ps_ = {"Probe.Select"};
@@ -197,52 +69,48 @@ class ReflectionProbeModule {
/** Mip level being sampled for remapping. */
int probe_mip_level_ = 0;
/** Updated Probe coordinates in the atlas. */
ReflectionProbeCoordinate probe_sampling_coord_;
ReflectionProbeWriteCoordinate probe_write_coord_;
SphereProbeUvArea probe_sampling_coord_;
SphereProbePixelArea probe_write_coord_;
/** World coordinates in the atlas. */
ReflectionProbeCoordinate world_sampling_coord_;
SphereProbeUvArea world_sampling_coord_;
/** Number of the probe to process in the select phase. */
int reflection_probe_count_ = 0;
/**
* True if the next redraw will trigger a light-probe sphere update.
* As syncing the draw passes for rendering has a significant overhead,
* we only trigger this sync path if we detect updates. But we only know
* this after `end_sync` which is too late to sync objects for light-probe
* rendering. So we tag the next redraw (or sample) to do the sync.
*/
bool update_probes_next_sample_ = false;
/** True if the this redraw will trigger a light-probe sphere update. */
bool update_probes_this_sample_ = false;
/** Compute world irradiance coefficient and store them into the volume probe atlas. */
bool do_world_irradiance_update = true;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
ReflectionProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"ReflectionProbeModule.Viewport Display"};
SphereProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"ProbeSphereModule.Viewport Display"};
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
SphereProbeModule(Instance &instance) : instance_(instance){};
void init();
void begin_sync();
void sync_world(::World *world);
void sync_world_lookdev();
void sync_object(Object *ob, ObjectHandle &ob_handle);
void end_sync();
void viewport_draw(View &view, GPUFrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(REFLECTION_PROBE_TEX_SLOT, &probes_tx_);
pass.bind_ubo(REFLECTION_PROBE_BUF_SLOT, &data_buf_);
pass.bind_texture(SPHERE_PROBE_TEX_SLOT, &probes_tx_);
pass.bind_ubo(SPHERE_PROBE_BUF_SLOT, &data_buf_);
}
bool do_world_update_get() const;
void do_world_update_set(bool value);
void do_world_update_irradiance_set(bool value);
void set_view(View &view);
void debug_print() const;
int atlas_extent() const
{
return probes_tx_.width();
}
/**
* Get the resolution of a single cube-map side when rendering probes.
*
@@ -250,59 +118,57 @@ class ReflectionProbeModule {
*/
int probe_render_extent() const;
ReflectionProbeAtlasCoordinate world_atlas_coord_get() const;
void tag_world_irradiance_for_update()
{
do_world_irradiance_update = true;
}
private:
/** Get the number of layers that is needed to store probes. */
int needed_layers_get() const;
bool remove_unused_probes();
/* Return the subdivision level for the requested probe resolution.
* Result is safely clamped to max resolution. */
int subdivision_level_get(const eLightProbeResolution probe_resolution)
{
return max_ii(int(log2(max_resolution_)) - int(probe_resolution), 0);
}
/**
* Create a reflection probe data element that points to an empty spot in the cubemap that can
* hold a texture with the given subdivision_level.
* Ensure atlas texture is the right size.
* Returns true if the texture has been cleared and all probes needs to be rendered again.
*/
ReflectionProbeAtlasCoordinate find_empty_atlas_region(int subdivision_level) const;
bool ensure_atlas();
/**
* Ensure the cube-map target texture for rendering the probe is allocated.
*/
void ensure_cubemap_render_target(int resolution);
struct UpdateInfo {
float3 probe_pos;
/** Resolution of the cube-map to be rendered. */
int resolution;
float2 clipping_distances;
SphereProbeAtlasCoord atlas_coord;
bool do_render;
bool do_world_irradiance_update;
};
UpdateInfo update_info_from_probe(const SphereProbe &probe);
/**
* Pop the next reflection probe that requires to be updated.
*/
std::optional<ReflectionProbeUpdateInfo> update_info_pop(ReflectionProbe::Type probe_type);
std::optional<UpdateInfo> world_update_info_pop();
std::optional<UpdateInfo> probe_update_info_pop();
void remap_to_octahedral_projection(const ReflectionProbeAtlasCoordinate &atlas_coord);
/**
* Internal processing passes.
*/
void remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord);
void update_probes_texture_mipmaps();
void update_world_irradiance();
bool has_only_world_probe() const;
eLightProbeResolution reflection_probe_resolution() const;
/* Capture View requires access to the cube-maps texture for frame-buffer configuration. */
friend class CaptureView;
/* Instance requires access to #update_probes_this_sample_ */
friend class Instance;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Update Info
* \{ */
struct ReflectionProbeUpdateInfo {
float3 probe_pos;
ReflectionProbe::Type probe_type;
/**
* Resolution of the cubemap to be rendered.
*/
int resolution;
float2 clipping_distances;
ReflectionProbeAtlasCoordinate atlas_coord;
bool do_render;
bool do_world_irradiance_update;
};
/** \} */

View File

@@ -214,11 +214,11 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_lightprobe_irradiance_ray";
case LIGHTPROBE_IRRADIANCE_LOAD:
return "eevee_lightprobe_irradiance_load";
case REFLECTION_PROBE_REMAP:
case SPHERE_PROBE_REMAP:
return "eevee_reflection_probe_remap";
case REFLECTION_PROBE_UPDATE_IRRADIANCE:
case SPHERE_PROBE_UPDATE_IRRADIANCE:
return "eevee_reflection_probe_update_irradiance";
case REFLECTION_PROBE_SELECT:
case SPHERE_PROBE_SELECT:
return "eevee_reflection_probe_select";
case SHADOW_CLIPMAP_CLEAR:
return "eevee_shadow_clipmap_clear";

View File

@@ -104,9 +104,9 @@ enum eShaderType {
RAY_TRACE_PLANAR,
RAY_TRACE_SCREEN,
REFLECTION_PROBE_REMAP,
REFLECTION_PROBE_UPDATE_IRRADIANCE,
REFLECTION_PROBE_SELECT,
SPHERE_PROBE_REMAP,
SPHERE_PROBE_UPDATE_IRRADIANCE,
SPHERE_PROBE_SELECT,
SHADOW_CLIPMAP_CLEAR,
SHADOW_DEBUG,

View File

@@ -1043,7 +1043,8 @@ enum LightProbeShape : uint32_t {
SHAPE_CUBOID = 1u,
};
struct ReflectionProbeCoordinate {
/* Sampling coordinates using UV space. */
struct SphereProbeUvArea {
/* Offset in UV space to the start of the sampling space of the octahedron map. */
float2 offset;
/* Scaling of the squared UV space of the octahedron map. */
@@ -1051,9 +1052,10 @@ struct ReflectionProbeCoordinate {
/* Layer of the atlas where the octahedron map is stored. */
float layer;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeCoordinate, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeUvArea, 16)
struct ReflectionProbeWriteCoordinate {
/* Pixel read/write coordinates using pixel space. */
struct SphereProbePixelArea {
/* Offset in pixel space to the start of the writing space of the octahedron map.
* Note that the writing space is not the same as the sampling space as we have borders. */
int2 offset;
@@ -1062,29 +1064,23 @@ struct ReflectionProbeWriteCoordinate {
/* Layer of the atlas where the octahedron map is stored. */
int layer;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeWriteCoordinate, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbePixelArea, 16)
/** Mapping data to locate a reflection probe in texture. */
struct ReflectionProbeData {
struct SphereProbeData {
/** Transform to probe local position with non-uniform scaling. */
float3x4 world_to_probe_transposed;
packed_float3 location;
float _pad2;
/** Shape of the parallax projection. */
float parallax_distance;
LightProbeShape parallax_shape;
LightProbeShape influence_shape;
float parallax_distance;
/** Influence factor based on the distance to the parallax shape. */
float influence_scale;
float influence_bias;
/** LOD factor for mipmap selection. */
float lod_factor;
float _pad0;
float _pad1;
ReflectionProbeCoordinate atlas_coord;
SphereProbeUvArea atlas_coord;
/**
* Irradiance at the probe location encoded as spherical harmonics.
@@ -1092,21 +1088,21 @@ struct ReflectionProbeData {
*/
ReflectionProbeLowFreqLight low_freq_light;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeData, 16)
/** Viewport Display Pass. */
struct ReflectionProbeDisplayData {
struct SphereProbeDisplayData {
int probe_index;
float display_size;
float _pad0;
float _pad1;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeDisplayData, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeDisplayData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Irradiance Cache
/** \name Volume Probe Cache
* \{ */
struct SurfelRadiance {
@@ -1197,7 +1193,7 @@ struct CaptureInfoData {
bool1 capture_emission;
int _pad0;
/* World light probe atlas coordinate. */
ReflectionProbeCoordinate world_atlas_coord;
SphereProbeUvArea world_atlas_coord;
};
BLI_STATIC_ASSERT_ALIGN(CaptureInfoData, 16)
@@ -1211,7 +1207,7 @@ struct SurfelListInfoData {
};
BLI_STATIC_ASSERT_ALIGN(SurfelListInfoData, 16)
struct IrradianceGridData {
struct VolumeProbeData {
/** 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. */
@@ -1224,7 +1220,7 @@ struct IrradianceGridData {
float facing_bias;
int _pad1;
};
BLI_STATIC_ASSERT_ALIGN(IrradianceGridData, 16)
BLI_STATIC_ASSERT_ALIGN(VolumeProbeData, 16)
struct IrradianceBrick {
/* Offset in pixel to the start of the data inside the atlas texture. */
@@ -1420,7 +1416,7 @@ static inline float3 burley_eval(float3 d, float r)
/** \name Light-probe Planar Data
* \{ */
struct ProbePlanarData {
struct PlanarProbeData {
/** Matrices used to render the planar capture. */
float4x4 viewmat;
float4x4 winmat;
@@ -1431,7 +1427,7 @@ struct ProbePlanarData {
/** Layer in the planar capture textures used by this probe. */
int layer_id;
};
BLI_STATIC_ASSERT_ALIGN(ProbePlanarData, 16)
BLI_STATIC_ASSERT_ALIGN(PlanarProbeData, 16)
struct ClipPlaneData {
/** World space clip plane equation. Used to render planar light-probes. */
@@ -1440,14 +1436,14 @@ struct ClipPlaneData {
BLI_STATIC_ASSERT_ALIGN(ClipPlaneData, 16)
/** Viewport Display Pass. */
struct ProbePlanarDisplayData {
struct PlanarProbeDisplayData {
float4x4 plane_to_world;
int probe_index;
float _pad0;
float _pad1;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(ProbePlanarDisplayData, 16)
BLI_STATIC_ASSERT_ALIGN(PlanarProbeDisplayData, 16)
/** \} */
@@ -1577,7 +1573,7 @@ using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, tru
using DrawIndirectBuf = draw::StorageBuffer<DrawCommand, true>;
using DispatchIndirectBuf = draw::StorageBuffer<DispatchCommand>;
using UniformDataBuf = draw::UniformBuffer<UniformData>;
using IrradianceGridDataBuf = draw::UniformArrayBuffer<IrradianceGridData, IRRADIANCE_GRID_MAX>;
using VolumeProbeDataBuf = draw::UniformArrayBuffer<VolumeProbeData, IRRADIANCE_GRID_MAX>;
using IrradianceBrickBuf = draw::StorageVectorBuffer<IrradianceBrickPacked, 16>;
using LightCullingDataBuf = draw::StorageBuffer<LightCullingData>;
using LightCullingKeyBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
@@ -1589,11 +1585,10 @@ using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
using RayTraceTileBuf = draw::StorageArrayBuffer<uint, 1024, true>;
using SubsurfaceTileBuf = RayTraceTileBuf;
using ReflectionProbeDataBuf =
draw::UniformArrayBuffer<ReflectionProbeData, REFLECTION_PROBES_MAX>;
using ReflectionProbeDisplayDataBuf = draw::StorageArrayBuffer<ReflectionProbeDisplayData>;
using ProbePlanarDataBuf = draw::UniformArrayBuffer<ProbePlanarData, PLANAR_PROBES_MAX>;
using ProbePlanarDisplayDataBuf = draw::StorageArrayBuffer<ProbePlanarDisplayData>;
using SphereProbeDataBuf = draw::UniformArrayBuffer<SphereProbeData, SPHERE_PROBE_MAX>;
using SphereProbeDisplayDataBuf = draw::StorageArrayBuffer<SphereProbeDisplayData>;
using PlanarProbeDataBuf = draw::UniformArrayBuffer<PlanarProbeData, PLANAR_PROBE_MAX>;
using PlanarProbeDisplayDataBuf = draw::StorageArrayBuffer<PlanarProbeDisplayData>;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using ShadowStatisticsBuf = draw::StorageBuffer<ShadowStatistics>;
using ShadowPagesInfoDataBuf = draw::StorageBuffer<ShadowPagesInfoData>;

View File

@@ -831,9 +831,9 @@ void ShadowModule::begin_sync()
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_;
SurfelBuf &surfels_buf = inst_.volume_probes.bake.surfels_buf_;
CaptureInfoBuf &capture_info_buf = inst_.volume_probes.bake.capture_info_buf_;
float surfel_coverage_area = inst_.volume_probes.bake.surfel_density_;
/* Directional shadows. */
float texel_size = ShadowDirectional::tile_size_get(0) / float(SHADOW_PAGE_RES);
@@ -850,7 +850,7 @@ void ShadowModule::begin_sync()
sub.push_constant("directional_level", directional_level);
sub.push_constant("tilemap_projection_ratio", projection_ratio);
sub.bind_resources(inst_.lights);
sub.dispatch(&inst_.irradiance_cache.bake.dispatch_per_surfel_);
sub.dispatch(&inst_.volume_probes.bake.dispatch_per_surfel_);
/* Skip opaque and transparent tagging for light baking. */
return;

View File

@@ -571,19 +571,6 @@ void SyncModule::sync_curves(Object *ob,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Light Probes
* \{ */
void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle)
{
inst_.light_probes.sync_probe(ob, ob_handle);
inst_.reflection_probes.sync_object(ob, ob_handle);
inst_.planar_probes.sync_object(ob, ob_handle);
}
/** \} */
void foreach_hair_particle_handle(Object *ob, ObjectHandle ob_handle, HairHandleCallback callback)
{
int sub_key = 1;

View File

@@ -186,7 +186,6 @@ class SyncModule {
const ObjectRef &ob_ref,
ModifierData *modifier_data = nullptr,
ParticleSystem *particle_sys = nullptr);
void sync_light_probe(Object *ob, ObjectHandle &ob_handle);
};
using HairHandleCallback = FunctionRef<void(ObjectHandle, ModifierData &, ParticleSystem &)>;

View File

@@ -120,7 +120,7 @@ void ShadingView::render()
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_, extent_);
inst_.reflection_probes.set_view(render_view_);
inst_.sphere_probes.set_view(render_view_);
inst_.pipelines.background.render(render_view_);
@@ -151,8 +151,8 @@ void ShadingView::render()
inst_.lights.debug_draw(render_view_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_, combined_fb_);
inst_.shadows.debug_draw(render_view_, combined_fb_);
inst_.irradiance_cache.viewport_draw(render_view_, combined_fb_);
inst_.reflection_probes.viewport_draw(render_view_, combined_fb_);
inst_.volume_probes.viewport_draw(render_view_, combined_fb_);
inst_.sphere_probes.viewport_draw(render_view_, combined_fb_);
inst_.planar_probes.viewport_draw(render_view_, combined_fb_);
inst_.ambient_occlusion.render_pass(render_view_);
@@ -237,8 +237,7 @@ void ShadingView::update_view()
void CaptureView::render_world()
{
const std::optional<ReflectionProbeUpdateInfo> update_info =
inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::WORLD);
const auto update_info = inst_.sphere_probes.world_update_info_pop();
if (!update_info.has_value()) {
return;
}
@@ -257,19 +256,18 @@ void CaptureView::render_world()
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
combined_fb_.ensure(
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
combined_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
GPU_framebuffer_bind(combined_fb_);
inst_.pipelines.world.render(view);
}
inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.reflection_probes.update_probes_texture_mipmaps();
inst_.sphere_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.sphere_probes.update_probes_texture_mipmaps();
}
if (update_info->do_world_irradiance_update) {
inst_.reflection_probes.update_world_irradiance();
inst_.sphere_probes.update_world_irradiance();
}
GPU_debug_group_end();
@@ -280,9 +278,7 @@ void CaptureView::render_probes()
Framebuffer prepass_fb;
View view = {"Capture.View"};
bool do_update_mipmap_chain = false;
while (const std::optional<ReflectionProbeUpdateInfo> update_info =
inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::PROBE))
{
while (const auto update_info = inst_.sphere_probes.probe_update_info_pop()) {
GPU_debug_group_begin("Probe.Capture");
do_update_mipmap_chain = true;
@@ -308,17 +304,15 @@ void CaptureView::render_probes()
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
combined_fb_.ensure(
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
gbuffer_fb_.ensure(
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face),
GPU_ATTACHMENT_TEXTURE(inst_.gbuffer.header_tx),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(1), 0));
gbuffer_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face),
GPU_ATTACHMENT_TEXTURE(inst_.gbuffer.header_tx),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(1), 0));
GPU_framebuffer_bind(combined_fb_);
GPU_framebuffer_clear_color_depth(combined_fb_, float4(0.0f, 0.0f, 0.0f, 1.0f), 1.0f);
@@ -328,12 +322,12 @@ void CaptureView::render_probes()
inst_.render_buffers.release();
inst_.gbuffer.release();
GPU_debug_group_end();
inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.sphere_probes.remap_to_octahedral_projection(update_info->atlas_coord);
}
if (do_update_mipmap_chain) {
/* TODO: only update the regions that have been updated. */
inst_.reflection_probes.update_probes_texture_mipmaps();
inst_.sphere_probes.update_probes_texture_mipmaps();
}
}

View File

@@ -192,8 +192,8 @@ void VolumeModule::end_sync()
scatter_ps_.shader_set(
inst_.shaders.static_shader_get(use_lights_ ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER));
inst_.lights.bind_resources(scatter_ps_);
inst_.reflection_probes.bind_resources(scatter_ps_);
inst_.irradiance_cache.bind_resources(scatter_ps_);
inst_.sphere_probes.bind_resources(scatter_ps_);
inst_.volume_probes.bind_resources(scatter_ps_);
inst_.shadows.bind_resources(scatter_ps_);
inst_.sampling.bind_resources(scatter_ps_);
scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_);

View File

@@ -119,17 +119,14 @@ void World::sync()
}
}
inst_.reflection_probes.sync_world(bl_world);
if (has_update) {
inst_.reflection_probes.do_world_update_set(true);
}
/* We have to manually test here because we have overrides. */
::World *orig_world = (::World *)DEG_get_original_id(&bl_world->id);
if (assign_if_different(prev_original_world, orig_world)) {
inst_.reflection_probes.do_world_update_set(true);
has_update = true;
}
inst_.light_probes.sync_world(bl_world, has_update);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_DEFERRED);
inst_.manager->register_layer_attributes(gpumat);

View File

@@ -11,7 +11,7 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#ifdef REFLECTION_PROBE
#ifdef SPHERE_PROBE
struct LightProbeSample {
SphericalHarmonicL1 volume_irradiance;
@@ -34,7 +34,7 @@ LightProbeSample lightprobe_load(vec3 P, vec3 Ng, vec3 V)
}
/* Return the best parallax corrected ray direction from the probe center. */
vec3 lightprobe_sphere_parallax(ReflectionProbeData probe, vec3 P, vec3 L)
vec3 lightprobe_sphere_parallax(SphereProbeData probe, vec3 P, vec3 L)
{
bool is_world = (probe.influence_scale == 0.0);
if (is_world) {
@@ -63,7 +63,7 @@ vec3 lightprobe_sphere_parallax(ReflectionProbeData probe, vec3 P, vec3 L)
vec3 lightprobe_spherical_sample_normalized_with_parallax(
int probe_index, vec3 P, vec3 L, float lod, SphericalHarmonicL1 P_sh)
{
ReflectionProbeData probe = reflection_probe_buf[probe_index];
SphereProbeData probe = reflection_probe_buf[probe_index];
ReflectionProbeLowFreqLight shading_sh = reflection_probes_extract_low_freq(P_sh);
vec3 normalization_factor = reflection_probes_normalization_eval(
L, shading_sh, probe.low_freq_light);
@@ -93,7 +93,7 @@ float lightprobe_roughness_to_cube_sh_mix_fac(float roughness)
float lightprobe_roughness_to_lod(float roughness)
{
/* Temporary. Do something better. */
return sqrt(roughness) * REFLECTION_PROBE_MIPMAP_LEVELS;
return sqrt(roughness) * SPHERE_PROBE_MIPMAP_LEVELS;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V)
@@ -150,4 +150,4 @@ vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V
return mix(radiance_cube, radiance_sh, fac);
}
#endif /* REFLECTION_PROBE */
#endif /* SPHERE_PROBE */

View File

@@ -67,7 +67,7 @@ void irradiance_capture_world(vec3 L, inout SphericalHarmonicL1 sh)
float visibility = 0.0;
if (capture_info_buf.capture_world_direct) {
ReflectionProbeCoordinate atlas_coord = capture_info_buf.world_atlas_coord;
SphereProbeUvArea atlas_coord = capture_info_buf.world_atlas_coord;
radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb;
/* Clamped brightness. */

View File

@@ -19,7 +19,7 @@ vec3 lightprobe_irradiance_grid_sample_position(mat4 grid_local_to_world_mat,
* 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)
bool lightprobe_irradiance_grid_local_coord(VolumeProbeData 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. */
@@ -29,7 +29,7 @@ bool lightprobe_irradiance_grid_local_coord(IrradianceGridData grid_data, vec3 P
return all(equal(lP, r_lP));
}
int lightprobe_irradiance_grid_brick_index_get(IrradianceGridData grid_data, ivec3 brick_coord)
int lightprobe_irradiance_grid_brick_index_get(VolumeProbeData grid_data, ivec3 brick_coord)
{
int3 grid_size_in_bricks = divide_ceil(grid_data.grid_size,
int3(IRRADIANCE_GRID_BRICK_SIZE - 1));
@@ -46,7 +46,7 @@ ivec3 lightprobe_irradiance_grid_cell_corner(int cell_corner_id)
return (ivec3(cell_corner_id) >> ivec3(0, 1, 2)) & 1;
}
float lightprobe_planar_score(ProbePlanarData planar, vec3 P, vec3 V, vec3 L)
float lightprobe_planar_score(PlanarProbeData planar, vec3 P, vec3 V, vec3 L)
{
vec3 lP = vec4(P, 1.0) * planar.world_to_object_transposed;
if (any(greaterThan(abs(lP), vec3(1.0)))) {
@@ -68,9 +68,9 @@ int lightprobe_planar_select(vec3 P, vec3 V, vec3 L)
float best_score = saturate(dot(L, -V));
int best_index = -1;
for (int index = 0; index < PLANAR_PROBES_MAX; index++) {
for (int index = 0; index < PLANAR_PROBE_MAX; index++) {
if (probe_planar_buf[index].layer_id == -1) {
/* ProbePlanarData doesn't contain any gap, exit at first item that is invalid. */
/* PlanarProbeData doesn't contain any gap, exit at first item that is invalid. */
break;
}
float score = lightprobe_planar_score(probe_planar_buf[index], P, V, L);

View File

@@ -30,7 +30,7 @@ ivec3 lightprobe_irradiance_grid_brick_coord(vec3 lP)
/**
* Return the local coordinated of the shading point inside the brick in unnormalized coordinate.
*/
vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
vec3 lightprobe_irradiance_grid_brick_local_coord(VolumeProbeData grid_data,
vec3 lP,
ivec3 brick_coord)
{
@@ -44,7 +44,7 @@ vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
/**
* Return the biased local brick local coordinated.
*/
vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data,
vec3 lightprobe_irradiance_grid_bias_sample_coord(VolumeProbeData grid_data,
uvec2 brick_atlas_coord,
vec3 brick_lP,
vec3 lNg)
@@ -161,7 +161,7 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(
}
}
IrradianceGridData grid_data = grids_infos_buf[index];
VolumeProbeData grid_data = grids_infos_buf[index];
/* TODO(fclem): Make sure this is working as expected. */
mat3x3 world_to_grid_transposed = mat3x3(grid_data.world_to_grid_transposed);

View File

@@ -58,7 +58,7 @@ void main()
return;
}
ProbePlanarData planar = probe_planar_buf[planar_id];
PlanarProbeData planar = probe_planar_buf[planar_id];
/* Tag the ray data so that screen trace will not try to evaluate it and override the result. */
imageStore(ray_data_img, texel, vec4(ray_data.xyz, -ray_data.w));

View File

@@ -164,7 +164,7 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data,
ScreenTraceHitData raytrace_planar(RayTraceData rt_data,
depth2DArray planar_depth_tx,
ProbePlanarData planar,
PlanarProbeData planar,
float stride_rand,
Ray ray)
{

View File

@@ -8,12 +8,12 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#ifdef REFLECTION_PROBE
#ifdef SPHERE_PROBE
int reflection_probes_select(vec3 P, float random_probe)
{
for (int index = 0; index < REFLECTION_PROBES_MAX; index++) {
ReflectionProbeData probe_data = reflection_probe_buf[index];
/* ReflectionProbeData doesn't contain any gap, exit at first item that is invalid. */
for (int index = 0; index < SPHERE_PROBE_MAX; index++) {
SphereProbeData probe_data = reflection_probe_buf[index];
/* SphereProbeData doesn't contain any gap, exit at first item that is invalid. */
if (probe_data.atlas_coord.layer == -1) {
/* We hit the end of the array. Return last valid index. */
return index - 1;
@@ -29,6 +29,6 @@ int reflection_probes_select(vec3 P, float random_probe)
}
}
/* This should never happen (world probe is always last). */
return REFLECTION_PROBES_MAX - 1;
return SPHERE_PROBE_MAX - 1;
}
#endif /* REFLECTION_PROBE */
#endif /* SPHERE_PROBE */

View File

@@ -6,8 +6,8 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#ifdef REFLECTION_PROBE
vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeCoordinate atlas_coord)
#ifdef SPHERE_PROBE
vec4 reflection_probes_sample(vec3 L, float lod, SphereProbeUvArea atlas_coord)
{
vec2 octahedral_uv = octahedral_uv_from_direction(L) * atlas_coord.scale + atlas_coord.offset;
return textureLod(reflection_probes_tx, vec3(octahedral_uv, atlas_coord.layer), lod);

View File

@@ -7,18 +7,18 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
ReflectionProbeCoordinate reinterpret_as_atlas_coord(ivec4 packed_coord)
SphereProbeUvArea reinterpret_as_atlas_coord(ivec4 packed_coord)
{
ReflectionProbeCoordinate unpacked;
SphereProbeUvArea unpacked;
unpacked.offset = intBitsToFloat(packed_coord.xy);
unpacked.scale = intBitsToFloat(packed_coord.z);
unpacked.layer = intBitsToFloat(packed_coord.w);
return unpacked;
}
ReflectionProbeWriteCoordinate reinterpret_as_write_coord(ivec4 packed_coord)
SphereProbePixelArea reinterpret_as_write_coord(ivec4 packed_coord)
{
ReflectionProbeWriteCoordinate unpacked;
SphereProbePixelArea unpacked;
unpacked.offset = packed_coord.xy;
unpacked.extent = packed_coord.z;
unpacked.layer = packed_coord.w;
@@ -40,9 +40,9 @@ vec2 mirror_repeat_uv(vec2 uv)
void main()
{
ReflectionProbeCoordinate world_coord = reinterpret_as_atlas_coord(world_coord_packed);
ReflectionProbeCoordinate sample_coord = reinterpret_as_atlas_coord(probe_coord_packed);
ReflectionProbeWriteCoordinate write_coord = reinterpret_as_write_coord(write_coord_packed);
SphereProbeUvArea world_coord = reinterpret_as_atlas_coord(world_coord_packed);
SphereProbeUvArea sample_coord = reinterpret_as_atlas_coord(probe_coord_packed);
SphereProbePixelArea write_coord = reinterpret_as_write_coord(write_coord_packed);
/* Texel in probe. */
ivec2 local_texel = ivec2(gl_GlobalInvocationID.xy);

View File

@@ -9,9 +9,9 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
ReflectionProbeCoordinate reinterpret_as_atlas_coord(ivec4 packed_coord)
SphereProbeUvArea reinterpret_as_atlas_coord(ivec4 packed_coord)
{
ReflectionProbeCoordinate unpacked;
SphereProbeUvArea unpacked;
unpacked.offset = intBitsToFloat(packed_coord.xy);
unpacked.scale = intBitsToFloat(packed_coord.z);
unpacked.layer = intBitsToFloat(packed_coord.w);
@@ -42,15 +42,14 @@ void main()
cooef.L1.M0 = vec4(0.0);
cooef.L1.Mp1 = vec4(0.0);
ReflectionProbeCoordinate atlas_coord = reinterpret_as_atlas_coord(world_coord_packed);
SphereProbeUvArea atlas_coord = reinterpret_as_atlas_coord(world_coord_packed);
float layer_mipmap = 5;
/* Perform multiple sample. */
uint store_index = gl_LocalInvocationID.x;
float total_samples = float(gl_WorkGroupSize.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP);
float total_samples = float(gl_WorkGroupSize.x * SPHERE_PROBE_SH_SAMPLES_PER_GROUP);
float sample_weight = 4.0 * M_PI / total_samples;
float sample_offset = float(gl_LocalInvocationID.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP);
for (int sample_index = 0; sample_index < REFLECTION_PROBE_SH_SAMPLES_PER_GROUP; sample_index++)
{
float sample_offset = float(gl_LocalInvocationID.x * SPHERE_PROBE_SH_SAMPLES_PER_GROUP);
for (int sample_index = 0; sample_index < SPHERE_PROBE_SH_SAMPLES_PER_GROUP; sample_index++) {
vec2 rand = fract(hammersley_2d(sample_index + sample_offset, total_samples));
vec3 direction = sample_sphere(rand);
vec4 light = reflection_probes_sample(direction, layer_mipmap, atlas_coord);

View File

@@ -88,7 +88,7 @@ void radiance_transfer_world(inout Surfel receiver, vec3 L)
float visibility = 0.0;
if (capture_info_buf.capture_world_indirect) {
ReflectionProbeCoordinate atlas_coord = capture_info_buf.world_atlas_coord;
SphereProbeUvArea atlas_coord = capture_info_buf.world_atlas_coord;
radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb;
}

View File

@@ -144,7 +144,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_planar_eval)
.early_fragment_test(true)
/* Inputs. */
.fragment_out(0, Type::VEC4, "out_radiance")
.define("REFLECTION_PROBE")
.define("SPHERE_PROBE")
.define("SHADOW_SUBSURFACE")
.define("LIGHT_CLOSURE_EVAL_COUNT", "2")
.additional_info("eevee_shared",

View File

@@ -182,7 +182,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
.push_constant(Type::FLOAT, "dilation_threshold")
.push_constant(Type::FLOAT, "dilation_radius")
.push_constant(Type::FLOAT, "grid_intensity_factor")
.uniform_buf(0, "IrradianceGridData", "grids_infos_buf[IRRADIANCE_GRID_MAX]")
.uniform_buf(0, "VolumeProbeData", "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")
@@ -200,20 +200,20 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
GPU_SHADER_CREATE_INFO(eevee_volume_probe_data)
.uniform_buf(IRRADIANCE_GRID_BUF_SLOT,
"IrradianceGridData",
"VolumeProbeData",
"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")
.sampler(VOLUME_PROBE_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx")
.define("IRRADIANCE_GRID_SAMPLING");
GPU_SHADER_CREATE_INFO(eevee_lightprobe_data)
.additional_info("eevee_reflection_probe_data", "eevee_volume_probe_data");
GPU_SHADER_CREATE_INFO(eevee_lightprobe_planar_data)
.define("REFLECTION_PROBE")
.uniform_buf(PLANAR_PROBE_BUF_SLOT, "ProbePlanarData", "probe_planar_buf[PLANAR_PROBES_MAX]")
.define("SPHERE_PROBE")
.uniform_buf(PLANAR_PROBE_BUF_SLOT, "PlanarProbeData", "probe_planar_buf[PLANAR_PROBE_MAX]")
.sampler(PLANAR_PROBE_RADIANCE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "planar_radiance_tx")
.sampler(PLANAR_PROBE_DEPTH_TEX_SLOT, ImageType::DEPTH_2D_ARRAY, "planar_depth_tx");

View File

@@ -10,15 +10,15 @@
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.define("REFLECTION_PROBE")
.uniform_buf(REFLECTION_PROBE_BUF_SLOT,
"ReflectionProbeData",
"reflection_probe_buf[REFLECTION_PROBES_MAX]")
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx");
.define("SPHERE_PROBE")
.uniform_buf(SPHERE_PROBE_BUF_SLOT,
"SphereProbeData",
"reflection_probe_buf[SPHERE_PROBE_MAX]")
.sampler(SPHERE_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx");
/* Sample cubemap and remap into an octahedral texture. */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.local_group_size(REFLECTION_PROBE_GROUP_SIZE, REFLECTION_PROBE_GROUP_SIZE)
.local_group_size(SPHERE_PROBE_GROUP_SIZE, SPHERE_PROBE_GROUP_SIZE)
.push_constant(Type::IVEC4, "probe_coord_packed")
.push_constant(Type::IVEC4, "write_coord_packed")
.push_constant(Type::IVEC4, "world_coord_packed")
@@ -34,8 +34,8 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
/* Extract spherical harmonics band L0 + L1 from octahedral mapped reflection probe and update the
* world brick of the irradiance cache. */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance)
.local_group_size(REFLECTION_PROBE_SH_GROUP_SIZE, 1)
.define("REFLECTION_PROBE")
.local_group_size(SPHERE_PROBE_SH_GROUP_SIZE, 1)
.define("SPHERE_PROBE")
.push_constant(Type::IVEC4, "world_coord_packed")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx")
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img")
@@ -44,11 +44,11 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance)
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select)
.local_group_size(REFLECTION_PROBE_SELECT_GROUP_SIZE)
.local_group_size(SPHERE_PROBE_SELECT_GROUP_SIZE)
.storage_buf(0,
Qualifier::READ_WRITE,
"ReflectionProbeData",
"reflection_probe_buf[REFLECTION_PROBES_MAX]")
"SphereProbeData",
"reflection_probe_buf[SPHERE_PROBE_MAX]")
.push_constant(Type::INT, "reflection_probe_count")
.additional_info("eevee_shared", "eevee_sampling_data", "eevee_volume_probe_data")
.compute_source("eevee_reflection_probe_select_comp.glsl")
@@ -60,7 +60,7 @@ GPU_SHADER_INTERFACE_INFO(eevee_display_probe_reflection_iface, "")
GPU_SHADER_CREATE_INFO(eevee_display_probe_reflection)
.additional_info("eevee_shared", "draw_view", "eevee_reflection_probe_data")
.storage_buf(0, Qualifier::READ, "ReflectionProbeDisplayData", "display_data_buf[]")
.storage_buf(0, Qualifier::READ, "SphereProbeDisplayData", "display_data_buf[]")
.vertex_source("eevee_display_probe_reflection_vert.glsl")
.vertex_out(eevee_display_probe_reflection_iface)
.fragment_source("eevee_display_probe_reflection_frag.glsl")
@@ -71,7 +71,7 @@ GPU_SHADER_INTERFACE_INFO(eevee_display_probe_planar_iface, "").flat(Type::INT,
GPU_SHADER_CREATE_INFO(eevee_display_probe_planar)
.additional_info("eevee_shared", "draw_view", "eevee_lightprobe_planar_data")
.storage_buf(0, Qualifier::READ, "ProbePlanarDisplayData", "display_data_buf[]")
.storage_buf(0, Qualifier::READ, "PlanarProbeDisplayData", "display_data_buf[]")
.vertex_source("eevee_display_probe_planar_vert.glsl")
.vertex_out(eevee_display_probe_planar_iface)
.fragment_source("eevee_display_probe_planar_frag.glsl")