This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl
Clément Foucault 6b6bcbe60c Fix T86612 EEVEE: Wrong AmbientOcclusion on refractive materials
This is because the refractive materials were using the deferred AO
computation instead of tracing their own.
2021-03-16 15:31:04 +01:00

412 lines
13 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(raytrace_lib.glsl)
/* Based on Practical Realtime Strategies for Accurate Indirect Occlusion
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pptx
*/
#if defined(MESH_SHADER)
# if !defined(USE_ALPHA_HASH)
# if !defined(DEPTH_SHADER)
# if !defined(USE_ALPHA_BLEND)
# if !defined(USE_REFRACTION)
# define ENABLE_DEFERED_AO
# endif
# endif
# endif
# endif
#endif
#ifndef ENABLE_DEFERED_AO
# if defined(STEP_RESOLVE)
# define ENABLE_DEFERED_AO
# endif
#endif
uniform sampler2D horizonBuffer;
/* aoSettings flags */
#define USE_AO 1
#define USE_BENT_NORMAL 2
#define USE_DENOISE 4
#define NO_OCCLUSION_DATA OcclusionData(vec4(M_PI, -M_PI, M_PI, -M_PI), 1.0)
struct OcclusionData {
/* 4 horizons angles, one in each direction around the view vector to form a cross pattern. */
vec4 horizons;
/* Custom large scale occlusion. */
float custom_occlusion;
};
vec4 pack_occlusion_data(OcclusionData data)
{
return vec4(1.0 - data.horizons * vec4(1, -1, 1, -1) * M_1_PI);
}
OcclusionData unpack_occlusion_data(vec4 v)
{
return OcclusionData((1.0 - v) * vec4(1, -1, 1, -1) * M_PI, 0.0);
}
vec2 get_ao_noise(void)
{
vec2 noise = texelfetch_noise_tex(gl_FragCoord.xy).xy;
/* Decorrelate noise from AA. */
/* TODO(fclem) we should use a more general approach for more random number dimentions. */
noise = fract(noise * 6.1803402007);
return noise;
}
vec2 get_ao_dir(float jitter)
{
/* Only a quarter of a turn because we integrate using 2 slices.
* We use this instead of using utiltex circle noise to improve cache hits
* since all tracing direction will be in the same quadrant. */
jitter *= M_PI_2;
return vec2(cos(jitter), sin(jitter));
}
/* Return horizon angle cosine. */
float search_horizon(vec3 vI,
vec3 vP,
float noise,
ScreenSpaceRay ssray,
sampler2D depth_tx,
const float inverted,
float radius,
const float sample_count)
{
/* Init at cos(M_PI). */
float h = (inverted != 0.0) ? 1.0 : -1.0;
ssray.max_time -= 1.0;
if (ssray.max_time <= 2.0) {
/* Produces self shadowing under this threshold. */
return fast_acos(h);
}
float prev_time, time = 0.0;
for (float iter = 0.0; time < ssray.max_time && iter < sample_count; iter++) {
prev_time = time;
/* Gives us good precision at center and ensure we cross at least one pixel per iteration. */
time = 1.0 + iter + sqr((iter + noise) / sample_count) * ssray.max_time;
float stride = time - prev_time;
float lod = (log2(stride) - noise) / (1.0 + aoQuality);
vec2 uv = ssray.origin.xy + ssray.direction.xy * time;
float depth = textureLod(depth_tx, uv * hizUvScale.xy, floor(lod)).r;
if (depth == 1.0 && inverted == 0.0) {
/* Skip background. Avoids making shadow on the geometry near the far plane. */
continue;
}
/* Bias depth a bit to avoid self shadowing issues. */
const float bias = 2.0 * 2.4e-7;
depth += (inverted != 0.0) ? -bias : bias;
vec3 s = get_view_space_from_depth(uv, depth);
vec3 omega_s = s - vP;
float len = length(omega_s);
/* Sample's horizon angle cosine. */
float s_h = dot(vI, omega_s / len);
/* Blend weight to fade artifacts. */
float dist_ratio = abs(len) / radius;
/* Sphere falloff. */
float dist_fac = sqr(saturate(dist_ratio));
/* Unbiased, gives too much hard cut behind objects */
// float dist_fac = step(0.999, dist_ratio);
if (inverted != 0.0) {
h = min(h, s_h);
}
else {
h = mix(max(h, s_h), h, dist_fac);
}
}
return fast_acos(h);
}
OcclusionData occlusion_search(
vec3 vP, sampler2D depth_tx, float radius, const float inverted, const float dir_sample_count)
{
if ((int(aoSettings) & USE_AO) == 0) {
return NO_OCCLUSION_DATA;
}
vec2 noise = get_ao_noise();
vec2 dir = get_ao_dir(noise.x);
vec2 uv = get_uvs_from_view(vP);
vec3 vI = ((ProjectionMatrix[3][3] == 0.0) ? normalize(-vP) : vec3(0.0, 0.0, 1.0));
vec3 avg_dir = vec3(0.0);
float avg_apperture = 0.0;
OcclusionData data = (inverted != 0.0) ? OcclusionData(vec4(0, 0, 0, 0), 1.0) :
NO_OCCLUSION_DATA;
for (int i = 0; i < 2; i++) {
Ray ray;
ray.origin = vP;
ray.direction = vec3(dir * radius, 0.0);
ScreenSpaceRay ssray;
ssray = raytrace_screenspace_ray_create(ray);
data.horizons[0 + i * 2] = search_horizon(
vI, vP, noise.y, ssray, depth_tx, inverted, radius, dir_sample_count);
ray.direction = -ray.direction;
ssray = raytrace_screenspace_ray_create(ray);
data.horizons[1 + i * 2] = -search_horizon(
vI, vP, noise.y, ssray, depth_tx, inverted, radius, dir_sample_count);
/* Rotate 90 degrees. */
dir = vec2(-dir.y, dir.x);
}
return data;
}
vec2 clamp_horizons_to_hemisphere(vec2 horizons, float angle_N, const float inverted)
{
/* Add a little bias to fight self shadowing. */
const float max_angle = M_PI_2 - 0.05;
if (inverted != 0.0) {
horizons.x = max(horizons.x, angle_N + max_angle);
horizons.y = min(horizons.y, angle_N - max_angle);
}
else {
horizons.x = min(horizons.x, angle_N + max_angle);
horizons.y = max(horizons.y, angle_N - max_angle);
}
return horizons;
}
void occlusion_eval(OcclusionData data,
vec3 V,
vec3 N,
vec3 Ng,
const float inverted,
out float visibility,
out float visibility_error,
out vec3 bent_normal)
{
if ((int(aoSettings) & USE_AO) == 0) {
visibility = data.custom_occlusion;
bent_normal = N;
return;
}
bool early_out = (inverted != 0.0) ? (max_v4(abs(data.horizons)) == 0.0) :
(min_v4(abs(data.horizons)) == M_PI);
if (early_out) {
visibility = dot(N, Ng) * 0.5 + 0.5;
visibility = min(visibility, data.custom_occlusion);
if ((int(aoSettings) & USE_BENT_NORMAL) == 0) {
bent_normal = N;
}
else {
bent_normal = normalize(N + Ng);
}
return;
}
vec2 noise = get_ao_noise();
vec2 dir = get_ao_dir(noise.x);
visibility_error = 0.0;
visibility = 0.0;
bent_normal = N * 0.001;
for (int i = 0; i < 2; i++) {
vec3 T = transform_direction(ViewMatrixInverse, vec3(dir, 0.0));
/* Setup integration domain around V. */
vec3 B = normalize(cross(V, T));
T = normalize(cross(B, V));
float proj_N_len;
vec3 proj_N = normalize_len(N - B * dot(N, B), proj_N_len);
vec3 proj_Ng = normalize(Ng - B * dot(Ng, B));
vec2 h = (i == 0) ? data.horizons.xy : data.horizons.zw;
float N_sin = dot(proj_N, T);
float Ng_sin = dot(proj_Ng, T);
float N_cos = saturate(dot(proj_N, V));
float Ng_cos = saturate(dot(proj_Ng, V));
/* Gamma, angle between normalized projected normal and view vector. */
float angle_Ng = sign(Ng_sin) * fast_acos(Ng_cos);
float angle_N = sign(N_sin) * fast_acos(N_cos);
/* Clamp horizons to hemisphere around shading normal. */
h = clamp_horizons_to_hemisphere(h, angle_N, inverted);
float bent_angle = (h.x + h.y) * 0.5;
/* NOTE: here we multiply z by 0.5 as it shows less difference with the geometric normal.
* Also modulate by projected normal length to reduce issues with slanted surfaces.
* All of this is ad-hoc and not really grounded. */
bent_normal += proj_N_len * (T * sin(bent_angle) + V * 0.5 * cos(bent_angle));
/* Clamp to geometric normal only for integral to keep smooth bent normal. */
/* This is done to match Cycles ground truth but adds some computation. */
h = clamp_horizons_to_hemisphere(h, angle_Ng, inverted);
/* Inner integral (Eq. 7). */
float a = dot(-cos(2.0 * h - angle_N) + N_cos + 2.0 * h * N_sin, vec2(0.25));
/* Correct normal not on plane (Eq. 8). */
visibility += proj_N_len * a;
/* Using a very low number of slices (2) leads to over-darkening of surfaces orthogonal to
* the view. This is particularly annoying for sharp reflections occlusion. So we compute how
* much the error is and correct the visibility later. */
visibility_error += proj_N_len;
/* Rotate 90 degrees. */
dir = vec2(-dir.y, dir.x);
}
/* We integrated 2 directions. */
visibility *= 0.5;
visibility_error *= 0.5;
visibility = min(visibility, data.custom_occlusion);
if ((int(aoSettings) & USE_BENT_NORMAL) == 0) {
bent_normal = N;
}
else {
bent_normal = normalize(mix(bent_normal, N, sqr(sqr(sqr(visibility)))));
}
}
/* Multibounce approximation base on surface albedo.
* Page 78 in the .pdf version. */
float gtao_multibounce(float visibility, vec3 albedo)
{
if (aoBounceFac == 0.0) {
return visibility;
}
/* Median luminance. Because Colored multibounce looks bad. */
float lum = dot(albedo, vec3(0.3333));
float a = 2.0404 * lum - 0.3324;
float b = -4.7951 * lum + 0.6417;
float c = 2.7552 * lum + 0.6903;
float x = visibility;
return max(x, ((x * a + b) * x + c) * x);
}
float diffuse_occlusion(OcclusionData data, vec3 V, vec3 N, vec3 Ng)
{
vec3 unused;
float unused_error;
float visibility;
occlusion_eval(data, V, N, Ng, 0.0, visibility, unused_error, unused);
/* Scale by user factor */
visibility = pow(saturate(visibility), aoFactor);
return visibility;
}
float diffuse_occlusion(
OcclusionData data, vec3 V, vec3 N, vec3 Ng, vec3 albedo, out vec3 bent_normal)
{
float visibility;
float unused_error;
occlusion_eval(data, V, N, Ng, 0.0, visibility, unused_error, bent_normal);
visibility = gtao_multibounce(visibility, albedo);
/* Scale by user factor */
visibility = pow(saturate(visibility), aoFactor);
return visibility;
}
/**
* Approximate the area of intersection of two spherical caps
* radius1 : First caps radius (arc length in radians)
* radius2 : Second caps radius (in radians)
* dist : Distance between caps (radians between centers of caps)
* Note: Result is divided by pi to save one multiply.
**/
float spherical_cap_intersection(float radius1, float radius2, float dist)
{
/* From "Ambient Aperture Lighting" by Chris Oat
* Slide 15. */
float max_radius = max(radius1, radius2);
float min_radius = min(radius1, radius2);
float sum_radius = radius1 + radius2;
float area;
if (dist <= max_radius - min_radius) {
/* One cap in completely inside the other */
area = 1.0 - cos(min_radius);
}
else if (dist >= sum_radius) {
/* No intersection exists */
area = 0;
}
else {
float diff = max_radius - min_radius;
area = smoothstep(0.0, 1.0, 1.0 - saturate((dist - diff) / (sum_radius - diff)));
area *= 1.0 - cos(min_radius);
}
return area;
}
float specular_occlusion(
OcclusionData data, vec3 V, vec3 N, float roughness, inout vec3 specular_dir)
{
vec3 visibility_dir;
float visibility_error;
float visibility;
occlusion_eval(data, V, N, N, 0.0, visibility, visibility_error, visibility_dir);
/* Correct visibility error for very sharp surfaces. */
visibility *= mix(safe_rcp(visibility_error), 1.0, roughness);
specular_dir = normalize(mix(specular_dir, visibility_dir, roughness * (1.0 - visibility)));
/* Visibility to cone angle (eq. 18). */
float vis_angle = fast_acos(sqrt(1 - visibility));
/* Roughness to cone angle (eq. 26). */
float spec_angle = max(0.001, fast_acos(cone_cosine(roughness)));
/* Angle between cone axes. */
float cone_cone_dist = fast_acos(saturate(dot(visibility_dir, specular_dir)));
float cone_nor_dist = fast_acos(saturate(dot(N, specular_dir)));
float isect_solid_angle = spherical_cap_intersection(vis_angle, spec_angle, cone_cone_dist);
float specular_solid_angle = spherical_cap_intersection(M_PI_2, spec_angle, cone_nor_dist);
float specular_occlusion = isect_solid_angle / specular_solid_angle;
/* Mix because it is unstable in unoccluded areas. */
visibility = mix(specular_occlusion, 1.0, pow(visibility, 8.0));
/* Scale by user factor */
visibility = pow(saturate(visibility), aoFactor);
return visibility;
}
/* Use the right occlusion. */
OcclusionData occlusion_load(vec3 vP, float custom_occlusion)
{
/* Default to fully openned cone. */
OcclusionData data = NO_OCCLUSION_DATA;
#ifdef ENABLE_DEFERED_AO
if ((int(aoSettings) & USE_AO) != 0) {
data = unpack_occlusion_data(texelFetch(horizonBuffer, ivec2(gl_FragCoord.xy), 0));
}
#else
/* For blended surfaces. */
data = occlusion_search(vP, maxzBuffer, aoDistance, 0.0, 8.0);
#endif
data.custom_occlusion = custom_occlusion;
return data;
}