diff --git a/scripts/startup/bl_ui/properties_data_lightprobe.py b/scripts/startup/bl_ui/properties_data_lightprobe.py index 86fd852ac5b..26c393dd021 100644 --- a/scripts/startup/bl_ui/properties_data_lightprobe.py +++ b/scripts/startup/bl_ui/properties_data_lightprobe.py @@ -111,6 +111,10 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel): col.prop(probe, "grid_bake_samples") col.prop(probe, "surfel_density") + elif probe.type == 'CUBEMAP': + col = layout.column() + col.prop(probe, "resolution") + elif probe.type == 'PLANAR': # Currently unsupported pass diff --git a/scripts/startup/bl_ui/properties_world.py b/scripts/startup/bl_ui/properties_world.py index 870e208ed4d..7bf68ebcef2 100644 --- a/scripts/startup/bl_ui/properties_world.py +++ b/scripts/startup/bl_ui/properties_world.py @@ -148,6 +148,27 @@ class EEVEE_WORLD_PT_volume(WorldButtonsPanel, Panel): layout.label(text="No output node") +class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel): + bl_label = "Probe" + bl_translation_context = i18n_contexts.id_id + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + @classmethod + def poll(cls, context): + engine = context.engine + world = context.world + return world and (engine in cls.COMPAT_ENGINES) + + def draw(self, context): + layout = self.layout + + world = context.world + + layout.use_property_split = True + layout.prop(world, "probe_resolution") + + class WORLD_PT_viewport_display(WorldButtonsPanel, Panel): bl_label = "Viewport Display" bl_options = {'DEFAULT_CLOSED'} @@ -169,6 +190,7 @@ classes = ( EEVEE_WORLD_PT_surface, EEVEE_WORLD_PT_volume, EEVEE_WORLD_PT_mist, + EEVEE_WORLD_PT_probe, WORLD_PT_viewport_display, WORLD_PT_custom_props, ) diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 7a9b0ffdb09..623cc87e347 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -14,6 +14,7 @@ #include "DNA_modifier_types.h" #include "DNA_movieclip_types.h" #include "DNA_scene_types.h" +#include "DNA_world_types.h" #include "DNA_genfile.h" @@ -286,6 +287,19 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + /* Set default bake resolution. */ + if (!DNA_struct_elem_find(fd->filesdna, "LightProbe", "int", "resolution")) { + LISTBASE_FOREACH (LightProbe *, lightprobe, &bmain->lightprobes) { + lightprobe->resolution = LIGHT_PROBE_RESOLUTION_1024; + } + } + + if (!DNA_struct_elem_find(fd->filesdna, "World", "int", "probe_resolution")) { + LISTBASE_FOREACH (World *, world, &bmain->worlds) { + world->probe_resolution = LIGHT_PROBE_RESOLUTION_1024; + } + } + /* Clear removed "Z Buffer" flag. */ { const int R_IMF_FLAG_ZBUF_LEGACY = 1 << 0; diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index c9708924de7..41311c921ab 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -27,7 +27,12 @@ #define CULLING_TILE_GROUP_SIZE 256 /* Reflection Probes. */ +#define REFLECTION_PROBES_MAX 256 #define REFLECTION_PROBE_GROUP_SIZE 16 +/* 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_BORDER_SIZE float(1 << (REFLECTION_PROBE_MIPMAP_LEVELS - 1)) /** * IMPORTANT: Some data packing are tweaked for these values. @@ -133,6 +138,9 @@ #define HIZ_BUF_SLOT 3 #define IRRADIANCE_GRID_BUF_SLOT 4 #define AO_BUF_SLOT 5 +/* SLOT 6 is used by render shaders (Film, DoF and Motion Blur). Need to check if it should be + * assigned a different slot. */ +#define REFLECTION_PROBE_BUF_SLOT 7 /* Only during pre-pass. */ #define VELOCITY_CAMERA_PREV_BUF 3 #define VELOCITY_CAMERA_CURR_BUF 4 diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 226ad3a3917..7d95631a127 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -138,6 +138,7 @@ void Instance::begin_sync() shadows.begin_sync(); pipelines.begin_sync(); cryptomatte.begin_sync(); + reflection_probes.begin_sync(); light_probes.begin_sync(); gpencil_engine_enabled = false; @@ -215,7 +216,7 @@ void Instance::object_sync(Object *ob) sync.sync_gpencil(ob, ob_handle, res_handle); break; case OB_LIGHTPROBE: - light_probes.sync_probe(ob, ob_handle); + sync.sync_light_probe(ob, ob_handle); break; default: break; @@ -247,6 +248,7 @@ void Instance::end_sync() cryptomatte.end_sync(); pipelines.end_sync(); light_probes.end_sync(); + reflection_probes.end_sync(); } void Instance::render_sync() diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc index 3394d36b782..5c9b7ed1e9e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -5,26 +5,51 @@ #include "eevee_reflection_probes.hh" #include "eevee_instance.hh" +/* Generate dummy light probe texture. + * + * Baking of Light probes aren't implemented yet. For testing purposes this can be enabled to + * generate a dummy texture. + */ +#define GENERATE_DUMMY_LIGHT_PROBE_TEXTURE false + namespace blender::eevee { void ReflectionProbeModule::init() { - if (!initialized_) { - initialized_ = true; + if (probes_.is_empty()) { + ReflectionProbeData init_probe_data = {}; + init_probe_data.layer = -1; + for (int i : IndexRange(REFLECTION_PROBES_MAX)) { + data_buf_[i] = init_probe_data; + } + + /* Initialize the world probe. */ + ReflectionProbeData world_probe_data{}; + world_probe_data.layer = 0; + world_probe_data.layer_subdivision = 0; + world_probe_data.area_index = 0; + world_probe_data.pos = float3(0.0f); + data_buf_[0] = world_probe_data; + + ReflectionProbe world_probe; + world_probe.type = ReflectionProbe::Type::World; + world_probe.do_update_data = true; + world_probe.do_render = true; + world_probe.index = 0; + probes_.add(world_object_key_, world_probe); - const int max_mipmap_levels = log(max_resolution_) + 1; probes_tx_.ensure_2d_array(GPU_RGBA16F, int2(max_resolution_), - max_probes_, + init_num_probes_, GPU_TEXTURE_USAGE_SHADER_WRITE, nullptr, - max_mipmap_levels); + REFLECTION_PROBE_MIPMAP_LEVELS); GPU_texture_mipmap_mode(probes_tx_, true, true); /* Cube-map is half of the resolution of the octahedral map. */ cubemap_tx_.ensure_cube( - GPU_RGBA16F, max_resolution_ / 2, GPU_TEXTURE_USAGE_ATTACHMENT, nullptr, 1); - GPU_texture_mipmap_mode(cubemap_tx_, false, true); + GPU_RGBA16F, max_resolution_ / 2, GPU_TEXTURE_USAGE_ATTACHMENT, nullptr, 9999); + GPU_texture_mipmap_mode(cubemap_tx_, true, true); } { @@ -33,13 +58,428 @@ void ReflectionProbeModule::init() pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP)); pass.bind_texture("cubemap_tx", cubemap_tx_); pass.bind_image("octahedral_img", probes_tx_); - pass.dispatch(int2(ceil_division(max_resolution_, REFLECTION_PROBE_GROUP_SIZE))); + pass.bind_ssbo(REFLECTION_PROBE_BUF_SLOT, data_buf_); + pass.dispatch(&dispatch_probe_pack_); + } +} +void ReflectionProbeModule::begin_sync() +{ + for (ReflectionProbe &reflection_probe : probes_.values()) { + if (reflection_probe.type == ReflectionProbe::Type::Probe) { + reflection_probe.is_probe_used = false; + } } } +int ReflectionProbeModule::needed_layers_get() const +{ + const int max_probe_data_index = reflection_probe_data_index_max(); + int max_layer = 0; + for (const ReflectionProbeData &data : + Span(data_buf_.data(), max_probe_data_index + 1)) + { + max_layer = max_ii(max_layer, data.layer); + } + return max_layer + 1; +} + +void ReflectionProbeModule::sync(const ReflectionProbe &probe) +{ + switch (probe.type) { + case ReflectionProbe::Type::World: { + break; + } + case ReflectionProbe::Type::Probe: { + if (probe.do_render) { + upload_dummy_texture(probe); + } + break; + } + case ReflectionProbe::Type::Unused: { + break; + } + } +} + +static int layer_subdivision_for(const int max_resolution, + const eLightProbeResolution probe_resolution) +{ + int i_probe_resolution = int(probe_resolution); + return max_ii(int(log2(max_resolution)) - i_probe_resolution, 0); +} + +void ReflectionProbeModule::sync_world(::World *world, WorldHandle & /*ob_handle*/) +{ + const ReflectionProbe &probe = probes_.lookup(world_object_key_); + ReflectionProbeData &probe_data = data_buf_[probe.index]; + probe_data.layer_subdivision = layer_subdivision_for( + max_resolution_, static_cast(world->probe_resolution)); +} + +void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle) +{ +#if GENERATE_DUMMY_LIGHT_PROBE_TEXTURE + const ::LightProbe *light_probe = (::LightProbe *)ob->data; + if (light_probe->type != LIGHTPROBE_TYPE_CUBE) { + return; + } + const bool is_dirty = ob_handle.recalc != 0; + int subdivision = layer_subdivision_for( + max_resolution_, static_cast(light_probe->resolution)); + ReflectionProbe &probe = find_or_insert(ob_handle, subdivision); + probe.do_update_data |= is_dirty; + probe.is_probe_used = true; + + ReflectionProbeData &probe_data = data_buf_[probe.index]; + probe_data.pos = float3(float4x4(ob->object_to_world) * float4(0.0, 0.0, 0.0, 1.0)); + probe_data.layer_subdivision = subdivision; +#else + UNUSED_VARS(ob, ob_handle); +#endif +} + +ReflectionProbe &ReflectionProbeModule::find_or_insert(ObjectHandle &ob_handle, + int subdivision_level) +{ + ReflectionProbe &reflection_probe = probes_.lookup_or_add_cb( + ob_handle.object_key.hash_value, [this, subdivision_level]() { + ReflectionProbe probe; + ReflectionProbeData probe_data = find_empty_reflection_probe_data(subdivision_level); + + probe.do_update_data = true; + probe.do_render = true; + probe.type = ReflectionProbe::Type::Probe; + probe.index = reflection_probe_data_index_max() + 1; + + data_buf_[probe.index] = probe_data; + return probe; + }); + + return reflection_probe; +} + +int ReflectionProbeModule::reflection_probe_data_index_max() const +{ + int result = -1; + for (const ReflectionProbe &probe : probes_.values()) { + if (probe.type != ReflectionProbe::Type::Unused) { + result = max_ii(result, probe.index); + } + } + return result; +} + +/** + * 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); + } + + /** + * 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 ReflectionProbeData &probe_data) + { + /* Number of spots that the probe data will occupied in a single dimension. */ + int clamped_subdivision_shift = max_ii(probe_data.layer_subdivision - subdivision_level_, 0); + int spots_per_dimension = 1 << max_ii(subdivision_level_ - probe_data.layer_subdivision, 0); + int probes_per_dimension_in_probe_data = 1 << probe_data.layer_subdivision; + int2 pos_in_probe_data = int2(probe_data.area_index % probes_per_dimension_in_probe_data, + probe_data.area_index / probes_per_dimension_in_probe_data); + int2 pos_in_location_finder = int2(pos_in_probe_data.x >> clamped_subdivision_shift, + pos_in_probe_data.y >> clamped_subdivision_shift); + int layer_offset = probe_data.layer * probes_per_layer_; + for (int y : IndexRange(spots_per_dimension)) { + for (int x : IndexRange(spots_per_dimension)) { + int2 pos = pos_in_location_finder + int2(x, y); + 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. + */ + ReflectionProbeData first_free_spot() const + { + ReflectionProbeData result = {}; + result.layer_subdivision = subdivision_level_; + for (int index : taken_spots_.index_range()) { + if (!taken_spots_[index]) { + int layer = index / probes_per_layer_; + int area_index = index % probes_per_layer_; + result.layer = layer; + result.area_index = area_index; + return result; + } + } + + BLI_assert_unreachable(); + return result; + } +}; + +ReflectionProbeData ReflectionProbeModule::find_empty_reflection_probe_data( + int subdivision_level) const +{ + ProbeLocationFinder location_finder(needed_layers_get() + 1, subdivision_level); + for (const ReflectionProbeData &data : + Span(data_buf_.data(), reflection_probe_data_index_max() + 1)) + { + location_finder.mark_space_used(data); + } + return location_finder.first_free_spot(); +} + +void ReflectionProbeModule::end_sync() +{ + remove_unused_probes(); + + int number_layers_needed = needed_layers_get(); + int current_layers = probes_tx_.depth(); + bool resize_layers = current_layers < number_layers_needed; + if (resize_layers) { + /* TODO: Create new texture and copy previous texture so we don't need to rerender all the + * probes.*/ + probes_tx_.ensure_2d_array(GPU_RGBA16F, + int2(max_resolution_), + number_layers_needed, + GPU_TEXTURE_USAGE_SHADER_WRITE, + nullptr, + REFLECTION_PROBE_MIPMAP_LEVELS); + GPU_texture_mipmap_mode(probes_tx_, true, true); + } + + recalc_lod_factors(); + data_buf_.push_update(); + + /* Regenerate mipmaps when a probe texture is updated. It can be postponed when the world probe + * is also updated. In this case it would happen as part of the WorldProbePipeline. */ + bool regenerate_mipmaps = false; + bool regenerate_mipmaps_postponed = false; + + for (ReflectionProbe &probe : probes_.values()) { + if (resize_layers) { + probe.do_update_data = true; + probe.do_render = true; + } + + if (!probe.needs_update()) { + continue; + } + sync(probe); + + switch (probe.type) { + case ReflectionProbe::Type::World: + regenerate_mipmaps_postponed = true; + break; + + case ReflectionProbe::Type::Probe: + regenerate_mipmaps = probe.do_render; + break; + + case ReflectionProbe::Type::Unused: + BLI_assert_unreachable(); + break; + } + probe.do_update_data = false; + probe.do_render = false; + } + + if (regenerate_mipmaps) { + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + if (!regenerate_mipmaps_postponed) { + GPU_texture_update_mipmap_chain(probes_tx_); + } + } +} + +void ReflectionProbeModule::remove_unused_probes() +{ + bool found = false; + do { + found = false; + uint64_t key_to_remove = 0; + for (const Map::Item &item : probes_.items()) { + const ReflectionProbe &probe = item.value; + if (probe.type == ReflectionProbe::Type::Probe && !probe.is_probe_used) { + key_to_remove = item.key; + found = true; + break; + } + } + if (found) { + probes_.remove(key_to_remove); + } + } while (found); +} + +void ReflectionProbeModule::remove_reflection_probe_data(int reflection_probe_data_index) +{ + int max_index = reflection_probe_data_index_max(); + BLI_assert_msg(reflection_probe_data_index <= max_index, + "Trying to remove reflection probes when it isn't part of the reflection probe " + "data. This can also happens when the state is set to " + "ReflectionProbe::Type::UNUSED, before removing the data."); + for (int index = reflection_probe_data_index; index < max_index; index++) { + data_buf_[index] = data_buf_[index + 1]; + } + for (ReflectionProbe &probe : probes_.values()) { + if (probe.index == reflection_probe_data_index) { + probe.index = -1; + } + if (probe.index > reflection_probe_data_index) { + probe.index--; + } + } + data_buf_[max_index].layer = -1; + BLI_assert(reflection_probe_data_index_max() == max_index - 1); +} + +void ReflectionProbeModule::recalc_lod_factors() +{ + for (ReflectionProbeData &probe_data : data_buf_) { + if (probe_data.layer == -1) { + return; + } + + const float bias = 0.0; + const float lod_factor = + bias + + 0.5 * log(float(square_i(probes_tx_.width() >> probe_data.layer_subdivision))) / log(2.0); + probe_data.lod_factor = lod_factor; + } +} + +/* -------------------------------------------------------------------- */ +/** \name Debugging + * + * \{ */ + +void ReflectionProbeModule::debug_print() const +{ + std::ostream &os = std::cout; + for (const ReflectionProbe &probe : probes_.values()) { + os << probe; + + if (probe.index != -1) { + os << data_buf_[probe.index]; + } + } +} + +std::ostream &operator<<(std::ostream &os, const ReflectionProbeData &probe_data) +{ + os << " - layer: " << probe_data.layer; + os << " subdivision: " << probe_data.layer_subdivision; + os << " area: " << probe_data.area_index; + os << "\n"; + + return os; +} + +std::ostream &operator<<(std::ostream &os, const ReflectionProbe &probe) +{ + switch (probe.type) { + case ReflectionProbe::Type::Unused: { + os << "UNUSED\n"; + + break; + } + case ReflectionProbe::Type::World: { + os << "WORLD"; + os << " is_dirty: " << probe.do_update_data; + os << " index: " << probe.index; + os << "\n"; + break; + } + case ReflectionProbe::Type::Probe: { + os << "PROBE"; + os << " is_dirty: " << probe.do_update_data; + os << " is_used: " << probe.is_probe_used; + os << " index: " << probe.index; + os << "\n"; + break; + } + } + return os; +} + +void ReflectionProbeModule::upload_dummy_texture(const ReflectionProbe &probe) +{ + const ReflectionProbeData &probe_data = data_buf_[probe.index]; + const int resolution = max_resolution_ >> probe_data.layer_subdivision; + float4 *data = static_cast( + MEM_mallocN(sizeof(float4) * resolution * resolution, __func__)); + + /* Generate dummy checker pattern. */ + int index = 0; + const int BLOCK_SIZE = max_ii(1024 >> probe_data.layer_subdivision, 1); + for (int y : IndexRange(resolution)) { + for (int x : IndexRange(resolution)) { + int tx = (x / BLOCK_SIZE) & 1; + int ty = (y / BLOCK_SIZE) & 1; + bool solid = (tx + ty) & 1; + if (solid) { + data[index] = float4((probe.index & 1) == 0 ? 0.0f : 1.0f, + (probe.index & 2) == 0 ? 0.0f : 1.0f, + (probe.index & 4) == 0 ? 0.0f : 1.0f, + 1.0f); + } + else { + data[index] = float4(0.0f); + } + + index++; + } + } + + /* Upload the checker pattern. */ + int probes_per_dimension = 1 << probe_data.layer_subdivision; + int2 probe_area_pos(probe_data.area_index % probes_per_dimension, + probe_data.area_index / probes_per_dimension); + int2 pos = probe_area_pos * int2(max_resolution_ / probes_per_dimension); + GPU_texture_update_sub( + probes_tx_, GPU_DATA_FLOAT, data, UNPACK2(pos), probe_data.layer, resolution, resolution, 1); + + MEM_freeN(data); +} + +/** \} */ + void ReflectionProbeModule::remap_to_octahedral_projection() { + const ReflectionProbe &world_probe = probes_.lookup(world_object_key_); + const ReflectionProbeData &probe_data = data_buf_[world_probe.index]; + dispatch_probe_pack_ = int3(int2(ceil_division(max_resolution_ >> probe_data.layer_subdivision, + REFLECTION_PROBE_GROUP_SIZE)), + 1); instance_.manager->submit(remap_ps_); + /* TODO: Performance - Should only update the area that has changed. */ GPU_texture_update_mipmap_chain(probes_tx_); } diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh index 22bf88ba31f..018571c38a2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -19,16 +19,58 @@ struct Material; namespace blender::eevee { class Instance; +struct ObjectHandle; +struct WorldHandle; class CaptureView; /* -------------------------------------------------------------------- */ /** \name Reflection Probes * \{ */ +struct ReflectionProbe { + enum Type { Unused, World, Probe }; + + Type type = Type::Unused; + + /* Probe data needs to be updated. */ + bool do_update_data = false; + /* Should the area in the probes_tx_ be updated? */ + bool do_render = false; + + /** + * Probes that aren't used during a draw can be cleared. + * + * Only valid when type == Type::Probe. + */ + bool is_probe_used = false; + + /** + * Index into ReflectionProbeDataBuf. + * -1 = not added yet + */ + int index = -1; + + /** + * Check if the probe needs to be updated during this sample. + */ + bool needs_update() const + { + switch (type) { + case Type::Unused: + return false; + case Type::World: + return do_update_data || do_render; + case Type::Probe: + return (do_update_data || do_render) && is_probe_used; + } + return false; + } +}; + class ReflectionProbeModule { private: - /** The max number of probes to track. */ - static constexpr int max_probes_ = 1; + /** The max number of probes to initially allocate. */ + static constexpr int init_num_probes_ = 1; /** * The maximum resolution of a cube-map side. @@ -37,27 +79,36 @@ class ReflectionProbeModule { */ static constexpr int max_resolution_ = 2048; - Instance &instance_; + static constexpr uint64_t world_object_key_ = 0; - /** Texture containing a cubemap used for updating #probes_tx_. */ + Instance &instance_; + ReflectionProbeDataBuf data_buf_; + Map probes_; + + /** Texture containing a cubemap used as input for updating #probes_tx_. */ Texture cubemap_tx_ = {"Probe.Cubemap"}; /** Probes texture stored in octahedral mapping. */ Texture probes_tx_ = {"Probes"}; PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"}; - bool initialized_ = false; - bool do_world_update_ = false; + int3 dispatch_probe_pack_ = int3(0); + public: ReflectionProbeModule(Instance &instance) : instance_(instance) {} void init(); + void begin_sync(); + void sync_world(::World *world, WorldHandle &ob_handle); + void sync_object(Object *ob, ObjectHandle &ob_handle); + void end_sync(); template void bind_resources(draw::detail::PassBase *pass) { pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, probes_tx_); + pass->bind_ssbo(REFLECTION_PROBE_BUF_SLOT, data_buf_); } void do_world_update_set(bool value) @@ -65,7 +116,36 @@ class ReflectionProbeModule { do_world_update_ = value; } + void debug_print() const; + private: + void sync(const ReflectionProbe &cubemap); + ReflectionProbe &find_or_insert(ObjectHandle &ob_handle, int subdivision_level); + + /** Get the number of layers that is needed to store probes. */ + int needed_layers_get() const; + + void remove_unused_probes(); + void recalc_lod_factors(); + + /* TODO: also add _len() which is a max + 1. */ + /* Get the number of reflection probe data elements. */ + int reflection_probe_data_index_max() const; + + /** + * Remove reflection probe data from the module. + * Ensures that data_buf is sequential and cubemaps are relinked to its corresponding data. + */ + void remove_reflection_probe_data(int reflection_probe_data_index); + + /** + * 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. + */ + ReflectionProbeData find_empty_reflection_probe_data(int subdivision_level) const; + + void upload_dummy_texture(const ReflectionProbe &probe); + bool do_world_update_get() const { return do_world_update_; @@ -77,4 +157,8 @@ class ReflectionProbeModule { friend class CaptureView; }; +std::ostream &operator<<(std::ostream &os, const ReflectionProbeModule &module); +std::ostream &operator<<(std::ostream &os, const ReflectionProbeData &probe_data); +std::ostream &operator<<(std::ostream &os, const ReflectionProbe &probe); + } // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 63a2ffcc832..ff83d599e17 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -1022,6 +1022,45 @@ BLI_STATIC_ASSERT_ALIGN(SubsurfaceData, 16) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Reflection Probes + * \{ */ + +/** Mapping data to locate a reflection probe in texture. */ +struct ReflectionProbeData { + /** + * Position of the light probe in world space. + * World probe uses origin. + */ + packed_float3 pos; + + /** On which layer of the texture array is this reflection probe stored. */ + int layer; + + /** + * Subdivision of the layer. 0 = no subdivision and resolution would be + * ReflectionProbeModule::MAX_RESOLUTION. + */ + int layer_subdivision; + + /** + * Which area of the subdivided layer is the reflection probe located. + * + * A layer has (2^layer_subdivision)^2 areas. + */ + int area_index; + + /** + * LOD factor for mipmap selection. + */ + float lod_factor; + + int _pad[1]; +}; +BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16) + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Utility Texture * \{ */ @@ -1078,6 +1117,8 @@ using LightCullingZdistBuf = draw::StorageArrayBuffer; using LightDataBuf = draw::StorageArrayBuffer; using MotionBlurDataBuf = draw::UniformBuffer; using MotionBlurTileIndirectionBuf = draw::StorageBuffer; +using ReflectionProbeDataBuf = + draw::UniformArrayBuffer; using SamplingDataBuf = draw::StorageBuffer; using ShadowStatisticsBuf = draw::StorageBuffer; using ShadowPagesInfoDataBuf = draw::StorageBuffer; diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index 0cd90613e88..a490f99c5a2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -356,4 +356,16 @@ 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); +} + +/** \} */ + } // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.hh b/source/blender/draw/engines/eevee_next/eevee_sync.hh index 3d44cb31107..20ea7675f39 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sync.hh @@ -169,6 +169,7 @@ class SyncModule { ObjectHandle &ob_handle, ResourceHandle res_handle, ModifierData *modifier_data = nullptr); + void sync_light_probe(Object *ob, ObjectHandle &ob_handle); }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index 033855e5d97..b4ac0646caf 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -215,6 +215,7 @@ void CaptureView::render() view.sync(view_m4, win_m4); inst_.pipelines.world.render(view); } + GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemap_tx_); inst_.reflection_probes.remap_to_octahedral_projection(); GPU_debug_group_end(); } diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc index 7a97234ef35..cf515a4b2ef 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.cc +++ b/source/blender/draw/engines/eevee_next/eevee_world.cc @@ -90,6 +90,7 @@ void World::sync() WorldHandle &wo_handle = inst_.sync.sync_world(bl_world); if (wo_handle.recalc != 0) { + inst_.reflection_probes.sync_world(bl_world, wo_handle); inst_.reflection_probes.do_world_update_set(true); } wo_handle.reset_recalc_flag(); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl index 8aa4d5ba3d1..81fd2c73efb 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl @@ -55,7 +55,7 @@ void main() vec3 reflection_light = vec3(0.0); float shadow = 1.0; - light_world_eval(reflection_data, P, V, reflection_light); + reflection_probes_eval(reflection_data, P, V, reflection_light); lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light); light_eval(diffuse_data, diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl index 959133dce7a..91364cbcb19 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl @@ -43,7 +43,7 @@ void irradiance_capture(Surfel surfel, vec3 P, inout SphericalHarmonicL1 sh) vec3 irradiance_sky_sample(vec3 R) { - return light_world_sample(R, 0.0); + return reflection_probes_world_sample(R, 0.0).rgb; } void main() diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl index 1393ef0a250..af7f2ec4954 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl @@ -20,6 +20,7 @@ vec2 octahedral_uv_from_direction(vec3 co) vec3 octahedral_uv_to_direction(vec2 co) { + /* Change range to between [-1..1] */ co = co * 2.0 - 1.0; vec2 abs_co = abs(co); @@ -31,3 +32,47 @@ vec3 octahedral_uv_to_direction(vec2 co) return v; } + +/** + * Return the UV coordinates on the packed octahedral texture layer when applying the given + * octahedral_uv to a specific probe. + */ +vec2 octahedral_uv_to_layer_texture_coords(vec2 octahedral_uv, + ReflectionProbeData probe_data, + vec2 texel_size) +{ + /* Fix artifacts near edges. Proved one texel on each side.*/ + octahedral_uv = octahedral_uv * (1.0 - 2.0 * REFLECTION_PROBE_BORDER_SIZE * texel_size) + + REFLECTION_PROBE_BORDER_SIZE * texel_size + 0.5 * texel_size; + + int areas_per_dimension = 1 << probe_data.layer_subdivision; + vec2 area_scalar = vec2(1.0 / float(areas_per_dimension)); + octahedral_uv *= area_scalar; + + vec2 area_offset = vec2(probe_data.area_index % areas_per_dimension, + probe_data.area_index / areas_per_dimension) * + area_scalar; + return octahedral_uv + area_offset; +} + +/** + * Return the octahedral uv coordinates for the given texture uv coordinate on the packed + * octahedral texture layer for the given probe. + * + * It also applies wrapping in the additional space near borders. + * NOTE: Doesn't apply the translation part of the packing. + */ +vec2 octahedral_uv_from_layer_texture_coords(vec2 uv, + ReflectionProbeData probe_data, + vec2 texel_size) + +{ + /* Apply border region. */ + vec2 shrinked_uv = (uv - REFLECTION_PROBE_BORDER_SIZE * texel_size) / + (1.0 - 2.0 * REFLECTION_PROBE_BORDER_SIZE * texel_size); + /* Use ping/pong to extend the octahedral coordinates. */ + vec2 translated_pos = clamp(-sign(shrinked_uv), vec2(0.0), vec2(1.0)) * vec2(2.0) + shrinked_uv; + ivec2 checker_pos = ivec2(translated_pos); + bool is_even = ((checker_pos.x + checker_pos.y) & 1) == 0; + return is_even ? fract(shrinked_uv) : vec2(1.0) - fract(shrinked_uv); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl index 68250ded591..6d2224ef851 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl @@ -1,13 +1,15 @@ + #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) -void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular) +vec4 reflection_probe_eval(ClosureReflection reflection, + vec3 P, + vec3 V, + ReflectionProbeData probe_data) { ivec3 texture_size = textureSize(reflectionProbes, 0); - /* TODO: This should be based by actual resolution. Currently the resolution is fixed but - * eventually this should based on a user setting and part of the reflection probe data that will - * be introduced by the reflection probe patch. */ - float lod_cube_max = 12.0; + float lod_cube_max = min(log(float(texture_size.x)) - float(probe_data.layer_subdivision) + 1.0, + REFLECTION_PROBE_MIPMAP_LEVELS); /* Pow2f to distributed across lod more evenly */ float roughness = clamp(pow2f(reflection.roughness), 1e-4f, 0.9999f); @@ -37,13 +39,8 @@ void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 o const float dist = 4.0 * M_PI / 6.0; /* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html : Equation 13 */ - /* TODO: lod_factor should be precalculated and stored inside the reflection probe data. */ - const float bias = 0.0; - const float lod_factor = bias + 0.5 * log(float(square_i(texture_size.x))) / log(2.0); - /* -2: Don't use LOD levels that are smaller than 4x4 pixels. */ - float lod = clamp(lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max - 2.0); - - vec3 l_col = light_world_sample(L, lod); + float lod = clamp(probe_data.lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max); + vec4 l_col = reflection_probes_sample(L, lod, probe_data); /* Clamped brightness. */ /* For artistic freedom this should be read from the scene/reflection probe. @@ -52,11 +49,46 @@ void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 o * account.*/ float luma = max(1e-8, max_v3(l_col)); const float firefly_factor = 1e16; - l_col *= 1.0 - max(0.0, luma - firefly_factor) / luma; + l_col.rgb *= 1.0 - max(0.0, luma - firefly_factor) / luma; - /* TODO: for artistic freedom want to read this from the reflection probe. That will be part of - * the reflection probe patch. */ - const float intensity_factor = 1.0; - out_specular += vec3(intensity_factor * l_col); + return l_col; } + return vec4(0.0); +} + +int reflection_probes_find_closest(vec3 P) +{ + int closest_index = -1; + float closest_distance = FLT_MAX; + + /* ReflectionProbeData doesn't contain any gab, exit at first item that is invalid. */ + for (int index = 1; reflection_probe_buf[index].layer != -1 && index < REFLECTION_PROBES_MAX; + index++) + { + float distance = distance(P, reflection_probe_buf[index].pos); + if (distance < closest_distance) { + closest_distance = distance; + closest_index = index; + } + } + return closest_index; +} + +void reflection_probes_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular) +{ + int closest_reflection_probe = reflection_probes_find_closest(P); + vec4 light_color = vec4(0.0); + if (closest_reflection_probe != -1) { + light_color = reflection_probe_eval( + reflection, P, V, reflection_probe_buf[closest_reflection_probe]); + } + + /* Mix world lighting. */ + if (light_color.a != 1.0) { + light_color.rgb = mix(reflection_probe_eval(reflection, P, V, reflection_probe_buf[0]).rgb, + light_color.rgb, + light_color.a); + } + + out_specular += light_color.rgb; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl index a50d7b5c3ec..6a50f3a2529 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl @@ -2,8 +2,16 @@ #pragma BLENDER_REQUIRE(eevee_cubemap_lib.glsl) #pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl) -vec3 light_world_sample(vec3 L, float lod) +vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeData probe_data) { - vec2 octahedral_uv = octahedral_uv_from_direction(L); - return textureLod(reflectionProbes, vec3(octahedral_uv, 0.0), lod).rgb; + vec2 octahedral_uv_packed = octahedral_uv_from_direction(L); + vec2 texel_size = vec2(1.0 / float(1 << (11 - probe_data.layer_subdivision))); + vec2 octahedral_uv = octahedral_uv_to_layer_texture_coords( + octahedral_uv_packed, probe_data, texel_size); + return textureLod(reflectionProbes, vec3(octahedral_uv, probe_data.layer), lod); } + +vec3 reflection_probes_world_sample(vec3 L, float lod) +{ + return reflection_probes_sample(L, lod, reflection_probe_buf[0]).rgb; +} \ No newline at end of file diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl index fae4bf5df1f..ade2d3326e2 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl @@ -5,15 +5,32 @@ void main() { + ReflectionProbeData probe_data = reflection_probe_buf[0]; + + ivec3 texture_coord = ivec3(gl_GlobalInvocationID.xyz); + ivec3 texture_size = imageSize(octahedral_img); + ivec3 octahedral_coord = ivec3(gl_GlobalInvocationID.xyz); - ivec3 octahedral_size = imageSize(octahedral_img); - /* Group doesn't fit in output texture. */ + ivec2 octahedral_size = ivec2(texture_size.x >> probe_data.layer_subdivision, + texture_size.y >> probe_data.layer_subdivision); + /* Exit when pixel being written doesn't fit in the area reserved for the probe. */ if (any(greaterThanEqual(octahedral_coord.xy, octahedral_size.xy))) { return; } - vec2 octahedral_uv = vec2(octahedral_coord.xy) / vec2(octahedral_size.xy); + + vec2 texel_size = vec2(1.0) / vec2(octahedral_size); + + vec2 uv = vec2(octahedral_coord.xy) / vec2(octahedral_size.xy); + vec2 octahedral_uv = octahedral_uv_from_layer_texture_coords(uv, probe_data, texel_size); vec3 R = octahedral_uv_to_direction(octahedral_uv); - vec4 col = textureLod(cubemap_tx, R, 0.0); - imageStore(octahedral_img, octahedral_coord, col); + vec4 col = textureLod(cubemap_tx, R, float(probe_data.layer_subdivision)); + // col.xy = octahedral_uv; + + int probes_per_dimension = 1 << probe_data.layer_subdivision; + ivec2 area_coord = ivec2(probe_data.area_index % probes_per_dimension, + probe_data.area_index / probes_per_dimension); + ivec2 area_offset = area_coord * octahedral_size; + + imageStore(octahedral_img, octahedral_coord + ivec3(area_offset, 0), col); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl index 29b1559a12d..3e4204929e9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl @@ -65,7 +65,7 @@ void radiance_transfer_surfel(inout Surfel receiver, Surfel sender) vec3 radiance_sky_sample(vec3 R) { - return light_world_sample(R, 0.0); + return reflection_probes_world_sample(R, 0.0).rgb; } void radiance_transfer_world(inout Surfel receiver, vec3 sky_L) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh index 838072f6a55..1980b114053 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh @@ -10,14 +10,23 @@ * \{ */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data) + .storage_buf(REFLECTION_PROBE_BUF_SLOT, + Qualifier::READ, + "ReflectionProbeData", + "reflection_probe_buf[]") .sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflectionProbes"); /* 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) + .storage_buf(REFLECTION_PROBE_BUF_SLOT, + Qualifier::READ, + "ReflectionProbeData", + "reflection_probe_buf[]") .sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "octahedral_img") .compute_source("eevee_reflection_probe_remap_comp.glsl") + .additional_info("eevee_shared") .do_static_compilation(true); /** \} */ diff --git a/source/blender/makesdna/DNA_lightprobe_defaults.h b/source/blender/makesdna/DNA_lightprobe_defaults.h index 5fc61b5d6b0..d963b124362 100644 --- a/source/blender/makesdna/DNA_lightprobe_defaults.h +++ b/source/blender/makesdna/DNA_lightprobe_defaults.h @@ -31,6 +31,7 @@ .vis_blur = 0.2f, \ .intensity = 1.0f, \ .flag = LIGHTPROBE_FLAG_SHOW_INFLUENCE, \ + .resolution = LIGHT_PROBE_RESOLUTION_1024, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_lightprobe_types.h b/source/blender/makesdna/DNA_lightprobe_types.h index 929d919a08e..e2586d26077 100644 --- a/source/blender/makesdna/DNA_lightprobe_types.h +++ b/source/blender/makesdna/DNA_lightprobe_types.h @@ -61,7 +61,10 @@ typedef struct LightProbe { /** Surface element density for scene surface cache. In surfel per unit distance. */ float surfel_density; - char _pad1[4]; + /** + * Resolution of the light probe when baked to a texture. Contains `eLightProbeResolution`. + */ + int resolution; /** Object to use as a parallax origin. */ struct Object *parallax_ob; @@ -71,6 +74,16 @@ typedef struct LightProbe { struct Collection *visibility_grp; } LightProbe; +/* LightProbe->resolution, World->probe_resolution. */ +typedef enum eLightProbeResolution { + LIGHT_PROBE_RESOLUTION_64 = 6, + LIGHT_PROBE_RESOLUTION_128 = 7, + LIGHT_PROBE_RESOLUTION_256 = 8, + LIGHT_PROBE_RESOLUTION_512 = 9, + LIGHT_PROBE_RESOLUTION_1024 = 10, + LIGHT_PROBE_RESOLUTION_2048 = 11, +} eLightProbeResolution; + /* Probe->type */ enum { LIGHTPROBE_TYPE_CUBE = 0, diff --git a/source/blender/makesdna/DNA_world_defaults.h b/source/blender/makesdna/DNA_world_defaults.h index 3429ea4761e..d06d4c85c12 100644 --- a/source/blender/makesdna/DNA_world_defaults.h +++ b/source/blender/makesdna/DNA_world_defaults.h @@ -27,6 +27,8 @@ .preview = NULL, \ .miststa = 5.0f, \ .mistdist = 25.0f, \ + \ + .probe_resolution = LIGHT_PROBE_RESOLUTION_1024, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_world_types.h b/source/blender/makesdna/DNA_world_types.h index d274651b9d5..639517fb349 100644 --- a/source/blender/makesdna/DNA_world_types.h +++ b/source/blender/makesdna/DNA_world_types.h @@ -61,7 +61,13 @@ typedef struct World { /** Assorted settings. */ short flag; - char _pad3[6]; + char _pad3[2]; + + /** Eevee settings. */ + /** + * Resolution of the world probe when baked to a texture. Contains `eLightProbeResolution`. + */ + int probe_resolution; /** Old animation system, deprecated for 2.5. */ struct Ipo *ipo DNA_DEPRECATED; diff --git a/source/blender/makesrna/intern/rna_lightprobe.cc b/source/blender/makesrna/intern/rna_lightprobe.cc index b3fbe9af979..45d2389cba5 100644 --- a/source/blender/makesrna/intern/rna_lightprobe.cc +++ b/source/blender/makesrna/intern/rna_lightprobe.cc @@ -57,6 +57,16 @@ static EnumPropertyItem lightprobe_type_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static EnumPropertyItem lightprobe_resolution_items[] = { + {LIGHT_PROBE_RESOLUTION_64, "64", 0, "64", ""}, + {LIGHT_PROBE_RESOLUTION_128, "128", 0, "128", ""}, + {LIGHT_PROBE_RESOLUTION_256, "256", 0, "256", ""}, + {LIGHT_PROBE_RESOLUTION_512, "512", 0, "512", ""}, + {LIGHT_PROBE_RESOLUTION_1024, "1024", 0, "1024", ""}, + {LIGHT_PROBE_RESOLUTION_2048, "2048", 0, "2048", ""}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void rna_def_lightprobe(BlenderRNA *brna) { StructRNA *srna; @@ -189,6 +199,12 @@ static void rna_def_lightprobe(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Visibility Blur", "Filter size of the visibility blur"); RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc"); + prop = RNA_def_property(srna, "resolution", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "resolution"); + RNA_def_property_enum_items(prop, lightprobe_resolution_items); + RNA_def_property_ui_text(prop, "Resolution", "Resolution when baked to a texture"); + RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc"); + prop = RNA_def_property(srna, "intensity", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, nullptr, "intensity"); RNA_def_property_range(prop, 0.0f, FLT_MAX); diff --git a/source/blender/makesrna/intern/rna_world.cc b/source/blender/makesrna/intern/rna_world.cc index 1af4f6068d9..85914039fcc 100644 --- a/source/blender/makesrna/intern/rna_world.cc +++ b/source/blender/makesrna/intern/rna_world.cc @@ -13,6 +13,7 @@ #include "rna_internal.h" +#include "DNA_lightprobe_types.h" #include "DNA_material_types.h" #include "DNA_texture_types.h" #include "DNA_world_types.h" @@ -108,6 +109,16 @@ void rna_World_lightgroup_set(PointerRNA *ptr, const char *value) #else +static const EnumPropertyItem world_probe_resolution_items[] = { + {LIGHT_PROBE_RESOLUTION_64, "64", 0, "64", ""}, + {LIGHT_PROBE_RESOLUTION_128, "128", 0, "128", ""}, + {LIGHT_PROBE_RESOLUTION_256, "256", 0, "256", ""}, + {LIGHT_PROBE_RESOLUTION_512, "512", 0, "512", ""}, + {LIGHT_PROBE_RESOLUTION_1024, "1024", 0, "1024", ""}, + {LIGHT_PROBE_RESOLUTION_2048, "2048", 0, "2048", ""}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void rna_def_lighting(BlenderRNA *brna) { StructRNA *srna; @@ -263,6 +274,13 @@ void RNA_def_world(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Lightgroup", "Lightgroup that the world belongs to"); + /* Reflection Probe Baking. */ + prop = RNA_def_property(srna, "probe_resolution", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "probe_resolution"); + RNA_def_property_enum_items(prop, world_probe_resolution_items); + RNA_def_property_ui_text(prop, "Resolution", "Resolution when baked to a texture"); + RNA_def_property_update(prop, 0, "rna_World_draw_update"); + rna_def_lighting(brna); rna_def_world_mist(brna); }