EEVEE-Next: Extract Spherical Harmonics from World
This PR adds diffuse light from environment to the scene without having an irradiance volume in the scene. It does this by extracting the sperical harmonics from the world probe and store it in the irradiance brick that is reserved for world diffuse light in the irradiance cache. This also fixes that selecting an LookDev HDRI didn't update the diffuse light. **Known Issues** - When sampling probes with lower resolution strokes are visible, leading to flickering and instability in the spherical harmonics. This is an issue that should be solved in a separate patch as it is already visible in main - When selecting a lookdev HDRI all irradiance volumes needs to be disabled manually for the expected results (but this also disabled GI). In the future this will be done automatically or we will add a solution to separate the world diffuse light from the irradiance cache so we can update it on the fly. Pull Request: https://projects.blender.org/blender/blender/pulls/110110
This commit is contained in:
@@ -523,6 +523,7 @@ set(GLSL_SRC
|
||||
engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl
|
||||
engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl
|
||||
engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.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
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
* Border size requires depends on the max number of mipmap levels. */
|
||||
#define REFLECTION_PROBE_MIPMAP_LEVELS 5
|
||||
#define REFLECTION_PROBE_BORDER_SIZE float(1 << (REFLECTION_PROBE_MIPMAP_LEVELS - 1))
|
||||
#define REFLECTION_PROBE_SH_GROUP_SIZE 512
|
||||
#define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64
|
||||
|
||||
/**
|
||||
* IMPORTANT: Some data packing are tweaked for these values.
|
||||
|
||||
@@ -73,8 +73,9 @@ void Instance::init(const int2 &output_res,
|
||||
shadows.init();
|
||||
motion_blur.init();
|
||||
main_view.init();
|
||||
irradiance_cache.init();
|
||||
/* Irradiance Cache needs reflection probes to be initialized. */
|
||||
reflection_probes.init();
|
||||
irradiance_cache.init();
|
||||
}
|
||||
|
||||
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
|
||||
@@ -103,8 +104,9 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
|
||||
depth_of_field.init();
|
||||
shadows.init();
|
||||
main_view.init();
|
||||
irradiance_cache.init();
|
||||
/* Irradiance Cache needs reflection probes to be initialized. */
|
||||
reflection_probes.init();
|
||||
irradiance_cache.init();
|
||||
}
|
||||
|
||||
void Instance::set_time(float time)
|
||||
|
||||
@@ -69,6 +69,8 @@ void IrradianceCache::init()
|
||||
/* Clear the pool to avoid any interpolation to undefined values. */
|
||||
irradiance_atlas_tx_.clear(float4(0.0f));
|
||||
}
|
||||
|
||||
inst_.reflection_probes.do_world_update_irradiance_set(true);
|
||||
}
|
||||
|
||||
if (irradiance_atlas_tx_.is_valid() == false) {
|
||||
|
||||
@@ -21,6 +21,7 @@ class Instance;
|
||||
class CapturePipeline;
|
||||
class ShadowModule;
|
||||
class Camera;
|
||||
class ReflectionProbeModule;
|
||||
|
||||
/**
|
||||
* Baking related pass and data. Not used at runtime.
|
||||
@@ -181,6 +182,8 @@ class IrradianceCache {
|
||||
private:
|
||||
void debug_pass_draw(View &view, GPUFrameBuffer *view_fb);
|
||||
void display_pass_draw(View &view, GPUFrameBuffer *view_fb);
|
||||
|
||||
friend class ReflectionProbeModule;
|
||||
};
|
||||
|
||||
} // namespace blender::eevee
|
||||
|
||||
@@ -55,6 +55,16 @@ void ReflectionProbeModule::init()
|
||||
pass.push_constant("reflection_probe_index", &reflection_probe_index_);
|
||||
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("reflection_probe_index", &reflection_probe_index_);
|
||||
pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_);
|
||||
bind_resources(&pass);
|
||||
pass.dispatch(int2(1, 1));
|
||||
}
|
||||
}
|
||||
void ReflectionProbeModule::begin_sync()
|
||||
{
|
||||
@@ -384,6 +394,13 @@ void ReflectionProbeModule::do_world_update_set(bool value)
|
||||
{
|
||||
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
|
||||
world_probe.do_render = value;
|
||||
world_probe.do_world_irradiance_update = 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
|
||||
@@ -453,7 +470,7 @@ std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
|
||||
const bool do_probe_sync = instance_.do_probe_sync();
|
||||
const int max_shift = int(log2(max_resolution_));
|
||||
for (const Map<uint64_t, ReflectionProbe>::Item &item : probes_.items()) {
|
||||
if (!item.value.do_render) {
|
||||
if (!item.value.do_render && !item.value.do_world_irradiance_update) {
|
||||
continue;
|
||||
}
|
||||
if (probe_type == ReflectionProbe::Type::World && item.value.type != probe_type) {
|
||||
@@ -466,7 +483,6 @@ std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
|
||||
if (item.value.type == ReflectionProbe::Type::Probe && !do_probe_sync) {
|
||||
continue;
|
||||
}
|
||||
probes_.lookup(item.key).do_render = false;
|
||||
|
||||
ReflectionProbeData &probe_data = data_buf_[item.value.index];
|
||||
ReflectionProbeUpdateInfo info = {};
|
||||
@@ -475,6 +491,12 @@ std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
|
||||
info.resolution = 1 << (max_shift - probe_data.layer_subdivision - 1);
|
||||
info.clipping_distances = item.value.clipping_distances;
|
||||
info.probe_pos = float3(probe_data.pos);
|
||||
info.do_render = item.value.do_render;
|
||||
info.do_world_irradiance_update = item.value.do_world_irradiance_update;
|
||||
|
||||
ReflectionProbe &probe = probes_.lookup(item.key);
|
||||
probe.do_render = false;
|
||||
probe.do_world_irradiance_update = false;
|
||||
|
||||
if (cubemap_tx_.ensure_cube(GPU_RGBA16F,
|
||||
info.resolution,
|
||||
@@ -510,6 +532,16 @@ void ReflectionProbeModule::remap_to_octahedral_projection(uint64_t object_key)
|
||||
instance_.manager->submit(remap_ps_);
|
||||
}
|
||||
|
||||
void ReflectionProbeModule::update_world_irradiance()
|
||||
{
|
||||
const ReflectionProbe &probe = probes_.lookup(world_object_key_);
|
||||
|
||||
/* Update shader parameters that change per dispatch. */
|
||||
reflection_probe_index_ = probe.index;
|
||||
|
||||
instance_.manager->submit(update_irradiance_ps_);
|
||||
}
|
||||
|
||||
void ReflectionProbeModule::update_probes_texture_mipmaps()
|
||||
{
|
||||
GPU_texture_update_mipmap_chain(probes_tx_);
|
||||
|
||||
@@ -38,6 +38,7 @@ struct ReflectionProbe {
|
||||
bool do_update_data = false;
|
||||
/* 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.
|
||||
@@ -102,6 +103,7 @@ class ReflectionProbeModule {
|
||||
Texture probes_tx_ = {"Probes"};
|
||||
|
||||
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
|
||||
PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"};
|
||||
|
||||
int3 dispatch_probe_pack_ = int3(0);
|
||||
|
||||
@@ -133,6 +135,7 @@ class ReflectionProbeModule {
|
||||
|
||||
bool do_world_update_get() const;
|
||||
void do_world_update_set(bool value);
|
||||
void do_world_update_irradiance_set(bool value);
|
||||
|
||||
void debug_print() const;
|
||||
|
||||
@@ -167,6 +170,7 @@ class ReflectionProbeModule {
|
||||
std::optional<ReflectionProbeUpdateInfo> update_info_pop(ReflectionProbe::Type probe_type);
|
||||
void remap_to_octahedral_projection(uint64_t object_key);
|
||||
void update_probes_texture_mipmaps();
|
||||
void update_world_irradiance();
|
||||
|
||||
bool has_only_world_probe() const;
|
||||
|
||||
@@ -196,6 +200,9 @@ struct ReflectionProbeUpdateInfo {
|
||||
|
||||
float2 clipping_distances;
|
||||
uint64_t object_key;
|
||||
|
||||
bool do_render;
|
||||
bool do_world_irradiance_update;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -160,6 +160,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
|
||||
return "eevee_lightprobe_irradiance_load";
|
||||
case REFLECTION_PROBE_REMAP:
|
||||
return "eevee_reflection_probe_remap";
|
||||
case REFLECTION_PROBE_UPDATE_IRRADIANCE:
|
||||
return "eevee_reflection_probe_update_irradiance";
|
||||
case SHADOW_CLIPMAP_CLEAR:
|
||||
return "eevee_shadow_clipmap_clear";
|
||||
case SHADOW_DEBUG:
|
||||
|
||||
@@ -76,6 +76,7 @@ enum eShaderType {
|
||||
MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
|
||||
|
||||
REFLECTION_PROBE_REMAP,
|
||||
REFLECTION_PROBE_UPDATE_IRRADIANCE,
|
||||
|
||||
SHADOW_CLIPMAP_CLEAR,
|
||||
SHADOW_DEBUG,
|
||||
|
||||
@@ -206,24 +206,31 @@ void CaptureView::render_world()
|
||||
View view = {"Capture.View"};
|
||||
GPU_debug_group_begin("World.Capture");
|
||||
|
||||
for (int face : IndexRange(6)) {
|
||||
float4x4 view_m4 = cubeface_mat(face);
|
||||
float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
-update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.y);
|
||||
view.sync(view_m4, win_m4);
|
||||
if (update_info->do_render) {
|
||||
for (int face : IndexRange(6)) {
|
||||
float4x4 view_m4 = cubeface_mat(face);
|
||||
float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
-update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.x,
|
||||
update_info->clipping_distances.y);
|
||||
view.sync(view_m4, win_m4);
|
||||
|
||||
capture_fb_.ensure(GPU_ATTACHMENT_NONE,
|
||||
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
|
||||
GPU_framebuffer_bind(capture_fb_);
|
||||
inst_.pipelines.world.render(view);
|
||||
capture_fb_.ensure(
|
||||
GPU_ATTACHMENT_NONE,
|
||||
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
|
||||
GPU_framebuffer_bind(capture_fb_);
|
||||
inst_.pipelines.world.render(view);
|
||||
}
|
||||
|
||||
inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key);
|
||||
inst_.reflection_probes.update_probes_texture_mipmaps();
|
||||
}
|
||||
|
||||
inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key);
|
||||
inst_.reflection_probes.update_probes_texture_mipmaps();
|
||||
if (update_info->do_world_irradiance_update) {
|
||||
inst_.reflection_probes.update_world_irradiance();
|
||||
}
|
||||
|
||||
GPU_debug_group_end();
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@ vec3 reflection_probes_world_sample(vec3 L, float lod)
|
||||
{
|
||||
ReflectionProbeData probe_data = reflection_probe_buf[0];
|
||||
return reflection_probes_sample(L, lod, probe_data).rgb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
/* Shader to extract spherical harmonics cooefs from octahedral mapped reflection probe. */
|
||||
|
||||
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
|
||||
|
||||
void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer)
|
||||
{
|
||||
for (int x = 0; x < IRRADIANCE_GRID_BRICK_SIZE; x++) {
|
||||
for (int y = 0; y < IRRADIANCE_GRID_BRICK_SIZE; y++) {
|
||||
for (int z = 0; z < IRRADIANCE_GRID_BRICK_SIZE; z++) {
|
||||
ivec3 brick_coord = ivec3(x, y, z);
|
||||
imageStore(irradiance_atlas_img,
|
||||
ivec3(atlas_coord, layer * IRRADIANCE_GRID_BRICK_SIZE) + brick_coord,
|
||||
sh_coefficient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared vec4 cooefs[gl_WorkGroupSize.x][4];
|
||||
|
||||
void main()
|
||||
{
|
||||
SphericalHarmonicL1 cooef;
|
||||
cooef.L0.M0 = vec4(0.0);
|
||||
cooef.L1.Mn1 = vec4(0.0);
|
||||
cooef.L1.M0 = vec4(0.0);
|
||||
cooef.L1.Mp1 = vec4(0.0);
|
||||
|
||||
ReflectionProbeData probe_data = reflection_probe_buf[reflection_probe_index];
|
||||
const int subdivision_64 = 5;
|
||||
float layer_mipmap = clamp(
|
||||
subdivision_64 - probe_data.layer_subdivision, 0, REFLECTION_PROBE_MIPMAP_LEVELS);
|
||||
|
||||
/* Perform multiple sample. */
|
||||
uint store_index = gl_LocalInvocationID.x;
|
||||
float total_samples = float(gl_WorkGroupSize.x * REFLECTION_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++)
|
||||
{
|
||||
vec2 rand = fract(hammersley_2d(sample_index + sample_offset, total_samples));
|
||||
vec3 direction = sample_sphere(rand);
|
||||
vec4 light = reflection_probes_sample(direction, layer_mipmap, probe_data);
|
||||
spherical_harmonics_encode_signal_sample(direction, light * sample_weight, cooef);
|
||||
}
|
||||
cooefs[store_index][0] = cooef.L0.M0;
|
||||
cooefs[store_index][1] = cooef.L1.Mn1;
|
||||
cooefs[store_index][2] = cooef.L1.M0;
|
||||
cooefs[store_index][3] = cooef.L1.Mp1;
|
||||
|
||||
barrier();
|
||||
if (gl_LocalInvocationID.x == 0) {
|
||||
/* Join results */
|
||||
vec4 result[4];
|
||||
result[0] = vec4(0.0);
|
||||
result[1] = vec4(0.0);
|
||||
result[2] = vec4(0.0);
|
||||
result[3] = vec4(0.0);
|
||||
|
||||
for (uint i = 0; i < gl_WorkGroupSize.x; i++) {
|
||||
result[0] += cooefs[i][0];
|
||||
result[1] += cooefs[i][1];
|
||||
result[2] += cooefs[i][2];
|
||||
result[3] += cooefs[i][3];
|
||||
}
|
||||
|
||||
ivec2 atlas_coord = ivec2(0, 0);
|
||||
atlas_store(result[0], atlas_coord, 0);
|
||||
atlas_store(result[1], atlas_coord, 1);
|
||||
atlas_store(result[2], atlas_coord, 2);
|
||||
atlas_store(result[3], atlas_coord, 3);
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,14 @@ vec3 sample_cylinder(vec2 rand)
|
||||
return vec3(theta, cos_phi, sin_phi);
|
||||
}
|
||||
|
||||
vec3 sample_sphere(vec2 rand)
|
||||
{
|
||||
float omega = rand.y * 2.0 * M_PI;
|
||||
float cos_theta = rand.x * 2.0 - 1.0;
|
||||
float sin_theta = safe_sqrt(1.0 - cos_theta * cos_theta);
|
||||
return vec3(sin_theta * vec2(cos(omega), sin(omega)), cos_theta);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -28,4 +28,14 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
|
||||
.additional_info("eevee_shared")
|
||||
.do_static_compilation(true);
|
||||
|
||||
/* 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)
|
||||
.push_constant(Type::INT, "reflection_probe_index")
|
||||
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img")
|
||||
.additional_info("eevee_shared", "eevee_reflection_probe_data")
|
||||
.compute_source("eevee_reflection_probe_update_irradiance_comp.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
/** \} */
|
||||
|
||||
Reference in New Issue
Block a user