1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-12 13:20:55 +00:00

Move screen space effects into a separate class

This commit is contained in:
Bastiaan Olij
2022-06-28 19:10:36 +10:00
parent 3953c1aa73
commit eefcb5ed67
27 changed files with 2632 additions and 2085 deletions

View File

@@ -0,0 +1,254 @@
#[compute]
#version 450
#VERSION_DEFINES
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(push_constant, std430) uniform Params {
vec4 proj_info;
ivec2 screen_size;
float camera_z_near;
float camera_z_far;
int num_steps;
float depth_tolerance;
float distance_fade;
float curve_fade_in;
bool orthogonal;
float filter_mipmap_levels;
bool use_half_res;
uint metallic_mask;
uint view_index;
uint pad1;
uint pad2;
uint pad3;
}
params;
#include "screen_space_reflection_inc.glsl"
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;
}
#define M_PI 3.14159265359
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC.xy, params.screen_size))) { //too large, do nothing
return;
}
vec2 pixel_size = 1.0 / vec2(params.screen_size);
vec2 uv = vec2(ssC.xy) * pixel_size;
uv += pixel_size * 0.5;
float base_depth = imageLoad(source_depth, ssC).r;
// 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 = normal_roughness.xyz * 2.0 - 1.0;
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 = 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;
}
//ray_dir = normalize(view_dir - normal * dot(normal,view_dir) * 2.0);
//ray_dir = normalize(vec3(1.0, 1.0, -1.0));
////////////////
// 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 = length(line_advance) / 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;
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
depth = imageLoad(source_depth, ivec2(pos - 0.5)).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;
}
z_from = z_to;
z_to = z / w;
if (depth > z_to) {
// if depth was surpassed
if (depth <= max(z_to, z_from) + params.depth_tolerance && -depth < params.camera_z_far) {
// 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 margin = vec2((params.screen_size.x + params.screen_size.y) * 0.5 * 0.05); // make a uniform margin
if (any(bvec4(lessThan(pos, -margin), greaterThan(pos, params.screen_size + margin)))) {
// clip outside screen + margin
imageStore(ssr_image, ssC, vec4(0.0));
return;
}
{
//blend fading out towards external margin
vec2 margin_grad = mix(pos - params.screen_size, -pos, lessThan(pos, vec2(0.0)));
margin_blend = 1.0 - smoothstep(0.0, margin.x, max(margin_grad.x, margin_grad.y));
//margin_blend = 1.0;
}
vec2 final_pos;
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;
final_pos = pos;
vec4 final_color;
#ifdef MODE_ROUGH
// if roughness is enabled, do screen space cone tracing
float blur_radius = 0.0;
float roughness = normal_roughness.w;
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
{
// fit to sphere inside cone (sphere ends at end of cone), something like this:
// ___
// \O/
// V
//
// as it avoids bleeding from beyond the reflection as much as possible. As a plus
// it also makes the rough reflection more elongated.
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);
}
}
// Isn't this going to be overwritten after our endif?
final_color = imageLoad(source_diffuse, ivec2((final_pos - 0.5) * pixel_size));
imageStore(blur_radius_image, ssC, vec4(blur_radius / 255.0)); //stored in r8
#endif // MODE_ROUGH
final_color = vec4(imageLoad(source_diffuse, ivec2(final_pos - 0.5)).rgb, fade * margin_blend);
//change blend by metallic
vec4 metallic_mask = unpackUnorm4x8(params.metallic_mask);
final_color.a *= dot(metallic_mask, texelFetch(source_metallic, ssC << 1, 0));
imageStore(ssr_image, ssC, final_color);
} else {
#ifdef MODE_ROUGH
imageStore(blur_radius_image, ssC, vec4(0.0));
#endif
imageStore(ssr_image, ssC, vec4(0.0));
}
}

View File

@@ -0,0 +1,148 @@
#[compute]
#version 450
#VERSION_DEFINES
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(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;
}
params;
#include "screen_space_reflection_inc.glsl"
#define GAUSS_TABLE_SIZE 15
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);
}
#define M_PI 3.14159265359
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;
float r = imageLoad(source_radius, tc).r;
float radius = round(r * 255.0);
float angle_n = 1.0 - abs(dot(normal, view_normal));
if (angle_n > params.edge_tolerance) {
break;
}
float angle = abs(dot(normal, normalize(view_pos - p_pos)));
if (angle > params.edge_tolerance) {
break;
}
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;
}
}
}
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC.xy, params.screen_size))) { //too large, do nothing
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
}

View File

@@ -0,0 +1,28 @@
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,106 @@
#[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 arctifacts 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 += nr.xyz * 2.0 - 1.0;
normal.w += nr.w;
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;
} 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)) * (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

@@ -0,0 +1,112 @@
#[vertex]
#version 450
#VERSION_DEFINES
#if defined(USE_MULTIVIEW) && defined(has_VK_KHR_multiview)
#extension GL_EXT_multiview : enable
#endif
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
#else // has_VK_KHR_multiview
// !BAS! This needs to become an input once we implement our fallback!
#define ViewIndex 0
#endif // has_VK_KHR_multiview
#else // USE_MULTIVIEW
// Set to zero, not supported in non stereo
#define ViewIndex 0
#endif //USE_MULTIVIEW
#ifdef USE_MULTIVIEW
layout(location = 0) out vec3 uv_interp;
#else // USE_MULTIVIEW
layout(location = 0) out vec2 uv_interp;
#endif //USE_MULTIVIEW
void main() {
vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
#ifdef USE_MULTIVIEW
uv_interp = vec3(base_arr[gl_VertexIndex], ViewIndex);
gl_Position = vec4(uv_interp.xy * 2.0 - 1.0, 0.0, 1.0);
#else
uv_interp = base_arr[gl_VertexIndex];
gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
#endif
}
#[fragment]
#version 450
#VERSION_DEFINES
#if defined(USE_MULTIVIEW) && defined(has_VK_KHR_multiview)
#extension GL_EXT_multiview : enable
#endif
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
#else // has_VK_KHR_multiview
// !BAS! This needs to become an input once we implement our fallback!
#define ViewIndex 0
#endif // has_VK_KHR_multiview
#else // USE_MULTIVIEW
// Set to zero, not supported in non stereo
#define ViewIndex 0
#endif //USE_MULTIVIEW
#ifdef USE_MULTIVIEW
layout(location = 0) in vec3 uv_interp;
#else // USE_MULTIVIEW
layout(location = 0) in vec2 uv_interp;
#endif //USE_MULTIVIEW
#ifdef USE_MULTIVIEW
layout(set = 0, binding = 0) uniform sampler2DArray specular;
#else // USE_MULTIVIEW
layout(set = 0, binding = 0) uniform sampler2D specular;
#endif //USE_MULTIVIEW
#ifdef MODE_SSR
#ifdef USE_MULTIVIEW
layout(set = 1, binding = 0) uniform sampler2DArray ssr;
#else // USE_MULTIVIEW
layout(set = 1, binding = 0) uniform sampler2D ssr;
#endif //USE_MULTIVIEW
#endif
#ifdef MODE_MERGE
#ifdef USE_MULTIVIEW
layout(set = 2, binding = 0) uniform sampler2DArray diffuse;
#else // USE_MULTIVIEW
layout(set = 2, binding = 0) uniform sampler2D diffuse;
#endif //USE_MULTIVIEW
#endif
layout(location = 0) out vec4 frag_color;
void main() {
frag_color.rgb = texture(specular, uv_interp).rgb;
frag_color.a = 0.0;
#ifdef MODE_SSR
vec4 ssr_color = texture(ssr, uv_interp);
frag_color.rgb = mix(frag_color.rgb, ssr_color.rgb, ssr_color.a);
#endif
#ifdef MODE_MERGE
frag_color += texture(diffuse, uv_interp);
#endif
//added using additive blend
}

View File

@@ -0,0 +1,229 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(push_constant, std430) uniform Params {
vec2 pixel_size;
float z_far;
float z_near;
bool orthogonal;
float radius_sq;
uvec2 pad;
}
params;
layout(set = 0, binding = 0) uniform sampler2D source_depth;
layout(r16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_image0; //rename
#ifdef GENERATE_MIPS
layout(r16f, set = 2, binding = 0) uniform restrict writeonly image2DArray dest_image1;
layout(r16f, set = 2, binding = 1) uniform restrict writeonly image2DArray dest_image2;
layout(r16f, set = 2, binding = 2) uniform restrict writeonly image2DArray dest_image3;
#ifdef GENERATE_FULL_MIPS
layout(r16f, set = 2, binding = 3) uniform restrict writeonly image2DArray dest_image4;
#endif
#endif
vec4 screen_space_to_view_space_depth(vec4 p_depth) {
if (params.orthogonal) {
vec4 depth = p_depth * 2.0 - 1.0;
return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
}
float depth_linearize_mul = params.z_near;
float depth_linearize_add = params.z_far;
// Optimised version of "-cameraClipNear / (cameraClipFar - projDepth * (cameraClipFar - cameraClipNear)) * cameraClipFar"
// Set your depth_linearize_mul and depth_linearize_add to:
// depth_linearize_mul = ( cameraClipFar * cameraClipNear) / ( cameraClipFar - cameraClipNear );
// depth_linearize_add = cameraClipFar / ( cameraClipFar - cameraClipNear );
return depth_linearize_mul / (depth_linearize_add - p_depth);
}
float screen_space_to_view_space_depth(float p_depth) {
if (params.orthogonal) {
float depth = p_depth * 2.0 - 1.0;
return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / (2.0 * params.z_far);
}
float depth_linearize_mul = params.z_near;
float depth_linearize_add = params.z_far;
return depth_linearize_mul / (depth_linearize_add - p_depth);
}
#ifdef GENERATE_MIPS
shared float depth_buffer[4][8][8];
float mip_smart_average(vec4 p_depths) {
float closest = min(min(p_depths.x, p_depths.y), min(p_depths.z, p_depths.w));
float fallof_sq = -1.0f / params.radius_sq;
vec4 dists = p_depths - closest.xxxx;
vec4 weights = clamp(dists * dists * fallof_sq + 1.0, 0.0, 1.0);
return dot(weights, p_depths) / dot(weights, vec4(1.0, 1.0, 1.0, 1.0));
}
void prepare_depths_and_mips(vec4 p_samples, uvec2 p_output_coord, uvec2 p_gtid) {
p_samples = screen_space_to_view_space_depth(p_samples);
depth_buffer[0][p_gtid.x][p_gtid.y] = p_samples.w;
depth_buffer[1][p_gtid.x][p_gtid.y] = p_samples.z;
depth_buffer[2][p_gtid.x][p_gtid.y] = p_samples.x;
depth_buffer[3][p_gtid.x][p_gtid.y] = p_samples.y;
imageStore(dest_image0, ivec3(p_output_coord.x, p_output_coord.y, 0), vec4(p_samples.w));
imageStore(dest_image0, ivec3(p_output_coord.x, p_output_coord.y, 1), vec4(p_samples.z));
imageStore(dest_image0, ivec3(p_output_coord.x, p_output_coord.y, 2), vec4(p_samples.x));
imageStore(dest_image0, ivec3(p_output_coord.x, p_output_coord.y, 3), vec4(p_samples.y));
uint depth_array_index = 2 * (p_gtid.y % 2) + (p_gtid.x % 2);
uvec2 depth_array_offset = ivec2(p_gtid.x % 2, p_gtid.y % 2);
ivec2 buffer_coord = ivec2(p_gtid) - ivec2(depth_array_offset);
p_output_coord /= 2;
groupMemoryBarrier();
barrier();
// if (still_alive) <-- all threads alive here
{
float sample_00 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 0];
float sample_01 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 1];
float sample_10 = depth_buffer[depth_array_index][buffer_coord.x + 1][buffer_coord.y + 0];
float sample_11 = depth_buffer[depth_array_index][buffer_coord.x + 1][buffer_coord.y + 1];
float avg = mip_smart_average(vec4(sample_00, sample_01, sample_10, sample_11));
imageStore(dest_image1, ivec3(p_output_coord.x, p_output_coord.y, depth_array_index), vec4(avg));
depth_buffer[depth_array_index][buffer_coord.x][buffer_coord.y] = avg;
}
bool still_alive = p_gtid.x % 4 == depth_array_offset.x && p_gtid.y % 4 == depth_array_offset.y;
p_output_coord /= 2;
groupMemoryBarrier();
barrier();
if (still_alive) {
float sample_00 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 0];
float sample_01 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 2];
float sample_10 = depth_buffer[depth_array_index][buffer_coord.x + 2][buffer_coord.y + 0];
float sample_11 = depth_buffer[depth_array_index][buffer_coord.x + 2][buffer_coord.y + 2];
float avg = mip_smart_average(vec4(sample_00, sample_01, sample_10, sample_11));
imageStore(dest_image2, ivec3(p_output_coord.x, p_output_coord.y, depth_array_index), vec4(avg));
depth_buffer[depth_array_index][buffer_coord.x][buffer_coord.y] = avg;
}
still_alive = p_gtid.x % 8 == depth_array_offset.x && depth_array_offset.y % 8 == depth_array_offset.y;
p_output_coord /= 2;
groupMemoryBarrier();
barrier();
if (still_alive) {
float sample_00 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 0];
float sample_01 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 4];
float sample_10 = depth_buffer[depth_array_index][buffer_coord.x + 4][buffer_coord.y + 0];
float sample_11 = depth_buffer[depth_array_index][buffer_coord.x + 4][buffer_coord.y + 4];
float avg = mip_smart_average(vec4(sample_00, sample_01, sample_10, sample_11));
imageStore(dest_image3, ivec3(p_output_coord.x, p_output_coord.y, depth_array_index), vec4(avg));
#ifndef GENERATE_FULL_MIPS
}
#else
depth_buffer[depth_array_index][buffer_coord.x][buffer_coord.y] = avg;
}
still_alive = p_gtid.x % 16 == depth_array_offset.x && depth_array_offset.y % 16 == depth_array_offset.y;
p_output_coord /= 2;
groupMemoryBarrier();
barrier();
if (still_alive) {
float sample_00 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 0];
float sample_01 = depth_buffer[depth_array_index][buffer_coord.x + 0][buffer_coord.y + 8];
float sample_10 = depth_buffer[depth_array_index][buffer_coord.x + 8][buffer_coord.y + 0];
float sample_11 = depth_buffer[depth_array_index][buffer_coord.x + 8][buffer_coord.y + 8];
float avg = mip_smart_average(vec4(sample_00, sample_01, sample_10, sample_11));
imageStore(dest_image4, ivec3(p_output_coord.x, p_output_coord.y, depth_array_index), vec4(avg));
}
#endif
}
#else
#ifndef USE_HALF_BUFFERS
void prepare_depths(vec4 p_samples, uvec2 p_tid) {
p_samples = screen_space_to_view_space_depth(p_samples);
imageStore(dest_image0, ivec3(p_tid, 0), vec4(p_samples.w));
imageStore(dest_image0, ivec3(p_tid, 1), vec4(p_samples.z));
imageStore(dest_image0, ivec3(p_tid, 2), vec4(p_samples.x));
imageStore(dest_image0, ivec3(p_tid, 3), vec4(p_samples.y));
}
#endif
#endif
void main() {
#ifdef USE_HALF_BUFFERS
#ifdef USE_HALF_SIZE
float sample_00 = texelFetch(source_depth, ivec2(4 * gl_GlobalInvocationID.x + 0, 4 * gl_GlobalInvocationID.y + 0), 0).x;
float sample_11 = texelFetch(source_depth, ivec2(4 * gl_GlobalInvocationID.x + 2, 4 * gl_GlobalInvocationID.y + 2), 0).x;
#else
float sample_00 = texelFetch(source_depth, ivec2(2 * gl_GlobalInvocationID.x + 0, 2 * gl_GlobalInvocationID.y + 0), 0).x;
float sample_11 = texelFetch(source_depth, ivec2(2 * gl_GlobalInvocationID.x + 1, 2 * gl_GlobalInvocationID.y + 1), 0).x;
#endif
sample_00 = screen_space_to_view_space_depth(sample_00);
sample_11 = screen_space_to_view_space_depth(sample_11);
imageStore(dest_image0, ivec3(gl_GlobalInvocationID.xy, 0), vec4(sample_00));
imageStore(dest_image0, ivec3(gl_GlobalInvocationID.xy, 3), vec4(sample_11));
#else //!USE_HALF_BUFFERS
#ifdef USE_HALF_SIZE
ivec2 depth_buffer_coord = 4 * ivec2(gl_GlobalInvocationID.xy);
ivec2 output_coord = ivec2(gl_GlobalInvocationID);
vec2 uv = (vec2(depth_buffer_coord) + 0.5f) * params.pixel_size;
vec4 samples;
samples.x = textureLodOffset(source_depth, uv, 0, ivec2(0, 2)).x;
samples.y = textureLodOffset(source_depth, uv, 0, ivec2(2, 2)).x;
samples.z = textureLodOffset(source_depth, uv, 0, ivec2(2, 0)).x;
samples.w = textureLodOffset(source_depth, uv, 0, ivec2(0, 0)).x;
#else
ivec2 depth_buffer_coord = 2 * ivec2(gl_GlobalInvocationID.xy);
ivec2 output_coord = ivec2(gl_GlobalInvocationID);
vec2 uv = (vec2(depth_buffer_coord) + 0.5f) * params.pixel_size;
vec4 samples = textureGather(source_depth, uv);
#endif
#ifdef GENERATE_MIPS
prepare_depths_and_mips(samples, output_coord, gl_LocalInvocationID.xy);
#else
prepare_depths(samples, gl_GlobalInvocationID.xy);
#endif
#endif
}

