EEVEE-Next: Simplify sphere probe storage

- Remove unlimited mip level.
- Make computation of sampling region simpler.
- Add correct mirroring of UV and border region.
- Fix crash when world probe is smaller than lightprobes.
This commit is contained in:
Clément Foucault 2024-01-13 20:21:47 +13:00
parent 29ec924f9f
commit f70b8f76e9
7 changed files with 54 additions and 54 deletions

@ -36,7 +36,6 @@
/* 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_BORDER_SIZE float(1 << (REFLECTION_PROBE_MIPMAP_LEVELS - 1))
#define REFLECTION_PROBE_SH_GROUP_SIZE 512
#define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64

@ -167,7 +167,7 @@ void ReflectionProbeModule::init()
1,
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr,
9999);
REFLECTION_PROBE_MIPMAP_LEVELS);
GPU_texture_mipmap_mode(probes_tx_, true, true);
probes_tx_.clear(float4(0.0f));
}
@ -177,11 +177,11 @@ void ReflectionProbeModule::init()
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP));
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_image("atlas_dst_mip_img", &atlas_dst_mip_tx_);
pass.bind_image("atlas_src_mip_img", &atlas_src_mip_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_write_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_sampling_coord_));
pass.push_constant("mip_level", &probe_mip_level_);
pass.dispatch(&dispatch_probe_pack_);
}
@ -468,21 +468,13 @@ std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
void ReflectionProbeModule::remap_to_octahedral_projection(
const ReflectionProbeAtlasCoordinate &atlas_coord)
{
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
int world_layer_subdivision = world_probe.atlas_coord.layer_subdivision;
int resolution = max_resolution_ >> atlas_coord.layer_subdivision;
/* 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;
world_write_coord_ = world_probe.atlas_coord.as_write_coord(atlas_extent(), probe_mip_level_);
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, REFLECTION_PROBE_GROUP_SIZE)), 1);
probes_tx_.ensure_mip_views();
atlas_dst_mip_tx_ = probes_tx_.mip_view(0);
atlas_src_mip_tx_ = probes_tx_.mip_view(probe_mip_level_ - world_layer_subdivision);
instance_.manager->submit(remap_ps_);
}

@ -64,16 +64,34 @@ struct ReflectionProbeAtlasCoordinate {
ReflectionProbeCoordinate as_sampling_coord(int atlas_extent) const
{
const int area_count_per_dimension = 1 << layer_subdivision;
const float area_scale = 1.0f / area_count_per_dimension;
const float2 area_location = float2(this->area_location());
float texel_size = 1.0f / atlas_extent;
float border_size = REFLECTION_PROBE_BORDER_SIZE * texel_size;
/**
* 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.offset = (border_size + 0.5f * texel_size + area_location) * area_scale;
coord.scale = (1.0f - 2.0f * border_size) * area_scale;
coord.scale = sampling_area_extent / float(atlas_extent);
coord.offset = float2(sampling_area_offset) / float(atlas_extent);
coord.layer = layer;
return coord;
}
@ -160,9 +178,6 @@ class ReflectionProbeModule {
/** Probes texture stored in octahedral mapping. */
Texture probes_tx_ = {"Probes"};
/* Reference to a specific mip map layer of a texture. */
GPUTexture *atlas_dst_mip_tx_ = nullptr;
GPUTexture *atlas_src_mip_tx_ = nullptr;
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"};
@ -186,7 +201,6 @@ class ReflectionProbeModule {
ReflectionProbeWriteCoordinate probe_write_coord_;
/** World coordinates in the atlas. */
ReflectionProbeCoordinate world_sampling_coord_;
ReflectionProbeWriteCoordinate world_write_coord_;
/** Number of the probe to process in the select phase. */
int reflection_probe_count_ = 0;

@ -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) * 11.0;
return sqrt(roughness) * REFLECTION_PROBE_MIPMAP_LEVELS;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V)

