1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-23 15:16:17 +00:00

Overhaul screen space reflections.

This commit is contained in:
Skyth
2025-09-16 15:17:49 +03:00
parent f50d7fa1e8
commit c128886c63
31 changed files with 1185 additions and 934 deletions

View File

@@ -6,228 +6,271 @@
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(rgba16f, set = 0, binding = 0) uniform restrict readonly image2D source_diffuse;
layout(r32f, set = 0, binding = 1) uniform restrict readonly image2D source_depth;
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2D ssr_image;
#ifdef MODE_ROUGH
layout(r8, set = 1, binding = 1) uniform restrict writeonly image2D blur_radius_image;
#endif
layout(rgba8, set = 2, binding = 0) uniform restrict readonly image2D source_normal_roughness;
layout(set = 3, binding = 0) uniform sampler2D source_metallic;
layout(set = 0, binding = 0) uniform sampler2D source_last_frame;
layout(set = 0, binding = 1) uniform sampler2D source_hiz;
layout(set = 0, binding = 2) uniform sampler2D source_normal_roughness;
layout(rgba16f, set = 0, binding = 3) uniform restrict writeonly image2D output_color;
layout(r8, set = 0, binding = 4) uniform restrict writeonly image2D output_mip_level;
layout(set = 0, binding = 5, std140) uniform SceneData {
mat4 projection[2];
mat4 inv_projection[2];
mat4 reprojection[2];
vec4 eye_offset[2];
}
scene_data;
layout(push_constant, std430) uniform Params {
vec4 proj_info;
ivec2 screen_size;
float camera_z_near;
float camera_z_far;
int mipmaps;
int num_steps;
float depth_tolerance;
float distance_fade;
float curve_fade_in;
float depth_tolerance;
bool orthogonal;
float filter_mipmap_levels;
bool use_half_res;
uint view_index;
int view_index;
}
params;
#include "screen_space_reflection_inc.glsl"
vec2 compute_cell_count(int level) {
int cell_count_x = max(1, params.screen_size.x >> level);
int cell_count_y = max(1, params.screen_size.y >> level);
return vec2(cell_count_x, cell_count_y);
}
vec2 view_to_screen(vec3 view_pos, out float w) {
vec4 projected = scene_data.projection[params.view_index] * vec4(view_pos, 1.0);
projected.xyz /= projected.w;
projected.xy = projected.xy * 0.5 + 0.5;
w = projected.w;
return projected.xy;
float linearize_depth(float depth) {
vec4 pos = vec4(0.0, 0.0, depth, 1.0);
pos = scene_data.inv_projection[params.view_index] * pos;
return pos.z / pos.w;
}
vec3 compute_view_pos(vec3 screen_pos) {
vec4 pos;
pos.xy = screen_pos.xy * 2.0 - 1.0;
pos.z = screen_pos.z;
pos.w = 1.0;
pos = scene_data.inv_projection[params.view_index] * pos;
return pos.xyz / pos.w;
}
vec3 compute_screen_pos(vec3 pos) {
vec4 screen_pos = scene_data.projection[params.view_index] * vec4(pos, 1.0);
screen_pos.xyz /= screen_pos.w;
screen_pos.xy = screen_pos.xy * 0.5 + 0.5;
return screen_pos.xyz;
}
// https://habr.com/ru/articles/744336/
vec3 compute_geometric_normal(ivec2 pixel_pos, float depth_c, vec3 view_c, float pixel_offset) {
vec4 H = vec4(
texelFetch(source_hiz, pixel_pos + ivec2(-1, 0), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(-2, 0), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(1, 0), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(2, 0), 0).x);
vec4 V = vec4(
texelFetch(source_hiz, pixel_pos + ivec2(0, -1), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(0, -2), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(0, 1), 0).x,
texelFetch(source_hiz, pixel_pos + ivec2(0, 2), 0).x);
vec2 he = abs((2.0 * H.xz - H.yw) - depth_c);
vec2 ve = abs((2.0 * V.xz - V.yw) - depth_c);
int h_sign = he.x < he.y ? -1 : 1;
int v_sign = ve.x < ve.y ? -1 : 1;
vec3 view_h = compute_view_pos(vec3((pixel_pos + vec2(h_sign, 0) + pixel_offset) / params.screen_size, H[1 + int(h_sign)]));
vec3 view_v = compute_view_pos(vec3((pixel_pos + vec2(0, v_sign) + pixel_offset) / params.screen_size, V[1 + int(v_sign)]));
vec3 h_der = h_sign * (view_h - view_c);
vec3 v_der = v_sign * (view_v - view_c);
return cross(v_der, h_der);
}
#define M_PI 3.14159265359
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
ivec2 pixel_pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC.xy, params.screen_size))) { //too large, do nothing
if (any(greaterThanEqual(pixel_pos, params.screen_size))) {
return;
}
vec2 pixel_size = 1.0 / vec2(params.screen_size);
vec2 uv = vec2(ssC.xy) * pixel_size;
vec4 color = vec4(0.0);
float mip_level = 0.0;
uv += pixel_size * 0.5;
vec3 screen_pos;
screen_pos.xy = vec2(pixel_pos + 0.5) / params.screen_size;
screen_pos.z = texelFetch(source_hiz, pixel_pos, 0).x;
float base_depth = imageLoad(source_depth, ssC).r;
bool should_trace = screen_pos.z != 0.0;
if (should_trace) {
vec3 pos = compute_view_pos(screen_pos);
// World space point being shaded
vec3 vertex = reconstructCSPosition(uv * vec2(params.screen_size), base_depth);
vec4 normal_roughness = imageLoad(source_normal_roughness, ssC);
vec3 normal = normalize(normal_roughness.xyz * 2.0 - 1.0);
float roughness = normal_roughness.w;
if (roughness > 0.5) {
roughness = 1.0 - roughness;
}
roughness /= (127.0 / 255.0);
// The roughness cutoff of 0.6 is chosen to match the roughness fadeout from GH-69828.
if (roughness > 0.6) {
// Do not compute SSR for rough materials to improve performance at the cost of
// subtle artifacting.
#ifdef MODE_ROUGH
imageStore(blur_radius_image, ssC, vec4(0.0));
#endif
imageStore(ssr_image, ssC, vec4(0.0));
return;
}
normal = normalize(normal);
normal.y = -normal.y; //because this code reads flipped
vec3 view_dir;
if (sc_multiview) {
view_dir = normalize(vertex + scene_data.eye_offset[params.view_index].xyz);
} else {
view_dir = params.orthogonal ? vec3(0.0, 0.0, -1.0) : normalize(vertex);
}
vec3 ray_dir = normalize(reflect(view_dir, normal));
if (dot(ray_dir, normal) < 0.001) {
imageStore(ssr_image, ssC, vec4(0.0));
return;
}
////////////////
// make ray length and clip it against the near plane (don't want to trace beyond visible)
float ray_len = (vertex.z + ray_dir.z * params.camera_z_far) > -params.camera_z_near ? (-params.camera_z_near - vertex.z) / ray_dir.z : params.camera_z_far;
vec3 ray_end = vertex + ray_dir * ray_len;
float w_begin;
vec2 vp_line_begin = view_to_screen(vertex, w_begin);
float w_end;
vec2 vp_line_end = view_to_screen(ray_end, w_end);
vec2 vp_line_dir = vp_line_end - vp_line_begin;
// we need to interpolate w along the ray, to generate perspective correct reflections
w_begin = 1.0 / w_begin;
w_end = 1.0 / w_end;
float z_begin = vertex.z * w_begin;
float z_end = ray_end.z * w_end;
vec2 line_begin = vp_line_begin / pixel_size;
vec2 line_dir = vp_line_dir / pixel_size;
float z_dir = z_end - z_begin;
float w_dir = w_end - w_begin;
// clip the line to the viewport edges
float scale_max_x = min(1.0, 0.99 * (1.0 - vp_line_begin.x) / max(1e-5, vp_line_dir.x));
float scale_max_y = min(1.0, 0.99 * (1.0 - vp_line_begin.y) / max(1e-5, vp_line_dir.y));
float scale_min_x = min(1.0, 0.99 * vp_line_begin.x / max(1e-5, -vp_line_dir.x));
float scale_min_y = min(1.0, 0.99 * vp_line_begin.y / max(1e-5, -vp_line_dir.y));
float line_clip = min(scale_max_x, scale_max_y) * min(scale_min_x, scale_min_y);
line_dir *= line_clip;
z_dir *= line_clip;
w_dir *= line_clip;
// clip z and w advance to line advance
vec2 line_advance = normalize(line_dir); // down to pixel
float step_size = 1.0 / length(line_dir);
float z_advance = z_dir * step_size; // adapt z advance to line advance
float w_advance = w_dir * step_size; // adapt w advance to line advance
// make line advance faster if direction is closer to pixel edges (this avoids sampling the same pixel twice)
float advance_angle_adj = 1.0 / max(abs(line_advance.x), abs(line_advance.y));
line_advance *= advance_angle_adj; // adapt z advance to line advance
z_advance *= advance_angle_adj;
w_advance *= advance_angle_adj;
vec2 pos = line_begin;
float z = z_begin;
float w = w_begin;
float z_from = z / w;
float z_to = z_from;
float depth;
vec2 prev_pos = pos;
if (ivec2(pos + line_advance - 0.5) == ssC) {
// It is possible for rounding to cause our first pixel to check to be the pixel we're reflecting.
// Make sure we skip it
pos += line_advance;
z += z_advance;
w += w_advance;
}
bool found = false;
float steps_taken = 0.0;
for (int i = 0; i < params.num_steps; i++) {
pos += line_advance;
z += z_advance;
w += w_advance;
// convert to linear depth
ivec2 test_pos = ivec2(pos - 0.5);
depth = imageLoad(source_depth, test_pos).r;
if (sc_multiview) {
depth = depth * 2.0 - 1.0;
depth = 2.0 * params.camera_z_near * params.camera_z_far / (params.camera_z_far + params.camera_z_near - depth * (params.camera_z_far - params.camera_z_near));
depth = -depth;
vec4 normal_roughness = texelFetch(source_normal_roughness, pixel_pos, 0);
vec3 normal = normalize(normal_roughness.xyz * 2.0 - 1.0);
float roughness = normal_roughness.w;
if (roughness > 0.5) {
roughness = 1.0 - roughness;
}
roughness /= (127.0 / 255.0);
z_from = z_to;
z_to = z / w;
if (depth > z_to) {
// Test if our ray is hitting the "right" side of the surface, if not we're likely self reflecting and should skip.
vec4 test_normal_roughness = imageLoad(source_normal_roughness, test_pos);
vec3 test_normal = test_normal_roughness.xyz * 2.0 - 1.0;
test_normal = normalize(test_normal);
test_normal.y = -test_normal.y; // Because this code reads flipped.
if (dot(ray_dir, test_normal) < 0.001) {
// if depth was surpassed
if (depth <= max(z_to, z_from) + params.depth_tolerance && -depth < params.camera_z_far * 0.95) {
// check the depth tolerance and far clip
// check that normal is valid
found = true;
}
break;
}
}
steps_taken += 1.0;
prev_pos = pos;
}
if (found) {
float margin_blend = 1.0;
vec2 final_pos = pos;
vec2 margin = vec2((params.screen_size.x + params.screen_size.y) * 0.05); // make a uniform margin
if (any(bvec4(lessThan(pos, vec2(0.0, 0.0)), greaterThan(pos, params.screen_size)))) {
// clip at the screen edges
imageStore(ssr_image, ssC, vec4(0.0));
// Do not compute SSR for rough materials to improve
// performance at the cost of subtle artifacting.
if (roughness >= 0.7) {
imageStore(output_color, pixel_pos, vec4(0.0));
imageStore(output_mip_level, pixel_pos, vec4(0.0));
return;
}
{
//blend fading out towards inner margin
// 0.5 = midpoint of reflection
vec2 margin_grad = mix(params.screen_size - pos, pos, lessThan(pos, params.screen_size * 0.5));
margin_blend = smoothstep(0.0, margin.x * margin.y, margin_grad.x * margin_grad.y);
//margin_blend = 1.0;
vec3 geom_normal = normalize(compute_geometric_normal(pixel_pos, screen_pos.z, pos, 0.5));
// Add a small bias towards the geometry normal to prevent self intersections.
pos += geom_normal * (1.0 - pow(clamp(dot(normal, geom_normal), 0.0, 1.0), 8.0));
screen_pos = compute_screen_pos(pos);
vec3 view_dir = params.orthogonal ? vec3(0.0, 0.0, -1.0) : normalize(pos + scene_data.eye_offset[params.view_index].xyz);
vec3 ray_dir = normalize(reflect(view_dir, normal));
// Check if the ray is immediately intersecting with itself. If so, bounce!
if (dot(ray_dir, geom_normal) < 0.0) {
ray_dir = normalize(reflect(ray_dir, geom_normal));
}
vec3 end_pos = pos + ray_dir;
// Clip to near plane. Add a small bias so we don't go to infinity.
if (end_pos.z > 0.0) {
end_pos -= ray_dir / ray_dir.z * (end_pos.z + 0.00001);
}
vec3 screen_end_pos = compute_screen_pos(end_pos);
// Normalize Z to -1.0 or +1.0 and do parametric T tracing as suggested here:
// https://hacksoflife.blogspot.com/2020/10/a-tip-for-hiz-ssr-parametric-t-tracing.html
vec3 screen_ray_dir = screen_end_pos - screen_pos;
screen_ray_dir /= abs(screen_ray_dir.z);
bool facing_camera = screen_ray_dir.z >= 0.0;
// Find the screen edge point where we will stop tracing.
vec2 t0 = (vec2(0.0) - screen_pos.xy) / screen_ray_dir.xy;
vec2 t1 = (vec2(1.0) - screen_pos.xy) / screen_ray_dir.xy;
vec2 t2 = max(t0, t1);
float t_max = min(t2.x, t2.y);
vec2 cell_step = vec2(screen_ray_dir.x < 0.0 ? -1.0 : 1.0, screen_ray_dir.y < 0.0 ? -1.0 : 1.0);
int cur_level = 0;
int cur_iteration = params.num_steps;
// Advance the start point to the closest next cell to prevent immediate self intersection.
float t;
{
vec2 cell_index = floor(screen_pos.xy * params.screen_size);
vec2 new_cell_index = cell_index + clamp(cell_step, vec2(0.0), vec2(1.0));
vec2 new_cell_pos = (new_cell_index / params.screen_size) + cell_step * 0.000001;
vec2 pos_t = (new_cell_pos - screen_pos.xy) / screen_ray_dir.xy;
float edge_t = min(pos_t.x, pos_t.y);
t = edge_t;
}
while (cur_level >= 0 && cur_iteration > 0 && t < t_max) {
vec3 cur_screen_pos = screen_pos + screen_ray_dir * t;
vec2 cell_count = compute_cell_count(cur_level);
vec2 cell_index = floor(cur_screen_pos.xy * cell_count);
float cell_depth = texelFetch(source_hiz, ivec2(cell_index), cur_level).x;
float depth_t = (cell_depth - screen_pos.z) * screen_ray_dir.z; // Z is either -1.0 or 1.0 so we don't need to do a divide.
vec2 new_cell_index = cell_index + clamp(cell_step, vec2(0.0), vec2(1.0));
vec2 new_cell_pos = (new_cell_index / cell_count) + cell_step * 0.000001;
vec2 pos_t = (new_cell_pos - screen_pos.xy) / screen_ray_dir.xy;
float edge_t = min(pos_t.x, pos_t.y);
bool hit = facing_camera ? (t <= depth_t) : (depth_t <= edge_t);
int mip_offset = hit ? -1 : +1;
if (cur_level == 0) {
float z0 = linearize_depth(cell_depth);
float z1 = linearize_depth(cur_screen_pos.z);
if ((z0 - z1) > params.depth_tolerance) {
hit = false;
mip_offset = 0; // Keep the mip index the same to prevent it from decreasing and increasing in repeat.
}
}
if (hit) {
if (!facing_camera) {
t = max(t, depth_t);
}
} else {
t = edge_t;
}
cur_level = min(cur_level + mip_offset, params.mipmaps - 1);
--cur_iteration;
}
vec3 cur_screen_pos = screen_pos + screen_ray_dir * t;
vec4 reprojected_pos;
reprojected_pos.xy = cur_screen_pos.xy * 2.0 - 1.0;
reprojected_pos.z = cur_screen_pos.z;
reprojected_pos.w = 1.0;
reprojected_pos = scene_data.reprojection[params.view_index] * reprojected_pos;
reprojected_pos.xy = reprojected_pos.xy / reprojected_pos.w * 0.5 + 0.5;
// Instead of hard rejecting samples, write sample validity to the alpha channel.
// This allows invalid samples to write mip levels to let valid samples have smoother roughness transitions.
float validity = 1.0;
// Hit validation logic is referenced from here:
// https://github.com/GPUOpen-Effects/FidelityFX-SSSR/blob/master/ffx-sssr/ffx_sssr.h
ivec2 cur_pixel_pos = ivec2(cur_screen_pos.xy * params.screen_size);
float hit_depth = texelFetch(source_hiz, cur_pixel_pos, 0).x;
if (t >= t_max || hit_depth == 0.0) {
validity = 0.0;
}
if (all(lessThan(abs(screen_ray_dir.xy * t), 2.0 / params.screen_size))) {
vec3 hit_normal = texelFetch(source_normal_roughness, cur_pixel_pos, 0).xyz * 2.0 - 1.0;
if (dot(ray_dir, hit_normal) >= 0.0) {
validity = 0.0;
}
}
vec3 cur_pos = compute_view_pos(cur_screen_pos);
vec3 hit_pos = compute_view_pos(vec3(cur_screen_pos.xy, hit_depth));
float delta = length(cur_pos - hit_pos);
float confidence = 1.0 - smoothstep(0.0, params.depth_tolerance, delta);
validity *= clamp(confidence * confidence, 0.0, 1.0);
float margin_blend = 1.0;
vec2 reprojected_pixel_pos = reprojected_pos.xy * params.screen_size;
vec2 margin = vec2((params.screen_size.x + params.screen_size.y) * 0.05); // Make a uniform margin.
{
// Blend fading out towards inner margin.
// 0.5 = midpoint of reflection
vec2 margin_grad = mix(params.screen_size - reprojected_pixel_pos, reprojected_pixel_pos, lessThan(reprojected_pixel_pos, params.screen_size * 0.5));
margin_blend = smoothstep(0.0, margin.x * margin.y, margin_grad.x * margin_grad.y);
}
float ray_len = length(screen_ray_dir.xy * t);
// Fade In / Fade Out
float grad = (steps_taken + 1.0) / float(params.num_steps);
float initial_fade = params.curve_fade_in == 0.0 ? 1.0 : pow(clamp(grad, 0.0, 1.0), params.curve_fade_in);
float fade = pow(clamp(1.0 - grad, 0.0, 1.0), params.distance_fade) * initial_fade;
float grad = ray_len;
float fade_in = params.curve_fade_in == 0.0 ? 1.0 : pow(clamp(grad, 0.0, 1.0), params.curve_fade_in);
float fade_out = params.distance_fade == 0.0 ? 1.0 : pow(clamp(1.0 - grad, 0.0, 1.0), params.distance_fade);
float fade = fade_in * fade_out;
// Ensure that precision errors do not introduce any fade. Even if it is just slightly below 1.0,
// strong specular light can leak through the reflection.
@@ -235,41 +278,23 @@ void main() {
fade = 1.0;
}
// This is an ad-hoc term to fade out the SSR as roughness increases. Values used
// are meant to match the visual appearance of a ReflectionProbe.
float roughness_fade = smoothstep(0.4, 0.7, 1.0 - roughness);
validity *= fade * margin_blend;
// Schlick term.
float metallic = texelFetch(source_metallic, ssC << 1, 0).w;
if (validity > 0.0) {
color = vec4(textureLod(source_last_frame, reprojected_pos.xy, 0).xyz, 1.0) * validity;
// F0 is the reflectance of normally incident light (perpendicular to the surface).
// Dielectric materials have a widely accepted default value of 0.04. We assume that metals reflect all light, so their F0 is 1.0.
float f0 = mix(0.04, 1.0, metallic);
float m = clamp(1.0 - dot(normal, -view_dir), 0.0, 1.0);
float m2 = m * m;
m = m2 * m2 * m; // pow(m,5)
float fresnel_term = f0 + (1.0 - f0) * m; // Fresnel Schlick term.
// The alpha value of final_color controls the blending with specular light in specular_merge.glsl.
// Note that the Fresnel term is multiplied with the RGB color instead of being a part of the alpha value.
// There is a key difference:
// - multiplying a term with RGB darkens the SSR light without introducing/taking away specular light.
// - combining a term into the Alpha value introduces specular light at the expense of the SSR light.
vec4 final_color = vec4(imageLoad(source_diffuse, ivec2(final_pos - 0.5)).rgb * fresnel_term, fade * margin_blend * roughness_fade);
imageStore(ssr_image, ssC, final_color);
#ifdef MODE_ROUGH
// if roughness is enabled, do screen space cone tracing
float blur_radius = 0.0;
// Tone map the SSR color to have smoother roughness filtering across samples with varying luminance.
const vec3 rec709_luminance_weights = vec3(0.2126, 0.7152, 0.0722);
color.rgb /= 1.0 + dot(color.rgb, rec709_luminance_weights);
}
if (roughness > 0.001) {
float cone_angle = min(roughness, 0.999) * M_PI * 0.5;
float cone_len = length(final_pos - line_begin);
float op_len = 2.0 * tan(cone_angle) * cone_len; // opposite side of iso triangle
float cone_len = ray_len;
float op_len = 2.0 * tan(cone_angle) * cone_len; // Opposite side of iso triangle.
float blur_radius;
{
// fit to sphere inside cone (sphere ends at end of cone), something like this:
// Fit to sphere inside cone (sphere ends at end of cone), something like this:
// ___
// \O/
// V
@@ -279,19 +304,19 @@ void main() {
float a = op_len;
float h = cone_len;
float a2 = a * a;
float fh2 = 4.0f * h * h;
blur_radius = (a * (sqrt(a2 + fh2) - a)) / (4.0f * h);
float fh2 = 4.0 * h * h;
blur_radius = (a * (sqrt(a2 + fh2) - a)) / (4.0 * h);
}
mip_level = clamp(log2(blur_radius * max(params.screen_size.x, params.screen_size.y) / 16.0), 0, params.mipmaps - 1);
}
imageStore(blur_radius_image, ssC, vec4(blur_radius / 255.0)); //stored in r8
#endif // MODE_ROUGH
} else {
#ifdef MODE_ROUGH
imageStore(blur_radius_image, ssC, vec4(0.0));
#endif
imageStore(ssr_image, ssC, vec4(0.0));
// Because we still write mip level for invalid pixels to allow for smooth roughness transitions,
// this sometimes ends up creating a pyramid-like shape at very rough levels.
// We can fade the mip level near the end to make it significantly less visible.
mip_level *= pow(clamp(1.25 - ray_len, 0.0, 1.0), 0.2);
}
imageStore(output_color, pixel_pos, color);
imageStore(output_mip_level, pixel_pos, vec4(mip_level / 14.0, 0.0, 0.0, 0.0));
}

View File

@@ -0,0 +1,58 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2D source_depth;
layout(set = 0, binding = 1) uniform sampler2D source_normal_roughness;
layout(r32f, set = 0, binding = 2) uniform restrict writeonly image2D dest_depth;
layout(rgba8, set = 0, binding = 3) uniform restrict writeonly image2D dest_normal_roughness;
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
}
params;
void get_sample(ivec2 sample_pos, inout float depth, inout ivec2 winner_sample_pos) {
float sample_depth = texelFetch(source_depth, sample_pos, 0).x;
if (depth < sample_depth) {
depth = sample_depth;
winner_sample_pos = sample_pos;
}
}
void main() {
ivec2 pixel_pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(pixel_pos, params.screen_size))) {
return;
}
ivec2 sample_pos = pixel_pos * 2 + ivec2(0, 0);
float depth = texelFetch(source_depth, sample_pos, 0).x;
get_sample(pixel_pos * 2 + ivec2(1, 0), depth, sample_pos);
get_sample(pixel_pos * 2 + ivec2(0, 1), depth, sample_pos);
get_sample(pixel_pos * 2 + ivec2(1, 1), depth, sample_pos);
#ifdef MODE_ODD_WIDTH
get_sample(pixel_pos * 2 + ivec2(2, 0), depth, sample_pos);
get_sample(pixel_pos * 2 + ivec2(2, 1), depth, sample_pos);
#endif
#ifdef MODE_ODD_HEIGHT
get_sample(pixel_pos * 2 + ivec2(0, 2), depth, sample_pos);
get_sample(pixel_pos * 2 + ivec2(1, 2), depth, sample_pos);
#endif
#if defined(MODE_ODD_WIDTH) && defined(MODE_ODD_HEIGHT)
get_sample(pixel_pos * 2 + ivec2(2, 2), depth, sample_pos);
#endif
imageStore(dest_depth, pixel_pos, vec4(depth, 0.0, 0.0, 0.0));
imageStore(dest_normal_roughness, pixel_pos, texelFetch(source_normal_roughness, sample_pos, 0));
}

View File

@@ -6,143 +6,125 @@
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(rgba16f, set = 0, binding = 0) uniform restrict readonly image2D source_ssr;
layout(r8, set = 0, binding = 1) uniform restrict readonly image2D source_radius;
layout(rgba8, set = 1, binding = 0) uniform restrict readonly image2D source_normal;
layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly image2D dest_ssr;
#ifndef VERTICAL_PASS
layout(r8, set = 2, binding = 1) uniform restrict writeonly image2D dest_radius;
#endif
layout(r32f, set = 3, binding = 0) uniform restrict readonly image2D source_depth;
layout(set = 0, binding = 0) uniform sampler2D source;
layout(set = 0, binding = 1) uniform restrict writeonly image2D dest;
layout(push_constant, std430) uniform Params {
vec4 proj_info;
bool orthogonal;
float edge_tolerance;
int increment;
uint view_index;
ivec2 screen_size;
bool vertical;
uint steps;
uint mip_level;
}
params;
#include "screen_space_reflection_inc.glsl"
shared vec4 cache[16][16];
#define GAUSS_TABLE_SIZE 15
const float WEIGHTS[7] = float[7](
0.07130343198685299,
0.1315141208431224,
0.18987923288883812,
0.21460642856237303,
0.18987923288883812,
0.1315141208431224,
0.07130343198685299);
const float gauss_table[GAUSS_TABLE_SIZE + 1] = float[](
0.1847392078702266,
0.16595854345772326,
0.12031364177766891,
0.07038755277896766,
0.03322925565155569,
0.012657819729901945,
0.0038903040680094217,
0.0009646503390864025,
0.00019297087402915717,
0.000031139936308099136,
0.000004053309048174758,
4.255228059965837e-7,
3.602517634249573e-8,
2.4592560765896795e-9,
1.3534945386863618e-10,
0.0 //one more for interpolation
);
float gauss_weight(float p_val) {
float idxf;
float c = modf(max(0.0, p_val * float(GAUSS_TABLE_SIZE)), idxf);
int idx = int(idxf);
if (idx >= GAUSS_TABLE_SIZE + 1) {
return 0.0;
}
return mix(gauss_table[idx], gauss_table[idx + 1], c);
float get_weight(vec4 c) {
return mix(clamp(params.mip_level * 0.2, 0.0, 1.0), 1.0, c.a);
}
#define M_PI 3.14159265359
vec4 apply_gaus_horz(ivec2 local) {
vec4 c0 = cache[local.x - 3][local.y];
float w0 = WEIGHTS[0] * get_weight(c0);
void do_filter(inout vec4 accum, inout float accum_radius, inout float divisor, ivec2 texcoord, ivec2 increment, vec3 p_pos, vec3 normal, float p_limit_radius) {
for (int i = 1; i < params.steps; i++) {
float d = float(i * params.increment);
ivec2 tc = texcoord + increment * i;
float depth = imageLoad(source_depth, tc).r;
vec3 view_pos = reconstructCSPosition(vec2(tc) + 0.5, depth);
vec3 view_normal = normalize(imageLoad(source_normal, tc).rgb * 2.0 - 1.0);
view_normal.y = -view_normal.y;
vec4 c1 = cache[local.x - 2][local.y];
float w1 = WEIGHTS[1] * get_weight(c1);
float r = imageLoad(source_radius, tc).r;
float radius = round(r * 255.0);
vec4 c2 = cache[local.x - 1][local.y];
float w2 = WEIGHTS[2] * get_weight(c2);
float angle_n = 1.0 - abs(dot(normal, view_normal));
if (angle_n > params.edge_tolerance) {
break;
}
vec4 c3 = cache[local.x][local.y];
float w3 = WEIGHTS[3] * get_weight(c3);
float angle = abs(dot(normal, normalize(view_pos - p_pos)));
vec4 c4 = cache[local.x + 1][local.y];
float w4 = WEIGHTS[4] * get_weight(c4);
if (angle > params.edge_tolerance) {
break;
}
vec4 c5 = cache[local.x + 2][local.y];
float w5 = WEIGHTS[5] * get_weight(c5);
if (d < radius) {
float w = gauss_weight(d / radius);
accum += imageLoad(source_ssr, tc) * w;
#ifndef VERTICAL_PASS
accum_radius += r * w;
#endif
divisor += w;
}
vec4 c6 = cache[local.x + 3][local.y];
float w6 = WEIGHTS[6] * get_weight(c6);
vec4 c = c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3 + c4 * w4 + c5 * w5 + c6 * w6;
float w = w0 + w1 + w2 + w3 + w4 + w5 + w6;
if (w > 0.0) {
c /= w;
} else {
c = vec4(0.0);
}
return c;
}
shared vec4 temp_cache[8][16];
vec4 apply_gaus_vert(ivec2 local) {
vec4 c0 = temp_cache[local.x][local.y - 3];
float w0 = WEIGHTS[0] * get_weight(c0);
vec4 c1 = temp_cache[local.x][local.y - 2];
float w1 = WEIGHTS[1] * get_weight(c1);
vec4 c2 = temp_cache[local.x][local.y - 1];
float w2 = WEIGHTS[2] * get_weight(c2);
vec4 c3 = temp_cache[local.x][local.y];
float w3 = WEIGHTS[3] * get_weight(c3);
vec4 c4 = temp_cache[local.x][local.y + 1];
float w4 = WEIGHTS[4] * get_weight(c4);
vec4 c5 = temp_cache[local.x][local.y + 2];
float w5 = WEIGHTS[5] * get_weight(c5);
vec4 c6 = temp_cache[local.x][local.y + 3];
float w6 = WEIGHTS[6] * get_weight(c6);
vec4 c = c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3 + c4 * w4 + c5 * w5 + c6 * w6;
float w = w0 + w1 + w2 + w3 + w4 + w5 + w6;
if (w > 0.0) {
c /= w;
} else {
c = vec4(0.0);
}
return c;
}
vec4 get_sample(ivec2 pixel_pos) {
return textureLod(source, (vec2(pixel_pos) + 0.5) / params.screen_size, 0);
}
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
ivec2 global = ivec2(gl_GlobalInvocationID.xy);
ivec2 local = ivec2(gl_LocalInvocationID.xy);
if (any(greaterThanEqual(ssC.xy, params.screen_size))) { //too large, do nothing
cache[local.x * 2 + 0][local.y * 2 + 0] = get_sample(global + local - 4 + ivec2(0, 0));
cache[local.x * 2 + 1][local.y * 2 + 0] = get_sample(global + local - 4 + ivec2(1, 0));
cache[local.x * 2 + 0][local.y * 2 + 1] = get_sample(global + local - 4 + ivec2(0, 1));
cache[local.x * 2 + 1][local.y * 2 + 1] = get_sample(global + local - 4 + ivec2(1, 1));
memoryBarrierShared();
barrier();
temp_cache[local.x][local.y * 2 + 0] = apply_gaus_horz(ivec2(local.x + 4, local.y * 2 + 0));
temp_cache[local.x][local.y * 2 + 1] = apply_gaus_horz(ivec2(local.x + 4, local.y * 2 + 1));
memoryBarrierShared();
barrier();
if (any(greaterThanEqual(global, params.screen_size))) {
return;
}
float base_contrib = gauss_table[0];
vec4 accum = imageLoad(source_ssr, ssC);
float accum_radius = imageLoad(source_radius, ssC).r;
float radius = accum_radius * 255.0;
float divisor = gauss_table[0];
accum *= divisor;
accum_radius *= divisor;
#ifdef VERTICAL_PASS
ivec2 direction = ivec2(0, params.increment);
#else
ivec2 direction = ivec2(params.increment, 0);
#endif
float depth = imageLoad(source_depth, ssC).r;
vec3 pos = reconstructCSPosition(vec2(ssC.xy) + 0.5, depth);
vec3 normal = imageLoad(source_normal, ssC).xyz * 2.0 - 1.0;
normal = normalize(normal);
normal.y = -normal.y;
do_filter(accum, accum_radius, divisor, ssC.xy, direction, pos, normal, radius);
do_filter(accum, accum_radius, divisor, ssC.xy, -direction, pos, normal, radius);
if (divisor > 0.0) {
accum /= divisor;
accum_radius /= divisor;
} else {
accum = vec4(0.0);
accum_radius = 0.0;
}
imageStore(dest_ssr, ssC, accum);
#ifndef VERTICAL_PASS
imageStore(dest_radius, ssC, vec4(accum_radius));
#endif
imageStore(dest, global, apply_gaus_vert(local + ivec2(0, 4)));
}

View File

@@ -0,0 +1,44 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2D source;
layout(r32f, set = 0, binding = 1) uniform restrict writeonly image2D dest;
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
}
params;
void main() {
ivec2 pixel_pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(pixel_pos, params.screen_size))) {
return;
}
float depth = texelFetch(source, pixel_pos * 2 + ivec2(0, 0), 0).x;
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(1, 0), 0).x);
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(0, 1), 0).x);
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(1, 1), 0).x);
#ifdef MODE_ODD_WIDTH
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(2, 0), 0).x);
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(2, 1), 0).x);
#endif
#ifdef MODE_ODD_HEIGHT
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(0, 2), 0).x);
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(1, 2), 0).x);
#endif
#if defined(MODE_ODD_WIDTH) && defined(MODE_ODD_HEIGHT)
depth = max(depth, texelFetch(source, pixel_pos * 2 + ivec2(2, 2), 0).x);
#endif
imageStore(dest, pixel_pos, vec4(depth, 0.0, 0.0, 0.0));
}