View File

@@ -0,0 +1,483 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
#define INTELSSAO_MAIN_DISK_SAMPLE_COUNT (32)
const vec4 sample_pattern[INTELSSAO_MAIN_DISK_SAMPLE_COUNT] = {
vec4(0.78488064, 0.56661671, 1.500000, -0.126083), vec4(0.26022232, -0.29575172, 1.500000, -1.064030), vec4(0.10459357, 0.08372527, 1.110000, -2.730563), vec4(-0.68286800, 0.04963045, 1.090000, -0.498827),
vec4(-0.13570161, -0.64190155, 1.250000, -0.532765), vec4(-0.26193795, -0.08205118, 0.670000, -1.783245), vec4(-0.61177456, 0.66664219, 0.710000, -0.044234), vec4(0.43675563, 0.25119025, 0.610000, -1.167283),
vec4(0.07884444, 0.86618668, 0.640000, -0.459002), vec4(-0.12790935, -0.29869005, 0.600000, -1.729424), vec4(-0.04031125, 0.02413622, 0.600000, -4.792042), vec4(0.16201244, -0.52851415, 0.790000, -1.067055),
vec4(-0.70991218, 0.47301072, 0.640000, -0.335236), vec4(0.03277707, -0.22349690, 0.600000, -1.982384), vec4(0.68921727, 0.36800742, 0.630000, -0.266718), vec4(0.29251814, 0.37775412, 0.610000, -1.422520),
vec4(-0.12224089, 0.96582592, 0.600000, -0.426142), vec4(0.11071457, -0.16131058, 0.600000, -2.165947), vec4(0.46562141, -0.59747696, 0.600000, -0.189760), vec4(-0.51548797, 0.11804193, 0.600000, -1.246800),
vec4(0.89141309, -0.42090443, 0.600000, 0.028192), vec4(-0.32402530, -0.01591529, 0.600000, -1.543018), vec4(0.60771245, 0.41635221, 0.600000, -0.605411), vec4(0.02379565, -0.08239821, 0.600000, -3.809046),
vec4(0.48951152, -0.23657045, 0.600000, -1.189011), vec4(-0.17611565, -0.81696892, 0.600000, -0.513724), vec4(-0.33930185, -0.20732205, 0.600000, -1.698047), vec4(-0.91974425, 0.05403209, 0.600000, 0.062246),
vec4(-0.15064627, -0.14949332, 0.600000, -1.896062), vec4(0.53180975, -0.35210401, 0.600000, -0.758838), vec4(0.41487166, 0.81442589, 0.600000, -0.505648), vec4(-0.24106961, -0.32721516, 0.600000, -1.665244)
};
// these values can be changed (up to SSAO_MAX_TAPS) with no changes required elsewhere; values for 4th and 5th preset are ignored but array needed to avoid compilation errors
// the actual number of texture samples is two times this value (each "tap" has two symmetrical depth texture samples)
const int num_taps[5] = { 3, 5, 12, 0, 0 };
#define SSAO_TILT_SAMPLES_ENABLE_AT_QUALITY_PRESET (99) // to disable simply set to 99 or similar
#define SSAO_TILT_SAMPLES_AMOUNT (0.4)
//
#define SSAO_HALOING_REDUCTION_ENABLE_AT_QUALITY_PRESET (1) // to disable simply set to 99 or similar
#define SSAO_HALOING_REDUCTION_AMOUNT (0.6) // values from 0.0 - 1.0, 1.0 means max weighting (will cause artifacts, 0.8 is more reasonable)
//
#define SSAO_NORMAL_BASED_EDGES_ENABLE_AT_QUALITY_PRESET (2) // to disable simply set to 99 or similar
#define SSAO_NORMAL_BASED_EDGES_DOT_THRESHOLD (0.5) // use 0-0.1 for super-sharp normal-based edges
//
#define SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET (1) // whether to use detail; to disable simply set to 99 or similar
//
#define SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET (2) // !!warning!! the MIP generation on the C++ side will be enabled on quality preset 2 regardless of this value, so if changing here, change the C++ side too
#define SSAO_DEPTH_MIPS_GLOBAL_OFFSET (-4.3) // best noise/quality/performance tradeoff, found empirically
//
// !!warning!! the edge handling is hard-coded to 'disabled' on quality level 0, and enabled above, on the C++ side; while toggling it here will work for
// testing purposes, it will not yield performance gains (or correct results)
#define SSAO_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET (1)
//
#define SSAO_REDUCE_RADIUS_NEAR_SCREEN_BORDER_ENABLE_AT_QUALITY_PRESET (1)
#define SSAO_MAX_TAPS 32
#define SSAO_ADAPTIVE_TAP_BASE_COUNT 5
#define SSAO_ADAPTIVE_TAP_FLEXIBLE_COUNT (SSAO_MAX_TAPS - SSAO_ADAPTIVE_TAP_BASE_COUNT)
#define SSAO_DEPTH_MIP_LEVELS 4
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2DArray source_depth_mipmaps;
layout(rgba8, set = 0, binding = 1) uniform restrict readonly image2D source_normal;
layout(set = 0, binding = 2) uniform Constants { //get into a lower set
vec4 rotation_matrices[20];
}
constants;
#ifdef ADAPTIVE
layout(rg8, set = 1, binding = 0) uniform restrict readonly image2DArray source_ssao;
layout(set = 1, binding = 1) uniform sampler2D source_importance;
layout(set = 1, binding = 2, std430) buffer Counter {
uint sum;
}
counter;
#endif
layout(rg8, set = 2, binding = 0) uniform restrict writeonly image2D dest_image;
// This push_constant is full - 128 bytes - if you need to add more data, consider adding to the uniform buffer instead
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
int pass;
int quality;
vec2 half_screen_pixel_size;
int size_multiplier;
float detail_intensity;
vec2 NDC_to_view_mul;
vec2 NDC_to_view_add;
vec2 pad2;
vec2 half_screen_pixel_size_x025;
float radius;
float intensity;
float shadow_power;
float shadow_clamp;
float fade_out_mul;
float fade_out_add;
float horizon_angle_threshold;
float inv_radius_near_limit;
bool is_orthogonal;
float neg_inv_radius;
float load_counter_avg_div;
float adaptive_sample_limit;
ivec2 pass_coord_offset;
vec2 pass_uv_offset;
}
params;
// packing/unpacking for edges; 2 bits per edge mean 4 gradient values (0, 0.33, 0.66, 1) for smoother transitions!
float pack_edges(vec4 p_edgesLRTB) {
p_edgesLRTB = round(clamp(p_edgesLRTB, 0.0, 1.0) * 3.05);
return dot(p_edgesLRTB, vec4(64.0 / 255.0, 16.0 / 255.0, 4.0 / 255.0, 1.0 / 255.0));
}
vec3 NDC_to_view_space(vec2 p_pos, float p_viewspace_depth) {
if (params.is_orthogonal) {
return vec3((params.NDC_to_view_mul * p_pos.xy + params.NDC_to_view_add), p_viewspace_depth);
} else {
return vec3((params.NDC_to_view_mul * p_pos.xy + params.NDC_to_view_add) * p_viewspace_depth, p_viewspace_depth);
}
}
// calculate effect radius and fit our screen sampling pattern inside it
void calculate_radius_parameters(const float p_pix_center_length, const vec2 p_pixel_size_at_center, out float r_lookup_radius, out float r_radius, out float r_fallof_sq) {
r_radius = params.radius;
// when too close, on-screen sampling disk will grow beyond screen size; limit this to avoid closeup temporal artifacts
const float too_close_limit = clamp(p_pix_center_length * params.inv_radius_near_limit, 0.0, 1.0) * 0.8 + 0.2;
r_radius *= too_close_limit;
// 0.85 is to reduce the radius to allow for more samples on a slope to still stay within influence
r_lookup_radius = (0.85 * r_radius) / p_pixel_size_at_center.x;
// used to calculate falloff (both for AO samples and per-sample weights)
r_fallof_sq = -1.0 / (r_radius * r_radius);
}
vec4 calculate_edges(const float p_center_z, const float p_left_z, const float p_right_z, const float p_top_z, const float p_bottom_z) {
// slope-sensitive depth-based edge detection
vec4 edgesLRTB = vec4(p_left_z, p_right_z, p_top_z, p_bottom_z) - p_center_z;
vec4 edgesLRTB_slope_adjusted = edgesLRTB + edgesLRTB.yxwz;
edgesLRTB = min(abs(edgesLRTB), abs(edgesLRTB_slope_adjusted));
return clamp((1.3 - edgesLRTB / (p_center_z * 0.040)), 0.0, 1.0);
}
vec3 decode_normal(vec3 p_encoded_normal) {
vec3 normal = p_encoded_normal * 2.0 - 1.0;
return normal;
}
vec3 load_normal(ivec2 p_pos) {
vec3 encoded_normal = imageLoad(source_normal, p_pos).xyz;
encoded_normal.z = 1.0 - encoded_normal.z;
return decode_normal(encoded_normal);
}
vec3 load_normal(ivec2 p_pos, ivec2 p_offset) {
vec3 encoded_normal = imageLoad(source_normal, p_pos + p_offset).xyz;
encoded_normal.z = 1.0 - encoded_normal.z;
return decode_normal(encoded_normal);
}
// all vectors in viewspace
float calculate_pixel_obscurance(vec3 p_pixel_normal, vec3 p_hit_delta, float p_fallof_sq) {
float length_sq = dot(p_hit_delta, p_hit_delta);
float NdotD = dot(p_pixel_normal, p_hit_delta) / sqrt(length_sq);
float falloff_mult = max(0.0, length_sq * p_fallof_sq + 1.0);
return max(0, NdotD - params.horizon_angle_threshold) * falloff_mult;
}
void SSAO_tap_inner(const int p_quality_level, inout float r_obscurance_sum, inout float r_weight_sum, const vec2 p_sampling_uv, const float p_mip_level, const vec3 p_pix_center_pos, vec3 p_pixel_normal, const float p_fallof_sq, const float p_weight_mod) {
// get depth at sample
float viewspace_sample_z = textureLod(source_depth_mipmaps, vec3(p_sampling_uv, params.pass), p_mip_level).x;
// convert to viewspace
vec3 hit_pos = NDC_to_view_space(p_sampling_uv.xy, viewspace_sample_z).xyz;
vec3 hit_delta = hit_pos - p_pix_center_pos;
float obscurance = calculate_pixel_obscurance(p_pixel_normal, hit_delta, p_fallof_sq);
float weight = 1.0;
if (p_quality_level >= SSAO_HALOING_REDUCTION_ENABLE_AT_QUALITY_PRESET) {
float reduct = max(0, -hit_delta.z);
reduct = clamp(reduct * params.neg_inv_radius + 2.0, 0.0, 1.0);
weight = SSAO_HALOING_REDUCTION_AMOUNT * reduct + (1.0 - SSAO_HALOING_REDUCTION_AMOUNT);
}
weight *= p_weight_mod;
r_obscurance_sum += obscurance * weight;
r_weight_sum += weight;
}
void SSAOTap(const int p_quality_level, inout float r_obscurance_sum, inout float r_weight_sum, const int p_tap_index, const mat2 p_rot_scale, const vec3 p_pix_center_pos, vec3 p_pixel_normal, const vec2 p_normalized_screen_pos, const float p_mip_offset, const float p_fallof_sq, float p_weight_mod, vec2 p_norm_xy, float p_norm_xy_length) {
vec2 sample_offset;
float sample_pow_2_len;
// patterns
{
vec4 new_sample = sample_pattern[p_tap_index];
sample_offset = new_sample.xy * p_rot_scale;
sample_pow_2_len = new_sample.w; // precalculated, same as: sample_pow_2_len = log2( length( new_sample.xy ) );
p_weight_mod *= new_sample.z;
}
// snap to pixel center (more correct obscurance math, avoids artifacts)
sample_offset = round(sample_offset);
// calculate MIP based on the sample distance from the centre, similar to as described
// in http://graphics.cs.williams.edu/papers/SAOHPG12/.
float mip_level = (p_quality_level < SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET) ? (0) : (sample_pow_2_len + p_mip_offset);
vec2 sampling_uv = sample_offset * params.half_screen_pixel_size + p_normalized_screen_pos;
SSAO_tap_inner(p_quality_level, r_obscurance_sum, r_weight_sum, sampling_uv, mip_level, p_pix_center_pos, p_pixel_normal, p_fallof_sq, p_weight_mod);
// for the second tap, just use the mirrored offset
vec2 sample_offset_mirrored_uv = -sample_offset;
// tilt the second set of samples so that the disk is effectively rotated by the normal
// effective at removing one set of artifacts, but too expensive for lower quality settings
if (p_quality_level >= SSAO_TILT_SAMPLES_ENABLE_AT_QUALITY_PRESET) {
float dot_norm = dot(sample_offset_mirrored_uv, p_norm_xy);
sample_offset_mirrored_uv -= dot_norm * p_norm_xy_length * p_norm_xy;
sample_offset_mirrored_uv = round(sample_offset_mirrored_uv);
}
// snap to pixel center (more correct obscurance math, avoids artifacts)
vec2 sampling_mirrored_uv = sample_offset_mirrored_uv * params.half_screen_pixel_size + p_normalized_screen_pos;
SSAO_tap_inner(p_quality_level, r_obscurance_sum, r_weight_sum, sampling_mirrored_uv, mip_level, p_pix_center_pos, p_pixel_normal, p_fallof_sq, p_weight_mod);
}
void generate_SSAO_shadows_internal(out float r_shadow_term, out vec4 r_edges, out float r_weight, const vec2 p_pos, int p_quality_level, bool p_adaptive_base) {
vec2 pos_rounded = trunc(p_pos);
uvec2 upos = uvec2(pos_rounded);
const int number_of_taps = (p_adaptive_base) ? (SSAO_ADAPTIVE_TAP_BASE_COUNT) : (num_taps[p_quality_level]);
float pix_z, pix_left_z, pix_top_z, pix_right_z, pix_bottom_z;
vec4 valuesUL = textureGather(source_depth_mipmaps, vec3(pos_rounded * params.half_screen_pixel_size, params.pass));
vec4 valuesBR = textureGather(source_depth_mipmaps, vec3((pos_rounded + vec2(1.0)) * params.half_screen_pixel_size, params.pass));
// get this pixel's viewspace depth
pix_z = valuesUL.y;
// get left right top bottom neighbouring pixels for edge detection (gets compiled out on quality_level == 0)
pix_left_z = valuesUL.x;
pix_top_z = valuesUL.z;
pix_right_z = valuesBR.z;
pix_bottom_z = valuesBR.x;
vec2 normalized_screen_pos = pos_rounded * params.half_screen_pixel_size + params.half_screen_pixel_size_x025;
vec3 pix_center_pos = NDC_to_view_space(normalized_screen_pos, pix_z);
// Load this pixel's viewspace normal
uvec2 full_res_coord = upos * 2 * params.size_multiplier + params.pass_coord_offset.xy;
vec3 pixel_normal = load_normal(ivec2(full_res_coord));
const vec2 pixel_size_at_center = NDC_to_view_space(normalized_screen_pos.xy + params.half_screen_pixel_size, pix_center_pos.z).xy - pix_center_pos.xy;
float pixel_lookup_radius;
float fallof_sq;
// calculate effect radius and fit our screen sampling pattern inside it
float viewspace_radius;
calculate_radius_parameters(length(pix_center_pos), pixel_size_at_center, pixel_lookup_radius, viewspace_radius, fallof_sq);
// calculate samples rotation/scaling
mat2 rot_scale_matrix;
uint pseudo_random_index;
{
vec4 rotation_scale;
// reduce effect radius near the screen edges slightly; ideally, one would render a larger depth buffer (5% on each side) instead
if (!p_adaptive_base && (p_quality_level >= SSAO_REDUCE_RADIUS_NEAR_SCREEN_BORDER_ENABLE_AT_QUALITY_PRESET)) {
float near_screen_border = min(min(normalized_screen_pos.x, 1.0 - normalized_screen_pos.x), min(normalized_screen_pos.y, 1.0 - normalized_screen_pos.y));
near_screen_border = clamp(10.0 * near_screen_border + 0.6, 0.0, 1.0);
pixel_lookup_radius *= near_screen_border;
}
// load & update pseudo-random rotation matrix
pseudo_random_index = uint(pos_rounded.y * 2 + pos_rounded.x) % 5;
rotation_scale = constants.rotation_matrices[params.pass * 5 + pseudo_random_index];
rot_scale_matrix = mat2(rotation_scale.x * pixel_lookup_radius, rotation_scale.y * pixel_lookup_radius, rotation_scale.z * pixel_lookup_radius, rotation_scale.w * pixel_lookup_radius);
}
// the main obscurance & sample weight storage
float obscurance_sum = 0.0;
float weight_sum = 0.0;
// edge mask for between this and left/right/top/bottom neighbour pixels - not used in quality level 0 so initialize to "no edge" (1 is no edge, 0 is edge)
vec4 edgesLRTB = vec4(1.0, 1.0, 1.0, 1.0);
// Move center pixel slightly towards camera to avoid imprecision artifacts due to using of 16bit depth buffer; a lot smaller offsets needed when using 32bit floats
pix_center_pos *= 0.9992;
if (!p_adaptive_base && (p_quality_level >= SSAO_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET)) {
edgesLRTB = calculate_edges(pix_z, pix_left_z, pix_right_z, pix_top_z, pix_bottom_z);
}
// adds a more high definition sharp effect, which gets blurred out (reuses left/right/top/bottom samples that we used for edge detection)
if (!p_adaptive_base && (p_quality_level >= SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET)) {
// disable in case of quality level 4 (reference)
if (p_quality_level != 4) {
//approximate neighbouring pixels positions (actually just deltas or "positions - pix_center_pos" )
vec3 normalized_viewspace_dir = vec3(pix_center_pos.xy / pix_center_pos.zz, 1.0);
vec3 pixel_left_delta = vec3(-pixel_size_at_center.x, 0.0, 0.0) + normalized_viewspace_dir * (pix_left_z - pix_center_pos.z);
vec3 pixel_right_delta = vec3(+pixel_size_at_center.x, 0.0, 0.0) + normalized_viewspace_dir * (pix_right_z - pix_center_pos.z);
vec3 pixel_top_delta = vec3(0.0, -pixel_size_at_center.y, 0.0) + normalized_viewspace_dir * (pix_top_z - pix_center_pos.z);
vec3 pixel_bottom_delta = vec3(0.0, +pixel_size_at_center.y, 0.0) + normalized_viewspace_dir * (pix_bottom_z - pix_center_pos.z);
const float range_reduction = 4.0f; // this is to avoid various artifacts
const float modified_fallof_sq = range_reduction * fallof_sq;
vec4 additional_obscurance;
additional_obscurance.x = calculate_pixel_obscurance(pixel_normal, pixel_left_delta, modified_fallof_sq);
additional_obscurance.y = calculate_pixel_obscurance(pixel_normal, pixel_right_delta, modified_fallof_sq);
additional_obscurance.z = calculate_pixel_obscurance(pixel_normal, pixel_top_delta, modified_fallof_sq);
additional_obscurance.w = calculate_pixel_obscurance(pixel_normal, pixel_bottom_delta, modified_fallof_sq);
obscurance_sum += params.detail_intensity * dot(additional_obscurance, edgesLRTB);
}
}
// Sharp normals also create edges - but this adds to the cost as well
if (!p_adaptive_base && (p_quality_level >= SSAO_NORMAL_BASED_EDGES_ENABLE_AT_QUALITY_PRESET)) {
vec3 neighbour_normal_left = load_normal(ivec2(full_res_coord), ivec2(-2, 0));
vec3 neighbour_normal_right = load_normal(ivec2(full_res_coord), ivec2(2, 0));
vec3 neighbour_normal_top = load_normal(ivec2(full_res_coord), ivec2(0, -2));
vec3 neighbour_normal_bottom = load_normal(ivec2(full_res_coord), ivec2(0, 2));
const float dot_threshold = SSAO_NORMAL_BASED_EDGES_DOT_THRESHOLD;
vec4 normal_edgesLRTB;
normal_edgesLRTB.x = clamp((dot(pixel_normal, neighbour_normal_left) + dot_threshold), 0.0, 1.0);
normal_edgesLRTB.y = clamp((dot(pixel_normal, neighbour_normal_right) + dot_threshold), 0.0, 1.0);
normal_edgesLRTB.z = clamp((dot(pixel_normal, neighbour_normal_top) + dot_threshold), 0.0, 1.0);
normal_edgesLRTB.w = clamp((dot(pixel_normal, neighbour_normal_bottom) + dot_threshold), 0.0, 1.0);
edgesLRTB *= normal_edgesLRTB;
}
const float global_mip_offset = SSAO_DEPTH_MIPS_GLOBAL_OFFSET;
float mip_offset = (p_quality_level < SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET) ? (0) : (log2(pixel_lookup_radius) + global_mip_offset);
// Used to tilt the second set of samples so that the disk is effectively rotated by the normal
// effective at removing one set of artifacts, but too expensive for lower quality settings
vec2 norm_xy = vec2(pixel_normal.x, pixel_normal.y);
float norm_xy_length = length(norm_xy);
norm_xy /= vec2(norm_xy_length, -norm_xy_length);
norm_xy_length *= SSAO_TILT_SAMPLES_AMOUNT;
// standard, non-adaptive approach
if ((p_quality_level != 3) || p_adaptive_base) {
for (int i = 0; i < number_of_taps; i++) {
SSAOTap(p_quality_level, obscurance_sum, weight_sum, i, rot_scale_matrix, pix_center_pos, pixel_normal, normalized_screen_pos, mip_offset, fallof_sq, 1.0, norm_xy, norm_xy_length);
}
}
#ifdef ADAPTIVE
else {
// add new ones if needed
vec2 full_res_uv = normalized_screen_pos + params.pass_uv_offset.xy;
float importance = textureLod(source_importance, full_res_uv, 0.0).x;
// this is to normalize SSAO_DETAIL_AO_AMOUNT across all pixel regardless of importance
obscurance_sum *= (SSAO_ADAPTIVE_TAP_BASE_COUNT / float(SSAO_MAX_TAPS)) + (importance * SSAO_ADAPTIVE_TAP_FLEXIBLE_COUNT / float(SSAO_MAX_TAPS));
// load existing base values
vec2 base_values = imageLoad(source_ssao, ivec3(upos, params.pass)).xy;
weight_sum += base_values.y * float(SSAO_ADAPTIVE_TAP_BASE_COUNT * 4.0);
obscurance_sum += (base_values.x) * weight_sum;
// increase importance around edges
float edge_count = dot(1.0 - edgesLRTB, vec4(1.0, 1.0, 1.0, 1.0));
float avg_total_importance = float(counter.sum) * params.load_counter_avg_div;
float importance_limiter = clamp(params.adaptive_sample_limit / avg_total_importance, 0.0, 1.0);
importance *= importance_limiter;
float additional_sample_count = SSAO_ADAPTIVE_TAP_FLEXIBLE_COUNT * importance;
const float blend_range = 3.0;
const float blend_range_inv = 1.0 / blend_range;
additional_sample_count += 0.5;
uint additional_samples = uint(additional_sample_count);
uint additional_samples_to = min(SSAO_MAX_TAPS, additional_samples + SSAO_ADAPTIVE_TAP_BASE_COUNT);
for (uint i = SSAO_ADAPTIVE_TAP_BASE_COUNT; i < additional_samples_to; i++) {
additional_sample_count -= 1.0f;
float weight_mod = clamp(additional_sample_count * blend_range_inv, 0.0, 1.0);
SSAOTap(p_quality_level, obscurance_sum, weight_sum, int(i), rot_scale_matrix, pix_center_pos, pixel_normal, normalized_screen_pos, mip_offset, fallof_sq, weight_mod, norm_xy, norm_xy_length);
}
}
#endif
// early out for adaptive base - just output weight (used for the next pass)
if (p_adaptive_base) {
float obscurance = obscurance_sum / weight_sum;
r_shadow_term = obscurance;
r_edges = vec4(0.0);
r_weight = weight_sum;
return;
}
// calculate weighted average
float obscurance = obscurance_sum / weight_sum;
// calculate fadeout (1 close, gradient, 0 far)
float fade_out = clamp(pix_center_pos.z * params.fade_out_mul + params.fade_out_add, 0.0, 1.0);
// Reduce the SSAO shadowing if we're on the edge to remove artifacts on edges (we don't care for the lower quality one)
if (!p_adaptive_base && (p_quality_level >= SSAO_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET)) {
// when there's more than 2 opposite edges, start fading out the occlusion to reduce aliasing artifacts
float edge_fadeout_factor = clamp((1.0 - edgesLRTB.x - edgesLRTB.y) * 0.35, 0.0, 1.0) + clamp((1.0 - edgesLRTB.z - edgesLRTB.w) * 0.35, 0.0, 1.0);
fade_out *= clamp(1.0 - edge_fadeout_factor, 0.0, 1.0);
}
// strength
obscurance = params.intensity * obscurance;
// clamp
obscurance = min(obscurance, params.shadow_clamp);
// fadeout
obscurance *= fade_out;
// conceptually switch to occlusion with the meaning being visibility (grows with visibility, occlusion == 1 implies full visibility),
// to be in line with what is more commonly used.
float occlusion = 1.0 - obscurance;
// modify the gradient
// note: this cannot be moved to a later pass because of loss of precision after storing in the render target
occlusion = pow(clamp(occlusion, 0.0, 1.0), params.shadow_power);
// outputs!
r_shadow_term = occlusion; // Our final 'occlusion' term (0 means fully occluded, 1 means fully lit)
r_edges = edgesLRTB; // These are used to prevent blurring across edges, 1 means no edge, 0 means edge, 0.5 means half way there, etc.
r_weight = weight_sum;
}
void main() {
float out_shadow_term;
float out_weight;
vec4 out_edges;
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing
return;
}
vec2 uv = vec2(gl_GlobalInvocationID) + vec2(0.5);
#ifdef SSAO_BASE
generate_SSAO_shadows_internal(out_shadow_term, out_edges, out_weight, uv, params.quality, true);
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), vec4(out_shadow_term, out_weight / (float(SSAO_ADAPTIVE_TAP_BASE_COUNT) * 4.0), 0.0, 0.0));
#else
generate_SSAO_shadows_internal(out_shadow_term, out_edges, out_weight, uv, params.quality, false); // pass in quality levels
if (params.quality == 0) {
out_edges = vec4(1.0);
}
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), vec4(out_shadow_term, pack_edges(out_edges), 0.0, 0.0));
#endif
}

View File

@@ -0,0 +1,154 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[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_ssao;
layout(rg8, set = 1, binding = 0) uniform restrict writeonly image2D dest_image;
layout(push_constant, std430) uniform Params {
float edge_sharpness;
float pad;
vec2 half_screen_pixel_size;
}
params;
vec4 unpack_edges(float p_packed_val) {
uint packed_val = uint(p_packed_val * 255.5);
vec4 edgesLRTB;
edgesLRTB.x = float((packed_val >> 6) & 0x03) / 3.0;
edgesLRTB.y = float((packed_val >> 4) & 0x03) / 3.0;
edgesLRTB.z = float((packed_val >> 2) & 0x03) / 3.0;
edgesLRTB.w = float((packed_val >> 0) & 0x03) / 3.0;
return clamp(edgesLRTB + params.edge_sharpness, 0.0, 1.0);
}
void add_sample(float p_ssao_value, float p_edge_value, inout float r_sum, inout float r_sum_weight) {
float weight = p_edge_value;
r_sum += (weight * p_ssao_value);
r_sum_weight += weight;
}
#ifdef MODE_WIDE
vec2 sample_blurred_wide(vec2 p_coord) {
vec2 vC = textureLodOffset(source_ssao, vec2(p_coord), 0.0, ivec2(0, 0)).xy;
vec2 vL = textureLodOffset(source_ssao, vec2(p_coord), 0.0, ivec2(-2, 0)).xy;
vec2 vT = textureLodOffset(source_ssao, vec2(p_coord), 0.0, ivec2(0, -2)).xy;
vec2 vR = textureLodOffset(source_ssao, vec2(p_coord), 0.0, ivec2(2, 0)).xy;
vec2 vB = textureLodOffset(source_ssao, vec2(p_coord), 0.0, ivec2(0, 2)).xy;
float packed_edges = vC.y;
vec4 edgesLRTB = unpack_edges(packed_edges);
edgesLRTB.x *= unpack_edges(vL.y).y;
edgesLRTB.z *= unpack_edges(vT.y).w;
edgesLRTB.y *= unpack_edges(vR.y).x;
edgesLRTB.w *= unpack_edges(vB.y).z;
float ssao_value = vC.x;
float ssao_valueL = vL.x;
float ssao_valueT = vT.x;
float ssao_valueR = vR.x;
float ssao_valueB = vB.x;
float sum_weight = 0.8f;
float sum = ssao_value * sum_weight;
add_sample(ssao_valueL, edgesLRTB.x, sum, sum_weight);
add_sample(ssao_valueR, edgesLRTB.y, sum, sum_weight);
add_sample(ssao_valueT, edgesLRTB.z, sum, sum_weight);
add_sample(ssao_valueB, edgesLRTB.w, sum, sum_weight);
float ssao_avg = sum / sum_weight;
ssao_value = ssao_avg;
return vec2(ssao_value, packed_edges);
}
#endif
#ifdef MODE_SMART
vec2 sample_blurred(vec3 p_pos, vec2 p_coord) {
float packed_edges = texelFetch(source_ssao, ivec2(p_pos.xy), 0).y;
vec4 edgesLRTB = unpack_edges(packed_edges);
vec4 valuesUL = textureGather(source_ssao, vec2(p_coord - params.half_screen_pixel_size * 0.5));
vec4 valuesBR = textureGather(source_ssao, vec2(p_coord + params.half_screen_pixel_size * 0.5));
float ssao_value = valuesUL.y;
float ssao_valueL = valuesUL.x;
float ssao_valueT = valuesUL.z;
float ssao_valueR = valuesBR.z;
float ssao_valueB = valuesBR.x;
float sum_weight = 0.5;
float sum = ssao_value * sum_weight;
add_sample(ssao_valueL, edgesLRTB.x, sum, sum_weight);
add_sample(ssao_valueR, edgesLRTB.y, sum, sum_weight);
add_sample(ssao_valueT, edgesLRTB.z, sum, sum_weight);
add_sample(ssao_valueB, edgesLRTB.w, sum, sum_weight);
float ssao_avg = sum / sum_weight;
ssao_value = ssao_avg;
return vec2(ssao_value, packed_edges);
}
#endif
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
#ifdef MODE_NON_SMART
vec2 half_pixel = params.half_screen_pixel_size * 0.5;
vec2 uv = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size;
vec2 center = textureLod(source_ssao, vec2(uv), 0.0).xy;
vec4 vals;
vals.x = textureLod(source_ssao, vec2(uv + vec2(-half_pixel.x * 3, -half_pixel.y)), 0.0).x;
vals.y = textureLod(source_ssao, vec2(uv + vec2(+half_pixel.x, -half_pixel.y * 3)), 0.0).x;
vals.z = textureLod(source_ssao, vec2(uv + vec2(-half_pixel.x, +half_pixel.y * 3)), 0.0).x;
vals.w = textureLod(source_ssao, vec2(uv + vec2(+half_pixel.x * 3, +half_pixel.y)), 0.0).x;
vec2 sampled = vec2(dot(vals, vec4(0.2)) + center.x * 0.2, center.y);
#else
#ifdef MODE_SMART
vec2 sampled = sample_blurred(vec3(gl_GlobalInvocationID), (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size);
#else // MODE_WIDE
vec2 sampled = sample_blurred_wide((vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size);
#endif
#endif
imageStore(dest_image, ivec2(ssC), vec4(sampled, 0.0, 0.0));
}

View File

@@ -0,0 +1,123 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#ifdef GENERATE_MAP
layout(set = 0, binding = 0) uniform sampler2DArray source_texture;
#else
layout(set = 0, binding = 0) uniform sampler2D source_importance;
#endif
layout(r8, set = 1, binding = 0) uniform restrict writeonly image2D dest_image;
#ifdef PROCESS_MAPB
layout(set = 2, binding = 0, std430) buffer Counter {
uint sum;
}
counter;
#endif
layout(push_constant, std430) uniform Params {
vec2 half_screen_pixel_size;
float intensity;
float power;
}
params;
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
#ifdef GENERATE_MAP
// importance map stuff
uvec2 base_position = ssC * 2;
vec2 base_uv = (vec2(base_position) + vec2(0.5f, 0.5f)) * params.half_screen_pixel_size;
float minV = 1.0;
float maxV = 0.0;
for (int i = 0; i < 4; i++) {
vec4 vals = textureGather(source_texture, vec3(base_uv, i));
// apply the same modifications that would have been applied in the main shader
vals = params.intensity * vals;
vals = 1 - vals;
vals = pow(clamp(vals, 0.0, 1.0), vec4(params.power));
maxV = max(maxV, max(max(vals.x, vals.y), max(vals.z, vals.w)));
minV = min(minV, min(min(vals.x, vals.y), min(vals.z, vals.w)));
}
float min_max_diff = maxV - minV;
imageStore(dest_image, ssC, vec4(pow(clamp(min_max_diff * 2.0, 0.0, 1.0), 0.8)));
#endif
#ifdef PROCESS_MAPA
vec2 uv = (vec2(ssC) + 0.5f) * params.half_screen_pixel_size * 2.0;
float centre = textureLod(source_importance, uv, 0.0).x;
vec2 half_pixel = params.half_screen_pixel_size;
vec4 vals;
vals.x = textureLod(source_importance, uv + vec2(-half_pixel.x * 3, -half_pixel.y), 0.0).x;
vals.y = textureLod(source_importance, uv + vec2(+half_pixel.x, -half_pixel.y * 3), 0.0).x;
vals.z = textureLod(source_importance, uv + vec2(+half_pixel.x * 3, +half_pixel.y), 0.0).x;
vals.w = textureLod(source_importance, uv + vec2(-half_pixel.x, +half_pixel.y * 3), 0.0).x;
float avg = dot(vals, vec4(0.25, 0.25, 0.25, 0.25));
imageStore(dest_image, ssC, vec4(avg));
#endif
#ifdef PROCESS_MAPB
vec2 uv = (vec2(ssC) + 0.5f) * params.half_screen_pixel_size * 2.0;
float centre = textureLod(source_importance, uv, 0.0).x;
vec2 half_pixel = params.half_screen_pixel_size;
vec4 vals;
vals.x = textureLod(source_importance, uv + vec2(-half_pixel.x, -half_pixel.y * 3), 0.0).x;
vals.y = textureLod(source_importance, uv + vec2(+half_pixel.x * 3, -half_pixel.y), 0.0).x;
vals.z = textureLod(source_importance, uv + vec2(+half_pixel.x, +half_pixel.y * 3), 0.0).x;
vals.w = textureLod(source_importance, uv + vec2(-half_pixel.x * 3, +half_pixel.y), 0.0).x;
float avg = dot(vals, vec4(0.25, 0.25, 0.25, 0.25));
imageStore(dest_image, ssC, vec4(avg));
// sum the average; to avoid overflowing we assume max AO resolution is not bigger than 16384x16384; so quarter res (used here) will be 4096x4096, which leaves us with 8 bits per pixel
uint sum = uint(clamp(avg, 0.0, 1.0) * 255.0 + 0.5);
// save every 9th to avoid InterlockedAdd congestion - since we're blurring, this is good enough; compensated by multiplying load_counter_avg_div by 9
if (((ssC.x % 3) + (ssC.y % 3)) == 0) {
atomicAdd(counter.sum, sum);
}
#endif
}

View File

@@ -0,0 +1,119 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(rgba8, set = 0, binding = 0) uniform restrict writeonly image2D dest_image;
layout(set = 1, binding = 0) uniform sampler2DArray source_texture;
layout(push_constant, std430) uniform Params {
float inv_sharpness;
uint size_modifier;
vec2 pixel_size;
}
params;
vec4 unpack_edges(float p_packed_val) {
uint packed_val = uint(p_packed_val * 255.5);
vec4 edgesLRTB;
edgesLRTB.x = float((packed_val >> 6) & 0x03) / 3.0;
edgesLRTB.y = float((packed_val >> 4) & 0x03) / 3.0;
edgesLRTB.z = float((packed_val >> 2) & 0x03) / 3.0;
edgesLRTB.w = float((packed_val >> 0) & 0x03) / 3.0;
return clamp(edgesLRTB + params.inv_sharpness, 0.0, 1.0);
}
void main() {
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC, ivec2(1.0 / params.pixel_size)))) { //too large, do nothing
return;
}
#ifdef MODE_SMART
float ao;
uvec2 pix_pos = uvec2(gl_GlobalInvocationID.xy);
vec2 uv = (gl_GlobalInvocationID.xy + vec2(0.5)) * params.pixel_size;
// calculate index in the four deinterleaved source array texture
int mx = int(pix_pos.x % 2);
int my = int(pix_pos.y % 2);
int index_center = mx + my * 2; // center index
int index_horizontal = (1 - mx) + my * 2; // neighbouring, horizontal
int index_vertical = mx + (1 - my) * 2; // neighbouring, vertical
int index_diagonal = (1 - mx) + (1 - my) * 2; // diagonal
vec2 center_val = texelFetch(source_texture, ivec3(pix_pos / uvec2(params.size_modifier), index_center), 0).xy;
ao = center_val.x;
vec4 edgesLRTB = unpack_edges(center_val.y);
// convert index shifts to sampling offsets
float fmx = float(mx);
float fmy = float(my);
// in case of an edge, push sampling offsets away from the edge (towards pixel center)
float fmxe = (edgesLRTB.y - edgesLRTB.x);
float fmye = (edgesLRTB.w - edgesLRTB.z);
// calculate final sampling offsets and sample using bilinear filter
vec2 uv_horizontal = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(fmx + fmxe - 0.5, 0.5 - fmy)) * params.pixel_size;
float ao_horizontal = textureLod(source_texture, vec3(uv_horizontal, index_horizontal), 0.0).x;
vec2 uv_vertical = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(0.5 - fmx, fmy - 0.5 + fmye)) * params.pixel_size;
float ao_vertical = textureLod(source_texture, vec3(uv_vertical, index_vertical), 0.0).x;
vec2 uv_diagonal = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(fmx - 0.5 + fmxe, fmy - 0.5 + fmye)) * params.pixel_size;
float ao_diagonal = textureLod(source_texture, vec3(uv_diagonal, index_diagonal), 0.0).x;
// reduce weight for samples near edge - if the edge is on both sides, weight goes to 0
vec4 blendWeights;
blendWeights.x = 1.0;
blendWeights.y = (edgesLRTB.x + edgesLRTB.y) * 0.5;
blendWeights.z = (edgesLRTB.z + edgesLRTB.w) * 0.5;
blendWeights.w = (blendWeights.y + blendWeights.z) * 0.5;
// calculate weighted average
float blendWeightsSum = dot(blendWeights, vec4(1.0, 1.0, 1.0, 1.0));
ao = dot(vec4(ao, ao_horizontal, ao_vertical, ao_diagonal), blendWeights) / blendWeightsSum;
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), vec4(ao));
#else // !MODE_SMART
vec2 uv = (gl_GlobalInvocationID.xy + vec2(0.5)) * params.pixel_size;
#ifdef MODE_HALF
float a = textureLod(source_texture, vec3(uv, 0), 0.0).x;
float d = textureLod(source_texture, vec3(uv, 3), 0.0).x;
float avg = (a + d) * 0.5;
#else
float a = textureLod(source_texture, vec3(uv, 0), 0.0).x;
float b = textureLod(source_texture, vec3(uv, 1), 0.0).x;
float c = textureLod(source_texture, vec3(uv, 2), 0.0).x;
float d = textureLod(source_texture, vec3(uv, 3), 0.0).x;
float avg = (a + b + c + d) * 0.25;
#endif
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), vec4(avg));
#endif
}

View File

@@ -0,0 +1,444 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
// 2021-05-27: clayjohn: convert SSAO to SSIL
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
#define SSIL_MAIN_DISK_SAMPLE_COUNT (32)
const vec4 sample_pattern[SSIL_MAIN_DISK_SAMPLE_COUNT] = {
vec4(0.78488064, 0.56661671, 1.500000, -0.126083), vec4(0.26022232, -0.29575172, 1.500000, -1.064030), vec4(0.10459357, 0.08372527, 1.110000, -2.730563), vec4(-0.68286800, 0.04963045, 1.090000, -0.498827),
vec4(-0.13570161, -0.64190155, 1.250000, -0.532765), vec4(-0.26193795, -0.08205118, 0.670000, -1.783245), vec4(-0.61177456, 0.66664219, 0.710000, -0.044234), vec4(0.43675563, 0.25119025, 0.610000, -1.167283),
vec4(0.07884444, 0.86618668, 0.640000, -0.459002), vec4(-0.12790935, -0.29869005, 0.600000, -1.729424), vec4(-0.04031125, 0.02413622, 0.600000, -4.792042), vec4(0.16201244, -0.52851415, 0.790000, -1.067055),
vec4(-0.70991218, 0.47301072, 0.640000, -0.335236), vec4(0.03277707, -0.22349690, 0.600000, -1.982384), vec4(0.68921727, 0.36800742, 0.630000, -0.266718), vec4(0.29251814, 0.37775412, 0.610000, -1.422520),
vec4(-0.12224089, 0.96582592, 0.600000, -0.426142), vec4(0.11071457, -0.16131058, 0.600000, -2.165947), vec4(0.46562141, -0.59747696, 0.600000, -0.189760), vec4(-0.51548797, 0.11804193, 0.600000, -1.246800),
vec4(0.89141309, -0.42090443, 0.600000, 0.028192), vec4(-0.32402530, -0.01591529, 0.600000, -1.543018), vec4(0.60771245, 0.41635221, 0.600000, -0.605411), vec4(0.02379565, -0.08239821, 0.600000, -3.809046),
vec4(0.48951152, -0.23657045, 0.600000, -1.189011), vec4(-0.17611565, -0.81696892, 0.600000, -0.513724), vec4(-0.33930185, -0.20732205, 0.600000, -1.698047), vec4(-0.91974425, 0.05403209, 0.600000, 0.062246),
vec4(-0.15064627, -0.14949332, 0.600000, -1.896062), vec4(0.53180975, -0.35210401, 0.600000, -0.758838), vec4(0.41487166, 0.81442589, 0.600000, -0.505648), vec4(-0.24106961, -0.32721516, 0.600000, -1.665244)
};
// these values can be changed (up to SSIL_MAX_TAPS) with no changes required elsewhere; values for 4th and 5th preset are ignored but array needed to avoid compilation errors
// the actual number of texture samples is two times this value (each "tap" has two symmetrical depth texture samples)
const int num_taps[5] = { 3, 5, 12, 0, 0 };
#define SSIL_TILT_SAMPLES_ENABLE_AT_QUALITY_PRESET (99) // to disable simply set to 99 or similar
#define SSIL_TILT_SAMPLES_AMOUNT (0.4)
//
#define SSIL_HALOING_REDUCTION_ENABLE_AT_QUALITY_PRESET (1) // to disable simply set to 99 or similar
#define SSIL_HALOING_REDUCTION_AMOUNT (0.8) // values from 0.0 - 1.0, 1.0 means max weighting (will cause artifacts, 0.8 is more reasonable)
//
#define SSIL_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET (2)
#define SSIL_DEPTH_MIPS_GLOBAL_OFFSET (-4.3) // best noise/quality/performance tradeoff, found empirically
//
// !!warning!! the edge handling is hard-coded to 'disabled' on quality level 0, and enabled above, on the C++ side; while toggling it here will work for
// testing purposes, it will not yield performance gains (or correct results)
#define SSIL_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET (1)
//
#define SSIL_REDUCE_RADIUS_NEAR_SCREEN_BORDER_ENABLE_AT_QUALITY_PRESET (1)
#define SSIL_MAX_TAPS 32
#define SSIL_ADAPTIVE_TAP_BASE_COUNT 5
#define SSIL_ADAPTIVE_TAP_FLEXIBLE_COUNT (SSIL_MAX_TAPS - SSIL_ADAPTIVE_TAP_BASE_COUNT)
#define SSIL_DEPTH_MIP_LEVELS 4
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2DArray source_depth_mipmaps;
layout(rgba8, set = 0, binding = 1) uniform restrict readonly image2D source_normal;
layout(set = 0, binding = 2) uniform Constants { //get into a lower set
vec4 rotation_matrices[20];
}
constants;
#ifdef ADAPTIVE
layout(rgba16, set = 1, binding = 0) uniform restrict readonly image2DArray source_ssil;
layout(set = 1, binding = 1) uniform sampler2D source_importance;
layout(set = 1, binding = 2, std430) buffer Counter {
uint sum;
}
counter;
#endif
layout(rgba16, set = 2, binding = 0) uniform restrict writeonly image2D dest_image;
layout(r8, set = 2, binding = 1) uniform image2D edges_weights_image;
layout(set = 3, binding = 0) uniform sampler2D last_frame;
layout(set = 3, binding = 1) uniform ProjectionConstants {
mat4 reprojection;
}
projection_constants;
layout(push_constant, std430) uniform Params {
ivec2 screen_size;
int pass;
int quality;
vec2 half_screen_pixel_size;
vec2 half_screen_pixel_size_x025;
vec2 NDC_to_view_mul;
vec2 NDC_to_view_add;
vec2 pad2;
float z_near;
float z_far;
float radius;
float intensity;
int size_multiplier;
int pad;
float fade_out_mul;
float fade_out_add;
float normal_rejection_amount;
float inv_radius_near_limit;
bool is_orthogonal;
float neg_inv_radius;
float load_counter_avg_div;
float adaptive_sample_limit;
ivec2 pass_coord_offset;
vec2 pass_uv_offset;
}
params;
float pack_edges(vec4 p_edgesLRTB) {
p_edgesLRTB = round(clamp(p_edgesLRTB, 0.0, 1.0) * 3.05);
return dot(p_edgesLRTB, vec4(64.0 / 255.0, 16.0 / 255.0, 4.0 / 255.0, 1.0 / 255.0));
}
vec3 NDC_to_view_space(vec2 p_pos, float p_viewspace_depth) {
if (params.is_orthogonal) {
return vec3((params.NDC_to_view_mul * p_pos.xy + params.NDC_to_view_add), p_viewspace_depth);
} else {
return vec3((params.NDC_to_view_mul * p_pos.xy + params.NDC_to_view_add) * p_viewspace_depth, p_viewspace_depth);
}
}
// calculate effect radius and fit our screen sampling pattern inside it
void calculate_radius_parameters(const float p_pix_center_length, const vec2 p_pixel_size_at_center, out float r_lookup_radius, out float r_radius, out float r_fallof_sq) {
r_radius = params.radius;
// when too close, on-screen sampling disk will grow beyond screen size; limit this to avoid closeup temporal artifacts
const float too_close_limit = clamp(p_pix_center_length * params.inv_radius_near_limit, 0.0, 1.0) * 0.8 + 0.2;
r_radius *= too_close_limit;
// 0.85 is to reduce the radius to allow for more samples on a slope to still stay within influence
r_lookup_radius = (0.85 * r_radius) / p_pixel_size_at_center.x;
// used to calculate falloff (both for AO samples and per-sample weights)
r_fallof_sq = -1.0 / (r_radius * r_radius);
}
vec4 calculate_edges(const float p_center_z, const float p_left_z, const float p_right_z, const float p_top_z, const float p_bottom_z) {
// slope-sensitive depth-based edge detection
vec4 edgesLRTB = vec4(p_left_z, p_right_z, p_top_z, p_bottom_z) - p_center_z;
vec4 edgesLRTB_slope_adjusted = edgesLRTB + edgesLRTB.yxwz;
edgesLRTB = min(abs(edgesLRTB), abs(edgesLRTB_slope_adjusted));
return clamp((1.3 - edgesLRTB / (p_center_z * 0.040)), 0.0, 1.0);
}
vec3 decode_normal(vec3 p_encoded_normal) {
vec3 normal = p_encoded_normal * 2.0 - 1.0;
return normal;
}
vec3 load_normal(ivec2 p_pos) {
vec3 encoded_normal = imageLoad(source_normal, p_pos).xyz;
encoded_normal.z = 1.0 - encoded_normal.z;
return decode_normal(encoded_normal);
}
vec3 load_normal(ivec2 p_pos, ivec2 p_offset) {
vec3 encoded_normal = imageLoad(source_normal, p_pos + p_offset).xyz;
encoded_normal.z = 1.0 - encoded_normal.z;
return decode_normal(encoded_normal);
}
// all vectors in viewspace
float calculate_pixel_obscurance(vec3 p_pixel_normal, vec3 p_hit_delta, float p_fallof_sq) {
float length_sq = dot(p_hit_delta, p_hit_delta);
float NdotD = dot(p_pixel_normal, p_hit_delta) / sqrt(length_sq);
float falloff_mult = max(0.0, length_sq * p_fallof_sq + 1.0);
return max(0, NdotD - 0.05) * falloff_mult;
}
void SSIL_tap_inner(const int p_quality_level, inout vec3 r_color_sum, inout float r_obscurance_sum, inout float r_weight_sum, const vec2 p_sampling_uv, const float p_mip_level, const vec3 p_pix_center_pos, vec3 p_pixel_normal, const float p_fallof_sq, const float p_weight_mod) {
// get depth at sample
float viewspace_sample_z = textureLod(source_depth_mipmaps, vec3(p_sampling_uv, params.pass), p_mip_level).x;
vec3 sample_normal = load_normal(ivec2(p_sampling_uv * vec2(params.screen_size)));
// convert to viewspace
vec3 hit_pos = NDC_to_view_space(p_sampling_uv.xy, viewspace_sample_z);
vec3 hit_delta = hit_pos - p_pix_center_pos;
float obscurance = calculate_pixel_obscurance(p_pixel_normal, hit_delta, p_fallof_sq);
float weight = 1.0;
if (p_quality_level >= SSIL_HALOING_REDUCTION_ENABLE_AT_QUALITY_PRESET) {
float reduct = max(0, -hit_delta.z);
reduct = clamp(reduct * params.neg_inv_radius + 2.0, 0.0, 1.0);
weight = SSIL_HALOING_REDUCTION_AMOUNT * reduct + (1.0 - SSIL_HALOING_REDUCTION_AMOUNT);
}
// Translate sampling_uv to last screen's coordinates
const vec4 sample_pos = projection_constants.reprojection * vec4(p_sampling_uv * 2.0 - 1.0, (viewspace_sample_z - params.z_near) / (params.z_far - params.z_near) * 2.0 - 1.0, 1.0);
vec2 reprojected_sampling_uv = (sample_pos.xy / sample_pos.w) * 0.5 + 0.5;
weight *= p_weight_mod;
r_obscurance_sum += obscurance * weight;
vec3 sample_color = textureLod(last_frame, reprojected_sampling_uv, 5.0).rgb;
// Reduce impact of fireflies by tonemapping before averaging: http://graphicrants.blogspot.com/2013/12/tone-mapping.html
sample_color /= (1.0 + dot(sample_color, vec3(0.299, 0.587, 0.114)));
r_color_sum += sample_color * obscurance * weight * mix(1.0, smoothstep(0.0, 0.1, -dot(sample_normal, normalize(hit_delta))), params.normal_rejection_amount);
r_weight_sum += weight;
}
void SSILTap(const int p_quality_level, inout vec3 r_color_sum, inout float r_obscurance_sum, inout float r_weight_sum, const int p_tap_index, const mat2 p_rot_scale, const vec3 p_pix_center_pos, vec3 p_pixel_normal, const vec2 p_normalized_screen_pos, const float p_mip_offset, const float p_fallof_sq, float p_weight_mod, vec2 p_norm_xy, float p_norm_xy_length) {
vec2 sample_offset;
float sample_pow_2_len;
// patterns
{
vec4 new_sample = sample_pattern[p_tap_index];
sample_offset = new_sample.xy * p_rot_scale;
sample_pow_2_len = new_sample.w; // precalculated, same as: sample_pow_2_len = log2( length( new_sample.xy ) );
p_weight_mod *= new_sample.z;
}
// snap to pixel center (more correct obscurance math, avoids artifacts)
sample_offset = round(sample_offset);
// calculate MIP based on the sample distance from the centre, similar to as described
// in http://graphics.cs.williams.edu/papers/SAOHPG12/.
float mip_level = (p_quality_level < SSIL_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET) ? (0) : (sample_pow_2_len + p_mip_offset);
vec2 sampling_uv = sample_offset * params.half_screen_pixel_size + p_normalized_screen_pos;
SSIL_tap_inner(p_quality_level, r_color_sum, r_obscurance_sum, r_weight_sum, sampling_uv, mip_level, p_pix_center_pos, p_pixel_normal, p_fallof_sq, p_weight_mod);
// for the second tap, just use the mirrored offset
vec2 sample_offset_mirrored_uv = -sample_offset;
// tilt the second set of samples so that the disk is effectively rotated by the normal
// effective at removing one set of artifacts, but too expensive for lower quality settings
if (p_quality_level >= SSIL_TILT_SAMPLES_ENABLE_AT_QUALITY_PRESET) {
float dot_norm = dot(sample_offset_mirrored_uv, p_norm_xy);
sample_offset_mirrored_uv -= dot_norm * p_norm_xy_length * p_norm_xy;
sample_offset_mirrored_uv = round(sample_offset_mirrored_uv);
}
// snap to pixel center (more correct obscurance math, avoids artifacts)
vec2 sampling_mirrored_uv = sample_offset_mirrored_uv * params.half_screen_pixel_size + p_normalized_screen_pos;
SSIL_tap_inner(p_quality_level, r_color_sum, r_obscurance_sum, r_weight_sum, sampling_mirrored_uv, mip_level, p_pix_center_pos, p_pixel_normal, p_fallof_sq, p_weight_mod);
}
void generate_SSIL(out vec3 r_color, out vec4 r_edges, out float r_obscurance, out float r_weight, const vec2 p_pos, int p_quality_level, bool p_adaptive_base) {
vec2 pos_rounded = trunc(p_pos);
uvec2 upos = uvec2(pos_rounded);
const int number_of_taps = (p_adaptive_base) ? (SSIL_ADAPTIVE_TAP_BASE_COUNT) : (num_taps[p_quality_level]);
float pix_z, pix_left_z, pix_top_z, pix_right_z, pix_bottom_z;
vec4 valuesUL = textureGather(source_depth_mipmaps, vec3(pos_rounded * params.half_screen_pixel_size, params.pass));
vec4 valuesBR = textureGather(source_depth_mipmaps, vec3((pos_rounded + vec2(1.0)) * params.half_screen_pixel_size, params.pass));
// get this pixel's viewspace depth
pix_z = valuesUL.y;
// get left right top bottom neighbouring pixels for edge detection (gets compiled out on quality_level == 0)
pix_left_z = valuesUL.x;
pix_top_z = valuesUL.z;
pix_right_z = valuesBR.z;
pix_bottom_z = valuesBR.x;
vec2 normalized_screen_pos = pos_rounded * params.half_screen_pixel_size + params.half_screen_pixel_size_x025;
vec3 pix_center_pos = NDC_to_view_space(normalized_screen_pos, pix_z);
// Load this pixel's viewspace normal
uvec2 full_res_coord = upos * 2 * params.size_multiplier + params.pass_coord_offset.xy;
vec3 pixel_normal = load_normal(ivec2(full_res_coord));
const vec2 pixel_size_at_center = NDC_to_view_space(normalized_screen_pos.xy + params.half_screen_pixel_size, pix_center_pos.z).xy - pix_center_pos.xy;
float pixel_lookup_radius;
float fallof_sq;
// calculate effect radius and fit our screen sampling pattern inside it
float viewspace_radius;
calculate_radius_parameters(length(pix_center_pos), pixel_size_at_center, pixel_lookup_radius, viewspace_radius, fallof_sq);
// calculate samples rotation/scaling
mat2 rot_scale_matrix;
uint pseudo_random_index;
{
vec4 rotation_scale;
// reduce effect radius near the screen edges slightly; ideally, one would render a larger depth buffer (5% on each side) instead
if (!p_adaptive_base && (p_quality_level >= SSIL_REDUCE_RADIUS_NEAR_SCREEN_BORDER_ENABLE_AT_QUALITY_PRESET)) {
float near_screen_border = min(min(normalized_screen_pos.x, 1.0 - normalized_screen_pos.x), min(normalized_screen_pos.y, 1.0 - normalized_screen_pos.y));
near_screen_border = clamp(10.0 * near_screen_border + 0.6, 0.0, 1.0);
pixel_lookup_radius *= near_screen_border;
}
// load & update pseudo-random rotation matrix
pseudo_random_index = uint(pos_rounded.y * 2 + pos_rounded.x) % 5;
rotation_scale = constants.rotation_matrices[params.pass * 5 + pseudo_random_index];
rot_scale_matrix = mat2(rotation_scale.x * pixel_lookup_radius, rotation_scale.y * pixel_lookup_radius, rotation_scale.z * pixel_lookup_radius, rotation_scale.w * pixel_lookup_radius);
}
// the main obscurance & sample weight storage
vec3 color_sum = vec3(0.0);
float obscurance_sum = 0.0;
float weight_sum = 0.0;
// edge mask for between this and left/right/top/bottom neighbour pixels - not used in quality level 0 so initialize to "no edge" (1 is no edge, 0 is edge)
vec4 edgesLRTB = vec4(1.0, 1.0, 1.0, 1.0);
// Move center pixel slightly towards camera to avoid imprecision artifacts due to using of 16bit depth buffer; a lot smaller offsets needed when using 32bit floats
pix_center_pos *= 0.9992;
if (!p_adaptive_base && (p_quality_level >= SSIL_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET)) {
edgesLRTB = calculate_edges(pix_z, pix_left_z, pix_right_z, pix_top_z, pix_bottom_z);
}
const float global_mip_offset = SSIL_DEPTH_MIPS_GLOBAL_OFFSET;
float mip_offset = (p_quality_level < SSIL_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET) ? (0) : (log2(pixel_lookup_radius) + global_mip_offset);
// Used to tilt the second set of samples so that the disk is effectively rotated by the normal
// effective at removing one set of artifacts, but too expensive for lower quality settings
vec2 norm_xy = vec2(pixel_normal.x, pixel_normal.y);
float norm_xy_length = length(norm_xy);
norm_xy /= vec2(norm_xy_length, -norm_xy_length);
norm_xy_length *= SSIL_TILT_SAMPLES_AMOUNT;
// standard, non-adaptive approach
if ((p_quality_level != 3) || p_adaptive_base) {
for (int i = 0; i < number_of_taps; i++) {
SSILTap(p_quality_level, color_sum, obscurance_sum, weight_sum, i, rot_scale_matrix, pix_center_pos, pixel_normal, normalized_screen_pos, mip_offset, fallof_sq, 1.0, norm_xy, norm_xy_length);
}
}
#ifdef ADAPTIVE
else {
// add new ones if needed
vec2 full_res_uv = normalized_screen_pos + params.pass_uv_offset.xy;
float importance = textureLod(source_importance, full_res_uv, 0.0).x;
//Need to store obscurance from base pass
// load existing base values
vec4 base_values = imageLoad(source_ssil, ivec3(upos, params.pass));
weight_sum += imageLoad(edges_weights_image, ivec2(upos)).r * float(SSIL_ADAPTIVE_TAP_BASE_COUNT * 4.0);
color_sum += (base_values.rgb) * weight_sum;
obscurance_sum += (base_values.a) * weight_sum;
// increase importance around edges
float edge_count = dot(1.0 - edgesLRTB, vec4(1.0, 1.0, 1.0, 1.0));
float avg_total_importance = float(counter.sum) * params.load_counter_avg_div;
float importance_limiter = clamp(params.adaptive_sample_limit / avg_total_importance, 0.0, 1.0);
importance *= importance_limiter;
float additional_sample_count = SSIL_ADAPTIVE_TAP_FLEXIBLE_COUNT * importance;
const float blend_range = 3.0;
const float blend_range_inv = 1.0 / blend_range;
additional_sample_count += 0.5;
uint additional_samples = uint(additional_sample_count);
uint additional_samples_to = min(SSIL_MAX_TAPS, additional_samples + SSIL_ADAPTIVE_TAP_BASE_COUNT);
for (uint i = SSIL_ADAPTIVE_TAP_BASE_COUNT; i < additional_samples_to; i++) {
additional_sample_count -= 1.0f;
float weight_mod = clamp(additional_sample_count * blend_range_inv, 0.0, 1.0);
SSILTap(p_quality_level, color_sum, obscurance_sum, weight_sum, int(i), rot_scale_matrix, pix_center_pos, pixel_normal, normalized_screen_pos, mip_offset, fallof_sq, weight_mod, norm_xy, norm_xy_length);
}
}
#endif
// Early out for adaptive base
if (p_adaptive_base) {
vec3 color = color_sum / weight_sum;
r_color = color;
r_edges = vec4(0.0);
r_obscurance = obscurance_sum / weight_sum;
r_weight = weight_sum;
return;
}
// Calculate weighted average
vec3 color = color_sum / weight_sum;
color /= 1.0 - dot(color, vec3(0.299, 0.587, 0.114));
// Calculate fadeout (1 close, gradient, 0 far)
float fade_out = clamp(pix_center_pos.z * params.fade_out_mul + params.fade_out_add, 0.0, 1.0);
// Reduce the SSIL if we're on the edge to remove artifacts on edges (we don't care for the lower quality one)
if (!p_adaptive_base && (p_quality_level >= SSIL_DEPTH_BASED_EDGES_ENABLE_AT_QUALITY_PRESET)) {
// when there's more than 2 opposite edges, start fading out the occlusion to reduce aliasing artifacts
float edge_fadeout_factor = clamp((1.0 - edgesLRTB.x - edgesLRTB.y) * 0.35, 0.0, 1.0) + clamp((1.0 - edgesLRTB.z - edgesLRTB.w) * 0.35, 0.0, 1.0);
fade_out *= clamp(1.0 - edge_fadeout_factor, 0.0, 1.0);
}
color = params.intensity * color;
color *= fade_out;
// outputs!
r_color = color;
r_edges = edgesLRTB; // These are used to prevent blurring across edges, 1 means no edge, 0 means edge, 0.5 means half way there, etc.
r_obscurance = clamp((obscurance_sum / weight_sum) * params.intensity, 0.0, 1.0);
r_weight = weight_sum;
}
void main() {
vec3 out_color;
float out_obscurance;
float out_weight;
vec4 out_edges;
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing
return;
}
vec2 uv = vec2(gl_GlobalInvocationID) + vec2(0.5);
#ifdef SSIL_BASE
generate_SSIL(out_color, out_edges, out_obscurance, out_weight, uv, params.quality, true);
imageStore(dest_image, ssC, vec4(out_color, out_obscurance));
imageStore(edges_weights_image, ssC, vec4(out_weight / (float(SSIL_ADAPTIVE_TAP_BASE_COUNT) * 4.0)));
#else
generate_SSIL(out_color, out_edges, out_obscurance, out_weight, uv, params.quality, false); // pass in quality levels
imageStore(dest_image, ssC, vec4(out_color, out_obscurance));
imageStore(edges_weights_image, ssC, vec4(pack_edges(out_edges)));
#endif
}

View File

@@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
// 2021-05-27: clayjohn: convert SSAO to SSIL
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[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_ssil;
layout(rgba16, set = 1, binding = 0) uniform restrict writeonly image2D dest_image;
layout(r8, set = 2, binding = 0) uniform restrict readonly image2D source_edges;
layout(push_constant, std430) uniform Params {
float edge_sharpness;
float pad;
vec2 half_screen_pixel_size;
}
params;
vec4 unpack_edges(float p_packed_val) {
uint packed_val = uint(p_packed_val * 255.5);
vec4 edgesLRTB;
edgesLRTB.x = float((packed_val >> 6) & 0x03) / 3.0;
edgesLRTB.y = float((packed_val >> 4) & 0x03) / 3.0;
edgesLRTB.z = float((packed_val >> 2) & 0x03) / 3.0;
edgesLRTB.w = float((packed_val >> 0) & 0x03) / 3.0;
return clamp(edgesLRTB + params.edge_sharpness, 0.0, 1.0);
}
void add_sample(vec4 p_ssil_value, float p_edge_value, inout vec4 r_sum, inout float r_sum_weight) {
float weight = p_edge_value;
r_sum += (weight * p_ssil_value);
r_sum_weight += weight;
}
#ifdef MODE_WIDE
vec4 sample_blurred_wide(ivec2 p_pos, vec2 p_coord) {
vec4 ssil_value = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, 0));
vec4 ssil_valueL = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(-2, 0));
vec4 ssil_valueT = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, -2));
vec4 ssil_valueR = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(2, 0));
vec4 ssil_valueB = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, 2));
vec4 edgesLRTB = unpack_edges(imageLoad(source_edges, p_pos).r);
edgesLRTB.x *= unpack_edges(imageLoad(source_edges, p_pos + ivec2(-2, 0)).r).y;
edgesLRTB.z *= unpack_edges(imageLoad(source_edges, p_pos + ivec2(0, -2)).r).w;
edgesLRTB.y *= unpack_edges(imageLoad(source_edges, p_pos + ivec2(2, 0)).r).x;
edgesLRTB.w *= unpack_edges(imageLoad(source_edges, p_pos + ivec2(0, 2)).r).z;
float sum_weight = 0.8;
vec4 sum = ssil_value * sum_weight;
add_sample(ssil_valueL, edgesLRTB.x, sum, sum_weight);
add_sample(ssil_valueR, edgesLRTB.y, sum, sum_weight);
add_sample(ssil_valueT, edgesLRTB.z, sum, sum_weight);
add_sample(ssil_valueB, edgesLRTB.w, sum, sum_weight);
vec4 ssil_avg = sum / sum_weight;
ssil_value = ssil_avg;
return ssil_value;
}
#endif
#ifdef MODE_SMART
vec4 sample_blurred(ivec2 p_pos, vec2 p_coord) {
vec4 vC = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, 0));
vec4 vL = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(-1, 0));
vec4 vT = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, -1));
vec4 vR = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(1, 0));
vec4 vB = textureLodOffset(source_ssil, vec2(p_coord), 0.0, ivec2(0, 1));
float packed_edges = imageLoad(source_edges, p_pos).r;
vec4 edgesLRTB = unpack_edges(packed_edges);
float sum_weight = 0.5;
vec4 sum = vC * sum_weight;
add_sample(vL, edgesLRTB.x, sum, sum_weight);
add_sample(vR, edgesLRTB.y, sum, sum_weight);
add_sample(vT, edgesLRTB.z, sum, sum_weight);
add_sample(vB, edgesLRTB.w, sum, sum_weight);
vec4 ssil_avg = sum / sum_weight;
vec4 ssil_value = ssil_avg;
return ssil_value;
}
#endif
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
#ifdef MODE_NON_SMART
vec2 half_pixel = params.half_screen_pixel_size * 0.5;
vec2 uv = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size;
vec4 centre = textureLod(source_ssil, uv, 0.0);
vec4 value = textureLod(source_ssil, vec2(uv + vec2(-half_pixel.x * 3, -half_pixel.y)), 0.0) * 0.2;
value += textureLod(source_ssil, vec2(uv + vec2(+half_pixel.x, -half_pixel.y * 3)), 0.0) * 0.2;
value += textureLod(source_ssil, vec2(uv + vec2(-half_pixel.x, +half_pixel.y * 3)), 0.0) * 0.2;
value += textureLod(source_ssil, vec2(uv + vec2(+half_pixel.x * 3, +half_pixel.y)), 0.0) * 0.2;
vec4 sampled = value + centre * 0.2;
#else
#ifdef MODE_SMART
vec4 sampled = sample_blurred(ssC, (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size);
#else // MODE_WIDE
vec4 sampled = sample_blurred_wide(ssC, (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size);
#endif
#endif // MODE_NON_SMART
imageStore(dest_image, ssC, sampled);
}

View File

@@ -0,0 +1,125 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
// 2021-05-27: clayjohn: convert SSAO to SSIL
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#ifdef GENERATE_MAP
layout(set = 0, binding = 0) uniform sampler2DArray source_texture;
#else
layout(set = 0, binding = 0) uniform sampler2D source_importance;
#endif
layout(r8, set = 1, binding = 0) uniform restrict writeonly image2D dest_image;
#ifdef PROCESS_MAPB
layout(set = 2, binding = 0, std430) buffer Counter {
uint sum;
}
counter;
#endif
layout(push_constant, std430) uniform Params {
vec2 half_screen_pixel_size;
float intensity;
float pad;
}
params;
void main() {
// Pixel being shaded
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
#ifdef GENERATE_MAP
// importance map stuff
uvec2 base_position = ssC * 2;
float avg = 0.0;
float minV = 1.0;
float maxV = 0.0;
for (int i = 0; i < 4; i++) {
vec3 value_a = texelFetch(source_texture, ivec3(base_position, i), 0).rgb * params.intensity;
vec3 value_b = texelFetch(source_texture, ivec3(base_position, i) + ivec3(0, 1, 0), 0).rgb * params.intensity;
vec3 value_c = texelFetch(source_texture, ivec3(base_position, i) + ivec3(1, 0, 0), 0).rgb * params.intensity;
vec3 value_d = texelFetch(source_texture, ivec3(base_position, i) + ivec3(1, 1, 0), 0).rgb * params.intensity;
// Calculate luminance (black and white value)
float a = dot(value_a, vec3(0.2125, 0.7154, 0.0721));
float b = dot(value_b, vec3(0.2125, 0.7154, 0.0721));
float c = dot(value_c, vec3(0.2125, 0.7154, 0.0721));
float d = dot(value_d, vec3(0.2125, 0.7154, 0.0721));
maxV = max(maxV, max(max(a, b), max(c, d)));
minV = min(minV, min(min(a, b), min(c, d)));
}
float min_max_diff = maxV - minV;
imageStore(dest_image, ssC, vec4(pow(clamp(min_max_diff * 2.0, 0.0, 1.0), 0.6)));
#endif
#ifdef PROCESS_MAPA
vec2 uv = (vec2(ssC) + 0.5) * params.half_screen_pixel_size * 2.0;
float centre = textureLod(source_importance, uv, 0.0).x;
vec2 half_pixel = params.half_screen_pixel_size;
vec4 vals;
vals.x = textureLod(source_importance, uv + vec2(-half_pixel.x * 3, -half_pixel.y), 0.0).x;
vals.y = textureLod(source_importance, uv + vec2(+half_pixel.x, -half_pixel.y * 3), 0.0).x;
vals.z = textureLod(source_importance, uv + vec2(+half_pixel.x * 3, +half_pixel.y), 0.0).x;
vals.w = textureLod(source_importance, uv + vec2(-half_pixel.x, +half_pixel.y * 3), 0.0).x;
float avg = dot(vals, vec4(0.25, 0.25, 0.25, 0.25));
imageStore(dest_image, ssC, vec4(avg));
#endif
#ifdef PROCESS_MAPB
vec2 uv = (vec2(ssC) + 0.5f) * params.half_screen_pixel_size * 2.0;
float centre = textureLod(source_importance, uv, 0.0).x;
vec2 half_pixel = params.half_screen_pixel_size;
vec4 vals;
vals.x = textureLod(source_importance, uv + vec2(-half_pixel.x, -half_pixel.y * 3), 0.0).x;
vals.y = textureLod(source_importance, uv + vec2(+half_pixel.x * 3, -half_pixel.y), 0.0).x;
vals.z = textureLod(source_importance, uv + vec2(+half_pixel.x, +half_pixel.y * 3), 0.0).x;
vals.w = textureLod(source_importance, uv + vec2(-half_pixel.x * 3, +half_pixel.y), 0.0).x;
float avg = dot(vals, vec4(0.25, 0.25, 0.25, 0.25));
imageStore(dest_image, ssC, vec4(avg));
// sum the average; to avoid overflowing we assume max AO resolution is not bigger than 16384x16384; so quarter res (used here) will be 4096x4096, which leaves us with 8 bits per pixel
uint sum = uint(clamp(avg, 0.0, 1.0) * 255.0 + 0.5);
// save every 9th to avoid InterlockedAdd congestion - since we're blurring, this is good enough; compensated by multiplying load_counter_avg_div by 9
if (((ssC.x % 3) + (ssC.y % 3)) == 0) {
atomicAdd(counter.sum, sum);
}
#endif
}

View File

@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016, Intel Corporation
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File changes (yyyy-mm-dd)
// 2016-09-07: filip.strugar@intel.com: first commit
// 2020-12-05: clayjohn: convert to Vulkan and Godot
// 2021-05-27: clayjohn: convert SSAO to SSIL
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(rgba16, set = 0, binding = 0) uniform restrict writeonly image2D dest_image;
layout(set = 1, binding = 0) uniform sampler2DArray source_texture;
layout(r8, set = 2, binding = 0) uniform restrict readonly image2DArray source_edges;
layout(push_constant, std430) uniform Params {
float inv_sharpness;
uint size_modifier;
vec2 pixel_size;
}
params;
vec4 unpack_edges(float p_packed_val) {
uint packed_val = uint(p_packed_val * 255.5);
vec4 edgesLRTB;
edgesLRTB.x = float((packed_val >> 6) & 0x03) / 3.0;
edgesLRTB.y = float((packed_val >> 4) & 0x03) / 3.0;
edgesLRTB.z = float((packed_val >> 2) & 0x03) / 3.0;
edgesLRTB.w = float((packed_val >> 0) & 0x03) / 3.0;
return clamp(edgesLRTB + params.inv_sharpness, 0.0, 1.0);
}
void main() {
ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(ssC, ivec2(1.0 / params.pixel_size)))) { //too large, do nothing
return;
}
#ifdef MODE_SMART
uvec2 pix_pos = uvec2(gl_GlobalInvocationID.xy);
vec2 uv = (gl_GlobalInvocationID.xy + vec2(0.5)) * params.pixel_size;
// calculate index in the four deinterleaved source array texture
int mx = int(pix_pos.x % 2);
int my = int(pix_pos.y % 2);
int index_center = mx + my * 2; // center index
int index_horizontal = (1 - mx) + my * 2; // neighbouring, horizontal
int index_vertical = mx + (1 - my) * 2; // neighbouring, vertical
int index_diagonal = (1 - mx) + (1 - my) * 2; // diagonal
vec4 color = texelFetch(source_texture, ivec3(pix_pos / uvec2(params.size_modifier), index_center), 0);
vec4 edgesLRTB = unpack_edges(imageLoad(source_edges, ivec3(pix_pos / uvec2(params.size_modifier), index_center)).r);
// convert index shifts to sampling offsets
float fmx = float(mx);
float fmy = float(my);
// in case of an edge, push sampling offsets away from the edge (towards pixel center)
float fmxe = (edgesLRTB.y - edgesLRTB.x);
float fmye = (edgesLRTB.w - edgesLRTB.z);
// calculate final sampling offsets and sample using bilinear filter
vec2 uv_horizontal = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(fmx + fmxe - 0.5, 0.5 - fmy)) * params.pixel_size;
vec4 color_horizontal = textureLod(source_texture, vec3(uv_horizontal, index_horizontal), 0.0);
vec2 uv_vertical = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(0.5 - fmx, fmy - 0.5 + fmye)) * params.pixel_size;
vec4 color_vertical = textureLod(source_texture, vec3(uv_vertical, index_vertical), 0.0);
vec2 uv_diagonal = (gl_GlobalInvocationID.xy + vec2(0.5) + vec2(fmx - 0.5 + fmxe, fmy - 0.5 + fmye)) * params.pixel_size;
vec4 color_diagonal = textureLod(source_texture, vec3(uv_diagonal, index_diagonal), 0.0);
// reduce weight for samples near edge - if the edge is on both sides, weight goes to 0
vec4 blendWeights;
blendWeights.x = 1.0;
blendWeights.y = (edgesLRTB.x + edgesLRTB.y) * 0.5;
blendWeights.z = (edgesLRTB.z + edgesLRTB.w) * 0.5;
blendWeights.w = (blendWeights.y + blendWeights.z) * 0.5;
// calculate weighted average
float blendWeightsSum = dot(blendWeights, vec4(1.0, 1.0, 1.0, 1.0));
color += color_horizontal * blendWeights.y;
color += color_vertical * blendWeights.z;
color += color_diagonal * blendWeights.w;
color /= blendWeightsSum;
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), color);
#else // !MODE_SMART
vec2 uv = (gl_GlobalInvocationID.xy + vec2(0.5)) * params.pixel_size;
#ifdef MODE_HALF
vec4 a = textureLod(source_texture, vec3(uv, 0), 0.0);
vec4 d = textureLod(source_texture, vec3(uv, 3), 0.0);
vec4 avg = (a + d) * 0.5;
#else
vec4 a = textureLod(source_texture, vec3(uv, 0), 0.0);
vec4 b = textureLod(source_texture, vec3(uv, 1), 0.0);
vec4 c = textureLod(source_texture, vec3(uv, 2), 0.0);
vec4 d = textureLod(source_texture, vec3(uv, 3), 0.0);
vec4 avg = (a + b + c + d) * 0.25;
#endif
imageStore(dest_image, ivec2(gl_GlobalInvocationID.xy), avg);
#endif
}