WIP: Brush assets project #106303
|
@ -19,13 +19,13 @@
|
|||
* which is then stored per instance. Many instances can use the same #InstanceReference.
|
||||
*/
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_index_mask_fwd.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_shared_cache.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
|
@ -99,19 +99,16 @@ class Instances {
|
|||
*/
|
||||
Vector<InstanceReference> references_;
|
||||
|
||||
/** Indices into `references_`. Determines what data is instanced. */
|
||||
Vector<int> reference_handles_;
|
||||
/** Transformation of the instances. */
|
||||
Vector<float4x4> transforms_;
|
||||
|
||||
CustomData attributes_;
|
||||
|
||||
/* These almost unique ids are generated based on the `id` attribute, which might not contain
|
||||
* unique ids at all. They are *almost* unique, because under certain very unlikely
|
||||
* circumstances, they are not unique. Code using these ids should not crash when they are not
|
||||
* unique but can generally expect them to be unique. */
|
||||
mutable std::mutex almost_unique_ids_mutex_;
|
||||
mutable Array<int> almost_unique_ids_;
|
||||
|
||||
CustomData attributes_;
|
||||
mutable SharedCache<Array<int>> almost_unique_ids_cache_;
|
||||
|
||||
public:
|
||||
Instances();
|
||||
|
@ -161,7 +158,7 @@ class Instances {
|
|||
GeometrySet &geometry_set_from_reference(int reference_index);
|
||||
|
||||
Span<int> reference_handles() const;
|
||||
MutableSpan<int> reference_handles();
|
||||
MutableSpan<int> reference_handles_for_write();
|
||||
MutableSpan<float4x4> transforms();
|
||||
Span<float4x4> transforms() const;
|
||||
|
||||
|
@ -189,6 +186,11 @@ class Instances {
|
|||
|
||||
bool owns_direct_data() const;
|
||||
void ensure_owns_direct_data();
|
||||
|
||||
void tag_reference_handles_changed()
|
||||
{
|
||||
almost_unique_ids_cache_.tag_dirty();
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -163,6 +163,9 @@ void nodeChainIter(const bNodeTree *ntree,
|
|||
* Can be called recursively (using another nodeChainIterBackwards) by
|
||||
* setting the recursion_lvl accordingly.
|
||||
*
|
||||
* WARN: No node is guaranteed to be iterated as a to_node,
|
||||
* since it could have been iterated earlier as a from_node.
|
||||
*
|
||||
* \note Needs updated socket links (ntreeUpdateTree).
|
||||
* \note Recursive
|
||||
*/
|
||||
|
|
|
@ -129,6 +129,9 @@ bool allow_procedural_attribute_access(StringRef attribute_name)
|
|||
if (attribute_name.startswith(".uv")) {
|
||||
return false;
|
||||
}
|
||||
if (attribute_name == ".reference_index") {
|
||||
return false;
|
||||
}
|
||||
if (attribute_name.startswith("." UV_VERTSEL_NAME ".")) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -724,19 +724,25 @@ static std::unique_ptr<Instances> try_load_instances(const DictionaryValue &io_g
|
|||
return {};
|
||||
}
|
||||
|
||||
const auto *io_handles = io_instances->lookup_dict("handles");
|
||||
if (!io_handles) {
|
||||
return {};
|
||||
}
|
||||
if (!read_blob_simple_gspan(blob_reader, *io_handles, instances->reference_handles())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MutableAttributeAccessor attributes = instances->attributes_for_write();
|
||||
if (!load_attributes(*io_attributes, attributes, blob_reader, blob_sharing)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!attributes.contains(".reference_index")) {
|
||||
/* Try reading the reference index attribute from the old bake format from before it was an
|
||||
* attribute. */
|
||||
const auto *io_handles = io_instances->lookup_dict("handles");
|
||||
if (!io_handles) {
|
||||
return {};
|
||||
}
|
||||
if (!read_blob_simple_gspan(
|
||||
blob_reader, *io_handles, instances->reference_handles_for_write()))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
|
@ -969,9 +975,6 @@ static std::shared_ptr<DictionaryValue> serialize_geometry_set(const GeometrySet
|
|||
|
||||
io_instances->append(
|
||||
"transforms", write_blob_simple_gspan(blob_writer, blob_sharing, instances.transforms()));
|
||||
io_instances->append(
|
||||
"handles",
|
||||
write_blob_simple_gspan(blob_writer, blob_sharing, instances.reference_handles()));
|
||||
|
||||
auto io_attributes = serialize_attributes(
|
||||
instances.attributes(), blob_writer, blob_sharing, {"position"});
|
||||
|
|
|
@ -168,6 +168,12 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider
|
|||
}
|
||||
};
|
||||
|
||||
static void tag_component_reference_index_changed(void *owner)
|
||||
{
|
||||
Instances &instances = *static_cast<Instances *>(owner);
|
||||
instances.tag_reference_handles_changed();
|
||||
}
|
||||
|
||||
static ComponentAttributeProviders create_attribute_providers_for_instances()
|
||||
{
|
||||
static InstancePositionAttributeProvider position;
|
||||
|
@ -200,10 +206,20 @@ static ComponentAttributeProviders create_attribute_providers_for_instances()
|
|||
instance_custom_data_access,
|
||||
nullptr);
|
||||
|
||||
/** Indices into `Instances::references_`. Determines what data is instanced. */
|
||||
static BuiltinCustomDataLayerProvider reference_index(".reference_index",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_INT32,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
instance_custom_data_access,
|
||||
tag_component_reference_index_changed);
|
||||
|
||||
static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance,
|
||||
instance_custom_data_access);
|
||||
|
||||
return ComponentAttributeProviders({&position, &id}, {&instance_custom_data});
|
||||
return ComponentAttributeProviders({&position, &id, &reference_index}, {&instance_custom_data});
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_instances_accessor_functions()
|
||||
|
|
|
@ -51,19 +51,17 @@ Instances::Instances()
|
|||
|
||||
Instances::Instances(Instances &&other)
|
||||
: references_(std::move(other.references_)),
|
||||
reference_handles_(std::move(other.reference_handles_)),
|
||||
transforms_(std::move(other.transforms_)),
|
||||
almost_unique_ids_(std::move(other.almost_unique_ids_)),
|
||||
attributes_(other.attributes_)
|
||||
attributes_(other.attributes_),
|
||||
almost_unique_ids_cache_(std::move(other.almost_unique_ids_cache_))
|
||||
{
|
||||
CustomData_reset(&other.attributes_);
|
||||
}
|
||||
|
||||
Instances::Instances(const Instances &other)
|
||||
: references_(other.references_),
|
||||
reference_handles_(other.reference_handles_),
|
||||
transforms_(other.transforms_),
|
||||
almost_unique_ids_(other.almost_unique_ids_)
|
||||
almost_unique_ids_cache_(other.almost_unique_ids_cache_)
|
||||
{
|
||||
CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num());
|
||||
}
|
||||
|
@ -96,7 +94,6 @@ Instances &Instances::operator=(Instances &&other)
|
|||
void Instances::resize(int capacity)
|
||||
{
|
||||
const int old_size = this->instances_num();
|
||||
reference_handles_.resize(capacity);
|
||||
transforms_.resize(capacity);
|
||||
CustomData_realloc(&attributes_, old_size, capacity, CD_SET_DEFAULT);
|
||||
}
|
||||
|
@ -106,19 +103,27 @@ void Instances::add_instance(const int instance_handle, const float4x4 &transfor
|
|||
BLI_assert(instance_handle >= 0);
|
||||
BLI_assert(instance_handle < references_.size());
|
||||
const int old_size = this->instances_num();
|
||||
reference_handles_.append(instance_handle);
|
||||
transforms_.append(transform);
|
||||
CustomData_realloc(&attributes_, old_size, transforms_.size());
|
||||
this->reference_handles_for_write().last() = instance_handle;
|
||||
}
|
||||
|
||||
Span<int> Instances::reference_handles() const
|
||||
{
|
||||
return reference_handles_;
|
||||
return {static_cast<const int *>(
|
||||
CustomData_get_layer_named(&attributes_, CD_PROP_INT32, ".reference_index")),
|
||||
this->instances_num()};
|
||||
}
|
||||
|
||||
MutableSpan<int> Instances::reference_handles()
|
||||
MutableSpan<int> Instances::reference_handles_for_write()
|
||||
{
|
||||
return reference_handles_;
|
||||
int *data = static_cast<int *>(CustomData_get_layer_named_for_write(
|
||||
&attributes_, CD_PROP_INT32, ".reference_index", this->instances_num()));
|
||||
if (!data) {
|
||||
data = static_cast<int *>(CustomData_add_layer_named(
|
||||
&attributes_, CD_PROP_INT32, CD_SET_DEFAULT, this->instances_num(), ".reference_index"));
|
||||
}
|
||||
return {data, this->instances_num()};
|
||||
}
|
||||
|
||||
MutableSpan<float4x4> Instances::transforms()
|
||||
|
@ -178,10 +183,7 @@ void Instances::remove(const IndexMask &mask,
|
|||
|
||||
Instances new_instances;
|
||||
new_instances.references_ = std::move(references_);
|
||||
new_instances.reference_handles_.resize(new_size);
|
||||
new_instances.transforms_.resize(new_size);
|
||||
array_utils::gather(
|
||||
reference_handles_.as_span(), mask, new_instances.reference_handles_.as_mutable_span());
|
||||
array_utils::gather(transforms_.as_span(), mask, new_instances.transforms_.as_mutable_span());
|
||||
|
||||
gather_attributes(this->attributes(),
|
||||
|
@ -212,6 +214,8 @@ void Instances::remove_unused_references()
|
|||
return;
|
||||
}
|
||||
|
||||
const Span<int> reference_handles = this->reference_handles();
|
||||
|
||||
Array<bool> usage_by_handle(tot_references_before, false);
|
||||
std::mutex mutex;
|
||||
|
||||
|
@ -221,7 +225,7 @@ void Instances::remove_unused_references()
|
|||
Array<bool> local_usage_by_handle(tot_references_before, false);
|
||||
|
||||
for (const int i : range) {
|
||||
const int handle = reference_handles_[i];
|
||||
const int handle = reference_handles[i];
|
||||
BLI_assert(handle >= 0 && handle < tot_references_before);
|
||||
local_usage_by_handle[handle] = true;
|
||||
}
|
||||
|
@ -266,11 +270,14 @@ void Instances::remove_unused_references()
|
|||
}
|
||||
|
||||
/* Update handles of instances. */
|
||||
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
reference_handles_[i] = handle_mapping[reference_handles_[i]];
|
||||
}
|
||||
});
|
||||
{
|
||||
const MutableSpan<int> reference_handles = this->reference_handles_for_write();
|
||||
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
reference_handles[i] = handle_mapping[reference_handles[i]];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int Instances::instances_num() const
|
||||
|
@ -357,21 +364,20 @@ static Array<int> generate_unique_instance_ids(Span<int> original_ids)
|
|||
|
||||
Span<int> Instances::almost_unique_ids() const
|
||||
{
|
||||
std::lock_guard lock(almost_unique_ids_mutex_);
|
||||
bke::AttributeReader<int> instance_ids_attribute = this->attributes().lookup<int>("id");
|
||||
if (instance_ids_attribute) {
|
||||
Span<int> instance_ids = instance_ids_attribute.varray.get_internal_span();
|
||||
if (almost_unique_ids_.size() != instance_ids.size()) {
|
||||
almost_unique_ids_ = generate_unique_instance_ids(instance_ids);
|
||||
almost_unique_ids_cache_.ensure([&](Array<int> &r_data) {
|
||||
bke::AttributeReader<int> instance_ids_attribute = this->attributes().lookup<int>("id");
|
||||
if (instance_ids_attribute) {
|
||||
Span<int> instance_ids = instance_ids_attribute.varray.get_internal_span();
|
||||
if (r_data.size() != instance_ids.size()) {
|
||||
r_data = generate_unique_instance_ids(instance_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
almost_unique_ids_.reinitialize(this->instances_num());
|
||||
for (const int i : almost_unique_ids_.index_range()) {
|
||||
almost_unique_ids_[i] = i;
|
||||
else {
|
||||
r_data.reinitialize(this->instances_num());
|
||||
array_utils::fill_index_range(r_data.as_mutable_span());
|
||||
}
|
||||
}
|
||||
return almost_unique_ids_;
|
||||
});
|
||||
return almost_unique_ids_cache_.data();
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
|
|
@ -60,6 +60,8 @@ bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL();
|
|||
# include <iosfwd>
|
||||
# include <string>
|
||||
|
||||
# include "BLI_string_ref.hh"
|
||||
|
||||
/** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */
|
||||
std::ostream &operator<<(std::ostream &stream, bUUID uuid);
|
||||
|
||||
|
@ -79,7 +81,7 @@ class bUUID : public ::bUUID {
|
|||
bUUID(std::initializer_list<uint32_t> field_values);
|
||||
|
||||
/** Initialize by parsing the string; undefined behavior when the string is invalid. */
|
||||
explicit bUUID(const std::string &string_formatted_uuid);
|
||||
explicit bUUID(const StringRefNull string_formatted_uuid);
|
||||
|
||||
uint64_t hash() const;
|
||||
}; // namespace blender
|
||||
|
|
|
@ -153,7 +153,7 @@ bUUID::bUUID(const std::initializer_list<uint32_t> field_values)
|
|||
std::copy(field_iter, field_values.end(), this->node);
|
||||
}
|
||||
|
||||
bUUID::bUUID(const std::string &string_formatted_uuid)
|
||||
bUUID::bUUID(const StringRefNull string_formatted_uuid)
|
||||
{
|
||||
const bool parsed_ok = BLI_uuid_parse_string(this, string_formatted_uuid.c_str());
|
||||
if (!parsed_ok) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(){};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 &)>;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -647,13 +647,14 @@ static void drw_call_calc_orco(const Object *ob, float (*r_orcofacs)[4])
|
|||
const Volume &volume = *reinterpret_cast<const Volume *>(ob_data);
|
||||
const std::optional<blender::Bounds<blender::float3>> bounds = BKE_volume_min_max(&volume);
|
||||
if (bounds) {
|
||||
mid_v3_v3v3(static_buf.texspace_location, bounds->max, bounds->min);
|
||||
sub_v3_v3v3(static_buf.texspace_size, bounds->max, bounds->min);
|
||||
texspace_location = static_buf.texspace_location;
|
||||
texspace_size = static_buf.texspace_size;
|
||||
mid_v3_v3v3(texspace_location, bounds->max, bounds->min);
|
||||
sub_v3_v3v3(texspace_size, bounds->max, bounds->min);
|
||||
texspace_size[0] = std::max(texspace_size[0], 0.001f);
|
||||
texspace_size[1] = std::max(texspace_size[1], 0.001f);
|
||||
texspace_size[2] = std::max(texspace_size[2], 0.001f);
|
||||
}
|
||||
static_buf.texspace_size[0] = std::max(static_buf.texspace_size[0], 0.001f);
|
||||
static_buf.texspace_size[1] = std::max(static_buf.texspace_size[1], 0.001f);
|
||||
static_buf.texspace_size[2] = std::max(static_buf.texspace_size[2], 0.001f);
|
||||
texspace_location = static_buf.texspace_location;
|
||||
break;
|
||||
}
|
||||
case ID_ME:
|
||||
|
|
|
@ -144,7 +144,7 @@ struct AssetEntryReader {
|
|||
*/
|
||||
DictionaryValue::Lookup lookup;
|
||||
|
||||
StringRefNull get_name_with_idcode() const
|
||||
StringRef get_name_with_idcode() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_NAME)->as_string_value()->value();
|
||||
}
|
||||
|
@ -154,13 +154,13 @@ struct AssetEntryReader {
|
|||
|
||||
ID_Type get_idcode() const
|
||||
{
|
||||
const StringRefNull name_with_idcode = get_name_with_idcode();
|
||||
return GS(name_with_idcode.c_str());
|
||||
const StringRef name_with_idcode = get_name_with_idcode();
|
||||
return GS(name_with_idcode.data());
|
||||
}
|
||||
|
||||
StringRef get_name() const
|
||||
{
|
||||
const StringRefNull name_with_idcode = get_name_with_idcode();
|
||||
const StringRef name_with_idcode = get_name_with_idcode();
|
||||
return name_with_idcode.substr(2);
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ struct AssetEntryReader {
|
|||
return lookup.contains(ATTRIBUTE_ENTRIES_DESCRIPTION);
|
||||
}
|
||||
|
||||
StringRefNull get_description() const
|
||||
StringRef get_description() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_DESCRIPTION)->as_string_value()->value();
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ struct AssetEntryReader {
|
|||
return lookup.contains(ATTRIBUTE_ENTRIES_AUTHOR);
|
||||
}
|
||||
|
||||
StringRefNull get_author() const
|
||||
StringRef get_author() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_AUTHOR)->as_string_value()->value();
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ struct AssetEntryReader {
|
|||
return lookup.contains(ATTRIBUTE_ENTRIES_COPYRIGHT);
|
||||
}
|
||||
|
||||
StringRefNull get_copyright() const
|
||||
StringRef get_copyright() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_COPYRIGHT)->as_string_value()->value();
|
||||
}
|
||||
|
@ -199,19 +199,19 @@ struct AssetEntryReader {
|
|||
return lookup.contains(ATTRIBUTE_ENTRIES_LICENSE);
|
||||
}
|
||||
|
||||
StringRefNull get_license() const
|
||||
StringRef get_license() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_LICENSE)->as_string_value()->value();
|
||||
}
|
||||
|
||||
StringRefNull get_catalog_name() const
|
||||
StringRef get_catalog_name() const
|
||||
{
|
||||
return lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_NAME)->as_string_value()->value();
|
||||
}
|
||||
|
||||
CatalogID get_catalog_id() const
|
||||
{
|
||||
const std::string &catalog_id =
|
||||
const StringRefNull catalog_id =
|
||||
lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_ID)->as_string_value()->value();
|
||||
CatalogID catalog_uuid(catalog_id);
|
||||
return catalog_uuid;
|
||||
|
@ -400,36 +400,24 @@ static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry,
|
|||
indexer_entry.datablock_info.free_asset_data = true;
|
||||
|
||||
if (entry.has_description()) {
|
||||
const StringRefNull description = entry.get_description();
|
||||
const size_t c_str_size = description.size() + 1;
|
||||
char *description_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
|
||||
memcpy(description_c_str, description.c_str(), c_str_size);
|
||||
asset_data->description = description_c_str;
|
||||
const StringRef description = entry.get_description();
|
||||
asset_data->description = BLI_strdupn(description.data(), description.size());
|
||||
}
|
||||
if (entry.has_author()) {
|
||||
const StringRefNull author = entry.get_author();
|
||||
const size_t c_str_size = author.size() + 1;
|
||||
char *author_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
|
||||
memcpy(author_c_str, author.c_str(), c_str_size);
|
||||
asset_data->author = author_c_str;
|
||||
const StringRef author = entry.get_author();
|
||||
asset_data->author = BLI_strdupn(author.data(), author.size());
|
||||
}
|
||||
if (entry.has_copyright()) {
|
||||
const StringRefNull copyright = entry.get_copyright();
|
||||
const size_t c_str_size = copyright.size() + 1;
|
||||
char *copyright_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
|
||||
memcpy(copyright_c_str, copyright.c_str(), c_str_size);
|
||||
asset_data->copyright = copyright_c_str;
|
||||
const StringRef copyright = entry.get_copyright();
|
||||
asset_data->copyright = BLI_strdupn(copyright.data(), copyright.size());
|
||||
}
|
||||
if (entry.has_license()) {
|
||||
const StringRefNull license = entry.get_license();
|
||||
const size_t c_str_size = license.size() + 1;
|
||||
char *license_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
|
||||
memcpy(license_c_str, license.c_str(), c_str_size);
|
||||
asset_data->license = license_c_str;
|
||||
const StringRef license = entry.get_license();
|
||||
asset_data->license = BLI_strdupn(license.data(), license.size());
|
||||
}
|
||||
|
||||
const StringRefNull catalog_name = entry.get_catalog_name();
|
||||
STRNCPY(asset_data->catalog_simple_name, catalog_name.c_str());
|
||||
const StringRef catalog_name = entry.get_catalog_name();
|
||||
catalog_name.copy(asset_data->catalog_simple_name);
|
||||
|
||||
asset_data->catalog_id = entry.get_catalog_id();
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
|
|||
dst_instances->resize(offsets.total_size());
|
||||
|
||||
MutableSpan<float4x4> all_transforms = dst_instances->transforms();
|
||||
MutableSpan<int> all_handles = dst_instances->reference_handles();
|
||||
MutableSpan<int> all_handles = dst_instances->reference_handles_for_write();
|
||||
|
||||
for (const int i : src_components.index_range()) {
|
||||
const auto &src_component = static_cast<const bke::InstancesComponent &>(*src_components[i]);
|
||||
|
@ -131,7 +131,7 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
|
|||
|
||||
result.replace_instances(dst_instances.release());
|
||||
auto &dst_component = result.get_component_for_write<bke::InstancesComponent>();
|
||||
join_attributes(src_components, dst_component, {"position"});
|
||||
join_attributes(src_components, dst_component, {"position", ".reference_index"});
|
||||
}
|
||||
|
||||
static void join_volumes(const Span<const GeometryComponent *> /*src_components*/,
|
||||
|
|
|
@ -253,7 +253,7 @@ void debug_randomize_instance_order(bke::Instances *instances)
|
|||
new_transforms[new_i] = old_transforms[old_i];
|
||||
}
|
||||
|
||||
instances->reference_handles().copy_from(new_reference_handles);
|
||||
instances->reference_handles_for_write().copy_from(new_reference_handles);
|
||||
instances->transforms().copy_from(new_transforms);
|
||||
}
|
||||
|
||||
|
|
|
@ -210,10 +210,6 @@ static void reorder_instaces_exec(const bke::Instances &src_instances,
|
|||
old_by_new_map,
|
||||
dst_instances.attributes_for_write());
|
||||
|
||||
const Span<int> old_reference_handles = src_instances.reference_handles();
|
||||
MutableSpan<int> new_reference_handles = dst_instances.reference_handles();
|
||||
array_utils::gather(old_reference_handles, old_by_new_map, new_reference_handles);
|
||||
|
||||
const Span<float4x4> old_transforms = src_instances.transforms();
|
||||
MutableSpan<float4x4> new_transforms = dst_instances.transforms();
|
||||
array_utils::gather(old_transforms, old_by_new_map, new_transforms);
|
||||
|
|
|
@ -956,13 +956,13 @@ static void duplicate_instances(GeometrySet &geometry_set,
|
|||
const int new_handle = dst_instances->add_reference(reference);
|
||||
const float4x4 transform = src_instances.transforms()[i_selection];
|
||||
dst_instances->transforms().slice(range).fill(transform);
|
||||
dst_instances->reference_handles().slice(range).fill(new_handle);
|
||||
dst_instances->reference_handles_for_write().slice(range).fill(new_handle);
|
||||
}
|
||||
|
||||
bke::gather_attributes_to_groups(src_instances.attributes(),
|
||||
AttrDomain::Instance,
|
||||
propagation_info,
|
||||
{"id"},
|
||||
{"id", ".reference_index"},
|
||||
duplicates,
|
||||
selection,
|
||||
dst_instances->attributes_for_write());
|
||||
|
|
|
@ -85,7 +85,8 @@ static void add_instances_from_component(
|
|||
const int select_len = selection.index_range().size();
|
||||
dst_component.resize(start_len + select_len);
|
||||
|
||||
MutableSpan<int> dst_handles = dst_component.reference_handles().slice(start_len, select_len);
|
||||
MutableSpan<int> dst_handles = dst_component.reference_handles_for_write().slice(start_len,
|
||||
select_len);
|
||||
MutableSpan<float4x4> dst_transforms = dst_component.transforms().slice(start_len, select_len);
|
||||
|
||||
const VArraySpan positions = *src_attributes.lookup<float3>("position");
|
||||
|
@ -213,6 +214,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
propagation_info,
|
||||
attributes_to_propagate);
|
||||
attributes_to_propagate.remove("position");
|
||||
attributes_to_propagate.remove(".reference_index");
|
||||
|
||||
for (const GeometryComponent::Type type : types) {
|
||||
if (geometry_set.has(type)) {
|
||||
|
|
|
@ -264,8 +264,6 @@ static void split_instance_groups(const InstancesComponent &component,
|
|||
}
|
||||
|
||||
array_utils::gather(src_instances.transforms(), mask, group_instances->transforms());
|
||||
array_utils::gather(
|
||||
src_instances.reference_handles(), mask, group_instances->reference_handles());
|
||||
bke::gather_attributes(src_instances.attributes(),
|
||||
AttrDomain::Instance,
|
||||
propagation_info,
|
||||
|
@ -343,7 +341,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
}
|
||||
|
||||
dst_instances->transforms().fill(float4x4::identity());
|
||||
array_utils::fill_index_range(dst_instances->reference_handles());
|
||||
array_utils::fill_index_range(dst_instances->reference_handles_for_write());
|
||||
|
||||
for (auto item : geometry_by_group_id.items()) {
|
||||
std::unique_ptr<GeometrySet> &group_geometry = item.value;
|
||||
|
|
|
@ -319,7 +319,7 @@ static void add_instances_from_handles(bke::Instances &instances,
|
|||
const TextLayout &layout)
|
||||
{
|
||||
instances.resize(layout.positions.size());
|
||||
MutableSpan<int> handles = instances.reference_handles();
|
||||
MutableSpan<int> handles = instances.reference_handles_for_write();
|
||||
MutableSpan<float4x4> transforms = instances.transforms();
|
||||
|
||||
threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "BLI_linklist.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_threads.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
@ -580,69 +581,87 @@ static bool ntree_branch_count_and_tag_nodes(bNode *fromnode, bNode *tonode, voi
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Create a copy of a branch starting from a given node.
|
||||
* callback is executed once for every copied node.
|
||||
* Returns input node copy. */
|
||||
static bNode *ntree_shader_copy_branch(bNodeTree *ntree,
|
||||
bNode *start_node,
|
||||
bool (*node_filter)(const bNode *node),
|
||||
void (*callback)(bNode *node, int user_data),
|
||||
int user_data)
|
||||
/* Create a copy of a branch starting from a given node. */
|
||||
static void ntree_shader_copy_branch(bNodeTree *ntree,
|
||||
bNode *start_node,
|
||||
bool (*node_filter)(const bNode *node))
|
||||
{
|
||||
auto gather_branch_nodes = [](bNode *fromnode, bNode * /*tonode*/, void *userdata) {
|
||||
blender::Set<bNode *> *set = static_cast<blender::Set<bNode *> *>(userdata);
|
||||
set->add(fromnode);
|
||||
return true;
|
||||
};
|
||||
blender::Set<bNode *> branch_nodes = {start_node};
|
||||
blender::bke::nodeChainIterBackwards(ntree, start_node, gather_branch_nodes, &branch_nodes, 0);
|
||||
|
||||
/* Initialize `runtime->tmp_flag`. */
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
node->runtime->tmp_flag = -1;
|
||||
}
|
||||
/* Count and tag all nodes inside the displacement branch of the tree. */
|
||||
start_node->runtime->tmp_flag = 0;
|
||||
branchIterData iter_data;
|
||||
iter_data.node_filter = node_filter;
|
||||
iter_data.node_count = 1;
|
||||
iter_data.node_count = 0;
|
||||
blender::bke::nodeChainIterBackwards(
|
||||
ntree, start_node, ntree_branch_count_and_tag_nodes, &iter_data, 1);
|
||||
/* Make a full copy of the branch */
|
||||
/* Copies of the non-filtered nodes on the branch. */
|
||||
Array<bNode *> nodes_copy(iter_data.node_count);
|
||||
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->runtime->tmp_flag >= 0) {
|
||||
int id = node->runtime->tmp_flag;
|
||||
/* Avoid creating unique names in the new tree, since it is very slow. The names on the new
|
||||
* nodes will be invalid. But identifiers must be created for the `bNodeTree::all_nodes()`
|
||||
* vector, though they won't match the original. */
|
||||
/* Avoid creating unique names in the new tree, since it is very slow.
|
||||
* The names on the new nodes will be invalid. */
|
||||
nodes_copy[id] = blender::bke::node_copy(
|
||||
ntree, *node, LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_MAIN, false);
|
||||
/* But identifiers must be created for the `bNodeTree::all_nodes()` vector,
|
||||
* so they won't match the original. */
|
||||
nodeUniqueID(ntree, nodes_copy[id]);
|
||||
|
||||
nodes_copy[id]->runtime->tmp_flag = -2; /* Copy */
|
||||
nodes_copy[id]->runtime->original = node->runtime->original;
|
||||
bNode *copy = nodes_copy[id];
|
||||
copy->runtime->tmp_flag = -2; /* Copy */
|
||||
copy->runtime->original = node->runtime->original;
|
||||
/* Make sure to clear all sockets links as they are invalid. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &nodes_copy[id]->inputs) {
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, ©->inputs) {
|
||||
sock->link = nullptr;
|
||||
}
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &nodes_copy[id]->outputs) {
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, ©->outputs) {
|
||||
sock->link = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Recreate links between copied nodes AND incoming links to the copied nodes. */
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
|
||||
if (link->tonode->runtime->tmp_flag >= 0) {
|
||||
bool from_node_copied = link->fromnode->runtime->tmp_flag >= 0;
|
||||
bNode *fromnode = from_node_copied ? nodes_copy[link->fromnode->runtime->tmp_flag] :
|
||||
link->fromnode;
|
||||
bNode *tonode = nodes_copy[link->tonode->runtime->tmp_flag];
|
||||
bNodeSocket *fromsock = ntree_shader_node_find_output(fromnode, link->fromsock->identifier);
|
||||
bNodeSocket *tosock = ntree_shader_node_find_input(tonode, link->tosock->identifier);
|
||||
nodeAddLink(ntree, fromnode, fromsock, tonode, tosock);
|
||||
|
||||
/* Unlink the original nodes from this branch and link the copies. */
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
|
||||
bool from_copy = link->fromnode->runtime->tmp_flag >= 0;
|
||||
bool to_copy = link->tonode->runtime->tmp_flag >= 0;
|
||||
if (from_copy && to_copy) {
|
||||
bNode *from_node = nodes_copy[link->fromnode->runtime->tmp_flag];
|
||||
bNode *to_node = nodes_copy[link->tonode->runtime->tmp_flag];
|
||||
nodeAddLink(ntree,
|
||||
from_node,
|
||||
ntree_shader_node_find_output(from_node, link->fromsock->identifier),
|
||||
to_node,
|
||||
ntree_shader_node_find_input(to_node, link->tosock->identifier));
|
||||
}
|
||||
else if (to_copy) {
|
||||
bNode *to_node = nodes_copy[link->tonode->runtime->tmp_flag];
|
||||
nodeAddLink(ntree,
|
||||
link->fromnode,
|
||||
link->fromsock,
|
||||
to_node,
|
||||
ntree_shader_node_find_input(to_node, link->tosock->identifier));
|
||||
}
|
||||
else if (from_copy && branch_nodes.contains(link->tonode)) {
|
||||
bNode *from_node = nodes_copy[link->fromnode->runtime->tmp_flag];
|
||||
nodeAddLink(ntree,
|
||||
from_node,
|
||||
ntree_shader_node_find_output(from_node, link->fromsock->identifier),
|
||||
link->tonode,
|
||||
link->tosock);
|
||||
nodeRemLink(ntree, link);
|
||||
}
|
||||
}
|
||||
/* Per node callback. */
|
||||
if (callback) {
|
||||
for (int i = 0; i < iter_data.node_count; i++) {
|
||||
callback(nodes_copy[i], user_data);
|
||||
}
|
||||
}
|
||||
bNode *start_node_copy = nodes_copy[start_node->runtime->tmp_flag];
|
||||
return start_node_copy;
|
||||
}
|
||||
|
||||
/* Generate emission node to convert regular data to closure sockets.
|
||||
|
@ -985,47 +1004,51 @@ static bool closure_node_filter(const bNode *node)
|
|||
}
|
||||
}
|
||||
|
||||
static bool shader_to_rgba_node_gather(bNode * /*fromnode*/, bNode *tonode, void *userdata)
|
||||
{
|
||||
Vector<bNode *> &shader_to_rgba_nodes = *(Vector<bNode *> *)userdata;
|
||||
if (tonode->runtime->tmp_flag == -1 && tonode->type == SH_NODE_SHADERTORGB) {
|
||||
tonode->runtime->tmp_flag = 0;
|
||||
shader_to_rgba_nodes.append(tonode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Shader to rgba needs their associated closure duplicated and the weight tree generated for. */
|
||||
static void ntree_shader_shader_to_rgba_branch(bNodeTree *ntree, bNode *output_node)
|
||||
{
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
node->runtime->tmp_flag = -1;
|
||||
}
|
||||
/* First gather the shader_to_rgba nodes linked to the output. This is separate to avoid
|
||||
* conflicting usage of the `node->runtime->tmp_flag`. */
|
||||
Vector<bNode *> shader_to_rgba_nodes;
|
||||
blender::bke::nodeChainIterBackwards(
|
||||
ntree, output_node, shader_to_rgba_node_gather, &shader_to_rgba_nodes, 0);
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->type == SH_NODE_SHADERTORGB) {
|
||||
shader_to_rgba_nodes.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
for (bNode *shader_to_rgba : shader_to_rgba_nodes) {
|
||||
bNodeSocket *closure_input = ntree_shader_node_input_get(shader_to_rgba, 0);
|
||||
if (closure_input->link == nullptr) {
|
||||
continue;
|
||||
}
|
||||
bNode *start_node = closure_input->link->fromnode;
|
||||
bNode *start_node_copy = ntree_shader_copy_branch(
|
||||
ntree, start_node, closure_node_filter, nullptr, 0);
|
||||
/* Replace node copy link. This assumes that every node possibly connected to the closure input
|
||||
* has only one output. */
|
||||
bNodeSocket *closure_output = ntree_shader_node_output_get(start_node_copy, 0);
|
||||
nodeRemLink(ntree, closure_input->link);
|
||||
nodeAddLink(ntree, start_node_copy, closure_output, shader_to_rgba, closure_input);
|
||||
ntree_shader_copy_branch(ntree, shader_to_rgba, closure_node_filter);
|
||||
BKE_ntree_update_main_tree(G.main, ntree, nullptr);
|
||||
|
||||
ntree_shader_weight_tree_invert(ntree, shader_to_rgba);
|
||||
}
|
||||
}
|
||||
|
||||
static void iter_shader_to_rgba_depth_count(bNode *node,
|
||||
int16_t &max_depth,
|
||||
int16_t depth_level = 0)
|
||||
{
|
||||
if (node->type == SH_NODE_SHADERTORGB) {
|
||||
depth_level++;
|
||||
max_depth = std::max(max_depth, depth_level);
|
||||
}
|
||||
node->runtime->tmp_flag = std::max(node->runtime->tmp_flag, depth_level);
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
bNodeLink *link = sock->link;
|
||||
if (link == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if ((link->flag & NODE_LINK_VALID) == 0) {
|
||||
/* Skip links marked as cyclic. */
|
||||
continue;
|
||||
}
|
||||
iter_shader_to_rgba_depth_count(link->fromnode, max_depth, depth_level);
|
||||
}
|
||||
}
|
||||
|
||||
static void shader_node_disconnect_input(bNodeTree *ntree, bNode *node, int index)
|
||||
{
|
||||
bNodeLink *link = ntree_shader_node_input_get(node, index)->link;
|
||||
|
@ -1180,10 +1203,24 @@ void ntreeGPUMaterialNodes(bNodeTree *localtree, GPUMaterial *mat)
|
|||
}
|
||||
|
||||
exec = ntreeShaderBeginExecTree(localtree);
|
||||
ntreeExecGPUNodes(exec, mat, output);
|
||||
/* Execute nodes ordered by the number of ShaderToRGB nodes found in their path,
|
||||
* so all closures can be properly evaluated. */
|
||||
int16_t max_depth = 0;
|
||||
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
|
||||
node->runtime->tmp_flag = -1;
|
||||
}
|
||||
iter_shader_to_rgba_depth_count(output, max_depth);
|
||||
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
|
||||
if (node->type == SH_NODE_OUTPUT_AOV) {
|
||||
ntreeExecGPUNodes(exec, mat, node);
|
||||
iter_shader_to_rgba_depth_count(node, max_depth);
|
||||
}
|
||||
}
|
||||
for (int depth = max_depth; depth >= 0; depth--) {
|
||||
ntreeExecGPUNodes(exec, mat, output, &depth);
|
||||
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
|
||||
if (node->type == SH_NODE_OUTPUT_AOV) {
|
||||
ntreeExecGPUNodes(exec, mat, node, &depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
ntreeShaderEndExecTree(exec);
|
||||
|
|
|
@ -305,7 +305,7 @@ bNode *nodeGetActivePaintCanvas(bNodeTree *ntree)
|
|||
}
|
||||
} // namespace blender::bke
|
||||
|
||||
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node)
|
||||
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node, int *depth_level)
|
||||
{
|
||||
bNodeExec *nodeexec;
|
||||
bNode *node;
|
||||
|
@ -321,6 +321,10 @@ void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node
|
|||
for (n = 0, nodeexec = exec->nodeexec; n < exec->totnodes; n++, nodeexec++) {
|
||||
node = nodeexec->node;
|
||||
|
||||
if (depth_level && node->runtime->tmp_flag != *depth_level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
do_it = false;
|
||||
/* for groups, only execute outputs for edited group */
|
||||
if (node->typeinfo->nclass == NODE_CLASS_OUTPUT) {
|
||||
|
@ -334,6 +338,7 @@ void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node
|
|||
}
|
||||
|
||||
if (do_it) {
|
||||
BLI_assert(!depth_level || node->runtime->tmp_flag >= 0);
|
||||
if (node->typeinfo->gpu_fn) {
|
||||
node_get_stack(node, stack, nsin, nsout);
|
||||
gpu_stack_from_data_list(gpuin, &node->inputs, nsin);
|
||||
|
|
|
@ -68,7 +68,14 @@ bNodeTreeExec *ntreeShaderBeginExecTree_internal(bNodeExecContext *context,
|
|||
bNodeInstanceKey parent_key);
|
||||
void ntreeShaderEndExecTree_internal(bNodeTreeExec *exec);
|
||||
|
||||
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node);
|
||||
/* If depth_level is not null, only nodes where `node->runtime->tmp_flag == depth_level` will be
|
||||
* executed. This allows finer control over node execution order without modifying the tree
|
||||
* topology. */
|
||||
void ntreeExecGPUNodes(bNodeTreeExec *exec,
|
||||
GPUMaterial *mat,
|
||||
bNode *output_node,
|
||||
int *depth_level = nullptr);
|
||||
|
||||
void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data);
|
||||
|
||||
bool node_socket_not_zero(const GPUNodeStack &socket);
|
||||
|
|
Loading…
Reference in New Issue