EEVEE-Next: Fast GI: Add angular thickness

While easier to understand, the conventionnal global
scene thickness parameter have some downside:
- It doesn't scale with larger scenes since the
  distant samples have small thickness
- It doesn't handle fine geometric variation in
  foreground.

The proposed angular thicknes makes all sample
have the same angular span. This makes it
distance independant and capture different occluder
thickness with less artifacts. The downside is
that the occluders have the same angular span
at any distance which makes the same occluder inflate
with distance.

A downside is that the geometry near the shading point is
under-represented. Leaving light leaking or lack of AO
at contact points. To fix this, we introduce back a
geometric thickness parameter.

Pull Request: https://projects.blender.org/blender/blender/pulls/122334
This commit is contained in:
Clément Foucault
2024-05-28 16:00:38 +02:00
committed by Clément Foucault
parent ceeaea0c8a
commit 117bfc870d
12 changed files with 62 additions and 71 deletions

View File

@@ -628,10 +628,13 @@ class RENDER_PT_eevee_next_gi_approximation(RenderButtonsPanel, Panel):
col = layout.column(align=True)
col.prop(props, "fast_gi_ray_count", text="Rays")
col.prop(props, "fast_gi_step_count", text="Steps")
col.prop(props, "horizon_quality", text="Precision")
col = layout.column(align=True)
col.prop(props, "fast_gi_distance")
col.prop(props, "fast_gi_thickness_near", text="Thickness Near")
col.prop(props, "fast_gi_thickness_far", text="Far")
layout.prop(props, "horizon_quality", text="Precision")
layout.prop(props, "fast_gi_distance")
layout.prop(props, "horizon_thickness", text="Thickness")
layout.prop(props, "horizon_bias", text="Bias")

View File

@@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 45
#define BLENDER_FILE_SUBVERSION 46
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -3664,6 +3664,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 46)) {
const Scene *default_scene = DNA_struct_default_get(Scene);
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.fast_gi_thickness_near = default_scene->eevee.fast_gi_thickness_near;
scene->eevee.fast_gi_thickness_far = default_scene->eevee.fast_gi_thickness_far;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -43,8 +43,9 @@ void AmbientOcclusion::init()
data_.distance = sce_eevee.gtao_distance;
data_.gi_distance = (sce_eevee.fast_gi_distance > 0.0f) ? sce_eevee.fast_gi_distance : 1e16f;
data_.lod_factor = 1.0f / (1.0f + sce_eevee.gtao_quality * 4.0f);
data_.thickness = sce_eevee.gtao_thickness;
data_.angle_bias = 1.0 / max_ff(1e-8f, 1.0 - sce_eevee.gtao_focus);
data_.thickness_near = sce_eevee.fast_gi_thickness_near;
data_.thickness_far = sce_eevee.fast_gi_thickness_far;
/* Size is multiplied by 2 because it is applied in NDC [-1..1] range. */
data_.pixel_size = float2(2.0f) / float2(inst_.film.render_extent_get());
}

View File

@@ -1934,10 +1934,10 @@ struct AOData {
float distance;
float lod_factor;
float thickness;
float thickness_near;
float thickness_far;
float angle_bias;
float gi_distance;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(AOData, 16)

View File

@@ -36,7 +36,8 @@ void main()
noise,
uniform_buf.ao.pixel_size,
uniform_buf.ao.distance,
uniform_buf.ao.thickness,
uniform_buf.ao.thickness_near,
uniform_buf.ao.thickness_far,
uniform_buf.ao.angle_bias,
2,
10,

View File

@@ -51,7 +51,8 @@ void main()
noise,
uniform_buf.ao.pixel_size,
uniform_buf.ao.gi_distance,
uniform_buf.ao.thickness,
uniform_buf.ao.thickness_near,
uniform_buf.ao.thickness_far,
uniform_buf.ao.angle_bias,
fast_gi_slice_count,
fast_gi_step_count,

View File

@@ -49,36 +49,6 @@ vec3 horizon_scan_sample_normal(vec2 uv)
#endif
}
/**
* Returns the start and end point of a ray clipped to its intersection
* with a sphere.
*/
void horizon_scan_occluder_intersection_ray_sphere_clip(Ray ray,
Sphere sphere,
out vec3 P_entry,
out vec3 P_exit)
{
vec3 f = ray.origin - sphere.center;
float a = length_squared(ray.direction);
float b = 2.0 * dot(ray.direction, f);
float c = length_squared(f) - square(sphere.radius);
float determinant = b * b - 4.0 * a * c;
if (determinant <= 0.0) {
/* No intersection. Return null segment. */
P_entry = P_exit = ray.origin;
return;
}
/* Using fast sqrt_fast doesn't seem to cause artifact here. */
float t_min = (-b - sqrt_fast(determinant)) / (2.0 * a);
float t_max = (-b + sqrt_fast(determinant)) / (2.0 * a);
/* Clip segment to the intersection range. */
float t_entry = clamp(0.0, t_min, t_max);
float t_exit = clamp(ray.max_time, t_min, t_max);
P_entry = ray.origin + ray.direction * t_entry;
P_exit = ray.origin + ray.direction * t_exit;
}
struct HorizonScanResult {
#ifdef HORIZON_OCCLUSION
float result;
@@ -98,7 +68,8 @@ HorizonScanResult horizon_scan_eval(vec3 vP,
vec2 noise,
vec2 pixel_size,
float search_distance,
float global_thickness,
float thickness_near,
float thickness_far,
float angle_bias,
const int slice_count,
const int sample_count,
@@ -184,31 +155,23 @@ HorizonScanResult horizon_scan_eval(vec3 vP,
sample_depth += reversed ? -bias : bias;
vec3 vP_sample = drw_point_screen_to_view(vec3(sample_uv, sample_depth));
vec3 vV_sample = drw_view_incident_vector(vP_sample);
Ray ray;
ray.origin = vP_sample;
ray.direction = -vV_sample;
ray.max_time = global_thickness;
if (reversed) {
/* Make the ray start above the surface and end exactly at the surface. */
ray.max_time = 2.0 * distance(vP, vP_sample);
ray.origin = vP_sample + vV_sample * ray.max_time;
ray.direction = -vV_sample;
float sample_distance;
vec3 vL_front = normalize_and_get_length(vP_sample - vP, sample_distance);
if (sample_distance > search_distance) {
continue;
}
Sphere sphere = shape_sphere(vP, search_distance);
vec3 vP_front = ray.origin, vP_back = ray.origin + ray.direction * ray.max_time;
horizon_scan_occluder_intersection_ray_sphere_clip(ray, sphere, vP_front, vP_back);
vec3 vL_front = normalize(vP_front - vP);
vec3 vL_back = normalize(vP_back - vP);
vec3 vL_back = normalize_and_get_length((vP_sample - vV * thickness_near) - vP,
sample_distance);
if (sample_distance > search_distance) {
continue;
}
/* Ordered pair of angle. Minimum in X, Maximum in Y.
* Front will always have the smallest angle here since it is the closest to the view. */
vec2 theta = acos_fast(vec2(dot(vL_front, vV), dot(vL_back, vV)));
theta.y = max(theta.x + thickness_far, theta.y);
/* If we are tracing backward, the angles are negative. Swizzle to keep correct order. */
theta = (side == 0) ? theta.xy : -theta.yx;
@@ -216,7 +179,7 @@ HorizonScanResult horizon_scan_eval(vec3 vP,
/* Take emitter surface normal into consideration. */
vec3 sample_normal = horizon_scan_sample_normal(sample_uv);
/* Discard back-facing samples.
* The 2 factor is to avoid loosing too much energy (which is something not
* The 2 factor is to avoid loosing too much energy v(which is something not
* explained in the paper...). Likely to be wrong, but we need a soft falloff. */
float facing_weight = saturate(-dot(sample_normal, vL_front) * 2.0);
@@ -227,8 +190,6 @@ HorizonScanResult horizon_scan_eval(vec3 vP,
~slice_bitmask);
sample_radiance *= facing_weight * weight_bitmask;
/* Encoding using front sample direction gives better result than
* `normalize(vL_front + vL_back)` */
spherical_harmonics_encode_signal_sample(
vL_front, vec4(sample_radiance, weight_bitmask), sh_slice);

View File

@@ -370,7 +370,8 @@ float ambient_occlusion_eval(vec3 normal,
noise,
uniform_buf.ao.pixel_size,
max_distance,
uniform_buf.ao.thickness,
uniform_buf.ao.thickness_near,
uniform_buf.ao.thickness_far,
uniform_buf.ao.angle_bias,
2,
10,

View File

@@ -216,6 +216,8 @@
.fast_gi_step_count = 8, \
.fast_gi_ray_count = 2, \
.fast_gi_distance = 0.0f, \
.fast_gi_thickness_near = 0.25f, \
.fast_gi_thickness_far = DEG2RAD(45), \
.fast_gi_method = FAST_GI_FULL, \
\
.bokeh_overblur = 5.0f, \

View File

@@ -1867,6 +1867,8 @@ typedef struct SceneEEVEE {
int fast_gi_step_count;
int fast_gi_ray_count;
float fast_gi_distance;
float fast_gi_thickness_near;
float fast_gi_thickness_far;
char fast_gi_method;
char _pad0[3];

View File

@@ -8294,16 +8294,27 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
/* Horizon Scan */
/* Fast GI approximation */
prop = RNA_def_property(srna, "horizon_thickness", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "gtao_thickness");
RNA_def_property_ui_text(prop,
"Thickness",
"Constant thickness of the surfaces considered when doing horizon scan "
"and by extension ambient occlusion");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1, 3);
prop = RNA_def_property(srna, "fast_gi_thickness_near", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_ui_text(
prop,
"Near Thickness",
"Geometric thickness of the surfaces when computing fast GI and ambient occlusion. "
"Reduces light leaking and missing contact occlusion");
RNA_def_property_range(prop, 0.0f, 100000.0f);
RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1.0f, 3);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "fast_gi_thickness_far", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_ui_text(
prop,
"Far Thickness",
"Angular thickness of the surfaces when computing fast GI and ambient occlusion. "
"Reduces energy loss and missing occlusion of far geometry");
RNA_def_property_range(prop, DEG2RADF(1.0f), DEG2RADF(180.0f));
RNA_def_property_ui_range(prop, DEG2RADF(1.0f), DEG2RADF(180.0f), 10.0f, 3);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);