EEVEE-Next: Irradiance Cache: Add manual trilinear weights #110312

Merged
Clément Foucault merged 1 commits from fclem/blender:eevee-next-irradiance-sampling-bias into main 2023-07-20 19:03:15 +02:00
9 changed files with 135 additions and 14 deletions
Showing only changes of commit a30d16189e - Show all commits

View File

@ -111,6 +111,12 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel):
col.prop(probe, "grid_bake_samples")
col.prop(probe, "surfel_density")
col.separator()
col.prop(probe, "grid_normal_bias")
col.prop(probe, "grid_view_bias")
col.prop(probe, "grid_irradiance_smoothing")
elif probe.type == 'CUBEMAP':
col = layout.column()
col.prop(probe, "resolution")

View File

@ -378,10 +378,13 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
/* Keep this block, even when empty. */
if (!DNA_struct_elem_find(fd->filesdna, "LightProbe", "int", "grid_bake_sample_count")) {
if (!DNA_struct_elem_find(fd->filesdna, "LightProbe", "int", "grid_bake_samples")) {
LISTBASE_FOREACH (LightProbe *, lightprobe, &bmain->lightprobes) {
lightprobe->grid_bake_samples = 2048;
lightprobe->surfel_density = 1.0f;
lightprobe->grid_normal_bias = 0.3f;
lightprobe->grid_view_bias = 0.0f;
lightprobe->grid_facing_bias = 0.5f;
}
}

View File

@ -30,6 +30,8 @@ void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
IrradianceGrid &grid = grid_map_.lookup_or_add_default(handle.object_key);
grid.used = true;
if (handle.recalc != 0 || grid.initialized == false) {
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
grid.initialized = true;
grid.updated = true;
grid.object_to_world = float4x4(ob->object_to_world);
@ -37,6 +39,9 @@ void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
math::normalize(math::transpose(float3x3(grid.object_to_world))));
grid.cache = ob->lightprobe_cache;
grid.normal_bias = lightprobe->grid_normal_bias;
grid.view_bias = lightprobe->grid_view_bias;
grid.facing_bias = lightprobe->grid_facing_bias;
/* Force reupload. */
inst_.irradiance_cache.bricks_free(grid.bricks);
}

View File

@ -922,6 +922,11 @@ struct IrradianceGridData {
packed_int3 grid_size;
/** Index in brick descriptor list of the first brick of this grid. */
int brick_offset;
/** Biases to apply to the shading point in order to sample a valid probe. */
float normal_bias;
float view_bias;
float facing_bias;
int _pad1;
};
BLI_STATIC_ASSERT_ALIGN(IrradianceGridData, 16)

View File

