WIP: Eevee-next: Reflection Probes #108497

Closed
Jeroen Bakker wants to merge 24 commits from Jeroen-Bakker:eevee-next-reflection-probes into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
20 changed files with 929 additions and 6 deletions

View File

@ -154,6 +154,7 @@ set(SRC
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_reflection_probes.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
engines/eevee_next/eevee_shader.cc
@ -294,6 +295,7 @@ set(SRC
engines/eevee_next/eevee_material.hh
engines/eevee_next/eevee_motion_blur.hh
engines/eevee_next/eevee_pipeline.hh
engines/eevee_next/eevee_reflection_probes.hh
engines/eevee_next/eevee_renderbuffers.hh
engines/eevee_next/eevee_sampling.hh
engines/eevee_next/eevee_shader.hh
@ -508,6 +510,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl
engines/eevee_next/shaders/eevee_shadow_lib.glsl

View File

@ -26,6 +26,9 @@
#define CULLING_ZBIN_GROUP_SIZE 1024
#define CULLING_TILE_GROUP_SIZE 256
/* Reflection probes */
#define REFLECTION_PROBES_MAX 256
/**
* IMPORTANT: Some data packing are tweaked for these values.
* Be sure to update them accordingly.
@ -108,6 +111,7 @@
#define SHADOW_ATLAS_TEX_SLOT 5
#define SSS_TRANSMITTANCE_TEX_SLOT 6
#define IRRADIANCE_ATLAS_TEX_SLOT 7
#define REFLECTION_PROBE_TEX_SLOT 8
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 4
@ -143,6 +147,8 @@
#define SHADOW_PAGE_INFO_SLOT 4
#define SAMPLING_BUF_SLOT 6
#define CRYPTOMATTE_BUF_SLOT 7
/* Only during reflection probe evaluation. */
#define REFLECTION_PROBE_BUF_SLOT 4
/* Only during pre-pass. */
#define VELOCITY_OBJ_PREV_BUF_SLOT 0

View File

@ -70,6 +70,7 @@ void Instance::init(const int2 &output_res,
motion_blur.init();
main_view.init();
irradiance_cache.init();
reflection_probes.init();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
@ -211,6 +212,8 @@ 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, res_handle);
/* TODO: should be moved into sync module? */
light_probes.sync_probe(ob, ob_handle);
break;
default:
@ -239,6 +242,7 @@ void Instance::end_sync()
lights.end_sync();
sampling.end_sync();
subsurface.end_sync();
reflection_probes.end_sync();
film.end_sync();
cryptomatte.end_sync();
pipelines.end_sync();

View File

@ -27,6 +27,7 @@
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_reflection_probes.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
@ -54,6 +55,7 @@ class Instance {
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
ReflectionProbeModule reflection_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -105,6 +107,7 @@ class Instance {
pipelines(*this),
shadows(*this),
lights(*this),
reflection_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),

View File

@ -56,6 +56,107 @@ void WorldPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
* \{ */
void WorldProbePipeline::sync()
{
for (int face : IndexRange(6)) {
CubemapSide &side = sides_[face];
/* View */
float4x4 view_m4 = cubeface_mat(face);
float4x4 win_m4;
cubeface_winmat_get(win_m4, 1.0f, 10.0f);
side.view.sync(view_m4, win_m4);
side.cubemap_face_ps.init();
side.cubemap_face_ps.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS);
}
const int2 extent(1);
constexpr eGPUTextureUsage usage = GPU_TEXTURE_USAGE_MEMORYLESS | GPU_TEXTURE_USAGE_SHADER_WRITE;
dummy_cryptomatte_tx_.ensure_2d(GPU_RGBA32F, extent, usage);
dummy_renderpass_tx_.ensure_2d(GPU_RGBA16F, extent, usage);
dummy_aov_color_tx_.ensure_2d_array(GPU_RGBA16F, extent, 1, usage);
dummy_aov_value_tx_.ensure_2d_array(GPU_R16F, extent, 1, usage);
has_draw_commands_ = false;
}
void WorldProbePipeline::sync(GPUMaterial *gpumat)
{
for (int face : IndexRange(6)) {
sync(gpumat, face);
}
has_draw_commands_ = true;
}
void WorldProbePipeline::sync(GPUMaterial *gpumat, int face)
{
Manager &manager = *inst_.manager;
CubemapSide &side = sides_[face];
/* Framebuffer. */
Texture &cubemap = inst_.reflection_probes.cubemaps_tx_;
side.cubemap_face_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE_CUBEFACE(cubemap, face));
ResourceHandle handle = manager.resource_handle(float4x4::identity());
PassSimple &pass = side.cubemap_face_ps;
pass.framebuffer_set(&side.cubemap_face_fb);
pass.material_set(manager, gpumat);
pass.push_constant("world_opacity_fade", 1.0f);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
pass.bind_ubo(RBUFS_BUF_SLOT, &inst_.render_buffers.data);
pass.bind_image("rp_normal_img", dummy_renderpass_tx_);
pass.bind_image("rp_light_img", dummy_renderpass_tx_);
pass.bind_image("rp_diffuse_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_specular_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_emission_img", dummy_renderpass_tx_);
pass.bind_image("rp_cryptomatte_img", dummy_cryptomatte_tx_);
pass.bind_image("rp_color_img", dummy_aov_color_tx_);
pass.bind_image("rp_value_img", dummy_aov_value_tx_);
/* Required by validation layers. */
inst_.cryptomatte.bind_resources(&pass);
pass.bind_image("aov_color_img", dummy_aov_color_tx_);
pass.bind_image("aov_value_img", dummy_aov_value_tx_);
pass.bind_ssbo("aov_buf", &inst_.film.aovs_info);
pass.draw(DRW_cache_fullscreen_quad_get(), handle);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void WorldProbePipeline::render()
{
if (!has_draw_commands_) {
return;
}
GPUFrameBuffer *previous_framebuffer = GPU_framebuffer_active_get();
GPU_debug_group_begin("World.Probe");
for (int face : IndexRange(6)) {
sides_[face].render(inst_);
}
GPU_debug_group_end();
GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemaps_tx_);
if (previous_framebuffer) {
GPU_framebuffer_bind(previous_framebuffer);
}
}
void WorldProbePipeline::CubemapSide::render(Instance &instance)
{
instance.manager->submit(cubemap_face_ps, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
@ -371,6 +472,7 @@ void DeferredLayer::end_sync()
inst_.shadows.bind_resources(&eval_light_ps_);
inst_.sampling.bind_resources(&eval_light_ps_);
inst_.hiz_buffer.bind_resources(&eval_light_ps_);
inst_.reflection_probes.bind_resources(&eval_light_ps_);
inst_.irradiance_cache.bind_resources(&eval_light_ps_);
eval_light_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);

View File

@ -43,6 +43,56 @@ class WorldPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
*
* Render reflection probe of the world background.
* \{ */
class WorldProbePipeline {
private:
Instance &inst_;
Texture dummy_renderpass_tx_;
Texture dummy_cryptomatte_tx_;
Texture dummy_aov_color_tx_;
Texture dummy_aov_value_tx_;
struct CubemapSide {
PassSimple cubemap_face_ps;
View view;
Framebuffer cubemap_face_fb;
void render(Instance &instance);
};
/**
* Keep track if the world probe needs to be updated. This should only be the case when the
* world is updated. This flag is used to skip updating mipmaps when the world isn't changed.
*/
bool has_draw_commands_ = false;
CubemapSide sides_[6] = {
{{"PosX"}, {"PosX"}},
{{"NegX"}, {"NegX"}},
{{"PosY"}, {"PosY"}},
{{"NegY"}, {"NegY"}},
{{"PosZ"}, {"PosZ"}},
{{"NegZ"}, {"NegZ"}},
};
public:
WorldProbePipeline(Instance &inst) : inst_(inst){};
void sync();
void sync(GPUMaterial *gpumat);
void render();
private:
void sync(GPUMaterial *gpumat, int face);
}; // namespace blender::eevee
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
@ -288,6 +338,7 @@ class UtilityTexture : public Texture {
class PipelineModule {
public:
WorldPipeline world;
WorldProbePipeline world_probe;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
@ -297,13 +348,19 @@ class PipelineModule {
public:
PipelineModule(Instance &inst)
: world(inst), deferred(inst), forward(inst), shadow(inst), capture(inst){};
: world(inst),
world_probe(inst),
deferred(inst),
forward(inst),
shadow(inst),
capture(inst){};
void begin_sync()
{
deferred.begin_sync();
forward.sync();
shadow.sync();
world_probe.sync();
capture.sync();
}

View File

@ -0,0 +1,445 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#include <sstream>
#include "BLI_bit_vector.hh"
#include "BLI_span.hh"
#include "eevee_instance.hh"
#include "eevee_reflection_probes.hh"
namespace blender::eevee {
void ReflectionProbeModule::init()
{
if (cubemaps_.is_empty()) {
cubemaps_.reserve(INITIAL_PROBES);
ReflectionProbeData init_probe_data = {};
init_probe_data.layer = -1;
for (int i : IndexRange(REFLECTION_PROBES_MAX)) {
data_buf_[i] = init_probe_data;
}
/* Initialize the world cubemap. */
ReflectionProbeData world_probe_data{};
world_probe_data.layer = 0;
world_probe_data.layer_subdivision = WORLD_SUBDIVISION_LEVEL;
world_probe_data.area_index = 0;
world_probe_data.color = float4(0.0f);
world_probe_data.pos = float3(0.0f);
world_probe_data.influence_distance = 999999.0f;
world_probe_data.falloff = 0.0f;
data_buf_[0] = world_probe_data;
ReflectionProbe world_cubemap;
world_cubemap.type = ReflectionProbe::Type::WORLD;
world_cubemap.is_dirty = true;
world_cubemap.index = 0;
cubemaps_.append(world_cubemap);
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
MAX_RESOLUTION,
INITIAL_PROBES,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
NULL,
MIPMAP_LEVELS);
GPU_texture_mipmap_mode(cubemaps_tx_, true, true);
}
for (ReflectionProbe &reflection_probe : cubemaps_) {
if (reflection_probe.type == ReflectionProbe::Type::PROBE) {
reflection_probe.is_used = false;
}
}
}
int ReflectionProbeModule::needed_layers_get() const
{
const int max_probe_data_index = reflection_probe_data_index_max();
int max_layer = 0;
for (const ReflectionProbeData &data :
Span<ReflectionProbeData>(data_buf_.data(), max_probe_data_index + 1))
{
max_layer = max_ii(max_layer, data.layer);
}
return max_layer + 1;
}
void ReflectionProbeModule::sync(const ReflectionProbe &cubemap)
{
switch (cubemap.type) {
case ReflectionProbe::Type::WORLD: {
GPUMaterial *world_material = instance_.world.get_world_material();
instance_.pipelines.world_probe.sync(world_material);
break;
}
case ReflectionProbe::Type::PROBE: {
// upload the baked probe to the cubemaps.
upload_dummy_cubemap(cubemap);
break;
}
case ReflectionProbe::Type::UNUSED: {
break;
}
}
}
void ReflectionProbeModule::set_world_dirty()
{
cubemaps_[WORLD_SLOT].is_dirty = true;
}
void ReflectionProbeModule::sync_object(Object *ob,
ObjectHandle &ob_handle,
ResourceHandle res_handle,
bool is_dirty)
{
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
ReflectionProbe &probe = find_or_insert(ob_handle, REFLECTION_PROBE_SUBDIVISION_LEVEL);
probe.is_dirty |= is_dirty;
probe.is_used = true;
/* TODO: remove debug color.*/
ReflectionProbeData &probe_data = data_buf_[probe.index];
probe_data.color = ob->color;
probe_data.influence_distance = light_probe->distinf;
probe_data.falloff = light_probe->falloff;
probe_data.pos = float3(float4x4(ob->object_to_world) * float4(0.0, 0.0, 0.0, 1.0));
}
ReflectionProbe &ReflectionProbeModule::find_or_insert(ObjectHandle &ob_handle,
int subdivision_level)
{
ReflectionProbe *first_unused = nullptr;
for (ReflectionProbe &reflection_probe : cubemaps_) {
if (reflection_probe.type == ReflectionProbe::Type::PROBE &&
reflection_probe.object_hash_value == ob_handle.object_key.hash_value)
{
return reflection_probe;
}
if (first_unused == nullptr && reflection_probe.type == ReflectionProbe::Type::UNUSED) {
first_unused = &reflection_probe;
}
}
if (first_unused == nullptr) {
ReflectionProbe new_slot{};
cubemaps_.append_as(new_slot);
first_unused = &cubemaps_.last();
}
BLI_assert(first_unused != nullptr);
first_unused->index = -1;
ReflectionProbeData probe_data = find_empty_reflection_probe_data(subdivision_level);
first_unused->is_dirty = true;
first_unused->object_hash_value = ob_handle.object_key.hash_value;
first_unused->type = ReflectionProbe::Type::PROBE;
first_unused->index = reflection_probe_data_index_max() + 1;
data_buf_[first_unused->index] = probe_data;
return *first_unused;
}
int ReflectionProbeModule::reflection_probe_data_index_max() const
{
int result = -1;
for (const ReflectionProbe &probe : cubemaps_) {
if (probe.type != ReflectionProbe::Type::UNUSED) {
result = max_ii(result, probe.index);
}
}
return result;
}
/**
* Utility class to find a location in the cubemap that can be used to store a new probe cubemap in
* a specified subdivision level.
*/
class ProbeLocationFinder {
BitVector<> taken_spots_;
int probes_per_dimension_;
int probes_per_layer_;
int subdivision_level_;
public:
ProbeLocationFinder(int num_layers, int subdivision_level)
{
subdivision_level_ = subdivision_level;
probes_per_dimension_ = 1 << subdivision_level_;
probes_per_layer_ = probes_per_dimension_ * probes_per_dimension_;
int num_spots = num_layers * probes_per_layer_;
taken_spots_.resize(num_spots, false);
}
/**
* Mark space to be occupied by the given probe_data.
*
* The input probe data can be stored in a different subdivision level and should be converted to
* the subdivision level what we are looking for.
*/
void mark_space_used(const ReflectionProbeData &probe_data)
{
/* Number of spots that the probe data will occupied in a single dimension. */
int clamped_subdivision_shift = max_ii(probe_data.layer_subdivision - subdivision_level_, 0);
int spots_per_dimension = 1 << max_ii(subdivision_level_ - probe_data.layer_subdivision, 0);
int probes_per_dimension_in_probe_data = 1 << probe_data.layer_subdivision;
int2 pos_in_probe_data = int2(probe_data.area_index % probes_per_dimension_in_probe_data,
probe_data.area_index / probes_per_dimension_in_probe_data);
int2 pos_in_location_finder = int2(pos_in_probe_data.x >> clamped_subdivision_shift,
pos_in_probe_data.y >> clamped_subdivision_shift);
int layer_offset = probe_data.layer * probes_per_layer_;
for (int y : IndexRange(spots_per_dimension)) {
for (int x : IndexRange(spots_per_dimension)) {
int2 pos = pos_in_location_finder + int2(x, y);
int area_index = pos.x + pos.y * probes_per_dimension_;
taken_spots_[area_index + layer_offset].set();
}
}
}
/**
* Get the first free spot.
*
* .x contains the layer the first free spot was detected.
* .y contains the area_index to use.
*
* Asserts when no free spot is found. ProbeLocationFinder should always be initialized with an
* additional layer to make sure that there is always a free spot.
*/
ReflectionProbeData first_free_spot() const
{
ReflectionProbeData result = {};
result.layer_subdivision = subdivision_level_;
for (int index : taken_spots_.index_range()) {
if (!taken_spots_[index]) {
int layer = index / probes_per_layer_;
int area_index = index % probes_per_layer_;
result.layer = layer;
result.area_index = area_index;
return result;
}
}
BLI_assert_unreachable();
return result;
}
};
ReflectionProbeData ReflectionProbeModule::find_empty_reflection_probe_data(
int subdivision_level) const
{
ProbeLocationFinder location_finder(needed_layers_get() + 1, subdivision_level);
for (const ReflectionProbeData &data :
Span<ReflectionProbeData>(data_buf_.data(), reflection_probe_data_index_max() + 1))
{
location_finder.mark_space_used(data);
}
return location_finder.first_free_spot();
}
void ReflectionProbeModule::end_sync()
{
remove_unused_probes();
data_buf_.push_update();
#if true
debug_print();
validate();
#endif
int number_layers_needed = needed_layers_get();
int current_layers = cubemaps_tx_.depth() / 6;
bool resize_layers = current_layers < number_layers_needed;
if (resize_layers) {
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
MAX_RESOLUTION,
number_layers_needed,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
NULL,
MIPMAP_LEVELS);
}
/* Regenerate mipmaps when a cubemap is updated. It can be postponed when the world probe is also
* updated. In this case it would happen as part of the WorldProbePipeline. */
bool regenerate_mipmaps = false;
bool regenerate_mipmaps_postponed = false;
for (ReflectionProbe &cubemap : cubemaps_) {
cubemap.is_dirty |= resize_layers;
if (!cubemap.needs_update()) {
continue;
}
sync(cubemap);
cubemap.is_dirty = false;
if (cubemap.type == ReflectionProbe::Type::WORLD) {
regenerate_mipmaps_postponed = true;
}
else {
regenerate_mipmaps = true;
}
}
if (regenerate_mipmaps) {
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
if (!regenerate_mipmaps_postponed) {
GPU_texture_update_mipmap_chain(cubemaps_tx_);
}
}
}
void ReflectionProbeModule::remove_unused_probes()
{
for (ReflectionProbe &probe : cubemaps_) {
if (probe.type == ReflectionProbe::Type::PROBE && !probe.is_used) {
probe.type = ReflectionProbe::Type::UNUSED;
probe.object_hash_value = 0;
BLI_assert(probe.index != -1);
remove_reflection_probe_data(probe.index);
}
}
}
void ReflectionProbeModule::remove_reflection_probe_data(int reflection_probe_data_index)
{
int max_index = reflection_probe_data_index_max();
for (int index = reflection_probe_data_index; index < max_index; index++) {
data_buf_[index] = data_buf_[index + 1];
}
for (ReflectionProbe &probe : cubemaps_) {
if (probe.index == reflection_probe_data_index) {
probe.index = -1;
}
if (probe.index > reflection_probe_data_index) {
probe.index--;
}
}
data_buf_[max_index].layer = -1;
BLI_assert(reflection_probe_data_index_max() == max_index - 1);
}
void ReflectionProbeModule::debug_print() const
{
std::stringstream out;
out << "\n *** " << __func__ << " ***\n";
for (const ReflectionProbe &probe : cubemaps_) {
switch (probe.type) {
case ReflectionProbe::Type::UNUSED: {
out << "UNUSED\n";
break;
}
case ReflectionProbe::Type::WORLD: {
out << "WORLD";
out << " is_dirty: " << probe.is_dirty;
out << " index: " << probe.index;
out << "\n";
break;
}
case ReflectionProbe::Type::PROBE: {
out << "PROBE";
out << " is_dirty: " << probe.is_dirty;
out << " is_used: " << probe.is_used;
out << " ob_hash: " << probe.object_hash_value;
out << " index: " << probe.index;
out << "\n";
break;
}
}
if (probe.index != -1) {
const ReflectionProbeData &data = data_buf_[probe.index];
out << " - layer: " << data.layer;
out << " subdivision: " << data.layer_subdivision;
out << " area: " << data.area_index;
out << "\n";
}
}
printf("%s", out.str().c_str());
}
void ReflectionProbeModule::validate() const
{
for (const ReflectionProbe &probe : cubemaps_) {
switch (probe.type) {
case ReflectionProbe::Type::UNUSED: {
BLI_assert(probe.index == -1);
break;
}
case ReflectionProbe::Type::WORLD: {
BLI_assert(probe.index != -1);
break;
}
case ReflectionProbe::Type::PROBE: {
BLI_assert(probe.index != -1);
break;
}
}
}
}
/* -------------------------------------------------------------------- */
/** \name World
*
* \{ */
bool ReflectionProbe::needs_update() const
{
switch (type) {
case Type::UNUSED:
return false;
case Type::WORLD:
return is_dirty;
case Type::PROBE:
return is_dirty && is_used;
}
return false;
}
/** \} */
void ReflectionProbeModule::upload_dummy_cubemap(const ReflectionProbe &probe)
{
const ReflectionProbeData &probe_data = data_buf_[probe.index];
const int resolution = MAX_RESOLUTION >> probe_data.layer_subdivision;
float4 *data = static_cast<float4 *>(
MEM_mallocN(sizeof(float4) * resolution * resolution, __func__));
/* Generate dummy checker pattern. */
int index = 0;
const int BLOCK_SIZE = max_ii(512 >> probe_data.layer_subdivision, 1);
for (int y : IndexRange(resolution)) {
for (int x : IndexRange(resolution)) {
int tx = (x / BLOCK_SIZE) & 1;
int ty = (y / BLOCK_SIZE) & 1;
bool solid = (tx + ty) & 1;
if (solid) {
data[index] = float4(probe_data.color.x, probe_data.color.y, probe_data.color.z, 1.0f);
}
else {
data[index] = float4(0.0f);
}
index++;
}
}
/* Upload the checker pattern to each side of the cubemap*/
int probes_per_dimension = 1 << probe_data.layer_subdivision;
int2 probe_area_pos(probe_data.area_index % probes_per_dimension,
probe_data.area_index / probes_per_dimension);
int2 pos = probe_area_pos * int2(MAX_RESOLUTION / probes_per_dimension);
for (int side : IndexRange(6)) {
int layer = 6 * probe_data.layer + side;
GPU_texture_update_sub(
cubemaps_tx_, GPU_DATA_FLOAT, data, UNPACK2(pos), layer, resolution, resolution, 1);
}
MEM_freeN(data);
}
} // namespace blender::eevee

View File

@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup eevee
*
* Cubemaps
*
* Cubemaps record light from different locations in the scene. These cubemaps are used to add
* environment and indirect lighting from light probes.
*
* - Although we have Global illumination light probes can still be used for situations that
* GI doesn't work. For example semi-transparent surfaces.
* - The first cubemap is always reserved for the world shading.
*
*/
#pragma once
#include "draw_manager.hh"
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
extern "C" {
struct Material;
}
namespace blender::eevee {
class Instance;
struct ObjectHandle;
class WorldProbePipeline;
/* -------------------------------------------------------------------- */
/** \name Reflection Probes
* \{ */
class ReflectionProbe {
public:
enum Type { UNUSED, WORLD, PROBE };
Type type = Type::UNUSED;
bool is_dirty = false;
/* When reflection probe is a probe its ObjectKey.hash_value is copied here to keep track between
* draws.*/
uint64_t object_hash_value = 0;
/**
* Probes that aren't used during a draw can be cleared.
*/
bool is_used = false;
/**
* Index into ReflectionProbeDataBuf.
* -1 = not added yet.
*/
int index = -1;
bool needs_update() const;
};
class ReflectionProbeModule {
private:
/** The max number of probes to track. */
static constexpr int INITIAL_PROBES = 1;
/**
* The maximum resolution of a cubemap side.
*
* Must be a power of two; intension to be used as a cubemap atlas.
*/
static constexpr int MAX_RESOLUTION = 2048;
static constexpr int MIPMAP_LEVELS = 12;
/**
* Which subdivision level to use for storing the world/reflection probes to the cubemap.
* Currently set to 0 as the evaluation only supports subdivision level 0.
*/
static constexpr int WORLD_SUBDIVISION_LEVEL = 0;
static constexpr int REFLECTION_PROBE_SUBDIVISION_LEVEL = 0;
/**
* Index of the probe that is used for world background.
*
* NOTE: First probe always contains the world probe.
*/
static constexpr int WORLD_SLOT = 0;
Instance &instance_;
ReflectionProbeDataBuf data_buf_;
Vector<ReflectionProbe> cubemaps_;
Texture cubemaps_tx_ = {"Probes"};
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
void init();
void set_world_dirty();
void sync_object(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle, bool is_dirty);
void end_sync();
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
pass->bind_ssbo(REFLECTION_PROBE_BUF_SLOT, data_buf_);
}
void debug_print() const;
void validate() const;
private:
void sync(const ReflectionProbe &cubemap);
ReflectionProbe &find_or_insert(ObjectHandle &ob_handle, int subdivision_level);
/** Get the number of layers that is needed to store probes. */
int needed_layers_get() const;
void remove_unused_probes();
/* TODO: also add _len() which is a max + 1. */
/* Get the number of reflection probe data elements. */
int reflection_probe_data_index_max() const;
/**
* Remove reflection probe data from the module.
* Ensures that data_buf is sequential and cubemaps are relinked to its corresponding data.
*/
void remove_reflection_probe_data(int reflection_probe_data_index);
/**
* Create a reflection probe data element that points to an empty spot in the cubemap that can
* hold a texture with the given subdivision_level.
*/
ReflectionProbeData find_empty_reflection_probe_data(int subdivision_level) const;
void upload_dummy_cubemap(const ReflectionProbe &probe);
friend class WorldProbePipeline;
};
} // namespace blender::eevee

View File

@ -1007,6 +1007,61 @@ BLI_STATIC_ASSERT_ALIGN(SubsurfaceData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probes
* \{ */
/** Mapping data to locate a reflection probe in cubemap array texture. */
struct ReflectionProbeData {
/**
* Dummy color of the reflection probe. Is used during development to generate a dummy reflection
* probe result, until baking has been implemented.
*
* This ensures that we are able to develop the reflection probe evaluation separately from
* baking.
*
* TODO: remove this attribute when baking has been implemented.
* NOTE: alpha channel is not used, but here for alignment reasons.
*/
float4 color;
packed_float3 pos;
/**
* Max distance in world units that is effected by this probe. (including falloff)
*/
float influence_distance;
/**
* Percentage of the radius when the falloff starts counting from the outside of the radius.
*
* 0.0: no falloff
* 1.0: falloff starts at the center of the reflection probe
*/
float falloff;
/** On which layer of the cubemaps array is this reflection probe stored. */
int layer;
/**
* Subdivision of the layer. 0 = no subdivision and resolution would be
* ReflectionProbeModule::MAX_RESOLUTION.
*
* Design: technically we could fill different subdivisions in a single layer to improve packing.
*/
int layer_subdivision;
/**
* Which area of the subdivided layer is the reflection probe located.
*
* A layer has (2^layer_subdivision)^2 areas.
*/
int area_index;
// int _pad1;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utility Texture
* \{ */
@ -1063,6 +1118,8 @@ using LightCullingZdistBuf = draw::StorageArrayBuffer<float, LIGHT_CHUNK, true>;
using LightDataBuf = draw::StorageArrayBuffer<LightData, LIGHT_CHUNK>;
using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
using ReflectionProbeDataBuf =
draw::StorageArrayBuffer<ReflectionProbeData, REFLECTION_PROBES_MAX>;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using ShadowStatisticsBuf = draw::StorageBuffer<ShadowStatistics>;
using ShadowPagesInfoDataBuf = draw::StorageBuffer<ShadowPagesInfoData>;

View File

@ -289,6 +289,19 @@ void SyncModule::sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandl
/** \} */
/* -------------------------------------------------------------------- */
/** \name LightProbe
* \{ */
void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle)
{
bool is_dirty = ob_handle.recalc;
inst_.reflection_probes.sync_object(ob, ob_handle, res_handle, is_dirty);
ob_handle.reset_recalc_flag();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hair
* \{ */

View File

@ -169,6 +169,7 @@ class SyncModule {
ObjectHandle &ob_handle,
ResourceHandle res_handle,
ModifierData *modifier_data = nullptr);
void sync_light_probe(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle);
};
/** \} */

View File

@ -118,6 +118,7 @@ void ShadingView::render()
GPU_framebuffer_bind(combined_fb_);
GPU_framebuffer_clear_color_depth(combined_fb_, clear_color, 1.0f);
inst_.pipelines.world_probe.render();
inst_.pipelines.world.render(render_view_new_);
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
@ -125,8 +126,6 @@ void ShadingView::render()
inst_.pipelines.deferred.render(render_view_new_, prepass_fb_, combined_fb_, extent_);
// inst_.lightprobes.draw_cache_display();
// inst_.lookdev.render_overlay(view_fb_);
inst_.pipelines.forward.render(render_view_new_, prepass_fb_, combined_fb_, rbufs.combined_tx);

View File

@ -90,7 +90,7 @@ void World::sync()
WorldHandle &wo_handle = inst_.sync.sync_world(bl_world);
if (wo_handle.recalc != 0) {
// inst_.lightprobes.set_world_dirty();
inst_.reflection_probes.set_world_dirty();
}
wo_handle.reset_recalc_flag();
@ -111,6 +111,21 @@ void World::sync()
inst_.pipelines.world.sync(gpumat);
}
GPUMaterial *World::get_world_material()
{
::World *bl_world = inst_.scene->world;
if (bl_world == nullptr) {
bl_world = default_world_get();
}
bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?
bl_world->nodetree :
default_tree.nodetree_get(bl_world);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree);
return gpumat;
}
/** \} */
} // namespace blender::eevee

View File

@ -62,6 +62,13 @@ class World {
~World();
void sync();
/**
* Get the world material.
*
* NOTE: this function should only be called after World::sync has been executed.
*/
GPUMaterial *get_world_material();
};
/** \} */

View File

@ -9,6 +9,7 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
void main()
@ -55,7 +56,7 @@ void main()
float shadow = 1.0;
lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light);
light_probes_eval(reflection_data, P, V, reflection_light);
light_eval(diffuse_data,
reflection_data,
P,

View File

@ -0,0 +1,46 @@
float4 light_probe_eval(ClosureReflection reflection, vec3 P, vec3 V, in ReflectionProbeData probe)
{
float linear_roughness = fast_sqrt(reflection.roughness);
/* TODO: This should be based by actual resolution. Currently the resolution is fixed but
* eventually this should based on a user setting.
* (12.0 - subdivision level)
*/
float lod_cube_max = 12.0;
float lod = linear_roughness * lod_cube_max;
vec3 R = -reflect(V, reflection.N);
vec4 probe_light = textureLod_cubemapArray(reflectionProbes, vec4(R, probe.layer), lod);
return probe_light;
}
void light_probes_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular)
{
vec4 light = vec4(0.0);
float tot_weight = 0.0;
for (int probe_index = 1; probe_index < REFLECTION_PROBES_MAX; probe_index++) {
ReflectionProbeData probe_data = reflection_probe_buf[probe_index];
/* Probe data are tightly packed so we can break the for loop. */
if (probe_data.layer == -1) {
break;
}
/* Calculate the weight of the probe on P. */
/* TODO: rewrite to mix? */
float probe_weight = 1.0 -
smoothstep(probe_data.influence_distance * (1.0 - probe_data.falloff),
probe_data.influence_distance,
length(probe_data.pos - P));
if (probe_weight > 0.0) {
vec4 probe_light = light_probe_eval(reflection, P, V, probe_data);
light.rgb += probe_light.rgb * probe_light.a * probe_weight;
light.a += probe_light.a * probe_weight;
}
tot_weight += probe_weight;
}
float world_mix = 1.0 - clamp(light.a, 0.0, 1.0);
if (world_mix > 0.0) {
out_specular += mix(
light.rgb, light_probe_eval(reflection, P, V, reflection_probe_buf[0]).rgb, world_mix);
}
}

View File

@ -37,6 +37,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
.additional_info("eevee_shared",
"eevee_utility_texture",
"eevee_light_data",
"eevee_reflection_probe_data",
"eevee_lightprobe_data",
"eevee_shadow_data",
"eevee_deferred_base",

View File

@ -0,0 +1,18 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
/* -------------------------------------------------------------------- */
/** \name Shared
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_world_data)
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_CUBE_ARRAY, "reflectionProbes");
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.additional_info("eevee_reflection_probe_world_data")
.storage_buf(REFLECTION_PROBE_BUF_SLOT,
Qualifier::READ,
"ReflectionProbeData",
"reflection_probe_buf[]");
/** \} */

View File

@ -691,7 +691,7 @@ class Texture : NonCopyable {
float *data = nullptr,
int mip_len = 1)
{
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, false, true);
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, true, true);
}
/**

View File

@ -651,6 +651,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh