diff --git a/.editorconfig b/.editorconfig index 77807af0d6..84af569000 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,7 +2,7 @@ root = true [*] -charset = latin1 +charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true diff --git a/ref/vk/shaders/bounce.comp b/ref/vk/shaders/bounce.comp index d87b4faa5a..d0ef8f659b 100644 --- a/ref/vk/shaders/bounce.comp +++ b/ref/vk/shaders/bounce.comp @@ -32,7 +32,8 @@ layout(set = 0, binding = 13, rgba8) uniform readonly image2D base_color_a; layout(set = 0, binding = 20, rgba16f) uniform writeonly image2D out_indirect_diffuse; layout(set = 0, binding = 21, rgba16f) uniform writeonly image2D out_indirect_specular; -layout(set = 0, binding = 22, rgba16f) uniform writeonly image2D out_first_bounce_direction; // for spherical harmonics denoising +layout(set = 0, binding = 22, rgba32f) uniform writeonly image2D out_first_bounce_direction; // for spherical harmonics denoising +layout(set = 0, binding = 23, rgba32f) uniform writeonly image2D out_reflection_direction_pdf; // for spatial reconstruction layout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers; layout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki; @@ -121,13 +122,13 @@ const vec3 kThrougputExtinctionThreshold = vec3(1e-3); const float kRayNormalOffsetFudge = .01; ivec2 pix; -void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, inout vec3 diffuse, inout vec3 specular) { +void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, int bouncesCount, inout vec3 diffuse, inout vec3 specular, inout vec3 first_bounce_direction) { vec3 throughput = vec3(1.); // TODO split into two distinct passes, see #734 int first_brdf_type = BRDF_TYPE_NONE; - for (int i = 0; i < kMaxBounces; ++i) { + for (int i = 0; i < bouncesCount; ++i) { vec3 bounce_direction; // TODO blue noise @@ -223,7 +224,7 @@ void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, inout vec3 diffuse specular += contribution; if (i == 0) - imageStore(out_first_bounce_direction, pix, vec4(normalize(bounce_direction), 0.f)); // for spherical harmonics denoising + first_bounce_direction = hit_pos - pos; if (!did_hit) break; @@ -262,6 +263,7 @@ void main() { const vec4 pos_t = imageLoad(position_t, pix * INDIRECT_SCALE); vec3 diffuse = vec3(0.), specular = vec3(0.); + vec3 first_bounce_direction = vec3(0.), reflection_direction = vec3(0.); if (pos_t.w > 0.) { const vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz; const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); @@ -288,7 +290,23 @@ void main() { mat.prop.roughness = material_data.r; } - computeBounces(mat, pos_t.xyz, direction, diffuse, specular); + computeBounces(mat, pos_t.xyz, direction, kMaxBounces, diffuse, specular, first_bounce_direction); + + if ((ubo.ubo.renderer_flags & RENDERER_FLAG_ONLY_DIFFUSE_GI) != 0) { + diffuse += specular; + specular = vec3(0.); + } + + if ((ubo.ubo.renderer_flags & RENDERER_FLAG_SEPARATED_REFLECTION) != 0) { + specular = vec3(0.); + + mat.prop.base_color = vec3(1.); + mat.prop.metalness = 1.0; + mat.prop.roughness = material_data.r; + + vec3 unusedDiffuse = vec3(0.); + computeBounces(mat, pos_t.xyz, direction, 1, unusedDiffuse, specular, reflection_direction); + } } #ifdef DEBUG_VALIDATE_EXTRA @@ -306,11 +324,12 @@ void main() { DEBUG_VALIDATE_RANGE_VEC3("bounce.specular", specular, 0., 1e6); #endif - if ((ubo.ubo.renderer_flags & RENDERER_FLAG_ONLY_DIFFUSE_GI) != 0) { - diffuse += specular; - specular = vec3(0.); + if (any(equal(reflection_direction, vec3(0.)))) { + reflection_direction = first_bounce_direction; } imageStore(out_indirect_diffuse, pix, vec4(diffuse, 0.f)); imageStore(out_indirect_specular, pix, vec4(specular, 0.f)); + imageStore(out_first_bounce_direction, pix, vec4(first_bounce_direction, 0.f)); // for spherical harmonics denoising + imageStore(out_reflection_direction_pdf, pix, vec4(reflection_direction, 0.f)); // TODO: calculate specular pdf in w for spatial reconstruction } diff --git a/ref/vk/shaders/denoiser.comp b/ref/vk/shaders/denoiser.comp index 0de52cdbe0..8027436e38 100644 --- a/ref/vk/shaders/denoiser.comp +++ b/ref/vk/shaders/denoiser.comp @@ -7,6 +7,10 @@ #include "ray_interop.h" #undef GLSL +#define DIFFUSE_TEMPORAL_HISTORY_MIX_WEIGHT 0.8 +#define SPECULAR_TEMPORAL_HISTORY_MIX_WEIGHT 0.8 +#define DIELECTRIC_SPECULAR_MULTIPLIER 0.02 // default value from pbr papers, but 0.04 in Unreal Engine + layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; @@ -29,24 +33,25 @@ layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; layout(set = 0, binding = 12, rgba16f) uniform readonly image2D indirect_diffuse; layout(set = 0, binding = 13, rgba16f) uniform readonly image2D indirect_diffuse_atrous1; -layout(set = 0, binding = 14, rgba16f) uniform readonly image2D indirect_specular; +layout(set = 0, binding = 14, rgba16f) uniform readonly image2D indirect_specular_reconstructed; layout(set = 0, binding = 15, rgba16f) uniform readonly image2D indirect_diffuse_denoised_by_sh; +layout(set = 0, binding = 16, rgba32f) uniform readonly image2D reflection_direction_pdf; -layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_diffuse; -layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_diffuse; +layout(set = 0, binding = 17, rgba16f) uniform image2D out_temporal_diffuse; +layout(set = 0, binding = 18, rgba16f) uniform image2D prev_temporal_diffuse; -layout(set = 0, binding = 18, rgba16f) uniform image2D out_temporal_specular; -layout(set = 0, binding = 19, rgba16f) uniform image2D prev_temporal_specular; +layout(set = 0, binding = 19, rgba16f) uniform image2D out_temporal_specular; +layout(set = 0, binding = 20, rgba16f) uniform image2D prev_temporal_specular; //#define DEBUG_NOISE #ifdef DEBUG_NOISE -layout(set = 0, binding = 20) uniform sampler3D blue_noise_texture; +layout(set = 0, binding = 21) uniform sampler3D blue_noise_texture; #include "bluenoise.glsl" #endif -layout(set = 0, binding = 21, rgba16f) uniform readonly image2D legacy_blend; +layout(set = 0, binding = 22, rgba16f) uniform readonly image2D legacy_blend; -//layout(set = 0, binding = 22) uniform sampler2D textures[MAX_TEXTURES]; +//layout(set = 0, binding = 23) uniform sampler2D textures[MAX_TEXTURES]; #include "atrous.glsl" @@ -88,6 +93,12 @@ void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { shading_normal = normalDecode(n.zw); } +vec3 closestColor(vec3 color1, vec3 color2, vec3 sampleColor) { + float dist1 = dot(color1 - sampleColor, color1 - sampleColor); + float dist2 = dot(color2 - sampleColor, color2 - sampleColor); + return (dist1 < dist2) ? color1 : color2; +} + struct Components { vec3 direct_diffuse, direct_specular, indirect_diffuse, indirect_specular; }; @@ -106,7 +117,7 @@ Components dontBlurSamples(const ivec2 res, const ivec2 pix) { } c.direct_specular += imageLoad(light_poly_specular, p).rgb; c.direct_specular += imageLoad(light_point_specular, p).rgb; - c.indirect_specular += imageLoad(indirect_specular, p_indirect).rgb; + c.indirect_specular += imageLoad(indirect_specular_reconstructed, p_indirect).rgb; return c; } @@ -152,12 +163,19 @@ Components boxBlurSamples(ivec2 res, ivec2 pix) { BOX_BLUR(c.indirect_diffuse, indirect_diffuse, res, pix, INDIRECT_DIFFUSE_KERNEL); } - BOX_BLUR(c.indirect_specular, indirect_specular, res, pix, INDIRECT_SPECULAR_KERNEL); + BOX_BLUR(c.indirect_specular, indirect_specular_reconstructed, res, pix, INDIRECT_SPECULAR_KERNEL); return c; } -Components blurATrous(const ivec2 res, const ivec2 pix, vec3 pos, vec3 shading_normal, vec3 geometry_normal) { +vec3 restoreSpecular(vec3 decolorized_specular, vec3 base_color, float metalness) { + // TODO: add fresnel and do like PBR + const vec3 plasticSpecular = decolorized_specular * DIELECTRIC_SPECULAR_MULTIPLIER; + const vec3 metalSpecular = decolorized_specular * base_color; + return mix(plasticSpecular, metalSpecular, metalness); +} + +Components blurATrous(const ivec2 res, const ivec2 pix, vec3 pos, vec3 shading_normal, vec3 geometry_normal, vec3 base_color, float metalness) { Components c; c.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.); @@ -237,7 +255,7 @@ Components blurATrous(const ivec2 res, const ivec2 pix, vec3 pos, vec3 shading_n const bool do_indirect = all(lessThan(p_scaled, res_scaled)); if (do_indirect) { weight_total_indirect_specular += weight; - c.indirect_specular += imageLoad(indirect_specular, p_scaled).rgb * weight; + c.indirect_specular += restoreSpecular(imageLoad(indirect_specular_reconstructed, p_scaled).rgb, base_color, metalness) * weight; } } } @@ -350,7 +368,7 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { // TODO indirect operates at different scale, do a separate pass const float specular_scale = scale * normpdf(x, indirect_specular_sigma) * normpdf(y, indirect_specular_sigma); indirect_specular_total += specular_scale; - c.indirect_specular += imageLoad(indirect_specular, p_indirect).rgb * specular_scale; + c.indirect_specular += imageLoad(indirect_specular_reconstructed, p_indirect).rgb * specular_scale; } } } @@ -398,6 +416,10 @@ void main() { } const vec3 position = imageLoad(position_t, pix).xyz; + + const vec3 base_color = SRGBtoLINEAR(imageLoad(base_color_a, pix).rgb); + const float metalness = imageLoad(material_rmxx, pix).g; + vec3 geometry_normal, shading_normal; readNormals(pix, geometry_normal, shading_normal); @@ -430,7 +452,7 @@ void main() { //const Components c = blurSamples(res, pix); //const Components c = boxBlurSamples(res, pix); //const Components c = dontBlurSamples(res, pix); - const Components c = blurATrous(res, pix, position, shading_normal, geometry_normal); + const Components c = blurATrous(res, pix, position, shading_normal, geometry_normal, base_color, metalness); if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DISABLED) { // Skip @@ -480,31 +502,69 @@ void main() { const vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz; const float depth_nessesary = length(prev_position - prev_origin); const float depth_treshold = 0.01 * clip_space.w; - float better_depth_offset = depth_treshold; vec3 history_diffuse = diffuse; vec3 history_specular = specular; - const int TEMPORAL_KERNEL = 1; // lifekilled says it should be fixed - for(int x = -TEMPORAL_KERNEL; x <=TEMPORAL_KERNEL; x++) { - for(int y = -TEMPORAL_KERNEL; y <=TEMPORAL_KERNEL; y++) { - const ivec2 p = reproj_pix + ivec2(x, y); - if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { - continue; - } + + if (any(greaterThanEqual(reproj_pix, ivec2(0))) && any(lessThan(reproj_pix, res))) { + const vec4 history_diffuse_depth = imageLoad( prev_temporal_diffuse, reproj_pix ); + const vec4 history_specular_sample = imageLoad( prev_temporal_specular, reproj_pix ); + + const float history_depth = history_diffuse_depth.w; + const float depth_offset = abs(history_depth - depth_nessesary); + if ( depth_offset < better_depth_offset ) { + better_depth_offset = depth_offset; + history_diffuse = history_diffuse_depth.rgb; + history_specular = history_specular_sample.rgb; + } + } - if (any(greaterThanEqual(reproj_pix, res)) || any(lessThan(reproj_pix, ivec2(0)))) { + // parallax reprojecting for specular + float average_ray_length = 0.; + float ray_length_samples_count = 0.; + const int AVERAGE_RAY_LENGTH_KERNEL = 1; + for(int x = -AVERAGE_RAY_LENGTH_KERNEL; x <=AVERAGE_RAY_LENGTH_KERNEL; x++) { + for(int y = -AVERAGE_RAY_LENGTH_KERNEL; y <=AVERAGE_RAY_LENGTH_KERNEL; y++) { + const ivec2 p = pix / INDIRECT_SCALE + ivec2(x, y); + if (any(greaterThanEqual(p, res / INDIRECT_SCALE)) || any(lessThan(p, ivec2(0)))) { continue; } - const vec4 history_diffuse_depth = imageLoad( prev_temporal_diffuse, reproj_pix ); - const vec4 history_specular_sample = imageLoad( prev_temporal_specular, reproj_pix ); - - const float history_depth = history_diffuse_depth.w; - const float depth_offset = abs(history_depth - depth_nessesary); - if ( depth_offset < better_depth_offset ) { - better_depth_offset = depth_offset; - history_diffuse = history_diffuse_depth.rgb; - history_specular = history_specular_sample.rgb; - } + + average_ray_length += length(imageLoad( reflection_direction_pdf, p ).xyz); + ray_length_samples_count += 1.; + } + } + if (ray_length_samples_count > 0. && average_ray_length > 0.) { + average_ray_length /= ray_length_samples_count; + + // origin (camera) + // []< reflection destination + // | \ (UwU) (reflected in + // | \ / | surface texel) + // | \ / | + // --------x------x-----x------------ + // origin on ^ reflection on plane + // plane | + // reflection center + // (surface texel in current frame) + // (need to find this in previous frame) + // + + const vec3 refl_position = reflect(normalize(position - origin), geometry_normal) * average_ray_length + position; + const float refl_distance_to_plane = dot(geometry_normal, refl_position - prev_position); + const vec3 refl_on_plane = refl_position - geometry_normal * refl_distance_to_plane; + const float prev_distance_to_plane = dot(geometry_normal, prev_origin - prev_position); + const vec3 prev_origin_on_plane = prev_origin - geometry_normal * prev_distance_to_plane; + const float refl_center = prev_distance_to_plane / (prev_distance_to_plane + refl_distance_to_plane); + const vec3 parallax_position = mix(prev_origin_on_plane, refl_on_plane, refl_center); + + const vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(parallax_position, 1.)).xyz, 1.); + const vec2 parallax_uv = clip_space.xy / clip_space.w; + const ivec2 parallax_pix = ivec2((parallax_uv * 0.5 + vec2(0.5)) * vec2(res)); + + if (any(greaterThanEqual(parallax_pix, ivec2(0))) && any(lessThan(parallax_pix, res))) { + const vec4 history_specular_sample = imageLoad( prev_temporal_specular, parallax_pix ); + history_specular = closestColor(history_specular_sample.xyz, history_specular, specular); } } @@ -521,8 +581,8 @@ void main() { #endif if (better_depth_offset < depth_treshold) { - diffuse = mix(diffuse, history_diffuse, 0.8); - specular = mix(specular, history_specular, 0.3); + diffuse = mix(diffuse, history_diffuse, DIFFUSE_TEMPORAL_HISTORY_MIX_WEIGHT); + specular = mix(specular, history_specular, SPECULAR_TEMPORAL_HISTORY_MIX_WEIGHT); } #ifdef DEBUG_VALIDATE_EXTRA @@ -552,8 +612,6 @@ void main() { vec3 colour = vec3(0.); if (ubo.ubo.debug_display_only != DEBUG_DISPLAY_LIGHTING) { - const vec3 base_color = SRGBtoLINEAR(imageLoad(base_color_a, pix).rgb); - const float metalness = imageLoad(material_rmxx, pix).g; colour = mixFinalColor(base_color, diffuse, specular, metalness); } else { colour = diffuse + specular; diff --git a/ref/vk/shaders/diffuse_gi_sh_denoise_init.comp b/ref/vk/shaders/diffuse_gi_sh_denoise_init.comp index 6960a9bcc8..feb3c6bca1 100644 --- a/ref/vk/shaders/diffuse_gi_sh_denoise_init.comp +++ b/ref/vk/shaders/diffuse_gi_sh_denoise_init.comp @@ -18,7 +18,7 @@ layout(set = 0, binding = 1, rgba16f) uniform image2D out_sh2_ping; layout(set = 0, binding = 2, rgba8) uniform readonly image2D base_color_a; layout(set = 0, binding = 3, rgba32f) uniform readonly image2D position_t; layout(set = 0, binding = 4, rgba16f) uniform readonly image2D indirect_diffuse; -layout(set = 0, binding = 5, rgba16f) uniform readonly image2D first_bounce_direction; +layout(set = 0, binding = 5, rgba32f) uniform readonly image2D first_bounce_direction; void main() { ivec2 res = ivec2(imageSize(base_color_a)); diff --git a/ref/vk/shaders/ray_interop.h b/ref/vk/shaders/ray_interop.h index df819ac3f4..9cd9784a7f 100644 --- a/ref/vk/shaders/ray_interop.h +++ b/ref/vk/shaders/ray_interop.h @@ -130,7 +130,7 @@ struct Kusok { }; struct PointLight { - vec4 origin_r2; // vec4(center.xyz, radius²) + vec4 origin_r2; // vec4(center.xyz, radius²) vec4 color_stopdot; vec4 dir_stopdot2; @@ -197,6 +197,7 @@ struct LightCluster { #define RENDERER_FLAG_SEPARATED_REFLECTION (1<<1) #define RENDERER_FLAG_DENOISE_GI_BY_SH (1<<2) #define RENDERER_FLAG_DISABLE_GI (1<<3) +#define RENDERER_FLAG_SPATIAL_RECONSTRUCTION (1<<4) struct UniformBuffer { mat4 inv_proj, inv_view; diff --git a/ref/vk/shaders/rt.json b/ref/vk/shaders/rt.json index db7f38b001..8e58aa0c00 100644 --- a/ref/vk/shaders/rt.json +++ b/ref/vk/shaders/rt.json @@ -62,6 +62,12 @@ "indiff_sh_save": { "comp": "diffuse_gi_sh_denoise_save" }, + "spatial_reconstruction_pass1": { + "comp": "spatial_reconstruction_pass1" + }, + "spatial_reconstruction_pass2": { + "comp": "spatial_reconstruction_pass2" + }, "denoiser": { "comp": "denoiser" }, diff --git a/ref/vk/shaders/spatial_reconstruction.glsl b/ref/vk/shaders/spatial_reconstruction.glsl new file mode 100644 index 0000000000..07d4246736 --- /dev/null +++ b/ref/vk/shaders/spatial_reconstruction.glsl @@ -0,0 +1,237 @@ +// originally implemented by Mikhail Gorobets for Diligent Engine +// https://github.com/DiligentGraphics/DiligentEngine + +#ifndef SPATIAL_RECONSTRUCTION_RADIUS +#define SPATIAL_RECONSTRUCTION_RADIUS 7. +#endif + +#ifndef SPECULAR_INPUT_IMAGE +#define SPECULAR_INPUT_IMAGE indirect_specular +#endif + +#ifndef SPECULAR_OUTPUT_IMAGE +#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed +#endif + +#include "debug.glsl" + +#define SPECULAR_CLAMPING_MAX 1.2 +#define SPATIAL_RECONSTRUCTION_SAMPLES 8 +#define SPATIAL_RECONSTRUCTION_ROUGHNESS_FACTOR 5. +#define SPATIAL_RECONSTRUCTION_SIGMA 0.9 +#define INDIRECT_SCALE 2 + +#define GLSL +#include "ray_interop.h" +#undef GLSL + +#define RAY_BOUNCE +#define RAY_QUERY +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0, rgba16f) uniform image2D SPECULAR_OUTPUT_IMAGE; + +layout(set = 0, binding = 1, rgba32f) uniform readonly image2D position_t; +layout(set = 0, binding = 2, rgba16f) uniform readonly image2D normals_gs; +layout(set = 0, binding = 3, rgba8) uniform readonly image2D material_rmxx; +layout(set = 0, binding = 4, rgba16f) uniform readonly image2D SPECULAR_INPUT_IMAGE; +layout(set = 0, binding = 5, rgba32f) uniform readonly image2D reflection_direction_pdf; + +layout(set = 0, binding = 6) uniform UBO { UniformBuffer ubo; } ubo; + +#include "utils.glsl" +#include "noise.glsl" +#include "brdf.glsl" + +#ifndef PI + #define PI 3.14 // FIXME please +#endif + +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(normals_gs, uv); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); +} + +struct PixelAreaStatistic { + float mean; + float variance; + float weightSum; + vec4 colorSum; +}; + +float computeGaussianWeight(float texelDistance) { + return exp(-0.66 * texelDistance * texelDistance); // assuming texelDistance is normalized to 1 +} + +// Visibility = G2(v,l,a) / (4 * (n,v) * (n,l)) +// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf +float smithGGXVisibilityCorrelated(float NdotL, float NdotV, float alphaRoughness) { + // G1 (masking) is % microfacets visible in 1 direction + // G2 (shadow-masking) is % microfacets visible in 2 directions + // If uncorrelated: + // G2(NdotL, NdotV) = G1(NdotL) * G1(NdotV) + // Less realistic as higher points are more likely visible to both L and V + // + // https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2017/Presentations/Hammon_Earl_PBR_Diffuse_Lighting.pdf + + float a2 = alphaRoughness * alphaRoughness; + + float GGXV = NdotL * sqrt(max(NdotV * NdotV * (1.0 - a2) + a2, 1e-7)); + float GGXL = NdotV * sqrt(max(NdotL * NdotL * (1.0 - a2) + a2, 1e-7)); + + return 0.5 / (GGXV + GGXL); +} + +// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) +// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz +// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games, Equation 3. +float normalDistribution_GGX(float NdotH, float alphaRoughness) { + // "Sampling the GGX Distribution of Visible Normals" (2018) by Eric Heitz - eq. (1) + // https://jcgt.org/published/0007/04/01/ + + // Make sure we reasonably handle alphaRoughness == 0 + // (which corresponds to delta function) + alphaRoughness = max(alphaRoughness, 1e-3); + + float a2 = alphaRoughness * alphaRoughness; + float nh2 = NdotH * NdotH; + float f = nh2 * a2 + (1.0 - nh2); + return a2 / max(PI * f * f, 1e-9); +} + +vec2 computeWeightRayLength(ivec2 pix, vec3 V, vec3 N, float roughness, float NdotV, float weight) { + vec4 rayDirectionPDF = imageLoad(reflection_direction_pdf, pix); + float rayLength = length(rayDirectionPDF.xyz); + vec3 rayDirection = normalize(rayDirectionPDF.xyz); + float PDF = rayDirectionPDF.w; + float alphaRoughness = roughness * roughness; + + vec3 L = rayDirection; + vec3 H = normalize(L + V); + + float NdotH = saturate(dot(N, H)); + float NdotL = saturate(dot(N, L)); + + float vis = smithGGXVisibilityCorrelated(NdotL, NdotV, alphaRoughness); + float D = normalDistribution_GGX(NdotH, alphaRoughness); + float localBRDF = vis * D * NdotL; + localBRDF *= computeGaussianWeight(weight); + float rcpRayLength = rayLength == 0. ? 0. : 1. / rayLength; + return vec2(max(localBRDF / max(PDF, 1.0e-5f), 1e-6), rcpRayLength); +} + +// Weighted incremental variance +// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance +void computeWeightedVariance(inout PixelAreaStatistic stat, vec3 sampleColor, float weight) { + stat.colorSum.xyz += weight * sampleColor; + stat.weightSum += weight; + + float value = luminance(sampleColor.rgb); + float prevMean = stat.mean; + + float rcpWeightSum = stat.weightSum == 0. ? 0. : 1. / stat.weightSum; + + stat.mean += weight * rcpWeightSum * (value - prevMean); + stat.variance += weight * (value - prevMean) * (value - stat.mean); +} + +float computeResolvedDepth(vec3 origin, vec3 position, float surfaceHitDistance) { + return distance(origin, position) + surfaceHitDistance; +} + +ivec2 clampScreenCoord(ivec2 pix, ivec2 res) { + return max(ivec2(0), min(ivec2(res - 1), pix)); +} + + +float computeSpatialWeight(float texelDistance, float sigma) { + return exp(-(texelDistance) / (2.0 * sigma * sigma)); +} + +vec3 clampSpecular(vec3 specular, float maxLuminace) { + float lum = luminance(specular); + if (lum == 0.) + return vec3(0.); + + float clamped = min(maxLuminace, lum); + return specular * (clamped / lum); +} + +void main() { + const ivec2 pix = ivec2(gl_GlobalInvocationID); + const ivec2 res = ubo.ubo.res / INDIRECT_SCALE; + if (any(greaterThanEqual(pix, res))) { + return; + } + + if ((ubo.ubo.renderer_flags & RENDERER_FLAG_SPATIAL_RECONSTRUCTION) == 0) { + imageStore(SPECULAR_OUTPUT_IMAGE, pix, imageLoad(SPECULAR_INPUT_IMAGE, pix)); + return; + } + + const vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.; + + const vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + const vec3 position = imageLoad(position_t, pix * INDIRECT_SCALE).xyz; + + // samples = 8, min distance = 0.5, average samples on radius = 2 + vec3 poisson[SPATIAL_RECONSTRUCTION_SAMPLES]; + poisson[0] = vec3(-0.4706069, -0.4427112, +0.6461146); + poisson[1] = vec3(-0.9057375, +0.3003471, +0.9542373); + poisson[2] = vec3(-0.3487388, +0.4037880, +0.5335386); + poisson[3] = vec3(+0.1023042, +0.6439373, +0.6520134); + poisson[4] = vec3(+0.5699277, +0.3513750, +0.6695386); + poisson[5] = vec3(+0.2939128, -0.1131226, +0.3149309); + poisson[6] = vec3(+0.7836658, -0.4208784, +0.8895339); + poisson[7] = vec3(+0.1564120, -0.8198990, +0.8346850); + + vec3 geometry_normal, shading_normal; + readNormals(pix * INDIRECT_SCALE, geometry_normal, shading_normal); + + vec3 V = normalize(origin - position); + float NdotV = saturate(dot(shading_normal, V)); + + float roughness = imageLoad(material_rmxx, pix * INDIRECT_SCALE).x; + + float roughness_factor = saturate(float(SPATIAL_RECONSTRUCTION_ROUGHNESS_FACTOR) * roughness); + float radius = mix(0.0, SPATIAL_RECONSTRUCTION_RADIUS, roughness_factor); + + PixelAreaStatistic pixelAreaStat; + pixelAreaStat.colorSum = vec4(0.0, 0.0, 0.0, 0.0); + pixelAreaStat.weightSum = 0.0; + pixelAreaStat.variance = 0.0; + pixelAreaStat.mean = 0.0; + + float nearestSurfaceHitDistance = 0.0; + + vec3 result_color = vec3(0.); + float weights_sum = 0.; + + // TODO: Try to implement sampling from https://youtu.be/MyTOGHqyquU?t=1043 + for (int i = 0; i < SPATIAL_RECONSTRUCTION_SAMPLES; i++) + { + ivec2 p = max(ivec2(0), min(ivec2(res) - ivec2(1), ivec2(pix + radius * poisson[i].xy))); + + float weightS = computeSpatialWeight(poisson[i].z * poisson[i].z, SPATIAL_RECONSTRUCTION_SIGMA); + vec2 weightLength = computeWeightRayLength(p, V, shading_normal, roughness, NdotV, weightS); + vec3 sampleColor = clampSpecular(imageLoad(SPECULAR_INPUT_IMAGE, p).xyz, SPECULAR_CLAMPING_MAX); + computeWeightedVariance(pixelAreaStat, sampleColor, weightLength.x); + + if (weightLength.x > 1.0e-6) + nearestSurfaceHitDistance = max(weightLength.y, nearestSurfaceHitDistance); + + result_color += sampleColor.xyz * weightLength.x; + weights_sum += weightLength.x; + } + + if (weights_sum > 0.) { + result_color /= weights_sum; + } + + vec4 resolvedRadiance = pixelAreaStat.colorSum / max(pixelAreaStat.weightSum, 1e-6f); + float resolvedVariance = pixelAreaStat.variance / max(pixelAreaStat.weightSum, 1e-6f); + float resolvedDepth = computeResolvedDepth(origin, position, nearestSurfaceHitDistance); + + imageStore(SPECULAR_OUTPUT_IMAGE, pix, vec4(resolvedRadiance.xyz, resolvedVariance)); +} diff --git a/ref/vk/shaders/spatial_reconstruction_pass1.comp b/ref/vk/shaders/spatial_reconstruction_pass1.comp new file mode 100644 index 0000000000..e1654096d6 --- /dev/null +++ b/ref/vk/shaders/spatial_reconstruction_pass1.comp @@ -0,0 +1,11 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_shader_16bit_storage : require +#extension GL_EXT_ray_query: require + +#define SPATIAL_RECONSTRUCTION_RADIUS 11. +#define SPECULAR_INPUT_IMAGE indirect_specular +#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed_pass1 + +#include "spatial_reconstruction.glsl" diff --git a/ref/vk/shaders/spatial_reconstruction_pass2.comp b/ref/vk/shaders/spatial_reconstruction_pass2.comp new file mode 100644 index 0000000000..c9c6ea2b55 --- /dev/null +++ b/ref/vk/shaders/spatial_reconstruction_pass2.comp @@ -0,0 +1,11 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_shader_16bit_storage : require +#extension GL_EXT_ray_query: require + +#define SPATIAL_RECONSTRUCTION_RADIUS 5. +#define SPECULAR_INPUT_IMAGE indirect_specular_reconstructed_pass1 +#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed + +#include "spatial_reconstruction.glsl" diff --git a/ref/vk/vk_cvar.c b/ref/vk/vk_cvar.c index cfdb58c4bc..7bfbc7b17a 100644 --- a/ref/vk/vk_cvar.c +++ b/ref/vk/vk_cvar.c @@ -44,6 +44,7 @@ void VK_LoadCvarsAfterInit( void ) rt_separated_reflection = gEngine.Cvar_Get("rt_separated_reflection", "", FCVAR_GLCONFIG, "Add separated high quality reflection pass"); rt_denoise_gi_by_sh = gEngine.Cvar_Get("rt_denoise_gi_by_sh", "", FCVAR_GLCONFIG, "Denoise global illumination by spherical harmonics"); rt_disable_gi = gEngine.Cvar_Get("rt_disable_gi", "", FCVAR_GLCONFIG, "Disable global illumination calculation"); + rt_spatial_reconstruction = gEngine.Cvar_Get("rt_spatial_reconstruction", "", FCVAR_GLCONFIG, "Apply spatial reconstruction to specular"); } else { rt_enable = gEngine.Cvar_Get( "rt_enable", "0", FCVAR_READ_ONLY, "DISABLED: Ray tracing is not supported by your hardware/drivers" ); } diff --git a/ref/vk/vk_cvar.h b/ref/vk/vk_cvar.h index e9dac33ea9..478161ccf6 100644 --- a/ref/vk/vk_cvar.h +++ b/ref/vk/vk_cvar.h @@ -30,6 +30,7 @@ void VK_LoadCvarsAfterInit( void ); X(rt_separated_reflection) \ X(rt_denoise_gi_by_sh) \ X(rt_disable_gi) \ + X(rt_spatial_reconstruction) \ #define EXTERN_CVAR(cvar) extern cvar_t *cvar; DECLARE_CVAR(EXTERN_CVAR) diff --git a/ref/vk/vk_rtx.c b/ref/vk/vk_rtx.c index 4c51229232..7c2a7a9bad 100644 --- a/ref/vk/vk_rtx.c +++ b/ref/vk/vk_rtx.c @@ -272,7 +272,8 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr ubo->renderer_flags = SET_RENDERER_FLAG(rt_only_diffuse_gi, RENDERER_FLAG_ONLY_DIFFUSE_GI) | SET_RENDERER_FLAG(rt_separated_reflection, RENDERER_FLAG_SEPARATED_REFLECTION) | SET_RENDERER_FLAG(rt_denoise_gi_by_sh, RENDERER_FLAG_DENOISE_GI_BY_SH) | - SET_RENDERER_FLAG(rt_disable_gi, RENDERER_FLAG_DISABLE_GI); + SET_RENDERER_FLAG(rt_disable_gi, RENDERER_FLAG_DISABLE_GI) | + SET_RENDERER_FLAG(rt_spatial_reconstruction, RENDERER_FLAG_SPATIAL_RECONSTRUCTION); #undef SET_RENDERER_FLAG }