@ -12,8 +12,15 @@
/**
* Return sample coordinates of the first SH coef in unormalized texture space.
*/
vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, vec3 lP)
vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data,
vec3 lP,
vec3 lV,
vec3 lNg)
{
/* Shading point bias. */
lP += lNg * grid_data.normal_bias;
lP += lV * grid_data.view_bias;
ivec3 brick_coord = ivec3((lP - 0.5) / float(IRRADIANCE_GRID_BRICK_SIZE - 1));
/* Avoid sampling adjacent bricks. */
brick_coord = max(brick_coord, ivec3(0));
@ -23,8 +30,58 @@ vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, vec3 l
vec3 brick_lP = lP - vec3(brick_coord) * float(IRRADIANCE_GRID_BRICK_SIZE - 1);
int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord);
IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]);
/* A cell is the interpolation region between 8 texels. */
vec3 cell_lP = brick_lP - 0.5;
vec3 cell_start = floor(cell_lP);
vec3 cell_fract = cell_lP - cell_start;
/**
* References:
*
* "Probe-based lighting, strand-based hair system, and physical hair shading in Unitys Enemies"
* by Francesco Cifariello Ciardi, Lasse Jon Fuglsang Pedersen and John Parsaie.
*
* "Multi-Scale Global Illumination in Quantum Break"
* by Ari Silvennoinen and Ville Timonen.
*
* Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields
* by Morgan McGuire.
*/
float trilinear_weights[8];
float total_weight = 0.0;
for (int i = 0; i < 8; i++) {
ivec3 sample_position = (ivec3(i) >> ivec3(0, 1, 2)) & 1;
vec3 trilinear = select(1.0 - cell_fract, cell_fract, bvec3(sample_position));
float positional_weight = trilinear.x * trilinear.y * trilinear.z;
float len;
vec3 corner_vec = vec3(sample_position) - cell_fract;
vec3 corner_dir = normalize_and_get_length(corner_vec, len);
float cos_theta = (len > 1e-8) ? dot(lNg, corner_dir) : 1.0;
float geometry_weight = saturate(cos_theta * 0.5 + 0.5);
/* TODO(fclem): Need to bake validity. */
float validity_weight = 1.0;
/* Biases. See McGuire's presentation. */
positional_weight += 0.001;
geometry_weight = sqr(geometry_weight) + 0.2 + grid_data.facing_bias;
trilinear_weights[i] = saturate(positional_weight * geometry_weight * validity_weight);
total_weight += trilinear_weights[i];
}
float total_weight_inv = safe_rcp(total_weight);
vec3 trilinear_coord = vec3(0.0);
for (int i = 0; i < 8; i++) {
vec3 sample_position = vec3((ivec3(i) >> ivec3(0, 1, 2)) & 1);
trilinear_coord += sample_position * trilinear_weights[i] * total_weight_inv;
}
/* Replace sampling coordinates with manually weighted trilinear coordinates. */
brick_lP = 0.5 + cell_start + trilinear_coord;
vec3 output_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP;
return output_coord;
@ -35,7 +92,7 @@ vec4 textureUnormalizedCoord(sampler3D tx, vec3 co)
return texture(tx, co / vec3(textureSize(tx, 0)));
}
SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P)
SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng)
{
vec3 lP;
int grid_index;
@ -52,16 +109,24 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P)
}
}
vec3 atlas_coord = lightprobe_irradiance_grid_atlas_coord(grids_infos_buf[grid_index], lP);
/* TODO(fclem): Make sure this is working as expected. */
mat3x3 world_to_grid_transposed = mat3x3(grids_infos_buf[grid_index].world_to_grid_transposed);
vec3 lNg = safe_normalize(world_to_grid_transposed * Ng);
vec3 lV = safe_normalize(V * world_to_grid_transposed);
vec3 atlas_coord = lightprobe_irradiance_grid_atlas_coord(
grids_infos_buf[grid_index], lP, lV, lNg);
vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) /
vec3(textureSize(atlas_tx, 0)).xyzz;
SphericalHarmonicL1 sh;
sh.L0.M0 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.Mn1 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.M0 = textureUnormalizedCoord(atlas_tx, atlas_coord);
atlas_coord.z += float(IRRADIANCE_GRID_BRICK_SIZE);
sh.L1.Mp1 = textureUnormalizedCoord(atlas_tx, atlas_coord);
sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
return sh;
}
@ -73,7 +138,10 @@ void lightprobe_eval(ClosureDiffuse diffuse,
inout vec3 out_diffuse,
inout vec3 out_specular)
{
SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample(irradiance_atlas_tx, P);
/* NOTE: Use the diffuse normal for biasing the probe sampling location since it is smoother than
* geometric normal. Could also try to use interp.N. */
SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample(
irradiance_atlas_tx, P, V, diffuse.N);
out_diffuse += spherical_harmonics_evaluate_lambert(diffuse.N, irradiance);
}

View File

@ -54,7 +54,7 @@ void main()
if (z_delta > 0.0) {
float fac = 1.0 - z_delta * 10000.0;
/* Smooth blend to avoid flickering. */
finalColor = mix(colorBackground, finalColor, clamp(fac, 0.5, 1.0));
finalColor = mix(colorBackground, finalColor, clamp(fac, 0.2, 1.0));
}
view_clipping_distances(ws_cell_location);

View File

@ -21,6 +21,9 @@
.grid_resolution_y = 4, \
.grid_resolution_z = 4, \
.grid_bake_samples = 2048, \
.grid_normal_bias = 0.3f, \
.grid_view_bias = 0.0f, \
.grid_facing_bias = 0.5f, \
.surfel_density = 1.0f, \
.distinf = 2.5f, \
.distpar = 2.5f, \

View File

@ -57,6 +57,11 @@ typedef struct LightProbe {
int grid_resolution_z;
/** Irradiance grid: number of directions to evaluate light transfer in. */
int grid_bake_samples;
/** Irradiance grid: Sampling biases. */
float grid_normal_bias;
float grid_view_bias;
float grid_facing_bias;
float _pad0;
/** Surface element density for scene surface cache. In surfel per unit distance. */
float surfel_density;

View File

@ -166,6 +166,32 @@ static void rna_def_lightprobe(BlenderRNA *brna)
prop, "Resolution Z", "Number of samples along the z axis of the volume");
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "grid_normal_bias", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_ui_text(prop,
"Normal Bias",
"Offset sampling of the irradiance grid in "
"the surface normal direction to reduce light bleeding");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, 3);
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "grid_view_bias", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_ui_text(prop,
"View Bias",
"Offset sampling of the irradiance grid in "
"the viewing direction to reduce light bleeding");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, 3);
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "grid_irradiance_smoothing", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, nullptr, "grid_facing_bias");
RNA_def_property_ui_text(
prop, "Facing Bias", "Smoother irradiance interpolation but introduce light bleeding");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, 3);
RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc");
prop = RNA_def_property(srna, "grid_bake_samples", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(
prop, "Bake Samples", "Number of ray directions to evaluate when baking");