@ -36,22 +36,3 @@ vec3 octahedral_uv_to_direction(vec2 co)
return v;
}
/**
* Return the octahedral uv coordinates for the given texture uv coordinate on the packed
* octahedral texture layer for the given probe.
*
* It also applies wrapping in the additional space near borders.
* NOTE: Doesn't apply the translation part of the packing.
*/
vec2 octahedral_uv_from_layer_texture_coords(vec2 uv, vec2 texel_size)
{
/* Apply border region. */
vec2 shrinked_uv = (uv - REFLECTION_PROBE_BORDER_SIZE * texel_size) /
(1.0 - 2.0 * REFLECTION_PROBE_BORDER_SIZE * texel_size);
/* Use ping/pong to extend the octahedral coordinates. */
vec2 translated_pos = clamp(-sign(shrinked_uv), vec2(0.0), vec2(1.0)) * vec2(2.0) + shrinked_uv;
ivec2 checker_pos = ivec2(translated_pos);
bool is_even = ((checker_pos.x + checker_pos.y) & 1) == 0;
return is_even ? fract(shrinked_uv) : vec2(1.0) - fract(shrinked_uv);
}

@ -24,11 +24,24 @@ ReflectionProbeWriteCoordinate reinterpret_as_write_coord(ivec4 packed_coord)
return unpacked;
}
/* Mirror the UV if they are not on the diagonal or unit UV squares.
* Doesn't extend outside of [-1..2] range. But this is fine since we use it only for borders. */
vec2 mirror_repeat_uv(vec2 uv)
{
vec2 m = abs(uv - 0.5) + 0.5;
vec2 f = floor(m);
float x = f.x - f.y;
if (x != 0.0) {
uv.xy = 1.0 - uv.xy;
}
return fract(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);
ReflectionProbeWriteCoordinate world_coord = reinterpret_as_write_coord(world_coord_packed);
/* Texel in probe. */
ivec2 local_texel = ivec2(gl_GlobalInvocationID.xy);
@ -41,11 +54,12 @@ void main()
/* Texel in probe atlas. */
ivec2 texel = local_texel + write_coord.offset;
/* UV in probe atlas. */
vec2 atlas_uv = (vec2(texel) + 0.5) / vec2(imageSize(atlas_dst_mip_img).xy);
vec2 atlas_uv = (vec2(texel) + 0.5) / vec2(imageSize(atlas_img).xy);
/* UV in sampling area. */
vec2 sampling_uv = (atlas_uv - sample_coord.offset) / sample_coord.scale;
vec2 wrapped_uv = mirror_repeat_uv(sampling_uv);
/* Direction in world space. */
vec3 direction = octahedral_uv_to_direction(sampling_uv);
vec3 direction = octahedral_uv_to_direction(wrapped_uv);
vec4 col = textureLod(cubemap_tx, direction, float(mip_level));
/* Convert transmittance to transparency. */
@ -54,10 +68,10 @@ void main()
/* Composite world into reflection probes. */
bool is_world = all(equal(write_coord_packed, world_coord_packed));
if (!is_world && col.a != 1.0) {
ivec2 world_texel = local_texel + world_coord.offset;
vec4 world_col = imageLoad(atlas_src_mip_img, ivec3(world_texel, world_coord.layer));
vec2 world_uv = wrapped_uv * world_coord.scale + world_coord.offset;
vec4 world_col = textureLod(atlas_tx, vec3(world_uv, world_coord.layer), 0.0);
col.rgb = mix(world_col.rgb, col.rgb, col.a);
}
imageStore(atlas_dst_mip_img, ivec3(texel, write_coord.layer), col);
imageStore(atlas_img, ivec3(texel, write_coord.layer), col);
}

@ -24,8 +24,8 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.push_constant(Type::IVEC4, "world_coord_packed")
.push_constant(Type::INT, "mip_level")
.sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "atlas_dst_mip_img")
.image(1, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D_ARRAY, "atlas_src_mip_img")
.sampler(1, ImageType::FLOAT_2D_ARRAY, "atlas_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "atlas_img")
.compute_source("eevee_reflection_probe_remap_comp.glsl")
.additional_info("eevee_shared")
.do_static_compilation(true);