WIP: eevee-next-world-irradiance #108304
|
@ -491,8 +491,10 @@ void Instance::light_bake_irradiance(
|
|||
});
|
||||
|
||||
int bounce_len = scene->eevee.gi_diffuse_bounces;
|
||||
/* Start at bounce 1 as 0 bounce is no indirect lighting. */
|
||||
for (int bounce = 1; bounce <= bounce_len; bounce++) {
|
||||
for (int bounce = 0; bounce <= bounce_len; bounce++) {
|
||||
/* Last iteration only captures lighting. */
|
||||
const bool is_last_bounce = (bounce == bounce_len);
|
||||
|
||||
sampling.init(scene);
|
||||
while (!sampling.finished()) {
|
||||
context_wrapper([&]() {
|
||||
|
@ -500,9 +502,16 @@ void Instance::light_bake_irradiance(
|
|||
* the update function & context switch. */
|
||||
for (int i = 0; i < 16 && !sampling.finished(); i++) {
|
||||
sampling.step();
|
||||
irradiance_cache.bake.propagate_light_sample();
|
||||
|
||||
irradiance_cache.bake.raylists_build();
|
||||
if (!is_last_bounce) {
|
||||
irradiance_cache.bake.propagate_light();
|
||||
}
|
||||
if (is_last_bounce) {
|
||||
irradiance_cache.bake.irradiance_capture();
|
||||
}
|
||||
}
|
||||
if (sampling.finished()) {
|
||||
if (sampling.finished() && !is_last_bounce) {
|
||||
irradiance_cache.bake.accumulate_bounce();
|
||||
}
|
||||
|
||||
|
@ -516,7 +525,7 @@ void Instance::light_bake_irradiance(
|
|||
}
|
||||
|
||||
float bounce_progress = sampling.sample_index() / float(sampling.sample_count());
|
||||
float progress = (bounce - 1 + bounce_progress) / float(bounce_len);
|
||||
float progress = (bounce + bounce_progress) / float(bounce_len + 1);
|
||||
result_update(cache_frame, progress);
|
||||
});
|
||||
|
||||
|
|
|
@ -165,7 +165,28 @@ void IrradianceCache::set_view(View & /*view*/)
|
|||
grid.grid_index = grids_len;
|
||||
grids_infos_buf_[grids_len++] = grid;
|
||||
}
|
||||
/* TODO: Stable sorting of grids. */
|
||||
|
||||
{
|
||||
/* Stable sorting of grids. */
|
||||
MutableSpan<IrradianceGridData> grid_span(grids_infos_buf_.data(), grids_len);
|
||||
|
||||
std::sort(grid_span.begin(),
|
||||
grid_span.end(),
|
||||
[](const IrradianceGridData &a, const IrradianceGridData &b) {
|
||||
float volume_a = math::determinant(float3x3(a.world_to_grid_transposed));
|
||||
float volume_b = math::determinant(float3x3(b.world_to_grid_transposed));
|
||||
if (volume_a != volume_b) {
|
||||
/* Smallest first. */
|
||||
return volume_a > volume_b;
|
||||
}
|
||||
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
|
||||
* Use position to avoid unstable result caused by depsgraph non deterministic eval
|
||||
* order. This could also become a parameter. */
|
||||
return a.world_to_grid_transposed[0][0] < a.world_to_grid_transposed[0][0] ||
|
||||
a.world_to_grid_transposed[0][1] < a.world_to_grid_transposed[0][1] ||
|
||||
a.world_to_grid_transposed[0][2] < a.world_to_grid_transposed[0][2];
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
/* Insert world grid last. */
|
||||
|
@ -399,7 +420,7 @@ void IrradianceBake::sync()
|
|||
pass.dispatch(&dispatch_per_surfel_);
|
||||
}
|
||||
{
|
||||
PassSimple &pass = surfel_light_propagate_ps_;
|
||||
PassSimple &pass = surfel_ray_build_ps_;
|
||||
pass.init();
|
||||
{
|
||||
PassSimple::Sub &sub = pass.sub("ListBuild");
|
||||
|
@ -421,6 +442,10 @@ void IrradianceBake::sync()
|
|||
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
sub.dispatch(&dispatch_per_list_);
|
||||
}
|
||||
}
|
||||
{
|
||||
PassSimple &pass = surfel_light_propagate_ps_;
|
||||
pass.init();
|
||||
{
|
||||
PassSimple::Sub &sub = pass.sub("RayEval");
|
||||
sub.shader_set(inst_.shaders.static_shader_get(SURFEL_RAY));
|
||||
|
@ -472,7 +497,7 @@ void IrradianceBake::surfel_raster_views_sync(const float3 &scene_min, const flo
|
|||
float3 extent_min = transform_point(invert(basis), scene_min);
|
||||
float3 extent_max = transform_point(invert(basis), scene_max);
|
||||
float4x4 winmat = projection::orthographic(
|
||||
extent_min.x, extent_max.x, extent_min.y, extent_max.y, extent_min.z, extent_max.z);
|
||||
extent_min.x, extent_max.x, extent_min.y, extent_max.y, -extent_min.z, -extent_max.z);
|
||||
float4x4 viewinv = from_rotation<float4x4>(to_quaternion<float>(basis));
|
||||
view.visibility_test(false);
|
||||
view.sync(invert(viewinv), winmat);
|
||||
|
@ -502,10 +527,7 @@ void IrradianceBake::surfels_create(const Object &probe_object)
|
|||
capture_info_buf_.irradiance_grid_local_to_world = grid_local_to_world;
|
||||
capture_info_buf_.irradiance_grid_world_to_local_rotation = float4x4(
|
||||
(invert(normalize(float3x3(grid_local_to_world)))));
|
||||
capture_info_buf_.irradiance_accum_solid_angle = 0.0f;
|
||||
/* Divide by twice the sample count because each ray is evaluated in both directions. */
|
||||
capture_info_buf_.irradiance_sample_solid_angle = 4.0f * float(M_PI) /
|
||||
(2 * inst_.sampling.sample_count());
|
||||
capture_info_buf_.irradiance_accum_sample_count = 0;
|
||||
|
||||
eGPUTextureUsage texture_usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
|
||||
GPU_TEXTURE_USAGE_HOST_READ | GPU_TEXTURE_USAGE_ATTACHMENT;
|
||||
|
@ -635,6 +657,8 @@ void IrradianceBake::surfels_create(const Object &probe_object)
|
|||
|
||||
/* Sync with any other following pass using the surfel buffer. */
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
/* Read back so that following push_update will contain correct surfel count. */
|
||||
capture_info_buf_.read();
|
||||
|
||||
DRW_stats_group_end();
|
||||
}
|
||||
|
@ -651,7 +675,7 @@ void IrradianceBake::surfels_lights_eval()
|
|||
inst_.manager->submit(surfel_light_eval_ps_, view_z_);
|
||||
}
|
||||
|
||||
void IrradianceBake::propagate_light_sample()
|
||||
void IrradianceBake::raylists_build()
|
||||
{
|
||||
using namespace blender::math;
|
||||
|
||||
|
@ -672,8 +696,7 @@ void IrradianceBake::propagate_light_sample()
|
|||
/* NOTE: Z values do not really matter since we are not doing any rasterization. */
|
||||
const float4x4 winmat = projection::orthographic<float>(min.x, max.x, min.y, max.y, 0, 1);
|
||||
|
||||
View ray_view = {"RayProjectionView"};
|
||||
ray_view.sync(viewmat, winmat);
|
||||
ray_view_.sync(viewmat, winmat);
|
||||
|
||||
/* This avoid light leaking by making sure that for one surface there will always be at least 1
|
||||
* surfel capture inside a ray list. Since the surface with the maximum distance (after
|
||||
|
@ -696,8 +719,22 @@ void IrradianceBake::propagate_light_sample()
|
|||
list_start_buf_.resize(ceil_to_multiple_u(list_info_buf_.list_max, 4));
|
||||
|
||||
GPU_storagebuf_clear(list_start_buf_, -1);
|
||||
inst_.manager->submit(surfel_light_propagate_ps_, ray_view);
|
||||
inst_.manager->submit(irradiance_capture_ps_, ray_view);
|
||||
inst_.manager->submit(surfel_ray_build_ps_, ray_view_);
|
||||
}
|
||||
|
||||
void IrradianceBake::propagate_light()
|
||||
{
|
||||
inst_.manager->submit(surfel_light_propagate_ps_, ray_view_);
|
||||
}
|
||||
|
||||
void IrradianceBake::irradiance_capture()
|
||||
{
|
||||
capture_info_buf_.push_update();
|
||||
|
||||
inst_.manager->submit(irradiance_capture_ps_, ray_view_);
|
||||
|
||||
/* Increment twice because each ray is evaluated in both directions. */
|
||||
capture_info_buf_.irradiance_accum_sample_count += 2;
|
||||
}
|
||||
|
||||
void IrradianceBake::accumulate_bounce()
|
||||
|
|
|
@ -41,6 +41,8 @@ class IrradianceBake {
|
|||
Framebuffer empty_raster_fb_ = {"empty_raster_fb_"};
|
||||
/** Evaluate light object contribution and store result to surfel. */
|
||||
PassSimple surfel_light_eval_ps_ = {"LightEval"};
|
||||
/** Create linked list of surfel to emulated raycast. */
|
||||
PassSimple surfel_ray_build_ps_ = {"RayBuild"};
|
||||
/** Propagate light from surfel to surfel. */
|
||||
PassSimple surfel_light_propagate_ps_ = {"LightPropagate"};
|
||||
/** Start of a light bounce. Accumulate light from previous propagation. */
|
||||
|
@ -78,6 +80,9 @@ class IrradianceBake {
|
|||
/* Dispatch size for per grid sample workload. */
|
||||
int3 dispatch_per_grid_sample_ = int3(1);
|
||||
|
||||
/** View used to flatten the surfels into surfel lists representing rays. */
|
||||
View ray_view_ = {"RayProjectionView"};
|
||||
|
||||
/** Irradiance textures for baking. Only represents one grid in there. */
|
||||
Texture irradiance_L0_tx_ = {"irradiance_L0_tx_"};
|
||||
Texture irradiance_L1_a_tx_ = {"irradiance_L1_a_tx_"};
|
||||
|
@ -100,8 +105,12 @@ class IrradianceBake {
|
|||
void surfels_create(const Object &probe_object);
|
||||
/** Evaluate direct lighting (and also clear the surfels radiance). */
|
||||
void surfels_lights_eval();
|
||||
/** Create a surfel lists to emulate ray-casts for the current sample random direction. */
|
||||
void raylists_build();
|
||||
/** Propagate light from surfel to surfel in a random direction over the sphere. */
|
||||
void propagate_light_sample();
|
||||
void propagate_light();
|
||||
/** Store surfel irradiance inside the irradiance grid samples. */
|
||||
void irradiance_capture();
|
||||
/** Accumulate light inside `surfel.radiance_bounce` to `surfel.radiance`. */
|
||||
void accumulate_bounce();
|
||||
|
||||
|
|
|
@ -870,10 +870,10 @@ struct CaptureInfoData {
|
|||
bool1 do_surfel_count;
|
||||
/** Number of surfels inside the surfel buffer or the needed len. */
|
||||
uint surfel_len;
|
||||
/** Solid angle subtended by a single ray sample. Equal to `4 * pi / sample_count`. */
|
||||
float irradiance_sample_solid_angle;
|
||||
/** Accumulated solid angle. Should reach `4 * pi` at the end of the accumulation. */
|
||||
float irradiance_accum_solid_angle;
|
||||
|
||||
float _pad3;
|
||||
/** Accumulated directional samples in irradiance grid. */
|
||||
int irradiance_accum_sample_count;
|
||||
/** Transform of the lightprobe object. */
|
||||
float4x4 irradiance_grid_local_to_world;
|
||||
/** Transform vectors from world space to local space. Does not have location component. */
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
|
|
|
@ -19,15 +19,15 @@ void irradiance_capture(vec3 L, vec3 irradiance, inout SphericalHarmonicL1 sh)
|
|||
{
|
||||
vec3 lL = transform_direction(capture_info_buf.irradiance_grid_world_to_local_rotation, L);
|
||||
|
||||
spherical_harmonics_encode_signal_sample(
|
||||
lL, vec4(irradiance, 1.0) * capture_info_buf.irradiance_sample_solid_angle, sh);
|
||||
spherical_harmonics_encode_signal_sample(lL, vec4(irradiance, 1.0), sh);
|
||||
}
|
||||
|
||||
void irradiance_capture(Surfel surfel_emitter, vec3 P, inout SphericalHarmonicL1 sh)
|
||||
{
|
||||
vec3 L = safe_normalize(surfel_emitter.position - P);
|
||||
bool facing = dot(-L, surfel_emitter.normal) > 0.0;
|
||||
vec3 irradiance = facing ? surfel_emitter.radiance_front : surfel_emitter.radiance_back;
|
||||
vec3 irradiance = facing ? surfel_emitter.outgoing_light_front :
|
||||
surfel_emitter.outgoing_light_back;
|
||||
|
||||
irradiance_capture(L, irradiance, sh);
|
||||
}
|
||||
|
@ -73,6 +73,15 @@ void main()
|
|||
sh.L1.M0 = imageLoad(irradiance_L1_b_img, grid_coord);
|
||||
sh.L1.Mp1 = imageLoad(irradiance_L1_c_img, grid_coord);
|
||||
|
||||
/* Spherical harmonics need to be weighted by sphere area. */
|
||||
const float sphere_area = 4.0 * M_PI;
|
||||
/* Un-normalize for accumulation. */
|
||||
float weight_captured = float(capture_info_buf.irradiance_accum_sample_count) / sphere_area;
|
||||
sh.L0.M0 *= weight_captured;
|
||||
sh.L1.Mn1 *= weight_captured;
|
||||
sh.L1.M0 *= weight_captured;
|
||||
sh.L1.Mp1 *= weight_captured;
|
||||
|
||||
if (surfel_next > -1) {
|
||||
irradiance_capture(surfel_buf[surfel_next], P, sh);
|
||||
}
|
||||
|
@ -89,6 +98,13 @@ void main()
|
|||
irradiance_capture(-sky_L, world_radiance, sh);
|
||||
}
|
||||
|
||||
/* Normalize for storage. */
|
||||
weight_captured += 2.0 / sphere_area;
|
||||
sh.L0.M0 /= weight_captured;
|
||||
sh.L1.Mn1 /= weight_captured;
|
||||
sh.L1.M0 /= weight_captured;
|
||||
sh.L1.Mp1 /= weight_captured;
|
||||
|
||||
imageStore(irradiance_L0_img, grid_coord, sh.L0.M0);
|
||||
imageStore(irradiance_L1_a_img, grid_coord, sh.L1.Mn1);
|
||||
imageStore(irradiance_L1_b_img, grid_coord, sh.L1.M0);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Spherical Harmonics Functions
|
||||
*
|
||||
|
@ -291,3 +293,199 @@ void spherical_harmonics_pack(SphericalHarmonicL1 sh,
|
|||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Deringing
|
||||
*
|
||||
* Adapted from Google's Filament under Apache 2.0 licence.
|
||||
* https://github.com/google/filament
|
||||
*
|
||||
* SH from environment with high dynamic range (or high frequencies -- high dynamic range creates
|
||||
* high frequencies) exhibit "ringing" and negative values when reconstructed.
|
||||
* To mitigate this, we need to low-pass the input image -- or equivalently window the SH by
|
||||
* coefficient that tapper towards zero with the band.
|
||||
*
|
||||
* "Stupid Spherical Harmonics (SH)"
|
||||
* "Deringing Spherical Harmonics"
|
||||
* by Peter-Pike Sloan
|
||||
* https://www.ppsloan.org/publications/shdering.pdf
|
||||
*
|
||||
* \{ */
|
||||
|
||||
float spherical_harmonics_sinc_window(const int l, float w)
|
||||
{
|
||||
if (l == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
else if (float(l) >= w) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/* We use a sinc window scaled to the desired window size in bands units a sinc window only has
|
||||
* zonal harmonics. */
|
||||
float x = (M_PI * float(l)) / w;
|
||||
x = sin(x) / x;
|
||||
|
||||
/* The convolution of a SH function f and a ZH function h is just the product of both
|
||||
* scaled by 1 / K(0,l) -- the window coefficients include this scale factor. */
|
||||
|
||||
/* Taking the window to power N is equivalent to applying the filter N times. */
|
||||
return pow4f(x);
|
||||
}
|
||||
|
||||
void spherical_harmonics_apply_windowing(inout SphericalHarmonicL2 sh, float cutoff)
|
||||
{
|
||||
float w_l0 = spherical_harmonics_sinc_window(0, cutoff);
|
||||
sh.L0.M0 *= w_l0;
|
||||
|
||||
float w_l1 = spherical_harmonics_sinc_window(1, cutoff);
|
||||
sh.L1.Mn1 *= w_l1;
|
||||
sh.L1.M0 *= w_l1;
|
||||
sh.L1.Mp1 *= w_l1;
|
||||
|
||||
float w_l2 = spherical_harmonics_sinc_window(2, cutoff);
|
||||
sh.L2.Mn2 *= w_l2;
|
||||
sh.L2.Mn1 *= w_l2;
|
||||
sh.L2.M0 *= w_l2;
|
||||
sh.L2.Mp1 *= w_l2;
|
||||
sh.L2.Mp2 *= w_l2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the minimum of the Spherical harmonic function.
|
||||
* Section 2.3 from the paper.
|
||||
* Only work on a single channel at a time.
|
||||
*/
|
||||
float spherical_harmonics_find_minimum(SphericalHarmonicL2 sh, int channel)
|
||||
{
|
||||
const int comp = channel;
|
||||
const float coef_L0_M0 = 0.282094792;
|
||||
const float coef_L1_Mn1 = -0.488602512;
|
||||
const float coef_L1_M0 = 0.488602512;
|
||||
const float coef_L1_Mp1 = -0.488602512;
|
||||
const float coef_L2_Mn2 = 1.092548431;
|
||||
const float coef_L2_Mn1 = -1.092548431;
|
||||
const float coef_L2_M0 = 0.315391565;
|
||||
const float coef_L2_Mp1 = -1.092548431;
|
||||
const float coef_L2_Mp2 = 0.546274215;
|
||||
|
||||
/* Align the SH Z axis with the optimal linear direction. */
|
||||
vec3 z_axis = normalize(vec3(sh.L1.Mp1[comp], sh.L1.Mn1[comp], -sh.L1.M0[comp]));
|
||||
vec3 x_axis = normalize(cross(z_axis, vec3(0.0, 1.0, 0.0)));
|
||||
vec3 y_axis = cross(x_axis, z_axis);
|
||||
mat3x3 rotation = transpose(mat3x3(x_axis, y_axis, -z_axis));
|
||||
|
||||
spherical_harmonics_L1_rotate(rotation, sh.L1);
|
||||
|
||||
/* 2.3.2: Find the min for |m| = 2:
|
||||
*
|
||||
* Peter-Pike Sloan shows that the minimum can be expressed as a function
|
||||
* of z such as: m2min = -m2_max * (1 - z^2) = m2_max * z^2 - m2_max
|
||||
* with m2_max = A[8] * sqrt(f[8] * f[8] + f[4] * f[4]);
|
||||
* We can therefore include this in the ZH min computation (which is function of z^2 as well).
|
||||
*/
|
||||
float m2_max = coef_L2_Mp2 * sqrt(square_f(sh.L2.Mp2[comp]) + square_f(sh.L2.Mn2[comp]));
|
||||
|
||||
/* 2.3.1: Find the min of the zonal harmonics:
|
||||
*
|
||||
* This comes from minimizing the function:
|
||||
* ZH(z) = (coef_L0_M0 * sh.L0.M0)
|
||||
* + (coef_L1_M0 * sh.L1.M0) * z
|
||||
* + (coef_L2_M0 * sh.L2.M0) * (3 * sqr(s.z) - 1)
|
||||
*
|
||||
* We do that by finding where it's derivative d/dz is zero:
|
||||
* dZH(z)/dz = a * z^2 + b * z + c
|
||||
* which is zero for z = -b / 2 * a
|
||||
*
|
||||
* We also needs to check that -1 < z < 1, otherwise the min is either in z = -1 or 1
|
||||
*/
|
||||
float a = 3.0 * coef_L2_M0 * sh.L2.M0[comp] + m2_max;
|
||||
float b = coef_L1_M0 * sh.L1.M0[comp];
|
||||
float c = coef_L0_M0 * sh.L0.M0[comp] - coef_L2_M0 * sh.L2.M0[comp] - m2_max;
|
||||
|
||||
float z_min = -b / (2.0 * a);
|
||||
float m0_min_z = a * pow2f(z_min) + b * z_min + c;
|
||||
float m0_min_b = min(a + b + c, a - b + c);
|
||||
|
||||
float m0_min = (a > 0.0 && z_min >= -1.0 && z_min <= 1.0) ? m0_min_z : m0_min_b;
|
||||
|
||||
/* 2.3.3: Find the min for l = 2, |m| = 1:
|
||||
*
|
||||
* Note l = 1, |m| = 1 is guaranteed to be 0 because of the rotation step
|
||||
*
|
||||
* The function considered is:
|
||||
* Y(x, y, z) = A[5] * f[5] * s.y * s.z
|
||||
* + A[7] * f[7] * s.z * s.x
|
||||
*/
|
||||
float d = coef_L2_Mn2 * sqrt(square_f(sh.L2.Mp1[comp]) + square_f(sh.L2.Mn1[comp]));
|
||||
|
||||
/* The |m|=1 function is minimal in -0.5 -- use that to skip the Newton's loop when possible. */
|
||||
float minimum = m0_min - 0.5 * d;
|
||||
if (minimum < 0) {
|
||||
/* We could be negative, to find the minimum we will use Newton's method
|
||||
* See https://en.wikipedia.org/wiki/Newton%27s_method_in_optimization */
|
||||
|
||||
/* This is the function we're trying to minimize:
|
||||
* y = a * sqr(x) + b * x + c) + (d * x * sqrt(1.0 - sqr(x))
|
||||
* First term accounts for ZH + |m| = 2, second terms for |m| = 1. */
|
||||
|
||||
/* We start guessing at the min of |m|=1 function. */
|
||||
float z = -M_SQRT1_2;
|
||||
float dz;
|
||||
do {
|
||||
/* Evaluate our function. */
|
||||
minimum = (a * pow2f(z) + b * z + c) + (d * z * sqrt(1.0 - pow2f(z)));
|
||||
/* This is `func' / func''`. This was computed with Mathematica. */
|
||||
dz = (pow2f(z) - 1.0) * (d - 2 * d * pow2f(z) + (b + 2.0 * a * z) * sqrt(1 - pow2f(z))) /
|
||||
(3 * d * z - 2 * d * pow3f(z) - 2.0 * a * pow(1 - pow2f(z), 1.5f));
|
||||
/* Refine our guess by this amount. */
|
||||
z = z - dz;
|
||||
/* Exit if z goes out of range, or if we have reached enough precision. */
|
||||
} while (abs(z) <= 1.0 && abs(dz) > 1.0e-5);
|
||||
|
||||
if (abs(z) > 1.0) {
|
||||
/* z was out of range. Compute `min(function(1), function(-1))`. */
|
||||
float func_pos = (a + b + c);
|
||||
float func_neg = (a - b + c);
|
||||
minimum = min(func_pos, func_neg);
|
||||
}
|
||||
}
|
||||
return minimum;
|
||||
}
|
||||
|
||||
void spherical_harmonics_dering(inout SphericalHarmonicL2 sh)
|
||||
{
|
||||
float cutoff = 0.0;
|
||||
if (true) {
|
||||
/* Auto windowing.
|
||||
* Find cutoff threshold automatically. Can be replaced by manual cutoff value. */
|
||||
SphericalHarmonicL2 tmp_sh = sh;
|
||||
|
||||
const int num_bands = 3;
|
||||
/* Start at a large band. */
|
||||
float cutoff = float(num_bands * 4 + 1);
|
||||
/* We need to process each channel separately. */
|
||||
for (int channel = 0; channel < 4; channel++) {
|
||||
/* Find a cut-off band that works. */
|
||||
float l = num_bands;
|
||||
float r = cutoff;
|
||||
for (int i = 0; i < 16 && (l + 0.1) < r; i++) {
|
||||
float m = 0.5 * (l + r);
|
||||
spherical_harmonics_apply_windowing(tmp_sh, m);
|
||||
|
||||
float sh_min = spherical_harmonics_find_minimum(tmp_sh, channel);
|
||||
if (sh_min < 0.0) {
|
||||
r = m;
|
||||
}
|
||||
else {
|
||||
l = m;
|
||||
}
|
||||
}
|
||||
cutoff = min(cutoff, l);
|
||||
}
|
||||
}
|
||||
|
||||
spherical_harmonics_apply_windowing(sh, cutoff);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
Loading…
Reference in New Issue