View File

@@ -1,28 +0,0 @@
layout(constant_id = 0) const bool sc_multiview = false;
layout(set = 4, binding = 0, std140) uniform SceneData {
mat4x4 projection[2];
mat4x4 inv_projection[2];
vec4 eye_offset[2];
}
scene_data;
vec3 reconstructCSPosition(vec2 screen_pos, float z) {
if (sc_multiview) {
vec4 pos;
pos.xy = (2.0 * vec2(screen_pos) / vec2(params.screen_size)) - 1.0;
pos.z = z * 2.0 - 1.0;
pos.w = 1.0;
pos = scene_data.inv_projection[params.view_index] * pos;
pos.xyz /= pos.w;
return pos.xyz;
} else {
if (params.orthogonal) {
return vec3(-(screen_pos.xy * params.proj_info.xy + params.proj_info.zw), z);
} else {
return vec3((screen_pos.xy * params.proj_info.xy + params.proj_info.zw) * z, z);
}
}
}

View File

@@ -0,0 +1,98 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2D source_depth;
layout(set = 0, binding = 1) uniform sampler2D source_normal_roughness;
layout(set = 0, binding = 2) uniform sampler2D source_depth_half;
layout(set = 0, binding = 3) uniform sampler2D source_normal_roughness_half;
layout(set = 0, binding = 4) uniform sampler2D source_color;
layout(set = 0, binding = 5) uniform sampler2D source_mip_level;
layout(rgba16f, set = 0, binding = 6) uniform restrict writeonly image2D output_color;
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
}
params;
void get_sample(float depth, vec3 normal, float roughness, ivec2 pixel_pos, out vec4 color, out float weight) {
float sample_depth = texelFetch(source_depth_half, pixel_pos, 0).x;
vec4 sample_normal_roughness = texelFetch(source_normal_roughness_half, pixel_pos, 0);
vec3 sample_normal = normalize(sample_normal_roughness.xyz * 2.0 - 1.0);
float sample_roughness = sample_normal_roughness.w;
if (sample_roughness > 0.5) {
sample_roughness = 1.0 - sample_roughness;
}
sample_roughness /= (127.0 / 255.0);
vec2 uv = (pixel_pos + 0.5) / (params.screen_size * 0.5);
float mip_level = texelFetch(source_mip_level, pixel_pos, 0).x * 14.0;
color = textureLod(source_color, uv, mip_level);
// Invert the tone mapping we applied in the main trace pass.
const vec3 rec709_luminance_weights = vec3(0.2126, 0.7152, 0.0722);
color.rgb /= 1.0 - dot(color.rgb, rec709_luminance_weights);
const float DEPTH_FACTOR = 2048.0;
const float NORMAL_FACTOR = 32.0;
const float ROUGHNESS_FACTOR = 16.0;
float depth_diff = abs(depth - sample_depth);
float weight_depth = exp(-depth_diff * DEPTH_FACTOR);
float normal_diff = clamp(1.0 - dot(normal, sample_normal), 0.0, 1.0);
float weight_normal = exp(-normal_diff * NORMAL_FACTOR);
float roughness_diff = abs(roughness - sample_roughness);
float weight_roughness = exp(-roughness_diff * ROUGHNESS_FACTOR);
weight = weight_depth * weight_normal * weight_roughness;
}
void main() {
ivec2 pixel_pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(pixel_pos, params.screen_size))) {
return;
}
float depth = texelFetch(source_depth, pixel_pos, 0).x;
vec4 normal_roughness = texelFetch(source_normal_roughness, pixel_pos, 0);
vec3 normal = normalize(normal_roughness.xyz * 2.0 - 1.0);
float roughness = normal_roughness.w;
if (roughness > 0.5) {
roughness = 1.0 - roughness;
}
roughness /= (127.0 / 255.0);
vec2 half_tex_coord = (pixel_pos + 0.5) * 0.5;
vec2 bilinear_weights = fract(half_tex_coord);
vec4 color0, color1, color2, color3;
float weight0, weight1, weight2, weight3;
get_sample(depth, normal, roughness, ((pixel_pos - 1) / 2) + ivec2(0, 0), color0, weight0);
get_sample(depth, normal, roughness, ((pixel_pos - 1) / 2) + ivec2(1, 0), color1, weight1);
get_sample(depth, normal, roughness, ((pixel_pos - 1) / 2) + ivec2(0, 1), color2, weight2);
get_sample(depth, normal, roughness, ((pixel_pos - 1) / 2) + ivec2(1, 1), color3, weight3);
weight0 *= bilinear_weights.x * bilinear_weights.y;
weight1 *= (1.0 - bilinear_weights.x) * bilinear_weights.y;
weight2 *= bilinear_weights.x * (1.0 - bilinear_weights.y);
weight3 *= (1.0 - bilinear_weights.x) * (1.0 - bilinear_weights.y);
vec4 result_color = color0 * weight0 + color1 * weight1 + color2 * weight2 + color3 * weight3;
float result_weight = weight0 + weight1 + weight2 + weight3;
if (result_weight > 0.0) {
result_color /= result_weight;
} else {
result_color = vec4(0.0);
}
imageStore(output_color, pixel_pos, result_color);
}

View File

@@ -1,112 +0,0 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
/* Specialization Constants (Toggles) */
layout(constant_id = 0) const bool sc_multiview = false;
/* inputs */
layout(set = 0, binding = 0) uniform sampler2D source_ssr;
layout(set = 1, binding = 0) uniform sampler2D source_depth;
layout(set = 1, binding = 1) uniform sampler2D source_normal;
layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly image2D dest_ssr;
layout(r32f, set = 3, binding = 0) uniform restrict writeonly image2D dest_depth;
layout(rgba8, set = 3, binding = 1) uniform restrict writeonly image2D dest_normal;
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
float camera_z_near;
float camera_z_far;
bool orthogonal;
bool filtered;
uint pad[2];
}
params;
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC.xy, params.screen_size))) { //too large, do nothing
return;
}
//do not filter, SSR will generate artifacts if this is done
float divisor = 0.0;
vec4 color;
float depth;
vec4 normal;
if (params.filtered) {
color = vec4(0.0);
depth = 0.0;
normal = vec4(0.0);
for (int i = 0; i < 4; i++) {
ivec2 ofs = ssC << 1;
if (bool(i & 1)) {
ofs.x += 1;
}
if (bool(i & 2)) {
ofs.y += 1;
}
color += texelFetch(source_ssr, ofs, 0);
float d = texelFetch(source_depth, ofs, 0).r;
vec4 nr = texelFetch(source_normal, ofs, 0);
normal.xyz += normalize(nr.xyz * 2.0 - 1.0);
float roughness = normal.w;
if (roughness > 0.5) {
roughness = 1.0 - roughness;
}
roughness /= (127.0 / 255.0);
normal.w += roughness;
if (sc_multiview) {
// we're doing a full unproject so we need the value as is.
depth += d;
} else {
// unproject our Z value so we can use it directly.
d = d * 2.0 - 1.0;
if (params.orthogonal) {
d = ((d + (params.camera_z_far + params.camera_z_near) / (params.camera_z_far - params.camera_z_near)) * (params.camera_z_far - params.camera_z_near)) / 2.0;
} else {
d = 2.0 * params.camera_z_near * params.camera_z_far / (params.camera_z_far + params.camera_z_near - d * (params.camera_z_far - params.camera_z_near));
}
depth += -d;
}
}
color /= 4.0;
depth /= 4.0;
normal.xyz = normalize(normal.xyz / 4.0) * 0.5 + 0.5;
normal.w /= 4.0;
normal.w = normal.w * (127.0 / 255.0);
} else {
ivec2 ofs = ssC << 1;
color = texelFetch(source_ssr, ofs, 0);
depth = texelFetch(source_depth, ofs, 0).r;
normal = texelFetch(source_normal, ofs, 0);
if (!sc_multiview) {
// unproject our Z value so we can use it directly.
depth = depth * 2.0 - 1.0;
if (params.orthogonal) {
depth = -(depth * (params.camera_z_far - params.camera_z_near) - (params.camera_z_far + params.camera_z_near)) / 2.0;
} else {
depth = 2.0 * params.camera_z_near * params.camera_z_far / (params.camera_z_far + params.camera_z_near + depth * (params.camera_z_far - params.camera_z_near));
}
depth = -depth;
}
}
imageStore(dest_ssr, ssC, color);
imageStore(dest_depth, ssC, vec4(depth));
imageStore(dest_normal, ssC, normal);
}

View File

@@ -1597,6 +1597,12 @@ void fragment_shader(in SceneData scene_data) {
float kernelRoughness2 = min(2.0 * variance, scene_data.roughness_limiter_limit); //limit effect
float filteredRoughness2 = min(1.0, roughness2 + kernelRoughness2);
roughness = sqrt(filteredRoughness2);
// Reject very small roughness values. Lack of precision can collapse
// roughness^4 to 0 in GGX specular equations and cause divisions by zero.
if (roughness < 0.00000001) {
roughness = 0.0;
}
}
#endif
//apply energy conservation
@@ -2029,6 +2035,39 @@ void fragment_shader(in SceneData scene_data) {
#endif
}
//process ssr
if (bool(implementation_data.ss_effects_flags & SCREEN_SPACE_EFFECTS_FLAGS_USE_SSR)) {
bool resolve_ssr = bool(implementation_data.ss_effects_flags & SCREEN_SPACE_EFFECTS_FLAGS_RESOLVE_SSR);
float ssr_mip_level = 0.0;
if (resolve_ssr) {
#ifdef USE_MULTIVIEW
ssr_mip_level = textureLod(sampler2DArray(ssr_mip_level_buffer, SAMPLER_NEAREST_CLAMP), vec3(screen_uv, ViewIndex), 0.0).x;
#else
ssr_mip_level = textureLod(sampler2D(ssr_mip_level_buffer, SAMPLER_NEAREST_CLAMP), screen_uv, 0.0).x;
#endif // USE_MULTIVIEW
ssr_mip_level *= 14.0;
}
#ifdef USE_MULTIVIEW
vec4 ssr = textureLod(sampler2DArray(ssr_buffer, SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec3(screen_uv, ViewIndex), ssr_mip_level);
#else
vec4 ssr = textureLod(sampler2D(ssr_buffer, SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), screen_uv, ssr_mip_level);
#endif // USE_MULTIVIEW
if (resolve_ssr) {
const vec3 rec709_luminance_weights = vec3(0.2126, 0.7152, 0.0722);
ssr.rgb /= 1.0 - dot(ssr.rgb, rec709_luminance_weights);
}
// Apply fade when approaching 0.7 roughness to smoothen the harsh cutoff in the main SSR trace pass.
ssr *= smoothstep(0.0, 1.0, 1.0 - clamp((roughness - 0.6) / (0.7 - 0.6), 0.0, 1.0));
// Alpha is premultiplied.
indirect_specular_light = indirect_specular_light * (1.0 - ssr.a) + ssr.rgb;
}
//finalize ambient light here
{
ambient_light *= ao;

View File

@@ -164,8 +164,10 @@ layout(set = 0, binding = 2) uniform sampler shadow_sampler;
//3 bits of stride
#define INSTANCE_FLAGS_PARTICLE_TRAIL_MASK 0xFF
#define SCREEN_SPACE_EFFECTS_FLAGS_USE_SSAO 1
#define SCREEN_SPACE_EFFECTS_FLAGS_USE_SSIL 2
#define SCREEN_SPACE_EFFECTS_FLAGS_USE_SSAO (1 << 0)
#define SCREEN_SPACE_EFFECTS_FLAGS_USE_SSIL (1 << 1)
#define SCREEN_SPACE_EFFECTS_FLAGS_USE_SSR (1 << 2)
#define SCREEN_SPACE_EFFECTS_FLAGS_RESOLVE_SSR (1 << 3)
layout(set = 0, binding = 3, std430) restrict readonly buffer OmniLights {
LightData data[];
@@ -435,8 +437,12 @@ layout(set = 1, binding = 33) uniform texture3D volumetric_fog_texture;
#ifdef USE_MULTIVIEW
layout(set = 1, binding = 34) uniform texture2DArray ssil_buffer;
layout(set = 1, binding = 35) uniform texture2DArray ssr_buffer;
layout(set = 1, binding = 36) uniform texture2DArray ssr_mip_level_buffer;
#else
layout(set = 1, binding = 34) uniform texture2D ssil_buffer;
layout(set = 1, binding = 35) uniform texture2D ssr_buffer;
layout(set = 1, binding = 36) uniform texture2D ssr_mip_level_buffer;
#endif // USE_MULTIVIEW
#endif