Parallax distance was not actually use. It was affecting the world and had unwanted effect if lower than influence radius.
583 lines
19 KiB
C++
583 lines
19 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_bit_vector.hh"
|
|
|
|
#include "eevee_instance.hh"
|
|
#include "eevee_reflection_probes.hh"
|
|
|
|
#include <iostream>
|
|
|
|
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;
|
|
}
|
|
|
|
void ReflectionProbeModule::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,
|
|
9999);
|
|
GPU_texture_mipmap_mode(probes_tx_, true, true);
|
|
probes_tx_.clear(float4(0.0f));
|
|
}
|
|
|
|
{
|
|
PassSimple &pass = remap_ps_;
|
|
pass.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.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&reflection_probe_coord_));
|
|
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_probe_coord_));
|
|
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_probe_coord_));
|
|
pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_);
|
|
pass.bind_texture("reflection_probes_tx", &probes_tx_);
|
|
pass.dispatch(int2(1, 1));
|
|
}
|
|
}
|
|
|
|
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;
|
|
instance_.sampling.reset();
|
|
}
|
|
|
|
{
|
|
PassSimple &pass = select_ps_;
|
|
pass.init();
|
|
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_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);
|
|
pass.dispatch(&dispatch_probe_select_);
|
|
pass.barrier(GPU_BARRIER_UNIFORM);
|
|
}
|
|
}
|
|
|
|
int ReflectionProbeModule::needed_layers_get() const
|
|
{
|
|
int max_layer = 0;
|
|
for (const ReflectionProbe &probe : probes_.values()) {
|
|
max_layer = max_ii(max_layer, probe.atlas_coord.layer);
|
|
}
|
|
return max_layer + 1;
|
|
}
|
|
|
|
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*/)
|
|
{
|
|
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_probe_coord_ = probe.atlas_coord;
|
|
}
|
|
|
|
void ReflectionProbeModule::sync_world_lookdev()
|
|
{
|
|
do_world_update_set(true);
|
|
|
|
if (!update_probes_this_sample_) {
|
|
update_probes_next_sample_ = 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;
|
|
}
|
|
|
|
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()) {
|
|
probe.do_render = true;
|
|
}
|
|
}
|
|
|
|
const bool do_update = instance_.do_reflection_probe_sync() || (only_world && world_updated);
|
|
if (!do_update) {
|
|
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));
|
|
}
|
|
|
|
data_buf_.push_update();
|
|
}
|
|
|
|
bool ReflectionProbeModule::remove_unused_probes()
|
|
{
|
|
const int64_t removed_count = probes_.remove_if(
|
|
[](const ReflectionProbes::Item &item) { return !item.value.is_probe_used; });
|
|
|
|
if (removed_count > 0) {
|
|
instance_.sampling.reset();
|
|
}
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/* Check reset probe updating as we completed rendering all Probes. */
|
|
if (probe_type == ReflectionProbe::Type::PROBE && update_probes_this_sample_) {
|
|
update_probes_next_sample_ = false;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ReflectionProbeModule::remap_to_octahedral_projection(
|
|
const ReflectionProbeAtlasCoordinate &atlas_coord)
|
|
{
|
|
int resolution = max_resolution_ >> atlas_coord.layer_subdivision;
|
|
/* Update shader parameters that change per dispatch. */
|
|
reflection_probe_coord_ = atlas_coord;
|
|
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, REFLECTION_PROBE_GROUP_SIZE)), 1);
|
|
|
|
instance_.manager->submit(remap_ps_);
|
|
}
|
|
|
|
void ReflectionProbeModule::update_world_irradiance()
|
|
{
|
|
instance_.manager->submit(update_irradiance_ps_);
|
|
}
|
|
|
|
void ReflectionProbeModule::update_probes_texture_mipmaps()
|
|
{
|
|
GPU_texture_update_mipmap_chain(probes_tx_);
|
|
}
|
|
|
|
void ReflectionProbeModule::set_view(View & /*view*/)
|
|
{
|
|
Vector<ReflectionProbe *> probe_active;
|
|
for (auto &probe : probes_.values()) {
|
|
/* Last slot is reserved for the world probe. */
|
|
if (reflection_probe_count_ >= REFLECTION_PROBES_MAX - 1) {
|
|
break;
|
|
}
|
|
probe.recalc_lod_factors(probes_tx_.width());
|
|
/* World is always considered active and added last. */
|
|
if (probe.type == ReflectionProbe::Type::WORLD) {
|
|
continue;
|
|
}
|
|
/* TODO(fclem): Culling. */
|
|
probe_active.append(&probe);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
});
|
|
|
|
/* Push all sorted data to the UBO. */
|
|
int probe_id = 0;
|
|
for (auto &probe : probe_active) {
|
|
data_buf_[probe_id++] = *probe;
|
|
}
|
|
/* Add world probe at the end. */
|
|
data_buf_[probe_id++] = probes_.lookup(world_object_key_);
|
|
/* Tag the end of the array. */
|
|
if (probe_id < REFLECTION_PROBES_MAX) {
|
|
data_buf_[probe_id].atlas_coord.layer = -1;
|
|
}
|
|
data_buf_.push_update();
|
|
|
|
/* 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);
|
|
instance_.manager->submit(select_ps_);
|
|
}
|
|
|
|
ReflectionProbeAtlasCoordinate ReflectionProbeModule::world_atlas_coord_get() const
|
|
{
|
|
return probes_.lookup(world_object_key_).atlas_coord;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \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
|