WIP: Eevee-next: Reflection Probes #108497
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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[]");
|
||||
|
||||
/** \} */
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue