Eevee-next: Octahedral mapping

Use octahedral mapping to store probe textures. Octahedral textures are easier to sample
and will increase performance. When extracting the world probe it will first be rendered into
a cubemap. This cubemap will then be remapped using octahedral texture coordinates.

* `CaptureView` captures the world light into a cubemap.
* `CaptureView` triggers the reflection probe module to update the octahedral texture using
   the cubemap.
* When sampling reflection probes it will convert the (cubemap) direction to octahedral
   coordinate and read from the octahedral texture.

![image](/attachments/d3331660-f893-41b7-8c17-ae12ddf2ad11)

Pull Request: https://projects.blender.org/blender/blender/pulls/109559
This commit is contained in:
Jeroen Bakker 2023-07-03 15:14:34 +02:00
parent 4e165c9d6f
commit c6c69c0656
12 changed files with 116 additions and 17 deletions

View File

@ -519,9 +519,11 @@ 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_octahedron_lib.glsl
engines/eevee_next/shaders/eevee_ray_types_lib.glsl
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_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_PROBE_GROUP_SIZE 16
/**
* IMPORTANT: Some data packing are tweaked for these values.
* Be sure to update them accordingly.

View File

@ -10,16 +10,37 @@ namespace blender::eevee {
void ReflectionProbeModule::init()
{
if (!initialized_) {
const int max_mipmap_levels = log(max_resolution_) + 1;
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
max_resolution_,
max_probes_,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
nullptr,
max_mipmap_levels);
GPU_texture_mipmap_mode(cubemaps_tx_, true, true);
initialized_ = true;
const int max_mipmap_levels = log(max_resolution_) + 1;
probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
max_probes_,
GPU_TEXTURE_USAGE_SHADER_WRITE,
nullptr,
max_mipmap_levels);
GPU_texture_mipmap_mode(probes_tx_, true, true);
/* Cubemap is half of the resolution of the octahedral map. */
cubemap_tx_.ensure_cube(
GPU_RGBA16F, max_resolution_ / 2, GPU_TEXTURE_USAGE_ATTACHMENT, nullptr, 1);
GPU_texture_mipmap_mode(cubemap_tx_, false, true);
}
{
PassSimple &pass = remap_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP));
pass.bind_texture("cubemap_tx", cubemap_tx_);
pass.bind_image("octahedral_img", probes_tx_);
pass.dispatch(int2(ceil_division(max_resolution_, REFLECTION_PROBE_GROUP_SIZE)));
}
}
void ReflectionProbeModule::remap_to_octahedral_projection()
{
instance_.manager->submit(remap_ps_);
GPU_texture_update_mipmap_chain(probes_tx_);
}
} // namespace blender::eevee

View File

@ -39,7 +39,12 @@ class ReflectionProbeModule {
Instance &instance_;
Texture cubemaps_tx_ = {"Probes"};
/** Texture containing a cubemap used for updating #probes_tx_. */
Texture cubemap_tx_ = {"Probe.Cubemap"};
/** Probes texture stored in octahedral mapping. */
Texture probes_tx_ = {"Probes"};
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
bool initialized_ = false;
@ -52,7 +57,7 @@ class ReflectionProbeModule {
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, probes_tx_);
}
void do_world_update_set(bool value)
@ -66,6 +71,8 @@ class ReflectionProbeModule {
return do_world_update_;
}
void remap_to_octahedral_projection();
/* Capture View requires access to the cubemaps texture for framebuffer configuration. */
friend class CaptureView;
};

View File

@ -156,6 +156,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_lightprobe_irradiance_ray";
case LIGHTPROBE_IRRADIANCE_LOAD:
return "eevee_lightprobe_irradiance_load";
case REFLECTION_PROBE_REMAP:
return "eevee_reflection_probe_remap";
case SHADOW_CLIPMAP_CLEAR:
return "eevee_shadow_clipmap_clear";
case SHADOW_DEBUG:

View File

@ -74,6 +74,8 @@ enum eShaderType {
MOTION_BLUR_TILE_FLATTEN_RENDER,
MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
REFLECTION_PROBE_REMAP,
SHADOW_CLIPMAP_CLEAR,
SHADOW_DEBUG,
SHADOW_PAGE_ALLOCATE,

View File

@ -206,9 +206,8 @@ void CaptureView::render()
View view = {"World.Capture.View"};
for (int face : IndexRange(6)) {
capture_fb_.ensure(
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemaps_tx_, face));
capture_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
GPU_framebuffer_bind(capture_fb_);
float4x4 view_m4 = cubeface_mat(face);
@ -216,7 +215,7 @@ void CaptureView::render()
view.sync(view_m4, win_m4);
inst_.pipelines.world.render(view);
}
GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemaps_tx_);
inst_.reflection_probes.remap_to_octahedral_projection();
GPU_debug_group_end();
}

View File

@ -0,0 +1,33 @@
/**
* Convert from a cubemap vector to an octahedron UV coordinate.
*/
vec2 octahedral_uv_from_direction(vec3 co)
{
/* Projection onto octahedron. */
co /= dot(vec3(1.0), abs(co));
/* Out-folding of the downward faces. */
if (co.z < 0.0) {
vec2 sign = step(0.0, co.xy) * 2.0 - 1.0;
co.xy = (1.0 - abs(co.yx)) * sign;
}
/* Mapping to [0;1]^2 texture space. */
vec2 uvs = co.xy * (0.5) + 0.5;
return uvs;
}
vec3 octahedral_uv_to_direction(vec2 co)
{
co = co * 2.0 - 1.0;
vec2 abs_co = abs(co);
vec3 v = vec3(co, 1.0 - (abs_co.x + abs_co.y));
if (abs_co.x + abs_co.y > 1.0) {
v.xy = (abs(co.yx) - 1.0) * -sign(co.xy);
}
return v;
}

View File

@ -38,7 +38,7 @@ void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 o
/* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html : Equation 13 */
/* TODO: lod_factor should be precalculated and stored inside the reflection probe data. */
const float bias = 0;
const float bias = 0.0;
const float lod_factor = bias + 0.5 * log(float(square_i(texture_size.x))) / log(2);
/* -2: Don't use LOD levels that are smaller than 4x4 pixels. */
float lod = clamp(lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max - 2.0);

View File

@ -1,6 +1,9 @@
#pragma BLENDER_REQUIRE(eevee_cubemap_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
vec3 light_world_sample(vec3 L, float lod)
{
return textureLod_cubemapArray(reflectionProbes, vec4(L, 0.0), lod).rgb;
vec2 octahedral_uv = octahedral_uv_from_direction(L);
return textureLod(reflectionProbes, vec3(octahedral_uv, 0.0), lod).rgb;
}

View File

@ -0,0 +1,19 @@
/* Shader to convert cubemap to octahedral projection. */
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
void main()
{
ivec3 octahedral_coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 octahedral_size = imageSize(octahedral_img);
/* Group doesn't fit in output texture. */
if (any(greaterThanEqual(octahedral_coord.xy, octahedral_size.xy))) {
return;
}
vec2 octahedral_uv = vec2(octahedral_coord) / vec2(octahedral_size);
vec3 R = octahedral_uv_to_direction(octahedral_uv);
vec4 col = textureLod(cubemap_tx, R, 0.0);
imageStore(octahedral_img, octahedral_coord, col);
}

View File

@ -10,6 +10,14 @@
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_CUBE_ARRAY, "reflectionProbes");
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflectionProbes");
/* Sample cubemap and remap into an octahedral texture. */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.local_group_size(REFLECTION_PROBE_GROUP_SIZE, REFLECTION_PROBE_GROUP_SIZE)
.sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "octahedral_img")
.compute_source("eevee_reflection_probe_remap_comp.glsl")
.do_static_compilation(true);
/** \} */