summaryrefslogtreecommitdiff
path: root/servers/rendering/renderer_rd/shaders
diff options
context:
space:
mode:
Diffstat (limited to 'servers/rendering/renderer_rd/shaders')
-rw-r--r--servers/rendering/renderer_rd/shaders/SCsub17
-rw-r--r--servers/rendering/renderer_rd/shaders/blit.glsl95
-rw-r--r--servers/rendering/renderer_rd/shaders/blur_raster.glsl136
-rw-r--r--servers/rendering/renderer_rd/shaders/blur_raster_inc.glsl21
-rw-r--r--servers/rendering/renderer_rd/shaders/bokeh_dof.glsl215
-rw-r--r--servers/rendering/renderer_rd/shaders/bokeh_dof_inc.glsl37
-rw-r--r--servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl253
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl704
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_occlusion.glsl59
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_sdf.glsl179
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl160
-rw-r--r--servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl3
-rw-r--r--servers/rendering/renderer_rd/shaders/cluster_debug.glsl115
-rw-r--r--servers/rendering/renderer_rd/shaders/cluster_render.glsl168
-rw-r--r--servers/rendering/renderer_rd/shaders/cluster_store.glsl119
-rw-r--r--servers/rendering/renderer_rd/shaders/copy.glsl279
-rw-r--r--servers/rendering/renderer_rd/shaders/copy_to_fb.glsl115
-rw-r--r--servers/rendering/renderer_rd/shaders/cube_to_dp.glsl84
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_downsampler.glsl145
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_downsampler_inc.glsl48
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_downsampler_raster.glsl163
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_filter.glsl326
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_filter_raster.glsl256
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl49
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl94
-rw-r--r--servers/rendering/renderer_rd/shaders/cubemap_roughness_raster.glsl63
-rw-r--r--servers/rendering/renderer_rd/shaders/decal_data_inc.glsl18
-rw-r--r--servers/rendering/renderer_rd/shaders/gi.glsl656
-rw-r--r--servers/rendering/renderer_rd/shaders/giprobe_write.glsl323
-rw-r--r--servers/rendering/renderer_rd/shaders/light_data_inc.glsl87
-rw-r--r--servers/rendering/renderer_rd/shaders/luminance_reduce.glsl82
-rw-r--r--servers/rendering/renderer_rd/shaders/luminance_reduce_raster.glsl74
-rw-r--r--servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl11
-rw-r--r--servers/rendering/renderer_rd/shaders/particles.glsl632
-rw-r--r--servers/rendering/renderer_rd/shaders/particles_copy.glsl225
-rw-r--r--servers/rendering/renderer_rd/shaders/resolve.glsl236
-rw-r--r--servers/rendering/renderer_rd/shaders/roughness_limiter.glsl70
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl58
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl1914
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl339
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl242
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl1035
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl1563
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl225
-rw-r--r--servers/rendering/renderer_rd/shaders/screen_space_reflection.glsl246
-rw-r--r--servers/rendering/renderer_rd/shaders/screen_space_reflection_filter.glsl154
-rw-r--r--servers/rendering/renderer_rd/shaders/screen_space_reflection_scale.glsl90
-rw-r--r--servers/rendering/renderer_rd/shaders/sdfgi_debug.glsl174
-rw-r--r--servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl231
-rw-r--r--servers/rendering/renderer_rd/shaders/sdfgi_direct_light.glsl508
-rw-r--r--servers/rendering/renderer_rd/shaders/sdfgi_integrate.glsl612
-rw-r--r--servers/rendering/renderer_rd/shaders/sdfgi_preprocess.glsl1056
-rw-r--r--servers/rendering/renderer_rd/shaders/skeleton.glsl246
-rw-r--r--servers/rendering/renderer_rd/shaders/sky.glsl249
-rw-r--r--servers/rendering/renderer_rd/shaders/sort.glsl203
-rw-r--r--servers/rendering/renderer_rd/shaders/specular_merge.glsl53
-rw-r--r--servers/rendering/renderer_rd/shaders/ssao.glsl486
-rw-r--r--servers/rendering/renderer_rd/shaders/ssao_blur.glsl154
-rw-r--r--servers/rendering/renderer_rd/shaders/ssao_downsample.glsl206
-rw-r--r--servers/rendering/renderer_rd/shaders/ssao_importance_map.glsl126
-rw-r--r--servers/rendering/renderer_rd/shaders/ssao_interleave.glsl119
-rw-r--r--servers/rendering/renderer_rd/shaders/subsurface_scattering.glsl189
-rw-r--r--servers/rendering/renderer_rd/shaders/tonemap.glsl435
-rw-r--r--servers/rendering/renderer_rd/shaders/volumetric_fog.glsl703
-rw-r--r--servers/rendering/renderer_rd/shaders/voxel_gi.glsl779
-rw-r--r--servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl229
-rw-r--r--servers/rendering/renderer_rd/shaders/voxel_gi_sdf.glsl181
67 files changed, 19122 insertions, 0 deletions
diff --git a/servers/rendering/renderer_rd/shaders/SCsub b/servers/rendering/renderer_rd/shaders/SCsub
new file mode 100644
index 0000000000..fc513d3fb9
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/SCsub
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+Import("env")
+
+if "RD_GLSL" in env["BUILDERS"]:
+ # find all include files
+ gl_include_files = [str(f) for f in Glob("*_inc.glsl")]
+
+ # find all shader code(all glsl files excluding our include files)
+ glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files]
+
+ # make sure we recompile shaders if include files change
+ env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files)
+
+ # compile shaders
+ for glsl_file in glsl_files:
+ env.RD_GLSL(glsl_file)
diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl
new file mode 100644
index 0000000000..967da1e6e4
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/blit.glsl
@@ -0,0 +1,95 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 0, std140) uniform Pos {
+ vec4 dst_rect;
+
+ vec2 eye_center;
+ float k1;
+ float k2;
+
+ float upscale;
+ float aspect_ratio;
+ uint layer;
+ uint pad1;
+}
+data;
+
+layout(location = 0) out vec2 uv;
+
+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));
+ uv = base_arr[gl_VertexIndex];
+ vec2 vtx = data.dst_rect.xy + uv * data.dst_rect.zw;
+ gl_Position = vec4(vtx * 2.0 - 1.0, 0.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 0, std140) uniform Pos {
+ vec4 dst_rect;
+
+ vec2 eye_center;
+ float k1;
+ float k2;
+
+ float upscale;
+ float aspect_ratio;
+ uint layer;
+ uint pad1;
+}
+data;
+
+layout(location = 0) in vec2 uv;
+
+layout(location = 0) out vec4 color;
+
+#ifdef USE_LAYER
+layout(binding = 0) uniform sampler2DArray src_rt;
+#else
+layout(binding = 0) uniform sampler2D src_rt;
+#endif
+
+void main() {
+#ifdef APPLY_LENS_DISTORTION
+ vec2 coords = uv * 2.0 - 1.0;
+ vec2 offset = coords - data.eye_center;
+
+ // take aspect ratio into account
+ offset.y /= data.aspect_ratio;
+
+ // distort
+ vec2 offset_sq = offset * offset;
+ float radius_sq = offset_sq.x + offset_sq.y;
+ float radius_s4 = radius_sq * radius_sq;
+ float distortion_scale = 1.0 + (data.k1 * radius_sq) + (data.k2 * radius_s4);
+ offset *= distortion_scale;
+
+ // reapply aspect ratio
+ offset.y *= data.aspect_ratio;
+
+ // add our eye center back in
+ coords = offset + data.eye_center;
+ coords /= data.upscale;
+
+ // and check our color
+ if (coords.x < -1.0 || coords.y < -1.0 || coords.x > 1.0 || coords.y > 1.0) {
+ color = vec4(0.0, 0.0, 0.0, 1.0);
+ } else {
+ // layer is always used here
+ coords = (coords + vec2(1.0)) / vec2(2.0);
+ color = texture(src_rt, vec3(coords, data.layer));
+ }
+#elif defined(USE_LAYER)
+ color = texture(src_rt, vec3(uv, data.layer));
+#else
+ color = texture(src_rt, uv);
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/blur_raster.glsl b/servers/rendering/renderer_rd/shaders/blur_raster.glsl
new file mode 100644
index 0000000000..0789a4b396
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/blur_raster.glsl
@@ -0,0 +1,136 @@
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "blur_raster_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "blur_raster_inc.glsl"
+
+layout(location = 0) in vec2 uv_interp;
+/* clang-format on */
+
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+
+#ifdef GLOW_USE_AUTO_EXPOSURE
+layout(set = 1, binding = 0) uniform sampler2D source_auto_exposure;
+#endif
+
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+#ifdef MODE_MIPMAP
+
+ vec2 pix_size = blur.pixel_size;
+ vec4 color = texture(source_color, uv_interp + vec2(-0.5, -0.5) * pix_size);
+ color += texture(source_color, uv_interp + vec2(0.5, -0.5) * pix_size);
+ color += texture(source_color, uv_interp + vec2(0.5, 0.5) * pix_size);
+ color += texture(source_color, uv_interp + vec2(-0.5, 0.5) * pix_size);
+ frag_color = color / 4.0;
+
+#endif
+
+#ifdef MODE_GAUSSIAN_BLUR
+
+ //Simpler blur uses SIGMA2 for the gaussian kernel for a stronger effect
+
+ if (bool(blur.flags & FLAG_HORIZONTAL)) {
+ vec2 pix_size = blur.pixel_size;
+ pix_size *= 0.5; //reading from larger buffer, so use more samples
+ vec4 color = texture(source_color, uv_interp + vec2(0.0, 0.0) * pix_size) * 0.214607;
+ color += texture(source_color, uv_interp + vec2(1.0, 0.0) * pix_size) * 0.189879;
+ color += texture(source_color, uv_interp + vec2(2.0, 0.0) * pix_size) * 0.131514;
+ color += texture(source_color, uv_interp + vec2(3.0, 0.0) * pix_size) * 0.071303;
+ color += texture(source_color, uv_interp + vec2(-1.0, 0.0) * pix_size) * 0.189879;
+ color += texture(source_color, uv_interp + vec2(-2.0, 0.0) * pix_size) * 0.131514;
+ color += texture(source_color, uv_interp + vec2(-3.0, 0.0) * pix_size) * 0.071303;
+ frag_color = color;
+ } else {
+ vec2 pix_size = blur.pixel_size;
+ vec4 color = texture(source_color, uv_interp + vec2(0.0, 0.0) * pix_size) * 0.38774;
+ color += texture(source_color, uv_interp + vec2(0.0, 1.0) * pix_size) * 0.24477;
+ color += texture(source_color, uv_interp + vec2(0.0, 2.0) * pix_size) * 0.06136;
+ color += texture(source_color, uv_interp + vec2(0.0, -1.0) * pix_size) * 0.24477;
+ color += texture(source_color, uv_interp + vec2(0.0, -2.0) * pix_size) * 0.06136;
+ frag_color = color;
+ }
+#endif
+
+#ifdef MODE_GAUSSIAN_GLOW
+
+ //Glow uses larger sigma 1 for a more rounded blur effect
+
+#define GLOW_ADD(m_ofs, m_mult) \
+ { \
+ vec2 ofs = uv_interp + m_ofs * pix_size; \
+ vec4 c = texture(source_color, ofs) * m_mult; \
+ if (any(lessThan(ofs, vec2(0.0))) || any(greaterThan(ofs, vec2(1.0)))) { \
+ c *= 0.0; \
+ } \
+ color += c; \
+ }
+
+ if (bool(blur.flags & FLAG_HORIZONTAL)) {
+ vec2 pix_size = blur.pixel_size;
+ pix_size *= 0.5; //reading from larger buffer, so use more samples
+ vec4 color = texture(source_color, uv_interp + vec2(0.0, 0.0) * pix_size) * 0.174938;
+ GLOW_ADD(vec2(1.0, 0.0), 0.165569);
+ GLOW_ADD(vec2(2.0, 0.0), 0.140367);
+ GLOW_ADD(vec2(3.0, 0.0), 0.106595);
+ GLOW_ADD(vec2(-1.0, 0.0), 0.165569);
+ GLOW_ADD(vec2(-2.0, 0.0), 0.140367);
+ GLOW_ADD(vec2(-3.0, 0.0), 0.106595);
+ color *= blur.glow_strength;
+ frag_color = color;
+ } else {
+ vec2 pix_size = blur.pixel_size;
+ vec4 color = texture(source_color, uv_interp + vec2(0.0, 0.0) * pix_size) * 0.288713;
+ GLOW_ADD(vec2(0.0, 1.0), 0.233062);
+ GLOW_ADD(vec2(0.0, 2.0), 0.122581);
+ GLOW_ADD(vec2(0.0, -1.0), 0.233062);
+ GLOW_ADD(vec2(0.0, -2.0), 0.122581);
+ color *= blur.glow_strength;
+ frag_color = color;
+ }
+
+#undef GLOW_ADD
+
+ if (bool(blur.flags & FLAG_GLOW_FIRST_PASS)) {
+#ifdef GLOW_USE_AUTO_EXPOSURE
+
+ frag_color /= texelFetch(source_auto_exposure, ivec2(0, 0), 0).r / blur.glow_auto_exposure_grey;
+#endif
+ frag_color *= blur.glow_exposure;
+
+ float luminance = max(frag_color.r, max(frag_color.g, frag_color.b));
+ float feedback = max(smoothstep(blur.glow_hdr_threshold, blur.glow_hdr_threshold + blur.glow_hdr_scale, luminance), blur.glow_bloom);
+
+ frag_color = min(frag_color * feedback, vec4(blur.glow_luminance_cap));
+ }
+
+#endif
+
+#ifdef MODE_COPY
+ vec4 color = textureLod(source_color, uv_interp, 0.0);
+ frag_color = color;
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/blur_raster_inc.glsl b/servers/rendering/renderer_rd/shaders/blur_raster_inc.glsl
new file mode 100644
index 0000000000..52bf2886b5
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/blur_raster_inc.glsl
@@ -0,0 +1,21 @@
+#define FLAG_HORIZONTAL (1 << 0)
+#define FLAG_USE_ORTHOGONAL_PROJECTION (1 << 1)
+#define FLAG_GLOW_FIRST_PASS (1 << 2)
+
+layout(push_constant, binding = 1, std430) uniform Blur {
+ vec2 pixel_size;
+ uint flags;
+ uint pad;
+
+ // Glow.
+ float glow_strength;
+ float glow_bloom;
+ float glow_hdr_threshold;
+ float glow_hdr_scale;
+
+ float glow_exposure;
+ float glow_white;
+ float glow_luminance_cap;
+ float glow_auto_exposure_grey;
+}
+blur;
diff --git a/servers/rendering/renderer_rd/shaders/bokeh_dof.glsl b/servers/rendering/renderer_rd/shaders/bokeh_dof.glsl
new file mode 100644
index 0000000000..0438671dd2
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/bokeh_dof.glsl
@@ -0,0 +1,215 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define BLOCK_SIZE 8
+
+layout(local_size_x = BLOCK_SIZE, local_size_y = BLOCK_SIZE, local_size_z = 1) in;
+
+#ifdef MODE_GEN_BLUR_SIZE
+layout(rgba16f, set = 0, binding = 0) uniform restrict image2D color_image;
+layout(set = 1, binding = 0) uniform sampler2D source_depth;
+#endif
+
+#if defined(MODE_BOKEH_BOX) || defined(MODE_BOKEH_HEXAGONAL) || defined(MODE_BOKEH_CIRCULAR)
+layout(set = 1, binding = 0) uniform sampler2D color_texture;
+layout(rgba16f, set = 0, binding = 0) uniform restrict writeonly image2D bokeh_image;
+#endif
+
+#ifdef MODE_COMPOSITE_BOKEH
+layout(rgba16f, set = 0, binding = 0) uniform restrict image2D color_image;
+layout(set = 1, binding = 0) uniform sampler2D source_bokeh;
+#endif
+
+// based on https://www.shadertoy.com/view/Xd3GDl
+
+#include "bokeh_dof_inc.glsl"
+
+#ifdef MODE_GEN_BLUR_SIZE
+
+float get_depth_at_pos(vec2 uv) {
+ float depth = textureLod(source_depth, uv, 0.0).x;
+ if (params.orthogonal) {
+ depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+ } else {
+ depth = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - depth * (params.z_far - params.z_near));
+ }
+ return depth;
+}
+
+float get_blur_size(float depth) {
+ if (params.blur_near_active && depth < params.blur_near_begin) {
+ return -(1.0 - smoothstep(params.blur_near_end, params.blur_near_begin, depth)) * params.blur_size - DEPTH_GAP; //near blur is negative
+ }
+
+ if (params.blur_far_active && depth > params.blur_far_begin) {
+ return smoothstep(params.blur_far_begin, params.blur_far_end, depth) * params.blur_size + DEPTH_GAP;
+ }
+
+ return 0.0;
+}
+
+#endif
+
+#if defined(MODE_BOKEH_BOX) || defined(MODE_BOKEH_HEXAGONAL)
+
+vec4 weighted_filter_dir(vec2 dir, vec2 uv, vec2 pixel_size) {
+ dir *= pixel_size;
+ vec4 color = texture(color_texture, uv);
+
+ vec4 accum = color;
+ float total = 1.0;
+
+ float blur_scale = params.blur_size / float(params.blur_steps);
+
+ if (params.use_jitter) {
+ uv += dir * (hash12n(uv + params.jitter_seed) - 0.5);
+ }
+
+ for (int i = -params.blur_steps; i <= params.blur_steps; i++) {
+ if (i == 0) {
+ continue;
+ }
+ float radius = float(i) * blur_scale;
+ vec2 suv = uv + dir * radius;
+ radius = abs(radius);
+
+ vec4 sample_color = texture(color_texture, suv);
+ float limit;
+
+ if (sample_color.a < color.a) {
+ limit = abs(sample_color.a);
+ } else {
+ limit = abs(color.a);
+ }
+
+ limit -= DEPTH_GAP;
+
+ float m = smoothstep(radius - 0.5, radius + 0.5, limit);
+
+ accum += mix(color, sample_color, m);
+
+ total += 1.0;
+ }
+
+ return accum / total;
+}
+
+#endif
+
+void main() {
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+
+ if (any(greaterThan(pos, params.size))) { //too large, do nothing
+ return;
+ }
+
+ vec2 pixel_size = 1.0 / vec2(params.size);
+ vec2 uv = vec2(pos) / vec2(params.size);
+
+#ifdef MODE_GEN_BLUR_SIZE
+ uv += pixel_size * 0.5;
+ //precompute size in alpha channel
+ float depth = get_depth_at_pos(uv);
+ float size = get_blur_size(depth);
+
+ vec4 color = imageLoad(color_image, pos);
+ color.a = size;
+ imageStore(color_image, pos, color);
+#endif
+
+#ifdef MODE_BOKEH_BOX
+
+ //pixel_size*=0.5; //resolution is doubled
+ if (params.second_pass || !params.half_size) {
+ uv += pixel_size * 0.5; //half pixel to read centers
+ } else {
+ uv += pixel_size * 0.25; //half pixel to read centers from full res
+ }
+
+ vec2 dir = (params.second_pass ? vec2(0.0, 1.0) : vec2(1.0, 0.0));
+
+ vec4 color = weighted_filter_dir(dir, uv, pixel_size);
+
+ imageStore(bokeh_image, pos, color);
+
+#endif
+
+#ifdef MODE_BOKEH_HEXAGONAL
+
+ //pixel_size*=0.5; //resolution is doubled
+ if (params.second_pass || !params.half_size) {
+ uv += pixel_size * 0.5; //half pixel to read centers
+ } else {
+ uv += pixel_size * 0.25; //half pixel to read centers from full res
+ }
+
+ vec2 dir = (params.second_pass ? normalize(vec2(1.0, 0.577350269189626)) : vec2(0.0, 1.0));
+
+ vec4 color = weighted_filter_dir(dir, uv, pixel_size);
+
+ if (params.second_pass) {
+ dir = normalize(vec2(-1.0, 0.577350269189626));
+
+ vec4 color2 = weighted_filter_dir(dir, uv, pixel_size);
+
+ color.rgb = min(color.rgb, color2.rgb);
+ color.a = (color.a + color2.a) * 0.5;
+ }
+
+ imageStore(bokeh_image, pos, color);
+
+#endif
+
+#ifdef MODE_BOKEH_CIRCULAR
+
+ if (params.half_size) {
+ pixel_size *= 0.5; //resolution is doubled
+ }
+
+ uv += pixel_size * 0.5; //half pixel to read centers
+
+ vec4 color = texture(color_texture, uv);
+ float accum = 1.0;
+ float radius = params.blur_scale;
+
+ for (float ang = 0.0; radius < params.blur_size; ang += GOLDEN_ANGLE) {
+ vec2 suv = uv + vec2(cos(ang), sin(ang)) * pixel_size * radius;
+ vec4 sample_color = texture(color_texture, suv);
+ float sample_size = abs(sample_color.a);
+ if (sample_color.a > color.a) {
+ sample_size = clamp(sample_size, 0.0, abs(color.a) * 2.0);
+ }
+
+ float m = smoothstep(radius - 0.5, radius + 0.5, sample_size);
+ color += mix(color / accum, sample_color, m);
+ accum += 1.0;
+ radius += params.blur_scale / radius;
+ }
+
+ color /= accum;
+
+ imageStore(bokeh_image, pos, color);
+#endif
+
+#ifdef MODE_COMPOSITE_BOKEH
+
+ uv += pixel_size * 0.5;
+ vec4 color = imageLoad(color_image, pos);
+ vec4 bokeh = texture(source_bokeh, uv);
+
+ float mix_amount;
+ if (bokeh.a < color.a) {
+ mix_amount = min(1.0, max(0.0, max(abs(color.a), abs(bokeh.a)) - DEPTH_GAP));
+ } else {
+ mix_amount = min(1.0, max(0.0, abs(color.a) - DEPTH_GAP));
+ }
+
+ color.rgb = mix(color.rgb, bokeh.rgb, mix_amount); //blend between hires and lowres
+
+ color.a = 0; //reset alpha
+ imageStore(color_image, pos, color);
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/bokeh_dof_inc.glsl b/servers/rendering/renderer_rd/shaders/bokeh_dof_inc.glsl
new file mode 100644
index 0000000000..fadea1631c
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/bokeh_dof_inc.glsl
@@ -0,0 +1,37 @@
+layout(push_constant, binding = 1, std430) uniform Params {
+ ivec2 size;
+ float z_far;
+ float z_near;
+
+ bool orthogonal;
+ float blur_size;
+ float blur_scale;
+ int blur_steps;
+
+ bool blur_near_active;
+ float blur_near_begin;
+ float blur_near_end;
+ bool blur_far_active;
+
+ float blur_far_begin;
+ float blur_far_end;
+ bool second_pass;
+ bool half_size;
+
+ bool use_jitter;
+ float jitter_seed;
+ uint pad[2];
+}
+params;
+
+//used to work around downsampling filter
+#define DEPTH_GAP 0.0
+
+const float GOLDEN_ANGLE = 2.39996323;
+
+//note: uniform pdf rand [0;1[
+float hash12n(vec2 p) {
+ p = fract(p * vec2(5.3987, 5.4421));
+ p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));
+ return fract(p.x * p.y * 95.4307);
+}
diff --git a/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl b/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl
new file mode 100644
index 0000000000..43a2a29616
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl
@@ -0,0 +1,253 @@
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "bokeh_dof_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "bokeh_dof_inc.glsl"
+
+layout(location = 0) in vec2 uv_interp;
+/* clang-format on */
+
+#ifdef MODE_GEN_BLUR_SIZE
+layout(location = 0) out float weight;
+
+layout(set = 0, binding = 0) uniform sampler2D source_depth;
+#else
+layout(location = 0) out vec4 frag_color;
+#ifdef OUTPUT_WEIGHT
+layout(location = 1) out float weight;
+#endif
+
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+layout(set = 1, binding = 0) uniform sampler2D source_weight;
+#ifdef MODE_COMPOSITE_BOKEH
+layout(set = 2, binding = 0) uniform sampler2D original_weight;
+#endif
+#endif
+
+//DOF
+// Bokeh single pass implementation based on http://tuxedolabs.blogspot.com/2018/05/bokeh-depth-of-field-in-single-pass.html
+
+#ifdef MODE_GEN_BLUR_SIZE
+
+float get_depth_at_pos(vec2 uv) {
+ float depth = textureLod(source_depth, uv, 0.0).x;
+ if (params.orthogonal) {
+ depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+ } else {
+ depth = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - depth * (params.z_far - params.z_near));
+ }
+ return depth;
+}
+
+float get_blur_size(float depth) {
+ if (params.blur_near_active && depth < params.blur_near_begin) {
+ return -(1.0 - smoothstep(params.blur_near_end, params.blur_near_begin, depth)) * params.blur_size - DEPTH_GAP; //near blur is negative
+ }
+
+ if (params.blur_far_active && depth > params.blur_far_begin) {
+ return smoothstep(params.blur_far_begin, params.blur_far_end, depth) * params.blur_size + DEPTH_GAP;
+ }
+
+ return 0.0;
+}
+
+#endif
+
+#if defined(MODE_BOKEH_BOX) || defined(MODE_BOKEH_HEXAGONAL)
+
+vec4 weighted_filter_dir(vec2 dir, vec2 uv, vec2 pixel_size) {
+ dir *= pixel_size;
+ vec4 color = texture(source_color, uv);
+ color.a = texture(source_weight, uv).r;
+
+ vec4 accum = color;
+ float total = 1.0;
+
+ float blur_scale = params.blur_size / float(params.blur_steps);
+
+ if (params.use_jitter) {
+ uv += dir * (hash12n(uv + params.jitter_seed) - 0.5);
+ }
+
+ for (int i = -params.blur_steps; i <= params.blur_steps; i++) {
+ if (i == 0) {
+ continue;
+ }
+ float radius = float(i) * blur_scale;
+ vec2 suv = uv + dir * radius;
+ radius = abs(radius);
+
+ vec4 sample_color = texture(source_color, suv);
+ sample_color.a = texture(source_weight, suv).r;
+ float limit;
+
+ if (sample_color.a < color.a) {
+ limit = abs(sample_color.a);
+ } else {
+ limit = abs(color.a);
+ }
+
+ limit -= DEPTH_GAP;
+
+ float m = smoothstep(radius - 0.5, radius + 0.5, limit);
+
+ accum += mix(color, sample_color, m);
+
+ total += 1.0;
+ }
+
+ return accum / total;
+}
+
+#endif
+
+void main() {
+ vec2 pixel_size = 1.0 / vec2(params.size);
+ vec2 uv = uv_interp;
+
+#ifdef MODE_GEN_BLUR_SIZE
+ uv += pixel_size * 0.5;
+ float center_depth = get_depth_at_pos(uv);
+ weight = get_blur_size(center_depth);
+#endif
+
+#ifdef MODE_BOKEH_BOX
+ //pixel_size*=0.5; //resolution is doubled
+ if (params.second_pass || !params.half_size) {
+ uv += pixel_size * 0.5; //half pixel to read centers
+ } else {
+ uv += pixel_size * 0.25; //half pixel to read centers from full res
+ }
+
+ float alpha = texture(source_color, uv).a; // retain this
+ vec2 dir = (params.second_pass ? vec2(0.0, 1.0) : vec2(1.0, 0.0));
+
+ vec4 color = weighted_filter_dir(dir, uv, pixel_size);
+
+ frag_color = color;
+ frag_color.a = alpha; // attempt to retain this in case we have a transparent background, ignored if half_size
+#ifdef OUTPUT_WEIGHT
+ weight = color.a;
+#endif
+
+#endif
+
+#ifdef MODE_BOKEH_HEXAGONAL
+
+ //pixel_size*=0.5; //resolution is doubled
+ if (params.second_pass || !params.half_size) {
+ uv += pixel_size * 0.5; //half pixel to read centers
+ } else {
+ uv += pixel_size * 0.25; //half pixel to read centers from full res
+ }
+
+ float alpha = texture(source_color, uv).a; // retain this
+
+ vec2 dir = (params.second_pass ? normalize(vec2(1.0, 0.577350269189626)) : vec2(0.0, 1.0));
+
+ vec4 color = weighted_filter_dir(dir, uv, pixel_size);
+
+ if (params.second_pass) {
+ dir = normalize(vec2(-1.0, 0.577350269189626));
+
+ vec4 color2 = weighted_filter_dir(dir, uv, pixel_size);
+
+ color.rgb = min(color.rgb, color2.rgb);
+ color.a = (color.a + color2.a) * 0.5;
+ }
+
+ frag_color = color;
+ frag_color.a = alpha; // attempt to retain this in case we have a transparent background, ignored if half_size
+#ifdef OUTPUT_WEIGHT
+ weight = color.a;
+#endif
+
+#endif
+
+#ifdef MODE_BOKEH_CIRCULAR
+ if (params.half_size) {
+ pixel_size *= 0.5; //resolution is doubled
+ }
+
+ uv += pixel_size * 0.5; //half pixel to read centers
+
+ vec4 color = texture(source_color, uv);
+ float alpha = color.a; // retain this
+ color.a = texture(source_weight, uv).r;
+
+ vec4 color_accum = color;
+ float accum = 1.0;
+
+ float radius = params.blur_scale;
+ for (float ang = 0.0; radius < params.blur_size; ang += GOLDEN_ANGLE) {
+ vec2 uv_adj = uv + vec2(cos(ang), sin(ang)) * pixel_size * radius;
+
+ vec4 sample_color = texture(source_color, uv_adj);
+ sample_color.a = texture(source_weight, uv_adj).r;
+
+ float limit;
+
+ if (sample_color.a < color.a) {
+ limit = abs(sample_color.a);
+ } else {
+ limit = abs(color.a);
+ }
+
+ limit -= DEPTH_GAP;
+
+ float m = smoothstep(radius - 0.5, radius + 0.5, limit);
+ color_accum += mix(color_accum / accum, sample_color, m);
+ accum += 1.0;
+
+ radius += params.blur_scale / radius;
+ }
+
+ color_accum = color_accum / accum;
+
+ frag_color.rgb = color_accum.rgb;
+ frag_color.a = alpha; // attempt to retain this in case we have a transparent background, ignored if half_size
+#ifdef OUTPUT_WEIGHT
+ weight = color_accum.a;
+#endif
+
+#endif
+
+#ifdef MODE_COMPOSITE_BOKEH
+ frag_color.rgb = texture(source_color, uv).rgb;
+
+ float center_weigth = texture(source_weight, uv).r;
+ float sample_weight = texture(original_weight, uv).r;
+
+ float mix_amount;
+ if (sample_weight < center_weigth) {
+ mix_amount = min(1.0, max(0.0, max(abs(center_weigth), abs(sample_weight)) - DEPTH_GAP));
+ } else {
+ mix_amount = min(1.0, max(0.0, abs(center_weigth) - DEPTH_GAP));
+ }
+
+ // let alpha blending take care of mixing
+ frag_color.a = mix_amount;
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
new file mode 100644
index 0000000000..a443bcdcb8
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -0,0 +1,704 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef USE_ATTRIBUTES
+layout(location = 0) in vec2 vertex_attrib;
+layout(location = 3) in vec4 color_attrib;
+layout(location = 4) in vec2 uv_attrib;
+
+layout(location = 10) in uvec4 bone_attrib;
+layout(location = 11) in vec4 weight_attrib;
+
+#endif
+
+#include "canvas_uniforms_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+layout(location = 1) out vec4 color_interp;
+layout(location = 2) out vec2 vertex_interp;
+
+#ifdef USE_NINEPATCH
+
+layout(location = 3) out vec2 pixel_size_interp;
+
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = 1, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#GLOBALS
+
+void main() {
+ vec4 instance_custom = vec4(0.0);
+#ifdef USE_PRIMITIVE
+
+ //weird bug,
+ //this works
+ vec2 vertex;
+ vec2 uv;
+ vec4 color;
+
+ if (gl_VertexIndex == 0) {
+ vertex = draw_data.points[0];
+ uv = draw_data.uvs[0];
+ color = vec4(unpackHalf2x16(draw_data.colors[0]), unpackHalf2x16(draw_data.colors[1]));
+ } else if (gl_VertexIndex == 1) {
+ vertex = draw_data.points[1];
+ uv = draw_data.uvs[1];
+ color = vec4(unpackHalf2x16(draw_data.colors[2]), unpackHalf2x16(draw_data.colors[3]));
+ } else {
+ vertex = draw_data.points[2];
+ uv = draw_data.uvs[2];
+ color = vec4(unpackHalf2x16(draw_data.colors[4]), unpackHalf2x16(draw_data.colors[5]));
+ }
+ uvec4 bones = uvec4(0, 0, 0, 0);
+ vec4 bone_weights = vec4(0.0);
+
+#elif defined(USE_ATTRIBUTES)
+
+ vec2 vertex = vertex_attrib;
+ vec4 color = color_attrib * draw_data.modulation;
+ vec2 uv = uv_attrib;
+
+ uvec4 bones = bone_attrib;
+ vec4 bone_weights = weight_attrib;
+#else
+
+ vec2 vertex_base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
+ vec2 vertex_base = vertex_base_arr[gl_VertexIndex];
+
+ vec2 uv = draw_data.src_rect.xy + abs(draw_data.src_rect.zw) * ((draw_data.flags & FLAGS_TRANSPOSE_RECT) != 0 ? vertex_base.yx : vertex_base.xy);
+ vec4 color = draw_data.modulation;
+ vec2 vertex = draw_data.dst_rect.xy + abs(draw_data.dst_rect.zw) * mix(vertex_base, vec2(1.0, 1.0) - vertex_base, lessThan(draw_data.src_rect.zw, vec2(0.0, 0.0)));
+ uvec4 bones = uvec4(0, 0, 0, 0);
+
+#endif
+
+ mat4 world_matrix = mat4(vec4(draw_data.world_x, 0.0, 0.0), vec4(draw_data.world_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(draw_data.world_ofs, 0.0, 1.0));
+
+#define FLAGS_INSTANCING_MASK 0x7F
+#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+
+ uint instancing = draw_data.flags & FLAGS_INSTANCING_MASK;
+
+#ifdef USE_ATTRIBUTES
+
+ if (instancing > 1) {
+ // trails
+
+ uint stride = 2 + 1 + 1; //particles always uses this format
+
+ uint trail_size = instancing;
+
+ uint offset = trail_size * stride * gl_InstanceIndex;
+
+ vec4 pcolor;
+ vec2 new_vertex;
+ {
+ uint boffset = offset + bone_attrib.x * stride;
+ new_vertex = (vec4(vertex, 0.0, 1.0) * mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy * weight_attrib.x;
+ pcolor = transforms.data[boffset + 2] * weight_attrib.x;
+ }
+ if (weight_attrib.y > 0.001) {
+ uint boffset = offset + bone_attrib.y * stride;
+ new_vertex += (vec4(vertex, 0.0, 1.0) * mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy * weight_attrib.y;
+ pcolor += transforms.data[boffset + 2] * weight_attrib.y;
+ }
+ if (weight_attrib.z > 0.001) {
+ uint boffset = offset + bone_attrib.z * stride;
+ new_vertex += (vec4(vertex, 0.0, 1.0) * mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy * weight_attrib.z;
+ pcolor += transforms.data[boffset + 2] * weight_attrib.z;
+ }
+ if (weight_attrib.w > 0.001) {
+ uint boffset = offset + bone_attrib.w * stride;
+ new_vertex += (vec4(vertex, 0.0, 1.0) * mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy * weight_attrib.w;
+ pcolor += transforms.data[boffset + 2] * weight_attrib.w;
+ }
+
+ instance_custom = transforms.data[offset + 3];
+
+ vertex = new_vertex;
+ color *= pcolor;
+
+ } else
+#endif // USE_ATTRIBUTES
+
+ if (instancing == 1) {
+ uint stride = 2;
+ {
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ stride += 1;
+ }
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ stride += 1;
+ }
+ }
+
+ uint offset = stride * gl_InstanceIndex;
+
+ mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
+
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ color *= transforms.data[offset];
+ offset += 1;
+ }
+
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
+ }
+
+ matrix = transpose(matrix);
+ world_matrix = world_matrix * matrix;
+ }
+
+#if !defined(USE_ATTRIBUTES) && !defined(USE_PRIMITIVE)
+ if (bool(draw_data.flags & FLAGS_USING_PARTICLES)) {
+ //scale by texture size
+ vertex /= draw_data.color_texture_pixel_size;
+ }
+#endif
+
+#ifdef USE_POINT_SIZE
+ float point_size = 1.0;
+#endif
+ {
+#CODE : VERTEX
+ }
+
+#ifdef USE_NINEPATCH
+ pixel_size_interp = abs(draw_data.dst_rect.zw) * vertex_base;
+#endif
+
+#if !defined(SKIP_TRANSFORM_USED)
+ vertex = (world_matrix * vec4(vertex, 0.0, 1.0)).xy;
+#endif
+
+ color_interp = color;
+
+ if (canvas_data.use_pixel_snap) {
+ vertex = floor(vertex + 0.5);
+ // precision issue on some hardware creates artifacts within texture
+ // offset uv by a small amount to avoid
+ uv += 1e-5;
+ }
+
+#ifdef USE_ATTRIBUTES
+#if 0
+ if (bool(draw_data.flags & FLAGS_USE_SKELETON) && bone_weights != vec4(0.0)) { //must be a valid bone
+ //skeleton transform
+ ivec4 bone_indicesi = ivec4(bone_indices);
+
+ uvec2 tex_ofs = bone_indicesi.x * 2;
+
+ mat2x4 m;
+ m = mat2x4(
+ texelFetch(skeleton_buffer, tex_ofs + 0),
+ texelFetch(skeleton_buffer, tex_ofs + 1)) *
+ bone_weights.x;
+
+ tex_ofs = bone_indicesi.y * 2;
+
+ m += mat2x4(
+ texelFetch(skeleton_buffer, tex_ofs + 0),
+ texelFetch(skeleton_buffer, tex_ofs + 1)) *
+ bone_weights.y;
+
+ tex_ofs = bone_indicesi.z * 2;
+
+ m += mat2x4(
+ texelFetch(skeleton_buffer, tex_ofs + 0),
+ texelFetch(skeleton_buffer, tex_ofs + 1)) *
+ bone_weights.z;
+
+ tex_ofs = bone_indicesi.w * 2;
+
+ m += mat2x4(
+ texelFetch(skeleton_buffer, tex_ofs + 0),
+ texelFetch(skeleton_buffer, tex_ofs + 1)) *
+ bone_weights.w;
+
+ mat4 bone_matrix = skeleton_data.skeleton_transform * transpose(mat4(m[0], m[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))) * skeleton_data.skeleton_transform_inverse;
+
+ //outvec = bone_matrix * outvec;
+ }
+#endif
+#endif
+
+ vertex = (canvas_data.canvas_transform * vec4(vertex, 0.0, 1.0)).xy;
+
+ vertex_interp = vertex;
+ uv_interp = uv;
+
+ gl_Position = canvas_data.screen_transform * vec4(vertex, 0.0, 1.0);
+
+#ifdef USE_POINT_SIZE
+ gl_PointSize = point_size;
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "canvas_uniforms_inc.glsl"
+
+layout(location = 0) in vec2 uv_interp;
+layout(location = 1) in vec4 color_interp;
+layout(location = 2) in vec2 vertex_interp;
+
+#ifdef USE_NINEPATCH
+
+layout(location = 3) in vec2 pixel_size_interp;
+
+#endif
+
+layout(location = 0) out vec4 frag_color;
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = 1, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+vec2 screen_uv_to_sdf(vec2 p_uv) {
+ return canvas_data.screen_to_sdf * p_uv;
+}
+
+float texture_sdf(vec2 p_sdf) {
+ vec2 uv = p_sdf * canvas_data.sdf_to_tex.xy + canvas_data.sdf_to_tex.zw;
+ float d = texture(sampler2D(sdf_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv).r;
+ d *= SDF_MAX_LENGTH;
+ return d * canvas_data.tex_to_sdf;
+}
+
+vec2 texture_sdf_normal(vec2 p_sdf) {
+ vec2 uv = p_sdf * canvas_data.sdf_to_tex.xy + canvas_data.sdf_to_tex.zw;
+
+ const float EPSILON = 0.001;
+ return normalize(vec2(
+ texture(sampler2D(sdf_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv + vec2(EPSILON, 0.0)).r - texture(sampler2D(sdf_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv - vec2(EPSILON, 0.0)).r,
+ texture(sampler2D(sdf_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv + vec2(0.0, EPSILON)).r - texture(sampler2D(sdf_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv - vec2(0.0, EPSILON)).r));
+}
+
+vec2 sdf_to_screen_uv(vec2 p_sdf) {
+ return p_sdf * canvas_data.sdf_to_screen;
+}
+
+#GLOBALS
+
+#ifdef LIGHT_CODE_USED
+
+vec4 light_compute(
+ vec3 light_vertex,
+ vec3 light_position,
+ vec3 normal,
+ vec4 light_color,
+ float light_energy,
+ vec4 specular_shininess,
+ inout vec4 shadow_modulate,
+ vec2 screen_uv,
+ vec2 uv,
+ vec4 color, bool is_directional) {
+ vec4 light = vec4(0.0);
+
+#CODE : LIGHT
+
+ return light;
+}
+
+#endif
+
+#ifdef USE_NINEPATCH
+
+float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, float margin_begin, float margin_end, int np_repeat, inout int draw_center) {
+ float tex_size = 1.0 / tex_pixel_size;
+
+ if (pixel < margin_begin) {
+ return pixel * tex_pixel_size;
+ } else if (pixel >= draw_size - margin_end) {
+ return (tex_size - (draw_size - pixel)) * tex_pixel_size;
+ } else {
+ if (!bool(draw_data.flags & FLAGS_NINEPACH_DRAW_CENTER)) {
+ draw_center--;
+ }
+
+ // np_repeat is passed as uniform using NinePatchRect::AxisStretchMode enum.
+ if (np_repeat == 0) { // Stretch.
+ // Convert to ratio.
+ float ratio = (pixel - margin_begin) / (draw_size - margin_begin - margin_end);
+ // Scale to source texture.
+ return (margin_begin + ratio * (tex_size - margin_begin - margin_end)) * tex_pixel_size;
+ } else if (np_repeat == 1) { // Tile.
+ // Convert to offset.
+ float ofs = mod((pixel - margin_begin), tex_size - margin_begin - margin_end);
+ // Scale to source texture.
+ return (margin_begin + ofs) * tex_pixel_size;
+ } else if (np_repeat == 2) { // Tile Fit.
+ // Calculate scale.
+ float src_area = draw_size - margin_begin - margin_end;
+ float dst_area = tex_size - margin_begin - margin_end;
+ float scale = max(1.0, floor(src_area / max(dst_area, 0.0000001) + 0.5));
+ // Convert to ratio.
+ float ratio = (pixel - margin_begin) / src_area;
+ ratio = mod(ratio * scale, 1.0);
+ // Scale to source texture.
+ return (margin_begin + ratio * dst_area) * tex_pixel_size;
+ } else { // Shouldn't happen, but silences compiler warning.
+ return 0.0;
+ }
+ }
+}
+
+#endif
+
+#ifdef USE_LIGHTING
+
+vec3 light_normal_compute(vec3 light_vec, vec3 normal, vec3 base_color, vec3 light_color, vec4 specular_shininess, bool specular_shininess_used) {
+ float cNdotL = max(0.0, dot(normal, light_vec));
+
+ if (specular_shininess_used) {
+ //blinn
+ vec3 view = vec3(0.0, 0.0, 1.0); // not great but good enough
+ vec3 half_vec = normalize(view + light_vec);
+
+ float cNdotV = max(dot(normal, view), 0.0);
+ float cNdotH = max(dot(normal, half_vec), 0.0);
+ float cVdotH = max(dot(view, half_vec), 0.0);
+ float cLdotH = max(dot(light_vec, half_vec), 0.0);
+ float shininess = exp2(15.0 * specular_shininess.a + 1.0) * 0.25;
+ float blinn = pow(cNdotH, shininess);
+ blinn *= (shininess + 8.0) * (1.0 / (8.0 * M_PI));
+ float s = (blinn) / max(4.0 * cNdotV * cNdotL, 0.75);
+
+ return specular_shininess.rgb * light_color * s + light_color * base_color * cNdotL;
+ } else {
+ return light_color * base_color * cNdotL;
+ }
+}
+
+//float distance = length(shadow_pos);
+vec4 light_shadow_compute(uint light_base, vec4 light_color, vec4 shadow_uv
+#ifdef LIGHT_CODE_USED
+ ,
+ vec3 shadow_modulate
+#endif
+) {
+ float shadow;
+ uint shadow_mode = light_array.data[light_base].flags & LIGHT_FLAGS_FILTER_MASK;
+
+ if (shadow_mode == LIGHT_FLAGS_SHADOW_NEAREST) {
+ shadow = textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv, 0.0).x;
+ } else if (shadow_mode == LIGHT_FLAGS_SHADOW_PCF5) {
+ vec4 shadow_pixel_size = vec4(light_array.data[light_base].shadow_pixel_size, 0.0, 0.0, 0.0);
+ shadow = 0.0;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 2.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 2.0, 0.0).x;
+ shadow /= 5.0;
+ } else { //PCF13
+ vec4 shadow_pixel_size = vec4(light_array.data[light_base].shadow_pixel_size, 0.0, 0.0, 0.0);
+ shadow = 0.0;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 6.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 5.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 4.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 3.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size * 2.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv - shadow_pixel_size, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 2.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 3.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 4.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 5.0, 0.0).x;
+ shadow += textureProjLod(sampler2DShadow(shadow_atlas_texture, shadow_sampler), shadow_uv + shadow_pixel_size * 6.0, 0.0).x;
+ shadow /= 13.0;
+ }
+
+ vec4 shadow_color = unpackUnorm4x8(light_array.data[light_base].shadow_color);
+#ifdef LIGHT_CODE_USED
+ shadow_color.rgb *= shadow_modulate;
+#endif
+
+ shadow_color.a *= light_color.a; //respect light alpha
+
+ return mix(light_color, shadow_color, shadow);
+}
+
+void light_blend_compute(uint light_base, vec4 light_color, inout vec3 color) {
+ uint blend_mode = light_array.data[light_base].flags & LIGHT_FLAGS_BLEND_MASK;
+
+ switch (blend_mode) {
+ case LIGHT_FLAGS_BLEND_MODE_ADD: {
+ color.rgb += light_color.rgb * light_color.a;
+ } break;
+ case LIGHT_FLAGS_BLEND_MODE_SUB: {
+ color.rgb -= light_color.rgb * light_color.a;
+ } break;
+ case LIGHT_FLAGS_BLEND_MODE_MIX: {
+ color.rgb = mix(color.rgb, light_color.rgb, light_color.a);
+ } break;
+ }
+}
+
+#endif
+
+void main() {
+ vec4 color = color_interp;
+ vec2 uv = uv_interp;
+ vec2 vertex = vertex_interp;
+
+#if !defined(USE_ATTRIBUTES) && !defined(USE_PRIMITIVE)
+
+#ifdef USE_NINEPATCH
+
+ int draw_center = 2;
+ uv = vec2(
+ map_ninepatch_axis(pixel_size_interp.x, abs(draw_data.dst_rect.z), draw_data.color_texture_pixel_size.x, draw_data.ninepatch_margins.x, draw_data.ninepatch_margins.z, int(draw_data.flags >> FLAGS_NINEPATCH_H_MODE_SHIFT) & 0x3, draw_center),
+ map_ninepatch_axis(pixel_size_interp.y, abs(draw_data.dst_rect.w), draw_data.color_texture_pixel_size.y, draw_data.ninepatch_margins.y, draw_data.ninepatch_margins.w, int(draw_data.flags >> FLAGS_NINEPATCH_V_MODE_SHIFT) & 0x3, draw_center));
+
+ if (draw_center == 0) {
+ color.a = 0.0;
+ }
+
+ uv = uv * draw_data.src_rect.zw + draw_data.src_rect.xy; //apply region if needed
+
+#endif
+ if (bool(draw_data.flags & FLAGS_CLIP_RECT_UV)) {
+ uv = clamp(uv, draw_data.src_rect.xy, draw_data.src_rect.xy + abs(draw_data.src_rect.zw));
+ }
+
+#endif
+
+ color *= texture(sampler2D(color_texture, texture_sampler), uv);
+
+ uint light_count = (draw_data.flags >> FLAGS_LIGHT_COUNT_SHIFT) & 0xF; //max 16 lights
+ bool using_light = light_count > 0 || canvas_data.directional_light_count > 0;
+
+ vec3 normal;
+
+#if defined(NORMAL_USED)
+ bool normal_used = true;
+#else
+ bool normal_used = false;
+#endif
+
+ if (normal_used || (using_light && bool(draw_data.flags & FLAGS_DEFAULT_NORMAL_MAP_USED))) {
+ normal.xy = texture(sampler2D(normal_texture, texture_sampler), uv).xy * vec2(2.0, -2.0) - vec2(1.0, -1.0);
+ normal.z = sqrt(1.0 - dot(normal.xy, normal.xy));
+ normal_used = true;
+ } else {
+ normal = vec3(0.0, 0.0, 1.0);
+ }
+
+ vec4 specular_shininess;
+
+#if defined(SPECULAR_SHININESS_USED)
+
+ bool specular_shininess_used = true;
+#else
+ bool specular_shininess_used = false;
+#endif
+
+ if (specular_shininess_used || (using_light && normal_used && bool(draw_data.flags & FLAGS_DEFAULT_SPECULAR_MAP_USED))) {
+ specular_shininess = texture(sampler2D(specular_texture, texture_sampler), uv);
+ specular_shininess *= unpackUnorm4x8(draw_data.specular_shininess);
+ specular_shininess_used = true;
+ } else {
+ specular_shininess = vec4(1.0);
+ }
+
+#if defined(SCREEN_UV_USED)
+ vec2 screen_uv = gl_FragCoord.xy * canvas_data.screen_pixel_size;
+#else
+ vec2 screen_uv = vec2(0.0);
+#endif
+
+ vec3 light_vertex = vec3(vertex, 0.0);
+ vec2 shadow_vertex = vertex;
+
+ {
+ float normal_map_depth = 1.0;
+
+#if defined(NORMAL_MAP_USED)
+ vec3 normal_map = vec3(0.0, 0.0, 1.0);
+ normal_used = true;
+#endif
+
+#CODE : FRAGMENT
+
+#if defined(NORMAL_MAP_USED)
+ normal = mix(vec3(0.0, 0.0, 1.0), normal_map * vec3(2.0, -2.0, 1.0) - vec3(1.0, -1.0, 0.0), normal_map_depth);
+#endif
+ }
+
+ if (normal_used) {
+ //convert by item transform
+ normal.xy = mat2(normalize(draw_data.world_x), normalize(draw_data.world_y)) * normal.xy;
+ //convert by canvas transform
+ normal = normalize((canvas_data.canvas_normal_transform * vec4(normal, 0.0)).xyz);
+ }
+
+ vec3 base_color = color.rgb;
+ if (bool(draw_data.flags & FLAGS_USING_LIGHT_MASK)) {
+ color = vec4(0.0); //invisible by default due to using light mask
+ }
+
+#ifdef MODE_LIGHT_ONLY
+ color = vec4(0.0);
+#else
+ color *= canvas_data.canvas_modulation;
+#endif
+
+#if defined(USE_LIGHTING) && !defined(MODE_UNSHADED)
+
+ // Directional Lights
+
+ for (uint i = 0; i < canvas_data.directional_light_count; i++) {
+ uint light_base = i;
+
+ vec2 direction = light_array.data[light_base].position;
+ vec4 light_color = light_array.data[light_base].color;
+
+#ifdef LIGHT_CODE_USED
+
+ vec4 shadow_modulate = vec4(1.0);
+ light_color = light_compute(light_vertex, vec3(direction, light_array.data[light_base].height), normal, light_color, light_color.a, specular_shininess, shadow_modulate, screen_uv, uv, color, true);
+#else
+
+ if (normal_used) {
+ vec3 light_vec = normalize(mix(vec3(direction, 0.0), vec3(0, 0, 1), light_array.data[light_base].height));
+ light_color.rgb = light_normal_compute(light_vec, normal, base_color, light_color.rgb, specular_shininess, specular_shininess_used);
+ }
+#endif
+
+ if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
+ vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+
+ vec4 shadow_uv = vec4(shadow_pos.x, light_array.data[light_base].shadow_y_ofs, shadow_pos.y * light_array.data[light_base].shadow_zfar_inv, 1.0);
+
+ light_color = light_shadow_compute(light_base, light_color, shadow_uv
+#ifdef LIGHT_CODE_USED
+ ,
+ shadow_modulate.rgb
+#endif
+ );
+ }
+
+ light_blend_compute(light_base, light_color, color.rgb);
+ }
+
+ // Positional Lights
+
+ for (uint i = 0; i < MAX_LIGHTS_PER_ITEM; i++) {
+ if (i >= light_count) {
+ break;
+ }
+ uint light_base;
+ if (i < 8) {
+ if (i < 4) {
+ light_base = draw_data.lights[0];
+ } else {
+ light_base = draw_data.lights[1];
+ }
+ } else {
+ if (i < 12) {
+ light_base = draw_data.lights[2];
+ } else {
+ light_base = draw_data.lights[3];
+ }
+ }
+ light_base >>= (i & 3) * 8;
+ light_base &= 0xFF;
+
+ vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+ vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
+ vec4 light_color = textureLod(sampler2D(atlas_texture, texture_sampler), tex_uv_atlas, 0.0);
+ vec4 light_base_color = light_array.data[light_base].color;
+
+#ifdef LIGHT_CODE_USED
+
+ vec4 shadow_modulate = vec4(1.0);
+ vec3 light_position = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
+
+ light_color.rgb *= light_base_color.rgb;
+ light_color = light_compute(light_vertex, light_position, normal, light_color, light_base_color.a, specular_shininess, shadow_modulate, screen_uv, uv, color, false);
+#else
+
+ light_color.rgb *= light_base_color.rgb * light_base_color.a;
+
+ if (normal_used) {
+ vec3 light_pos = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
+ vec3 pos = light_vertex;
+ vec3 light_vec = normalize(light_pos - pos);
+ float cNdotL = max(0.0, dot(normal, light_vec));
+
+ light_color.rgb = light_normal_compute(light_vec, normal, base_color, light_color.rgb, specular_shininess, specular_shininess_used);
+ }
+#endif
+ if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
+ //if outside the light texture, light color is zero
+ light_color.a = 0.0;
+ }
+
+ if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
+ vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+
+ vec2 pos_norm = normalize(shadow_pos);
+ vec2 pos_abs = abs(pos_norm);
+ vec2 pos_box = pos_norm / max(pos_abs.x, pos_abs.y);
+ vec2 pos_rot = pos_norm * mat2(vec2(0.7071067811865476, -0.7071067811865476), vec2(0.7071067811865476, 0.7071067811865476)); //is there a faster way to 45 degrees rot?
+ float tex_ofs;
+ float distance;
+ if (pos_rot.y > 0) {
+ if (pos_rot.x > 0) {
+ tex_ofs = pos_box.y * 0.125 + 0.125;
+ distance = shadow_pos.x;
+ } else {
+ tex_ofs = pos_box.x * -0.125 + (0.25 + 0.125);
+ distance = shadow_pos.y;
+ }
+ } else {
+ if (pos_rot.x < 0) {
+ tex_ofs = pos_box.y * -0.125 + (0.5 + 0.125);
+ distance = -shadow_pos.x;
+ } else {
+ tex_ofs = pos_box.x * 0.125 + (0.75 + 0.125);
+ distance = -shadow_pos.y;
+ }
+ }
+
+ distance *= light_array.data[light_base].shadow_zfar_inv;
+
+ //float distance = length(shadow_pos);
+ vec4 shadow_uv = vec4(tex_ofs, light_array.data[light_base].shadow_y_ofs, distance, 1.0);
+
+ light_color = light_shadow_compute(light_base, light_color, shadow_uv
+#ifdef LIGHT_CODE_USED
+ ,
+ shadow_modulate.rgb
+#endif
+ );
+ }
+
+ light_blend_compute(light_base, light_color, color.rgb);
+ }
+#endif
+
+ frag_color = color;
+}
diff --git a/servers/rendering/renderer_rd/shaders/canvas_occlusion.glsl b/servers/rendering/renderer_rd/shaders/canvas_occlusion.glsl
new file mode 100644
index 0000000000..9f89f4b3b7
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/canvas_occlusion.glsl
@@ -0,0 +1,59 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in highp vec3 vertex;
+
+layout(push_constant, binding = 0, std430) uniform Constants {
+ mat4 projection;
+ mat2x4 modelview;
+ vec2 direction;
+ float z_far;
+ float pad;
+}
+constants;
+
+#ifdef MODE_SHADOW
+layout(location = 0) out highp float depth;
+#endif
+
+void main() {
+ highp vec4 vtx = vec4(vertex, 1.0) * mat4(constants.modelview[0], constants.modelview[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+
+#ifdef MODE_SHADOW
+ depth = dot(constants.direction, vtx.xy);
+#endif
+ gl_Position = constants.projection * vtx;
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 0, std430) uniform Constants {
+ mat4 projection;
+ mat2x4 modelview;
+ vec2 direction;
+ float z_far;
+ float pad;
+}
+constants;
+
+#ifdef MODE_SHADOW
+layout(location = 0) in highp float depth;
+layout(location = 0) out highp float distance_buf;
+#else
+layout(location = 0) out highp float sdf_buf;
+#endif
+
+void main() {
+#ifdef MODE_SHADOW
+ distance_buf = depth / constants.z_far;
+#else
+ sdf_buf = 1.0;
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/canvas_sdf.glsl b/servers/rendering/renderer_rd/shaders/canvas_sdf.glsl
new file mode 100644
index 0000000000..2bdfbabfcf
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/canvas_sdf.glsl
@@ -0,0 +1,179 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout(r8, set = 0, binding = 1) uniform restrict readonly image2D src_pixels;
+layout(r16_snorm, set = 0, binding = 2) uniform restrict writeonly image2D dst_sdf;
+
+layout(rg16i, set = 0, binding = 3) uniform restrict readonly iimage2D src_process;
+layout(rg16i, set = 0, binding = 4) uniform restrict writeonly iimage2D dst_process;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec2 size;
+ int stride;
+ int shift;
+ ivec2 base_size;
+ uvec2 pad;
+}
+params;
+
+#define SDF_MAX_LENGTH 16384.0
+
+void main() {
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(pos, params.size))) { //too large, do nothing
+ return;
+ }
+
+#ifdef MODE_LOAD
+
+ bool solid = imageLoad(src_pixels, pos).r > 0.5;
+ imageStore(dst_process, pos, solid ? ivec4(ivec2(-32767), 0, 0) : ivec4(ivec2(32767), 0, 0));
+#endif
+
+#ifdef MODE_LOAD_SHRINK
+
+ int s = 1 << params.shift;
+ ivec2 base = pos << params.shift;
+ ivec2 center = base + ivec2(params.shift);
+
+ ivec2 rel = ivec2(32767);
+ float d = 1e20;
+ int found = 0;
+ int solid_found = 0;
+ for (int i = 0; i < s; i++) {
+ for (int j = 0; j < s; j++) {
+ ivec2 src_pos = base + ivec2(i, j);
+ if (any(greaterThanEqual(src_pos, params.base_size))) {
+ continue;
+ }
+ bool solid = imageLoad(src_pixels, src_pos).r > 0.5;
+ if (solid) {
+ float dist = length(vec2(src_pos - center));
+ if (dist < d) {
+ d = dist;
+ rel = src_pos;
+ }
+ solid_found++;
+ }
+ found++;
+ }
+ }
+
+ if (solid_found == found) {
+ //mark solid only if all are solid
+ rel = ivec2(-32767);
+ }
+
+ imageStore(dst_process, pos, ivec4(rel, 0, 0));
+#endif
+
+#ifdef MODE_PROCESS
+
+ ivec2 base = pos << params.shift;
+ ivec2 center = base + ivec2(params.shift);
+
+ ivec2 rel = imageLoad(src_process, pos).xy;
+
+ bool solid = rel.x < 0;
+
+ if (solid) {
+ rel = -rel - ivec2(1);
+ }
+
+ if (center != rel) {
+ //only process if it does not point to itself
+ const int ofs_table_size = 8;
+ const ivec2 ofs_table[ofs_table_size] = ivec2[](
+ ivec2(-1, -1),
+ ivec2(0, -1),
+ ivec2(+1, -1),
+
+ ivec2(-1, 0),
+ ivec2(+1, 0),
+
+ ivec2(-1, +1),
+ ivec2(0, +1),
+ ivec2(+1, +1));
+
+ float dist = length(vec2(rel - center));
+ for (int i = 0; i < ofs_table_size; i++) {
+ ivec2 src_pos = pos + ofs_table[i] * params.stride;
+ if (any(lessThan(src_pos, ivec2(0))) || any(greaterThanEqual(src_pos, params.size))) {
+ continue;
+ }
+ ivec2 src_rel = imageLoad(src_process, src_pos).xy;
+ bool src_solid = src_rel.x < 0;
+ if (src_solid) {
+ src_rel = -src_rel - ivec2(1);
+ }
+
+ if (src_solid != solid) {
+ src_rel = ivec2(src_pos << params.shift); //point to itself if of different type
+ }
+
+ float src_dist = length(vec2(src_rel - center));
+ if (src_dist < dist) {
+ dist = src_dist;
+ rel = src_rel;
+ }
+ }
+ }
+
+ if (solid) {
+ rel = -rel - ivec2(1);
+ }
+
+ imageStore(dst_process, pos, ivec4(rel, 0, 0));
+#endif
+
+#ifdef MODE_STORE
+
+ ivec2 rel = imageLoad(src_process, pos).xy;
+
+ bool solid = rel.x < 0;
+
+ if (solid) {
+ rel = -rel - ivec2(1);
+ }
+
+ float d = length(vec2(rel - pos));
+
+ if (solid) {
+ d = -d;
+ }
+
+ d /= SDF_MAX_LENGTH;
+ d = clamp(d, -1.0, 1.0);
+ imageStore(dst_sdf, pos, vec4(d));
+
+#endif
+
+#ifdef MODE_STORE_SHRINK
+
+ ivec2 base = pos << params.shift;
+ ivec2 center = base + ivec2(params.shift);
+
+ ivec2 rel = imageLoad(src_process, pos).xy;
+
+ bool solid = rel.x < 0;
+
+ if (solid) {
+ rel = -rel - ivec2(1);
+ }
+
+ float d = length(vec2(rel - center));
+
+ if (solid) {
+ d = -d;
+ }
+ d /= SDF_MAX_LENGTH;
+ d = clamp(d, -1.0, 1.0);
+ imageStore(dst_sdf, pos, vec4(d));
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
new file mode 100644
index 0000000000..451f9b0089
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
@@ -0,0 +1,160 @@
+
+#define MAX_LIGHTS_PER_ITEM 16
+
+#define M_PI 3.14159265359
+
+#define SDF_MAX_LENGTH 16384.0
+
+//1 means enabled, 2+ means trails in use
+#define FLAGS_INSTANCING_MASK 0x7F
+#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+
+#define FLAGS_CLIP_RECT_UV (1 << 9)
+#define FLAGS_TRANSPOSE_RECT (1 << 10)
+#define FLAGS_USING_LIGHT_MASK (1 << 11)
+#define FLAGS_NINEPACH_DRAW_CENTER (1 << 12)
+#define FLAGS_USING_PARTICLES (1 << 13)
+
+#define FLAGS_NINEPATCH_H_MODE_SHIFT 16
+#define FLAGS_NINEPATCH_V_MODE_SHIFT 18
+
+#define FLAGS_LIGHT_COUNT_SHIFT 20
+
+#define FLAGS_DEFAULT_NORMAL_MAP_USED (1 << 26)
+#define FLAGS_DEFAULT_SPECULAR_MAP_USED (1 << 27)
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+// Push Constant
+
+layout(push_constant, binding = 0, std430) uniform DrawData {
+ vec2 world_x;
+ vec2 world_y;
+ vec2 world_ofs;
+ uint flags;
+ uint specular_shininess;
+#ifdef USE_PRIMITIVE
+ vec2 points[3];
+ vec2 uvs[3];
+ uint colors[6];
+#else
+ vec4 modulation;
+ vec4 ninepatch_margins;
+ vec4 dst_rect; //for built-in rect and UV
+ vec4 src_rect;
+ vec2 pad;
+
+#endif
+ vec2 color_texture_pixel_size;
+ uint lights[4];
+}
+draw_data;
+
+// In vulkan, sets should always be ordered using the following logic:
+// Lower Sets: Sets that change format and layout less often
+// Higher sets: Sets that change format and layout very often
+// This is because changing a set for another with a different layout or format,
+// invalidates all the upper ones (as likely internal base offset changes)
+
+/* SET0: Globals */
+
+// The values passed per draw primitives are cached within it
+
+layout(set = 0, binding = 1, std140) uniform CanvasData {
+ mat4 canvas_transform;
+ mat4 screen_transform;
+ mat4 canvas_normal_transform;
+ vec4 canvas_modulation;
+ vec2 screen_pixel_size;
+ float time;
+ bool use_pixel_snap;
+
+ vec4 sdf_to_tex;
+ vec2 screen_to_sdf;
+ vec2 sdf_to_screen;
+
+ uint directional_light_count;
+ float tex_to_sdf;
+ uint pad1;
+ uint pad2;
+}
+canvas_data;
+
+#define LIGHT_FLAGS_BLEND_MASK (3 << 16)
+#define LIGHT_FLAGS_BLEND_MODE_ADD (0 << 16)
+#define LIGHT_FLAGS_BLEND_MODE_SUB (1 << 16)
+#define LIGHT_FLAGS_BLEND_MODE_MIX (2 << 16)
+#define LIGHT_FLAGS_BLEND_MODE_MASK (3 << 16)
+#define LIGHT_FLAGS_HAS_SHADOW (1 << 20)
+#define LIGHT_FLAGS_FILTER_SHIFT 22
+#define LIGHT_FLAGS_FILTER_MASK (3 << 22)
+#define LIGHT_FLAGS_SHADOW_NEAREST (0 << 22)
+#define LIGHT_FLAGS_SHADOW_PCF5 (1 << 22)
+#define LIGHT_FLAGS_SHADOW_PCF13 (2 << 22)
+
+struct Light {
+ mat2x4 texture_matrix; //light to texture coordinate matrix (transposed)
+ mat2x4 shadow_matrix; //light to shadow coordinate matrix (transposed)
+ vec4 color;
+
+ uint shadow_color; // packed
+ uint flags; //index to light texture
+ float shadow_pixel_size;
+ float height;
+
+ vec2 position;
+ float shadow_zfar_inv;
+ float shadow_y_ofs;
+
+ vec4 atlas_rect;
+};
+
+layout(set = 0, binding = 2, std140) uniform LightData {
+ Light data[MAX_LIGHTS];
+}
+light_array;
+
+layout(set = 0, binding = 3) uniform texture2D atlas_texture;
+layout(set = 0, binding = 4) uniform texture2D shadow_atlas_texture;
+
+layout(set = 0, binding = 5) uniform sampler shadow_sampler;
+
+layout(set = 0, binding = 6) uniform texture2D screen_texture;
+layout(set = 0, binding = 7) uniform texture2D sdf_texture;
+
+layout(set = 0, binding = 8) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 9, std430) restrict readonly buffer GlobalVariableData {
+ vec4 data[];
+}
+global_variables;
+
+/* SET1: Is reserved for the material */
+
+//
+
+/* SET2: Instancing and Skeleton */
+
+layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms {
+ vec4 data[];
+}
+transforms;
+
+/* SET3: Texture */
+
+layout(set = 3, binding = 0) uniform texture2D color_texture;
+layout(set = 3, binding = 1) uniform texture2D normal_texture;
+layout(set = 3, binding = 2) uniform texture2D specular_texture;
+layout(set = 3, binding = 3) uniform sampler texture_sampler;
diff --git a/servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl b/servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl
new file mode 100644
index 0000000000..8e616ebe1f
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl
@@ -0,0 +1,3 @@
+#define CLUSTER_COUNTER_SHIFT 20
+#define CLUSTER_POINTER_MASK ((1 << CLUSTER_COUNTER_SHIFT) - 1)
+#define CLUSTER_COUNTER_MASK 0xfff
diff --git a/servers/rendering/renderer_rd/shaders/cluster_debug.glsl b/servers/rendering/renderer_rd/shaders/cluster_debug.glsl
new file mode 100644
index 0000000000..40da2c6e5c
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cluster_debug.glsl
@@ -0,0 +1,115 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+const vec3 usage_gradient[33] = vec3[]( // 1 (none) + 32
+ vec3(0.14, 0.17, 0.23),
+ vec3(0.24, 0.44, 0.83),
+ vec3(0.23, 0.57, 0.84),
+ vec3(0.22, 0.71, 0.84),
+ vec3(0.22, 0.85, 0.83),
+ vec3(0.21, 0.85, 0.72),
+ vec3(0.21, 0.85, 0.57),
+ vec3(0.20, 0.85, 0.42),
+ vec3(0.20, 0.85, 0.27),
+ vec3(0.27, 0.86, 0.19),
+ vec3(0.51, 0.85, 0.19),
+ vec3(0.57, 0.86, 0.19),
+ vec3(0.62, 0.85, 0.19),
+ vec3(0.67, 0.86, 0.20),
+ vec3(0.73, 0.85, 0.20),
+ vec3(0.78, 0.85, 0.20),
+ vec3(0.83, 0.85, 0.20),
+ vec3(0.85, 0.82, 0.20),
+ vec3(0.85, 0.76, 0.20),
+ vec3(0.85, 0.81, 0.20),
+ vec3(0.85, 0.65, 0.20),
+ vec3(0.84, 0.60, 0.21),
+ vec3(0.84, 0.56, 0.21),
+ vec3(0.84, 0.51, 0.21),
+ vec3(0.84, 0.46, 0.21),
+ vec3(0.84, 0.41, 0.21),
+ vec3(0.84, 0.36, 0.21),
+ vec3(0.84, 0.31, 0.21),
+ vec3(0.84, 0.27, 0.21),
+ vec3(0.83, 0.22, 0.22),
+ vec3(0.83, 0.22, 0.27),
+ vec3(0.83, 0.22, 0.32),
+ vec3(1.00, 0.63, 0.70));
+layout(push_constant, binding = 0, std430) uniform Params {
+ uvec2 screen_size;
+ uvec2 cluster_screen_size;
+
+ uint cluster_shift;
+ uint cluster_type;
+ float z_near;
+ float z_far;
+
+ bool orthogonal;
+ uint max_cluster_element_count_div_32;
+ uint pad1;
+ uint pad2;
+}
+params;
+
+layout(set = 0, binding = 1, std430) buffer restrict readonly ClusterData {
+ uint data[];
+}
+cluster_data;
+
+layout(rgba16f, set = 0, binding = 2) uniform restrict writeonly image2D screen_buffer;
+layout(set = 0, binding = 3) uniform texture2D depth_buffer;
+layout(set = 0, binding = 4) uniform sampler depth_buffer_sampler;
+
+void main() {
+ uvec2 screen_pos = gl_GlobalInvocationID.xy;
+ if (any(greaterThanEqual(screen_pos, params.screen_size))) {
+ return;
+ }
+
+ uvec2 cluster_pos = screen_pos >> params.cluster_shift;
+
+ uint offset = cluster_pos.y * params.cluster_screen_size.x + cluster_pos.x;
+ offset += params.cluster_screen_size.x * params.cluster_screen_size.y * params.cluster_type;
+ offset *= (params.max_cluster_element_count_div_32 + 32);
+
+ //depth buffers generally can't be accessed via image API
+ float depth = texelFetch(sampler2D(depth_buffer, depth_buffer_sampler), ivec2(screen_pos), 0).r * 2.0 - 1.0;
+
+ if (params.orthogonal) {
+ depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+ } else {
+ depth = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - depth * (params.z_far - params.z_near));
+ }
+ depth /= params.z_far;
+
+ uint slice = uint(clamp(floor(depth * 32.0), 0.0, 31.0));
+ uint slice_minmax = cluster_data.data[offset + params.max_cluster_element_count_div_32 + slice];
+ uint item_min = slice_minmax & 0xFFFF;
+ uint item_max = slice_minmax >> 16;
+
+ uint item_count = 0;
+ for (uint i = 0; i < params.max_cluster_element_count_div_32; i++) {
+ uint slice_bits = cluster_data.data[offset + i];
+ while (slice_bits != 0) {
+ uint bit = findLSB(slice_bits);
+ uint item = i * 32 + bit;
+ if ((item >= item_min && item < item_max)) {
+ item_count++;
+ }
+ slice_bits &= ~(1 << bit);
+ }
+ }
+
+ item_count = min(item_count, 32);
+
+ vec3 color = usage_gradient[item_count];
+
+ color = mix(color * 1.2, color * 0.3, float(slice) / 31.0);
+
+ imageStore(screen_buffer, ivec2(screen_pos), vec4(color, 1.0));
+}
diff --git a/servers/rendering/renderer_rd/shaders/cluster_render.glsl b/servers/rendering/renderer_rd/shaders/cluster_render.glsl
new file mode 100644
index 0000000000..da7d189281
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cluster_render.glsl
@@ -0,0 +1,168 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec3 vertex_attrib;
+
+layout(location = 0) out float depth_interp;
+layout(location = 1) out flat uint element_index;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ uint base_index;
+ uint pad0;
+ uint pad1;
+ uint pad2;
+}
+params;
+
+layout(set = 0, binding = 1, std140) uniform State {
+ mat4 projection;
+
+ float inv_z_far;
+ uint screen_to_clusters_shift; // shift to obtain coordinates in block indices
+ uint cluster_screen_width; //
+ uint cluster_data_size; // how much data for a single cluster takes
+
+ uint cluster_depth_offset;
+ uint pad0;
+ uint pad1;
+ uint pad2;
+}
+state;
+
+struct RenderElement {
+ uint type; //0-4
+ bool touches_near;
+ bool touches_far;
+ uint original_index;
+ mat3x4 transform_inv;
+ vec3 scale;
+ uint pad;
+};
+
+layout(set = 0, binding = 2, std430) buffer restrict readonly RenderElements {
+ RenderElement data[];
+}
+render_elements;
+
+void main() {
+ element_index = params.base_index + gl_InstanceIndex;
+
+ vec3 vertex = vertex_attrib;
+ vertex *= render_elements.data[element_index].scale;
+
+ vertex = vec4(vertex, 1.0) * render_elements.data[element_index].transform_inv;
+ depth_interp = -vertex.z;
+
+ gl_Position = state.projection * vec4(vertex, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#if defined(has_GL_KHR_shader_subgroup_ballot) && defined(has_GL_KHR_shader_subgroup_arithmetic) && defined(has_GL_KHR_shader_subgroup_vote)
+
+#extension GL_KHR_shader_subgroup_ballot : enable
+#extension GL_KHR_shader_subgroup_arithmetic : enable
+#extension GL_KHR_shader_subgroup_vote : enable
+
+#define USE_SUBGROUPS
+#endif
+
+layout(location = 0) in float depth_interp;
+layout(location = 1) in flat uint element_index;
+
+layout(set = 0, binding = 1, std140) uniform State {
+ mat4 projection;
+ float inv_z_far;
+ uint screen_to_clusters_shift; // shift to obtain coordinates in block indices
+ uint cluster_screen_width; //
+ uint cluster_data_size; // how much data for a single cluster takes
+ uint cluster_depth_offset;
+ uint pad0;
+ uint pad1;
+ uint pad2;
+}
+state;
+
+//cluster data is layout linearly, each cell contains the follow information:
+// - list of bits for every element to mark as used, so (max_elem_count/32)*4 uints
+// - a uint for each element to mark the depth bits used when rendering (0-31)
+
+layout(set = 0, binding = 3, std430) buffer restrict ClusterRender {
+ uint data[];
+}
+cluster_render;
+
+void main() {
+ //convert from screen to cluster
+ uvec2 cluster = uvec2(gl_FragCoord.xy) >> state.screen_to_clusters_shift;
+
+ //get linear cluster offset from screen poss
+ uint cluster_offset = cluster.x + state.cluster_screen_width * cluster.y;
+ //multiply by data size to position at the beginning of the element list for this cluster
+ cluster_offset *= state.cluster_data_size;
+
+ //find the current element in the list and plot the bit to mark it as used
+ uint usage_write_offset = cluster_offset + (element_index >> 5);
+ uint usage_write_bit = 1 << (element_index & 0x1F);
+
+#ifdef USE_SUBGROUPS
+
+ uint cluster_thread_group_index;
+
+ if (!gl_HelperInvocation) {
+ //http://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf
+
+ uvec4 mask;
+
+ while (true) {
+ // find the cluster offset of the first active thread
+ // threads that did break; go inactive and no longer count
+ uint first = subgroupBroadcastFirst(cluster_offset);
+ // update the mask for thread that match this cluster
+ mask = subgroupBallot(first == cluster_offset);
+ if (first == cluster_offset) {
+ // This thread belongs to the group of threads that match this offset,
+ // so exit the loop.
+ break;
+ }
+ }
+
+ cluster_thread_group_index = subgroupBallotExclusiveBitCount(mask);
+
+ if (cluster_thread_group_index == 0) {
+ atomicOr(cluster_render.data[usage_write_offset], usage_write_bit);
+ }
+ }
+#else
+ if (!gl_HelperInvocation) {
+ atomicOr(cluster_render.data[usage_write_offset], usage_write_bit);
+ }
+#endif
+ //find the current element in the depth usage list and mark the current depth as used
+ float unit_depth = depth_interp * state.inv_z_far;
+
+ uint z_bit = clamp(uint(floor(unit_depth * 32.0)), 0, 31);
+
+ uint z_write_offset = cluster_offset + state.cluster_depth_offset + element_index;
+ uint z_write_bit = 1 << z_bit;
+
+#ifdef USE_SUBGROUPS
+ if (!gl_HelperInvocation) {
+ z_write_bit = subgroupOr(z_write_bit); //merge all Zs
+ if (cluster_thread_group_index == 0) {
+ atomicOr(cluster_render.data[z_write_offset], z_write_bit);
+ }
+ }
+#else
+ if (!gl_HelperInvocation) {
+ atomicOr(cluster_render.data[z_write_offset], z_write_bit);
+ }
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/cluster_store.glsl b/servers/rendering/renderer_rd/shaders/cluster_store.glsl
new file mode 100644
index 0000000000..b0606efa94
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cluster_store.glsl
@@ -0,0 +1,119 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ uint cluster_render_data_size; // how much data for a single cluster takes
+ uint max_render_element_count_div_32; //divided by 32
+ uvec2 cluster_screen_size;
+ uint render_element_count_div_32; //divided by 32
+
+ uint max_cluster_element_count_div_32; //divided by 32
+ uint pad1;
+ uint pad2;
+}
+params;
+
+layout(set = 0, binding = 1, std430) buffer restrict readonly ClusterRender {
+ uint data[];
+}
+cluster_render;
+
+layout(set = 0, binding = 2, std430) buffer restrict ClusterStore {
+ uint data[];
+}
+cluster_store;
+
+struct RenderElement {
+ uint type; //0-4
+ bool touches_near;
+ bool touches_far;
+ uint original_index;
+ mat3x4 transform_inv;
+ vec3 scale;
+ uint pad;
+};
+
+layout(set = 0, binding = 3, std430) buffer restrict readonly RenderElements {
+ RenderElement data[];
+}
+render_elements;
+
+void main() {
+ uvec2 pos = gl_GlobalInvocationID.xy;
+ if (any(greaterThanEqual(pos, params.cluster_screen_size))) {
+ return;
+ }
+
+ //counter for each type of render_element
+
+ //base offset for this cluster
+ uint base_offset = (pos.x + params.cluster_screen_size.x * pos.y);
+ uint src_offset = base_offset * params.cluster_render_data_size;
+
+ uint render_element_offset = 0;
+
+ //check all render_elements and see which one was written to
+ while (render_element_offset < params.render_element_count_div_32) {
+ uint bits = cluster_render.data[src_offset + render_element_offset];
+ while (bits != 0) {
+ //if bits exist, check the render_element
+ uint index_bit = findLSB(bits);
+ uint index = render_element_offset * 32 + index_bit;
+ uint type = render_elements.data[index].type;
+
+ uint z_range_offset = src_offset + params.max_render_element_count_div_32 + index;
+ uint z_range = cluster_render.data[z_range_offset];
+
+ //if object was written, z was written, but check just in case
+ if (z_range != 0) { //should always be > 0
+
+ uint from_z = findLSB(z_range);
+ uint to_z = findMSB(z_range) + 1;
+
+ if (render_elements.data[index].touches_near) {
+ from_z = 0;
+ }
+
+ if (render_elements.data[index].touches_far) {
+ to_z = 32;
+ }
+
+ // find cluster offset in the buffer used for indexing in the renderer
+ uint dst_offset = (base_offset + type * (params.cluster_screen_size.x * params.cluster_screen_size.y)) * (params.max_cluster_element_count_div_32 + 32);
+
+ uint orig_index = render_elements.data[index].original_index;
+ //store this index in the Z slices by setting the relevant bit
+ for (uint i = from_z; i < to_z; i++) {
+ uint slice_ofs = dst_offset + params.max_cluster_element_count_div_32 + i;
+
+ uint minmax = cluster_store.data[slice_ofs];
+
+ if (minmax == 0) {
+ minmax = 0xFFFF; //min 0, max 0xFFFF
+ }
+
+ uint elem_min = min(orig_index, minmax & 0xFFFF);
+ uint elem_max = max(orig_index + 1, minmax >> 16); //always store plus one, so zero means range is empty when not written to
+
+ minmax = elem_min | (elem_max << 16);
+ cluster_store.data[slice_ofs] = minmax;
+ }
+
+ uint store_word = orig_index >> 5;
+ uint store_bit = orig_index & 0x1F;
+
+ //store the actual render_element index at the end, so the rendering code can reference it
+ cluster_store.data[dst_offset + store_word] |= 1 << store_bit;
+ }
+
+ bits &= ~(1 << index_bit); //clear the bit to continue iterating
+ }
+
+ render_element_offset++;
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/copy.glsl b/servers/rendering/renderer_rd/shaders/copy.glsl
new file mode 100644
index 0000000000..4110a95ddb
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/copy.glsl
@@ -0,0 +1,279 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#define FLAG_HORIZONTAL (1 << 0)
+#define FLAG_USE_BLUR_SECTION (1 << 1)
+#define FLAG_USE_ORTHOGONAL_PROJECTION (1 << 2)
+#define FLAG_DOF_NEAR_FIRST_TAP (1 << 3)
+#define FLAG_GLOW_FIRST_PASS (1 << 4)
+#define FLAG_FLIP_Y (1 << 5)
+#define FLAG_FORCE_LUMINANCE (1 << 6)
+#define FLAG_COPY_ALL_SOURCE (1 << 7)
+#define FLAG_HIGH_QUALITY_GLOW (1 << 8)
+#define FLAG_ALPHA_TO_ONE (1 << 9)
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ ivec4 section;
+ ivec2 target;
+ uint flags;
+ uint pad;
+ // Glow.
+ float glow_strength;
+ float glow_bloom;
+ float glow_hdr_threshold;
+ float glow_hdr_scale;
+
+ float glow_exposure;
+ float glow_white;
+ float glow_luminance_cap;
+ float glow_auto_exposure_grey;
+ // DOF.
+ float camera_z_far;
+ float camera_z_near;
+ uint pad2[2];
+
+ vec4 set_color;
+}
+params;
+
+#ifdef MODE_CUBEMAP_ARRAY_TO_PANORAMA
+layout(set = 0, binding = 0) uniform samplerCubeArray source_color;
+#elif defined(MODE_CUBEMAP_TO_PANORAMA)
+layout(set = 0, binding = 0) uniform samplerCube source_color;
+#elif !defined(MODE_SET_COLOR)
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+#endif
+
+#ifdef GLOW_USE_AUTO_EXPOSURE
+layout(set = 1, binding = 0) uniform sampler2D source_auto_exposure;
+#endif
+
+#if defined(MODE_LINEARIZE_DEPTH_COPY) || defined(MODE_SIMPLE_COPY_DEPTH)
+layout(r32f, set = 3, binding = 0) uniform restrict writeonly image2D dest_buffer;
+#elif defined(DST_IMAGE_8BIT)
+layout(rgba8, set = 3, binding = 0) uniform restrict writeonly image2D dest_buffer;
+#else
+layout(rgba32f, set = 3, binding = 0) uniform restrict writeonly image2D dest_buffer;
+#endif
+
+#ifdef MODE_GAUSSIAN_GLOW
+shared vec4 local_cache[256];
+shared vec4 temp_cache[128];
+#endif
+
+void main() {
+ // Pixel being shaded
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+
+#ifndef MODE_GAUSSIAN_GLOW // Glow needs the extra threads
+ if (any(greaterThanEqual(pos, params.section.zw))) { //too large, do nothing
+ return;
+ }
+#endif
+
+#ifdef MODE_MIPMAP
+
+ ivec2 base_pos = (pos + params.section.xy) << 1;
+ vec4 color = texelFetch(source_color, base_pos, 0);
+ color += texelFetch(source_color, base_pos + ivec2(0, 1), 0);
+ color += texelFetch(source_color, base_pos + ivec2(1, 0), 0);
+ color += texelFetch(source_color, base_pos + ivec2(1, 1), 0);
+ color /= 4.0;
+
+ imageStore(dest_buffer, pos + params.target, color);
+#endif
+
+#ifdef MODE_GAUSSIAN_BLUR
+
+ //Simpler blur uses SIGMA2 for the gaussian kernel for a stronger effect
+
+ if (bool(params.flags & FLAG_HORIZONTAL)) {
+ ivec2 base_pos = (pos + params.section.xy) << 1;
+ vec4 color = texelFetch(source_color, base_pos + ivec2(0, 0), 0) * 0.214607;
+ color += texelFetch(source_color, base_pos + ivec2(1, 0), 0) * 0.189879;
+ color += texelFetch(source_color, base_pos + ivec2(2, 0), 0) * 0.131514;
+ color += texelFetch(source_color, base_pos + ivec2(3, 0), 0) * 0.071303;
+ color += texelFetch(source_color, base_pos + ivec2(-1, 0), 0) * 0.189879;
+ color += texelFetch(source_color, base_pos + ivec2(-2, 0), 0) * 0.131514;
+ color += texelFetch(source_color, base_pos + ivec2(-3, 0), 0) * 0.071303;
+ imageStore(dest_buffer, pos + params.target, color);
+ } else {
+ ivec2 base_pos = (pos + params.section.xy);
+ vec4 color = texelFetch(source_color, base_pos + ivec2(0, 0), 0) * 0.38774;
+ color += texelFetch(source_color, base_pos + ivec2(0, 1), 0) * 0.24477;
+ color += texelFetch(source_color, base_pos + ivec2(0, 2), 0) * 0.06136;
+ color += texelFetch(source_color, base_pos + ivec2(0, -1), 0) * 0.24477;
+ color += texelFetch(source_color, base_pos + ivec2(0, -2), 0) * 0.06136;
+ imageStore(dest_buffer, pos + params.target, color);
+ }
+#endif
+
+#ifdef MODE_GAUSSIAN_GLOW
+
+ // First pass copy texture into 16x16 local memory for every 8x8 thread block
+ vec2 quad_center_uv = clamp(vec2(gl_GlobalInvocationID.xy + gl_LocalInvocationID.xy - 3.5) / params.section.zw, vec2(0.5 / params.section.zw), vec2(1.0 - 1.5 / params.section.zw));
+ uint dest_index = gl_LocalInvocationID.x * 2 + gl_LocalInvocationID.y * 2 * 16;
+
+ if (bool(params.flags & FLAG_HIGH_QUALITY_GLOW)) {
+ vec2 quad_offset_uv = clamp((vec2(gl_GlobalInvocationID.xy + gl_LocalInvocationID.xy - 3.0)) / params.section.zw, vec2(0.5 / params.section.zw), vec2(1.0 - 1.5 / params.section.zw));
+
+ local_cache[dest_index] = (textureLod(source_color, quad_center_uv, 0) + textureLod(source_color, quad_offset_uv, 0)) * 0.5;
+ local_cache[dest_index + 1] = (textureLod(source_color, quad_center_uv + vec2(1.0 / params.section.z, 0.0), 0) + textureLod(source_color, quad_offset_uv + vec2(1.0 / params.section.z, 0.0), 0)) * 0.5;
+ local_cache[dest_index + 16] = (textureLod(source_color, quad_center_uv + vec2(0.0, 1.0 / params.section.w), 0) + textureLod(source_color, quad_offset_uv + vec2(0.0, 1.0 / params.section.w), 0)) * 0.5;
+ local_cache[dest_index + 16 + 1] = (textureLod(source_color, quad_center_uv + vec2(1.0 / params.section.zw), 0) + textureLod(source_color, quad_offset_uv + vec2(1.0 / params.section.zw), 0)) * 0.5;
+ } else {
+ local_cache[dest_index] = textureLod(source_color, quad_center_uv, 0);
+ local_cache[dest_index + 1] = textureLod(source_color, quad_center_uv + vec2(1.0 / params.section.z, 0.0), 0);
+ local_cache[dest_index + 16] = textureLod(source_color, quad_center_uv + vec2(0.0, 1.0 / params.section.w), 0);
+ local_cache[dest_index + 16 + 1] = textureLod(source_color, quad_center_uv + vec2(1.0 / params.section.zw), 0);
+ }
+
+ memoryBarrierShared();
+ barrier();
+
+ // Horizontal pass. Needs to copy into 8x16 chunk of local memory so vertical pass has full resolution
+ uint read_index = gl_LocalInvocationID.x + gl_LocalInvocationID.y * 32 + 4;
+ vec4 color_top = vec4(0.0);
+ color_top += local_cache[read_index] * 0.174938;
+ color_top += local_cache[read_index + 1] * 0.165569;
+ color_top += local_cache[read_index + 2] * 0.140367;
+ color_top += local_cache[read_index + 3] * 0.106595;
+ color_top += local_cache[read_index - 1] * 0.165569;
+ color_top += local_cache[read_index - 2] * 0.140367;
+ color_top += local_cache[read_index - 3] * 0.106595;
+
+ vec4 color_bottom = vec4(0.0);
+ color_bottom += local_cache[read_index + 16] * 0.174938;
+ color_bottom += local_cache[read_index + 1 + 16] * 0.165569;
+ color_bottom += local_cache[read_index + 2 + 16] * 0.140367;
+ color_bottom += local_cache[read_index + 3 + 16] * 0.106595;
+ color_bottom += local_cache[read_index - 1 + 16] * 0.165569;
+ color_bottom += local_cache[read_index - 2 + 16] * 0.140367;
+ color_bottom += local_cache[read_index - 3 + 16] * 0.106595;
+
+ // rotate samples to take advantage of cache coherency
+ uint write_index = gl_LocalInvocationID.y * 2 + gl_LocalInvocationID.x * 16;
+
+ temp_cache[write_index] = color_top;
+ temp_cache[write_index + 1] = color_bottom;
+
+ memoryBarrierShared();
+ barrier();
+
+ // Vertical pass
+ uint index = gl_LocalInvocationID.y + gl_LocalInvocationID.x * 16 + 4;
+ vec4 color = vec4(0.0);
+
+ color += temp_cache[index] * 0.174938;
+ color += temp_cache[index + 1] * 0.165569;
+ color += temp_cache[index + 2] * 0.140367;
+ color += temp_cache[index + 3] * 0.106595;
+ color += temp_cache[index - 1] * 0.165569;
+ color += temp_cache[index - 2] * 0.140367;
+ color += temp_cache[index - 3] * 0.106595;
+
+ color *= params.glow_strength;
+
+ if (bool(params.flags & FLAG_GLOW_FIRST_PASS)) {
+#ifdef GLOW_USE_AUTO_EXPOSURE
+
+ color /= texelFetch(source_auto_exposure, ivec2(0, 0), 0).r / params.glow_auto_exposure_grey;
+#endif
+ color *= params.glow_exposure;
+
+ float luminance = max(color.r, max(color.g, color.b));
+ float feedback = max(smoothstep(params.glow_hdr_threshold, params.glow_hdr_threshold + params.glow_hdr_scale, luminance), params.glow_bloom);
+
+ color = min(color * feedback, vec4(params.glow_luminance_cap));
+ }
+
+ imageStore(dest_buffer, pos + params.target, color);
+
+#endif
+
+#ifdef MODE_SIMPLE_COPY
+
+ vec4 color;
+ if (bool(params.flags & FLAG_COPY_ALL_SOURCE)) {
+ vec2 uv = vec2(pos) / vec2(params.section.zw);
+ if (bool(params.flags & FLAG_FLIP_Y)) {
+ uv.y = 1.0 - uv.y;
+ }
+ color = textureLod(source_color, uv, 0.0);
+
+ } else {
+ color = texelFetch(source_color, pos + params.section.xy, 0);
+
+ if (bool(params.flags & FLAG_FLIP_Y)) {
+ pos.y = params.section.w - pos.y - 1;
+ }
+ }
+
+ if (bool(params.flags & FLAG_FORCE_LUMINANCE)) {
+ color.rgb = vec3(max(max(color.r, color.g), color.b));
+ }
+
+ if (bool(params.flags & FLAG_ALPHA_TO_ONE)) {
+ color.a = 1.0;
+ }
+
+ imageStore(dest_buffer, pos + params.target, color);
+
+#endif
+
+#ifdef MODE_SIMPLE_COPY_DEPTH
+
+ vec4 color = texelFetch(source_color, pos + params.section.xy, 0);
+
+ if (bool(params.flags & FLAG_FLIP_Y)) {
+ pos.y = params.section.w - pos.y - 1;
+ }
+
+ imageStore(dest_buffer, pos + params.target, vec4(color.r));
+
+#endif
+
+#ifdef MODE_LINEARIZE_DEPTH_COPY
+
+ float depth = texelFetch(source_color, pos + params.section.xy, 0).r;
+ 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));
+ vec4 color = vec4(depth / params.camera_z_far);
+
+ if (bool(params.flags & FLAG_FLIP_Y)) {
+ pos.y = params.section.w - pos.y - 1;
+ }
+
+ imageStore(dest_buffer, pos + params.target, color);
+#endif
+
+#if defined(MODE_CUBEMAP_TO_PANORAMA) || defined(MODE_CUBEMAP_ARRAY_TO_PANORAMA)
+
+ const float PI = 3.14159265359;
+ vec2 uv = vec2(pos) / vec2(params.section.zw);
+ uv.y = 1.0 - uv.y;
+ float phi = uv.x * 2.0 * PI;
+ float theta = uv.y * PI;
+
+ vec3 normal;
+ normal.x = sin(phi) * sin(theta) * -1.0;
+ normal.y = cos(theta);
+ normal.z = cos(phi) * sin(theta) * -1.0;
+
+#ifdef MODE_CUBEMAP_TO_PANORAMA
+ vec4 color = textureLod(source_color, normal, params.camera_z_far); //the biggest the lod the least the acne
+#else
+ vec4 color = textureLod(source_color, vec4(normal, params.camera_z_far), 0.0); //the biggest the lod the least the acne
+#endif
+ imageStore(dest_buffer, pos + params.target, color);
+#endif
+
+#ifdef MODE_SET_COLOR
+ imageStore(dest_buffer, pos + params.target, params.set_color);
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl b/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl
new file mode 100644
index 0000000000..8c68e2dc2f
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl
@@ -0,0 +1,115 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) out vec2 uv_interp;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ vec4 section;
+ vec2 pixel_size;
+ bool flip_y;
+ bool use_section;
+
+ bool force_luminance;
+ uint pad[3];
+}
+params;
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+
+ vec2 vpos = uv_interp;
+ if (params.use_section) {
+ vpos = params.section.xy + vpos * params.section.zw;
+ }
+
+ gl_Position = vec4(vpos * 2.0 - 1.0, 0.0, 1.0);
+
+ if (params.flip_y) {
+ uv_interp.y = 1.0 - uv_interp.y;
+ }
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ vec4 section;
+ vec2 pixel_size;
+ bool flip_y;
+ bool use_section;
+
+ bool force_luminance;
+ bool alpha_to_zero;
+ bool srgb;
+ uint pad;
+}
+params;
+
+layout(location = 0) in vec2 uv_interp;
+
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+#ifdef MODE_TWO_SOURCES
+layout(set = 1, binding = 0) uniform sampler2D source_color2;
+#endif
+layout(location = 0) out vec4 frag_color;
+
+vec3 linear_to_srgb(vec3 color) {
+ //if going to srgb, clamp from 0 to 1.
+ color = clamp(color, vec3(0.0), vec3(1.0));
+ const vec3 a = vec3(0.055f);
+ return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
+}
+
+void main() {
+ vec2 uv = uv_interp;
+
+#ifdef MODE_PANORAMA_TO_DP
+
+ //obtain normal from dual paraboloid uv
+#define M_PI 3.14159265359
+
+ float side;
+ uv.y = modf(uv.y * 2.0, side);
+ side = side * 2.0 - 1.0;
+ vec3 normal = vec3(uv * 2.0 - 1.0, 0.0);
+ normal.z = 0.5 - 0.5 * ((normal.x * normal.x) + (normal.y * normal.y));
+ normal *= -side;
+ normal = normalize(normal);
+
+ //now convert normal to panorama uv
+
+ vec2 st = vec2(atan(normal.x, normal.z), acos(normal.y));
+
+ if (st.x < 0.0) {
+ st.x += M_PI * 2.0;
+ }
+
+ uv = st / vec2(M_PI * 2.0, M_PI);
+
+ if (side < 0.0) {
+ //uv.y = 1.0 - uv.y;
+ uv = 1.0 - uv;
+ }
+#endif
+ vec4 color = textureLod(source_color, uv, 0.0);
+#ifdef MODE_TWO_SOURCES
+ color += textureLod(source_color2, uv, 0.0);
+#endif
+ if (params.force_luminance) {
+ color.rgb = vec3(max(max(color.r, color.g), color.b));
+ }
+ if (params.alpha_to_zero) {
+ color.rgb *= color.a;
+ }
+ if (params.srgb) {
+ color.rgb = linear_to_srgb(color.rgb);
+ }
+ frag_color = color;
+}
diff --git a/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl
new file mode 100644
index 0000000000..69b895ed29
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl
@@ -0,0 +1,84 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ float z_far;
+ float z_near;
+ vec2 texel_size;
+ vec4 screen_rect;
+}
+params;
+
+layout(location = 0) out vec2 uv_interp;
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+ vec2 screen_pos = uv_interp * params.screen_rect.zw + params.screen_rect.xy;
+ gl_Position = vec4(screen_pos * 2.0 - 1.0, 0.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec2 uv_interp;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cube;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ float z_far;
+ float z_near;
+ vec2 texel_size;
+ vec4 screen_rect;
+}
+params;
+
+void main() {
+ vec2 uv = uv_interp;
+ vec2 texel_size = abs(params.texel_size);
+
+ uv = clamp(uv * (1.0 + 2.0 * texel_size) - texel_size, vec2(0.0), vec2(1.0));
+
+ vec3 normal = vec3(uv * 2.0 - 1.0, 0.0);
+ normal.z = 0.5 * (1.0 - dot(normal.xy, normal.xy)); // z = 1/2 - 1/2 * (x^2 + y^2)
+ normal = normalize(normal);
+
+ normal.y = -normal.y; //needs to be flipped to match projection matrix
+ if (params.texel_size.x >= 0.0) { // Sign is used to encode Z flip
+ normal.z = -normal.z;
+ }
+
+ float depth = texture(source_cube, normal).r;
+
+ // absolute values for direction cosines, bigger value equals closer to basis axis
+ vec3 unorm = abs(normal);
+
+ if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
+ // x code
+ unorm = normal.x > 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0);
+ } else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
+ // y code
+ unorm = normal.y > 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0);
+ } else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
+ // z code
+ unorm = normal.z > 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0);
+ } else {
+ // oh-no we messed up code
+ // has to be
+ unorm = vec3(1.0, 0.0, 0.0);
+ }
+
+ float depth_fix = 1.0 / dot(normal, unorm);
+
+ depth = 2.0 * depth - 1.0;
+ float linear_depth = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - depth * (params.z_far - params.z_near));
+ depth = (linear_depth * depth_fix) / params.z_far;
+
+ gl_FragDepth = depth;
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_downsampler.glsl b/servers/rendering/renderer_rd/shaders/cubemap_downsampler.glsl
new file mode 100644
index 0000000000..63f0ce690e
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_downsampler.glsl
@@ -0,0 +1,145 @@
+// Copyright 2016 Activision Publishing, Inc.
+//
+// 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.
+
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define BLOCK_SIZE 8
+
+layout(local_size_x = BLOCK_SIZE, local_size_y = BLOCK_SIZE, local_size_z = 1) in;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
+
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly imageCube dest_cubemap;
+
+#include "cubemap_downsampler_inc.glsl"
+
+void main() {
+ uvec3 id = gl_GlobalInvocationID;
+ uint face_size = params.face_size;
+
+ if (id.x < face_size && id.y < face_size) {
+ float inv_face_size = 1.0 / float(face_size);
+
+ float u0 = (float(id.x) * 2.0 + 1.0 - 0.75) * inv_face_size - 1.0;
+ float u1 = (float(id.x) * 2.0 + 1.0 + 0.75) * inv_face_size - 1.0;
+
+ float v0 = (float(id.y) * 2.0 + 1.0 - 0.75) * -inv_face_size + 1.0;
+ float v1 = (float(id.y) * 2.0 + 1.0 + 0.75) * -inv_face_size + 1.0;
+
+ float weights[4];
+ weights[0] = calcWeight(u0, v0);
+ weights[1] = calcWeight(u1, v0);
+ weights[2] = calcWeight(u0, v1);
+ weights[3] = calcWeight(u1, v1);
+
+ const float wsum = 0.5 / (weights[0] + weights[1] + weights[2] + weights[3]);
+ for (int i = 0; i < 4; i++) {
+ weights[i] = weights[i] * wsum + .125;
+ }
+
+ vec3 dir;
+ vec4 color;
+ switch (id.z) {
+ case 0:
+ get_dir_0(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_0(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_0(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_0(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 1:
+ get_dir_1(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_1(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_1(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_1(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 2:
+ get_dir_2(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_2(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_2(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_2(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 3:
+ get_dir_3(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_3(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_3(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_3(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 4:
+ get_dir_4(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_4(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_4(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_4(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ default:
+ get_dir_5(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_5(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_5(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_5(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ }
+ imageStore(dest_cubemap, ivec3(id), color);
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_downsampler_inc.glsl b/servers/rendering/renderer_rd/shaders/cubemap_downsampler_inc.glsl
new file mode 100644
index 0000000000..b329e67293
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_downsampler_inc.glsl
@@ -0,0 +1,48 @@
+layout(push_constant, binding = 1, std430) uniform Params {
+ uint face_size;
+ uint face_id; // only used in raster shader
+}
+params;
+
+#define M_PI 3.14159265359
+
+void get_dir_0(out vec3 dir, in float u, in float v) {
+ dir[0] = 1.0;
+ dir[1] = v;
+ dir[2] = -u;
+}
+
+void get_dir_1(out vec3 dir, in float u, in float v) {
+ dir[0] = -1.0;
+ dir[1] = v;
+ dir[2] = u;
+}
+
+void get_dir_2(out vec3 dir, in float u, in float v) {
+ dir[0] = u;
+ dir[1] = 1.0;
+ dir[2] = -v;
+}
+
+void get_dir_3(out vec3 dir, in float u, in float v) {
+ dir[0] = u;
+ dir[1] = -1.0;
+ dir[2] = v;
+}
+
+void get_dir_4(out vec3 dir, in float u, in float v) {
+ dir[0] = u;
+ dir[1] = v;
+ dir[2] = 1.0;
+}
+
+void get_dir_5(out vec3 dir, in float u, in float v) {
+ dir[0] = -u;
+ dir[1] = v;
+ dir[2] = -1.0;
+}
+
+float calcWeight(float u, float v) {
+ float val = u * u + v * v + 1.0;
+ return val * sqrt(val);
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_downsampler_raster.glsl b/servers/rendering/renderer_rd/shaders/cubemap_downsampler_raster.glsl
new file mode 100644
index 0000000000..0828ffd921
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_downsampler_raster.glsl
@@ -0,0 +1,163 @@
+// Copyright 2016 Activision Publishing, Inc.
+//
+// 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.
+
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "cubemap_downsampler_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex] * float(params.face_size);
+ gl_Position = vec4(base_arr[gl_VertexIndex] * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "cubemap_downsampler_inc.glsl"
+
+layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
+
+layout(location = 0) in vec2 uv_interp;
+layout(location = 0) out vec4 frag_color;
+/* clang-format on */
+
+void main() {
+ // Converted from compute shader which uses absolute coordinates.
+ // Could possibly simplify this
+ float face_size = float(params.face_size);
+
+ if (uv_interp.x < face_size && uv_interp.y < face_size) {
+ float inv_face_size = 1.0 / face_size;
+
+ float u0 = (uv_interp.x * 2.0 + 1.0 - 0.75) * inv_face_size - 1.0;
+ float u1 = (uv_interp.x * 2.0 + 1.0 + 0.75) * inv_face_size - 1.0;
+
+ float v0 = (uv_interp.y * 2.0 + 1.0 - 0.75) * -inv_face_size + 1.0;
+ float v1 = (uv_interp.y * 2.0 + 1.0 + 0.75) * -inv_face_size + 1.0;
+
+ float weights[4];
+ weights[0] = calcWeight(u0, v0);
+ weights[1] = calcWeight(u1, v0);
+ weights[2] = calcWeight(u0, v1);
+ weights[3] = calcWeight(u1, v1);
+
+ const float wsum = 0.5 / (weights[0] + weights[1] + weights[2] + weights[3]);
+ for (int i = 0; i < 4; i++) {
+ weights[i] = weights[i] * wsum + .125;
+ }
+
+ vec3 dir;
+ vec4 color;
+ switch (params.face_id) {
+ case 0:
+ get_dir_0(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_0(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_0(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_0(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 1:
+ get_dir_1(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_1(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_1(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_1(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 2:
+ get_dir_2(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_2(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_2(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_2(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 3:
+ get_dir_3(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_3(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_3(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_3(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ case 4:
+ get_dir_4(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_4(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_4(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_4(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ default:
+ get_dir_5(dir, u0, v0);
+ color = textureLod(source_cubemap, normalize(dir), 0.0) * weights[0];
+
+ get_dir_5(dir, u1, v0);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[1];
+
+ get_dir_5(dir, u0, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[2];
+
+ get_dir_5(dir, u1, v1);
+ color += textureLod(source_cubemap, normalize(dir), 0.0) * weights[3];
+ break;
+ }
+ frag_color = color;
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_filter.glsl b/servers/rendering/renderer_rd/shaders/cubemap_filter.glsl
new file mode 100644
index 0000000000..2a774b0eb4
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_filter.glsl
@@ -0,0 +1,326 @@
+// Copyright 2016 Activision Publishing, Inc.
+//
+// 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.
+
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define GROUP_SIZE 64
+
+layout(local_size_x = GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
+layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly imageCube dest_cubemap0;
+layout(rgba16f, set = 2, binding = 1) uniform restrict writeonly imageCube dest_cubemap1;
+layout(rgba16f, set = 2, binding = 2) uniform restrict writeonly imageCube dest_cubemap2;
+layout(rgba16f, set = 2, binding = 3) uniform restrict writeonly imageCube dest_cubemap3;
+layout(rgba16f, set = 2, binding = 4) uniform restrict writeonly imageCube dest_cubemap4;
+layout(rgba16f, set = 2, binding = 5) uniform restrict writeonly imageCube dest_cubemap5;
+layout(rgba16f, set = 2, binding = 6) uniform restrict writeonly imageCube dest_cubemap6;
+
+#ifdef USE_HIGH_QUALITY
+#define NUM_TAPS 32
+#else
+#define NUM_TAPS 8
+#endif
+
+#define BASE_RESOLUTION 128
+
+#ifdef USE_HIGH_QUALITY
+layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
+ vec4[7][5][3][24] coeffs;
+}
+data;
+#else
+layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
+ vec4[7][5][6] coeffs;
+}
+data;
+#endif
+
+void get_dir(out vec3 dir, in vec2 uv, in uint face) {
+ switch (face) {
+ case 0:
+ dir = vec3(1.0, uv[1], -uv[0]);
+ break;
+ case 1:
+ dir = vec3(-1.0, uv[1], uv[0]);
+ break;
+ case 2:
+ dir = vec3(uv[0], 1.0, -uv[1]);
+ break;
+ case 3:
+ dir = vec3(uv[0], -1.0, uv[1]);
+ break;
+ case 4:
+ dir = vec3(uv[0], uv[1], 1.0);
+ break;
+ default:
+ dir = vec3(-uv[0], uv[1], -1.0);
+ break;
+ }
+}
+
+void main() {
+ // INPUT:
+ // id.x = the linear address of the texel (ignoring face)
+ // id.y = the face
+ // -> use to index output texture
+ // id.x = texel x
+ // id.y = texel y
+ // id.z = face
+ uvec3 id = gl_GlobalInvocationID;
+
+ // determine which texel this is
+#ifndef USE_TEXTURE_ARRAY
+ // NOTE (macOS/MoltenVK): Do not rename, "level" variable name conflicts with the Metal "level(float lod)" mipmap sampling function name.
+ int mip_level = 0;
+ if (id.x < (128 * 128)) {
+ mip_level = 0;
+ } else if (id.x < (128 * 128 + 64 * 64)) {
+ mip_level = 1;
+ id.x -= (128 * 128);
+ } else if (id.x < (128 * 128 + 64 * 64 + 32 * 32)) {
+ mip_level = 2;
+ id.x -= (128 * 128 + 64 * 64);
+ } else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16)) {
+ mip_level = 3;
+ id.x -= (128 * 128 + 64 * 64 + 32 * 32);
+ } else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8)) {
+ mip_level = 4;
+ id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16);
+ } else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4)) {
+ mip_level = 5;
+ id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8);
+ } else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4 + 2 * 2)) {
+ mip_level = 6;
+ id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4);
+ } else {
+ return;
+ }
+ int res = BASE_RESOLUTION >> mip_level;
+#else // Using Texture Arrays so all levels are the same resolution
+ int res = BASE_RESOLUTION;
+ int mip_level = int(id.x / (BASE_RESOLUTION * BASE_RESOLUTION));
+ id.x -= mip_level * BASE_RESOLUTION * BASE_RESOLUTION;
+#endif
+
+ // determine dir / pos for the texel
+ vec3 dir, adir, frameZ;
+ {
+ id.z = id.y;
+ id.y = id.x / res;
+ id.x -= id.y * res;
+
+ vec2 uv;
+ uv.x = (float(id.x) * 2.0 + 1.0) / float(res) - 1.0;
+ uv.y = -(float(id.y) * 2.0 + 1.0) / float(res) + 1.0;
+
+ get_dir(dir, uv, id.z);
+ frameZ = normalize(dir);
+
+ adir = abs(dir);
+ }
+
+ // GGX gather colors
+ vec4 color = vec4(0.0);
+ for (int axis = 0; axis < 3; axis++) {
+ const int otherAxis0 = 1 - (axis & 1) - (axis >> 1);
+ const int otherAxis1 = 2 - (axis >> 1);
+
+ float frameweight = (max(adir[otherAxis0], adir[otherAxis1]) - .75) / .25;
+ if (frameweight > 0.0) {
+ // determine frame
+ vec3 UpVector;
+ switch (axis) {
+ case 0:
+ UpVector = vec3(1, 0, 0);
+ break;
+ case 1:
+ UpVector = vec3(0, 1, 0);
+ break;
+ default:
+ UpVector = vec3(0, 0, 1);
+ break;
+ }
+
+ vec3 frameX = normalize(cross(UpVector, frameZ));
+ vec3 frameY = cross(frameZ, frameX);
+
+ // calculate parametrization for polynomial
+ float Nx = dir[otherAxis0];
+ float Ny = dir[otherAxis1];
+ float Nz = adir[axis];
+
+ float NmaxXY = max(abs(Ny), abs(Nx));
+ Nx /= NmaxXY;
+ Ny /= NmaxXY;
+
+ float theta;
+ if (Ny < Nx) {
+ if (Ny <= -0.999)
+ theta = Nx;
+ else
+ theta = Ny;
+ } else {
+ if (Ny >= 0.999)
+ theta = -Nx;
+ else
+ theta = -Ny;
+ }
+
+ float phi;
+ if (Nz <= -0.999)
+ phi = -NmaxXY;
+ else if (Nz >= 0.999)
+ phi = NmaxXY;
+ else
+ phi = Nz;
+
+ float theta2 = theta * theta;
+ float phi2 = phi * phi;
+
+ // sample
+ for (int iSuperTap = 0; iSuperTap < NUM_TAPS / 4; iSuperTap++) {
+ const int index = (NUM_TAPS / 4) * axis + iSuperTap;
+
+#ifdef USE_HIGH_QUALITY
+ vec4 coeffsDir0[3];
+ vec4 coeffsDir1[3];
+ vec4 coeffsDir2[3];
+ vec4 coeffsLevel[3];
+ vec4 coeffsWeight[3];
+
+ for (int iCoeff = 0; iCoeff < 3; iCoeff++) {
+ coeffsDir0[iCoeff] = data.coeffs[mip_level][0][iCoeff][index];
+ coeffsDir1[iCoeff] = data.coeffs[mip_level][1][iCoeff][index];
+ coeffsDir2[iCoeff] = data.coeffs[mip_level][2][iCoeff][index];
+ coeffsLevel[iCoeff] = data.coeffs[mip_level][3][iCoeff][index];
+ coeffsWeight[iCoeff] = data.coeffs[mip_level][4][iCoeff][index];
+ }
+
+ for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
+ // determine sample attributes (dir, weight, mip_level)
+ vec3 sample_dir = frameX * (coeffsDir0[0][iSubTap] + coeffsDir0[1][iSubTap] * theta2 + coeffsDir0[2][iSubTap] * phi2) + frameY * (coeffsDir1[0][iSubTap] + coeffsDir1[1][iSubTap] * theta2 + coeffsDir1[2][iSubTap] * phi2) + frameZ * (coeffsDir2[0][iSubTap] + coeffsDir2[1][iSubTap] * theta2 + coeffsDir2[2][iSubTap] * phi2);
+
+ float sample_level = coeffsLevel[0][iSubTap] + coeffsLevel[1][iSubTap] * theta2 + coeffsLevel[2][iSubTap] * phi2;
+
+ float sample_weight = coeffsWeight[0][iSubTap] + coeffsWeight[1][iSubTap] * theta2 + coeffsWeight[2][iSubTap] * phi2;
+#else
+ vec4 coeffsDir0 = data.coeffs[mip_level][0][index];
+ vec4 coeffsDir1 = data.coeffs[mip_level][1][index];
+ vec4 coeffsDir2 = data.coeffs[mip_level][2][index];
+ vec4 coeffsLevel = data.coeffs[mip_level][3][index];
+ vec4 coeffsWeight = data.coeffs[mip_level][4][index];
+
+ for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
+ // determine sample attributes (dir, weight, mip_level)
+ vec3 sample_dir = frameX * coeffsDir0[iSubTap] + frameY * coeffsDir1[iSubTap] + frameZ * coeffsDir2[iSubTap];
+
+ float sample_level = coeffsLevel[iSubTap];
+
+ float sample_weight = coeffsWeight[iSubTap];
+#endif
+
+ sample_weight *= frameweight;
+
+ // adjust for jacobian
+ sample_dir /= max(abs(sample_dir[0]), max(abs(sample_dir[1]), abs(sample_dir[2])));
+ sample_level += 0.75 * log2(dot(sample_dir, sample_dir));
+#ifndef USE_TEXTURE_ARRAY
+ sample_level += float(mip_level) / 6.0; // Hack to increase the perceived roughness and reduce upscaling artifacts
+#endif
+ // sample cubemap
+ color.xyz += textureLod(source_cubemap, normalize(sample_dir), sample_level).xyz * sample_weight;
+ color.w += sample_weight;
+ }
+ }
+ }
+ }
+ color /= color.w;
+
+ // write color
+ color.xyz = max(vec3(0.0), color.xyz);
+ color.w = 1.0;
+#ifdef USE_TEXTURE_ARRAY
+ id.xy *= uvec2(2, 2);
+#endif
+
+ switch (mip_level) {
+ case 0:
+ imageStore(dest_cubemap0, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap0, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap0, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap0, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ case 1:
+ imageStore(dest_cubemap1, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap1, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap1, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap1, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ case 2:
+ imageStore(dest_cubemap2, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap2, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap2, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap2, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ case 3:
+ imageStore(dest_cubemap3, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap3, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap3, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap3, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ case 4:
+ imageStore(dest_cubemap4, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap4, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap4, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap4, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ case 5:
+ imageStore(dest_cubemap5, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap5, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap5, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap5, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ default:
+ imageStore(dest_cubemap6, ivec3(id), color);
+#ifdef USE_TEXTURE_ARRAY
+ imageStore(dest_cubemap6, ivec3(id) + ivec3(1.0, 0.0, 0.0), color);
+ imageStore(dest_cubemap6, ivec3(id) + ivec3(0.0, 1.0, 0.0), color);
+ imageStore(dest_cubemap6, ivec3(id) + ivec3(1.0, 1.0, 0.0), color);
+#endif
+ break;
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_filter_raster.glsl b/servers/rendering/renderer_rd/shaders/cubemap_filter_raster.glsl
new file mode 100644
index 0000000000..324d306218
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_filter_raster.glsl
@@ -0,0 +1,256 @@
+// Copyright 2016 Activision Publishing, Inc.
+//
+// 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.
+
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ int mip_level;
+ uint face_id;
+}
+params;
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+ gl_Position = vec4(base_arr[gl_VertexIndex] * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ int mip_level;
+ uint face_id;
+}
+params;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
+
+layout(location = 0) in vec2 uv_interp;
+layout(location = 0) out vec4 frag_color;
+
+/* clang-format on */
+
+#ifdef USE_HIGH_QUALITY
+#define NUM_TAPS 32
+#else
+#define NUM_TAPS 8
+#endif
+
+#define BASE_RESOLUTION 128
+
+#ifdef USE_HIGH_QUALITY
+layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
+ vec4[7][5][3][24] coeffs;
+}
+data;
+#else
+layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
+ vec4[7][5][6] coeffs;
+}
+data;
+#endif
+
+void get_dir(out vec3 dir, in vec2 uv, in uint face) {
+ switch (face) {
+ case 0:
+ dir = vec3(1.0, uv[1], -uv[0]);
+ break;
+ case 1:
+ dir = vec3(-1.0, uv[1], uv[0]);
+ break;
+ case 2:
+ dir = vec3(uv[0], 1.0, -uv[1]);
+ break;
+ case 3:
+ dir = vec3(uv[0], -1.0, uv[1]);
+ break;
+ case 4:
+ dir = vec3(uv[0], uv[1], 1.0);
+ break;
+ default:
+ dir = vec3(-uv[0], uv[1], -1.0);
+ break;
+ }
+}
+
+void main() {
+ // determine dir / pos for the texel
+ vec3 dir, adir, frameZ;
+ {
+ vec2 uv;
+ uv.x = uv_interp.x;
+ uv.y = 1.0 - uv_interp.y;
+ uv = uv * 2.0 - 1.0;
+
+ get_dir(dir, uv, params.face_id);
+ frameZ = normalize(dir);
+
+ adir = abs(dir);
+ }
+
+ // determine which texel this is
+ // NOTE (macOS/MoltenVK): Do not rename, "level" variable name conflicts with the Metal "level(float lod)" mipmap sampling function name.
+ int mip_level = 0;
+
+ if (params.mip_level < 0) {
+ // return as is
+ frag_color.rgb = textureLod(source_cubemap, frameZ, 0.0).rgb;
+ frag_color.a = 1.0;
+ return;
+ } else if (params.mip_level > 6) {
+ // maximum level
+ mip_level = 6;
+ } else {
+ mip_level = params.mip_level;
+ }
+
+ // GGX gather colors
+ vec4 color = vec4(0.0);
+ for (int axis = 0; axis < 3; axis++) {
+ const int otherAxis0 = 1 - (axis & 1) - (axis >> 1);
+ const int otherAxis1 = 2 - (axis >> 1);
+
+ float frameweight = (max(adir[otherAxis0], adir[otherAxis1]) - .75) / .25;
+ if (frameweight > 0.0) {
+ // determine frame
+ vec3 UpVector;
+ switch (axis) {
+ case 0:
+ UpVector = vec3(1, 0, 0);
+ break;
+ case 1:
+ UpVector = vec3(0, 1, 0);
+ break;
+ default:
+ UpVector = vec3(0, 0, 1);
+ break;
+ }
+
+ vec3 frameX = normalize(cross(UpVector, frameZ));
+ vec3 frameY = cross(frameZ, frameX);
+
+ // calculate parametrization for polynomial
+ float Nx = dir[otherAxis0];
+ float Ny = dir[otherAxis1];
+ float Nz = adir[axis];
+
+ float NmaxXY = max(abs(Ny), abs(Nx));
+ Nx /= NmaxXY;
+ Ny /= NmaxXY;
+
+ float theta;
+ if (Ny < Nx) {
+ if (Ny <= -0.999)
+ theta = Nx;
+ else
+ theta = Ny;
+ } else {
+ if (Ny >= 0.999)
+ theta = -Nx;
+ else
+ theta = -Ny;
+ }
+
+ float phi;
+ if (Nz <= -0.999)
+ phi = -NmaxXY;
+ else if (Nz >= 0.999)
+ phi = NmaxXY;
+ else
+ phi = Nz;
+
+ float theta2 = theta * theta;
+ float phi2 = phi * phi;
+
+ // sample
+ for (int iSuperTap = 0; iSuperTap < NUM_TAPS / 4; iSuperTap++) {
+ const int index = (NUM_TAPS / 4) * axis + iSuperTap;
+
+#ifdef USE_HIGH_QUALITY
+ vec4 coeffsDir0[3];
+ vec4 coeffsDir1[3];
+ vec4 coeffsDir2[3];
+ vec4 coeffsLevel[3];
+ vec4 coeffsWeight[3];
+
+ for (int iCoeff = 0; iCoeff < 3; iCoeff++) {
+ coeffsDir0[iCoeff] = data.coeffs[mip_level][0][iCoeff][index];
+ coeffsDir1[iCoeff] = data.coeffs[mip_level][1][iCoeff][index];
+ coeffsDir2[iCoeff] = data.coeffs[mip_level][2][iCoeff][index];
+ coeffsLevel[iCoeff] = data.coeffs[mip_level][3][iCoeff][index];
+ coeffsWeight[iCoeff] = data.coeffs[mip_level][4][iCoeff][index];
+ }
+
+ for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
+ // determine sample attributes (dir, weight, mip_level)
+ vec3 sample_dir = frameX * (coeffsDir0[0][iSubTap] + coeffsDir0[1][iSubTap] * theta2 + coeffsDir0[2][iSubTap] * phi2) + frameY * (coeffsDir1[0][iSubTap] + coeffsDir1[1][iSubTap] * theta2 + coeffsDir1[2][iSubTap] * phi2) + frameZ * (coeffsDir2[0][iSubTap] + coeffsDir2[1][iSubTap] * theta2 + coeffsDir2[2][iSubTap] * phi2);
+
+ float sample_level = coeffsLevel[0][iSubTap] + coeffsLevel[1][iSubTap] * theta2 + coeffsLevel[2][iSubTap] * phi2;
+
+ float sample_weight = coeffsWeight[0][iSubTap] + coeffsWeight[1][iSubTap] * theta2 + coeffsWeight[2][iSubTap] * phi2;
+#else
+ vec4 coeffsDir0 = data.coeffs[mip_level][0][index];
+ vec4 coeffsDir1 = data.coeffs[mip_level][1][index];
+ vec4 coeffsDir2 = data.coeffs[mip_level][2][index];
+ vec4 coeffsLevel = data.coeffs[mip_level][3][index];
+ vec4 coeffsWeight = data.coeffs[mip_level][4][index];
+
+ for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
+ // determine sample attributes (dir, weight, mip_level)
+ vec3 sample_dir = frameX * coeffsDir0[iSubTap] + frameY * coeffsDir1[iSubTap] + frameZ * coeffsDir2[iSubTap];
+
+ float sample_level = coeffsLevel[iSubTap];
+
+ float sample_weight = coeffsWeight[iSubTap];
+#endif
+
+ sample_weight *= frameweight;
+
+ // adjust for jacobian
+ sample_dir /= max(abs(sample_dir[0]), max(abs(sample_dir[1]), abs(sample_dir[2])));
+ sample_level += 0.75 * log2(dot(sample_dir, sample_dir));
+ // sample cubemap
+ color.xyz += textureLod(source_cubemap, normalize(sample_dir), sample_level).xyz * sample_weight;
+ color.w += sample_weight;
+ }
+ }
+ }
+ }
+ color /= color.w;
+
+ // write color
+ color.xyz = max(vec3(0.0), color.xyz);
+ color.w = 1.0;
+
+ frag_color = color;
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl b/servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl
new file mode 100644
index 0000000000..28f4dc59ec
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl
@@ -0,0 +1,49 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define GROUP_SIZE 8
+
+layout(local_size_x = GROUP_SIZE, local_size_y = GROUP_SIZE, local_size_z = 1) in;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cube;
+
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly imageCube dest_cubemap;
+
+#include "cubemap_roughness_inc.glsl"
+
+void main() {
+ uvec3 id = gl_GlobalInvocationID;
+ id.z += params.face_id;
+
+ vec2 uv = ((vec2(id.xy) * 2.0 + 1.0) / (params.face_size) - 1.0);
+ vec3 N = texelCoordToVec(uv, id.z);
+
+ //vec4 color = color_interp;
+
+ if (params.use_direct_write) {
+ imageStore(dest_cubemap, ivec3(id), vec4(texture(source_cube, N).rgb, 1.0));
+ } else {
+ vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
+
+ for (uint sampleNum = 0u; sampleNum < params.sample_count; sampleNum++) {
+ vec2 xi = Hammersley(sampleNum, params.sample_count);
+
+ vec3 H = ImportanceSampleGGX(xi, params.roughness, N);
+ vec3 V = N;
+ vec3 L = (2.0 * dot(V, H) * H - V);
+
+ float ndotl = clamp(dot(N, L), 0.0, 1.0);
+
+ if (ndotl > 0.0) {
+ sum.rgb += textureLod(source_cube, L, 0.0).rgb * ndotl;
+ sum.a += ndotl;
+ }
+ }
+ sum /= sum.a;
+
+ imageStore(dest_cubemap, ivec3(id), vec4(sum.rgb, 1.0));
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl b/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl
new file mode 100644
index 0000000000..80c0ac4fb4
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl
@@ -0,0 +1,94 @@
+#define M_PI 3.14159265359
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ uint face_id;
+ uint sample_count;
+ float roughness;
+ bool use_direct_write;
+ float face_size;
+}
+params;
+
+vec3 texelCoordToVec(vec2 uv, uint faceID) {
+ mat3 faceUvVectors[6];
+
+ // -x
+ faceUvVectors[1][0] = vec3(0.0, 0.0, 1.0); // u -> +z
+ faceUvVectors[1][1] = vec3(0.0, -1.0, 0.0); // v -> -y
+ faceUvVectors[1][2] = vec3(-1.0, 0.0, 0.0); // -x face
+
+ // +x
+ faceUvVectors[0][0] = vec3(0.0, 0.0, -1.0); // u -> -z
+ faceUvVectors[0][1] = vec3(0.0, -1.0, 0.0); // v -> -y
+ faceUvVectors[0][2] = vec3(1.0, 0.0, 0.0); // +x face
+
+ // -y
+ faceUvVectors[3][0] = vec3(1.0, 0.0, 0.0); // u -> +x
+ faceUvVectors[3][1] = vec3(0.0, 0.0, -1.0); // v -> -z
+ faceUvVectors[3][2] = vec3(0.0, -1.0, 0.0); // -y face
+
+ // +y
+ faceUvVectors[2][0] = vec3(1.0, 0.0, 0.0); // u -> +x
+ faceUvVectors[2][1] = vec3(0.0, 0.0, 1.0); // v -> +z
+ faceUvVectors[2][2] = vec3(0.0, 1.0, 0.0); // +y face
+
+ // -z
+ faceUvVectors[5][0] = vec3(-1.0, 0.0, 0.0); // u -> -x
+ faceUvVectors[5][1] = vec3(0.0, -1.0, 0.0); // v -> -y
+ faceUvVectors[5][2] = vec3(0.0, 0.0, -1.0); // -z face
+
+ // +z
+ faceUvVectors[4][0] = vec3(1.0, 0.0, 0.0); // u -> +x
+ faceUvVectors[4][1] = vec3(0.0, -1.0, 0.0); // v -> -y
+ faceUvVectors[4][2] = vec3(0.0, 0.0, 1.0); // +z face
+
+ // out = u * s_faceUv[0] + v * s_faceUv[1] + s_faceUv[2].
+ vec3 result = (faceUvVectors[faceID][0] * uv.x) + (faceUvVectors[faceID][1] * uv.y) + faceUvVectors[faceID][2];
+ return normalize(result);
+}
+
+vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N) {
+ float a = Roughness * Roughness; // DISNEY'S ROUGHNESS [see Burley'12 siggraph]
+
+ // Compute distribution direction
+ float Phi = 2.0 * M_PI * Xi.x;
+ float CosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y));
+ float SinTheta = sqrt(1.0 - CosTheta * CosTheta);
+
+ // Convert to spherical direction
+ vec3 H;
+ H.x = SinTheta * cos(Phi);
+ H.y = SinTheta * sin(Phi);
+ H.z = CosTheta;
+
+ vec3 UpVector = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+ vec3 TangentX = normalize(cross(UpVector, N));
+ vec3 TangentY = cross(N, TangentX);
+
+ // Tangent to world space
+ return TangentX * H.x + TangentY * H.y + N * H.z;
+}
+
+// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html
+float GGX(float NdotV, float a) {
+ float k = a / 2.0;
+ return NdotV / (NdotV * (1.0 - k) + k);
+}
+
+// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html
+float G_Smith(float a, float nDotV, float nDotL) {
+ return GGX(nDotL, a * a) * GGX(nDotV, a * a);
+}
+
+float radicalInverse_VdC(uint bits) {
+ bits = (bits << 16u) | (bits >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+ return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 Hammersley(uint i, uint N) {
+ return vec2(float(i) / float(N), radicalInverse_VdC(i));
+}
diff --git a/servers/rendering/renderer_rd/shaders/cubemap_roughness_raster.glsl b/servers/rendering/renderer_rd/shaders/cubemap_roughness_raster.glsl
new file mode 100644
index 0000000000..2570308816
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/cubemap_roughness_raster.glsl
@@ -0,0 +1,63 @@
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "cubemap_roughness_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "cubemap_roughness_inc.glsl"
+
+layout(location = 0) in vec2 uv_interp;
+
+layout(set = 0, binding = 0) uniform samplerCube source_cube;
+
+layout(location = 0) out vec4 frag_color;
+/* clang-format on */
+
+void main() {
+ vec3 N = texelCoordToVec(uv_interp * 2.0 - 1.0, params.face_id);
+
+ //vec4 color = color_interp;
+
+ if (params.use_direct_write) {
+ frag_color = vec4(texture(source_cube, N).rgb, 1.0);
+ } else {
+ vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
+
+ for (uint sampleNum = 0u; sampleNum < params.sample_count; sampleNum++) {
+ vec2 xi = Hammersley(sampleNum, params.sample_count);
+
+ vec3 H = ImportanceSampleGGX(xi, params.roughness, N);
+ vec3 V = N;
+ vec3 L = (2.0 * dot(V, H) * H - V);
+
+ float ndotl = clamp(dot(N, L), 0.0, 1.0);
+
+ if (ndotl > 0.0) {
+ sum.rgb += textureLod(source_cube, L, 0.0).rgb * ndotl;
+ sum.a += ndotl;
+ }
+ }
+ sum /= sum.a;
+
+ frag_color = vec4(sum.rgb, 1.0);
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl b/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl
new file mode 100644
index 0000000000..158096d3c7
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl
@@ -0,0 +1,18 @@
+
+struct DecalData {
+ highp mat4 xform; //to decal transform
+ highp vec3 inv_extents;
+ mediump float albedo_mix;
+ highp vec4 albedo_rect;
+ highp vec4 normal_rect;
+ highp vec4 orm_rect;
+ highp vec4 emission_rect;
+ highp vec4 modulate;
+ mediump float emission_energy;
+ uint mask;
+ mediump float upper_fade;
+ mediump float lower_fade;
+ mediump mat3x4 normal_xform;
+ mediump vec3 normal;
+ mediump float normal_fade;
+};
diff --git a/servers/rendering/renderer_rd/shaders/gi.glsl b/servers/rendering/renderer_rd/shaders/gi.glsl
new file mode 100644
index 0000000000..60c881881d
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/gi.glsl
@@ -0,0 +1,656 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#define M_PI 3.141592
+
+#define SDFGI_MAX_CASCADES 8
+
+//set 0 for SDFGI and render buffers
+
+layout(set = 0, binding = 1) uniform texture3D sdf_cascades[SDFGI_MAX_CASCADES];
+layout(set = 0, binding = 2) uniform texture3D light_cascades[SDFGI_MAX_CASCADES];
+layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[SDFGI_MAX_CASCADES];
+layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[SDFGI_MAX_CASCADES];
+layout(set = 0, binding = 5) uniform texture3D occlusion_texture;
+
+layout(set = 0, binding = 6) uniform sampler linear_sampler;
+layout(set = 0, binding = 7) uniform sampler linear_sampler_with_mipmaps;
+
+struct ProbeCascadeData {
+ vec3 position;
+ float to_probe;
+ ivec3 probe_world_offset;
+ float to_cell; // 1/bounds * grid_size
+};
+
+layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image2D ambient_buffer;
+layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image2D reflection_buffer;
+
+layout(set = 0, binding = 11) uniform texture2DArray lightprobe_texture;
+
+layout(set = 0, binding = 12) uniform texture2D depth_buffer;
+layout(set = 0, binding = 13) uniform texture2D normal_roughness_buffer;
+layout(set = 0, binding = 14) uniform utexture2D voxel_gi_buffer;
+
+layout(set = 0, binding = 15, std140) uniform SDFGI {
+ vec3 grid_size;
+ uint max_cascades;
+
+ bool use_occlusion;
+ int probe_axis_size;
+ float probe_to_uvw;
+ float normal_bias;
+
+ vec3 lightprobe_tex_pixel_size;
+ float energy;
+
+ vec3 lightprobe_uv_offset;
+ float y_mult;
+
+ vec3 occlusion_clamp;
+ uint pad3;
+
+ vec3 occlusion_renormalize;
+ uint pad4;
+
+ vec3 cascade_probe_size;
+ uint pad5;
+
+ ProbeCascadeData cascades[SDFGI_MAX_CASCADES];
+}
+sdfgi;
+
+#define MAX_VOXEL_GI_INSTANCES 8
+
+struct VoxelGIData {
+ mat4 xform;
+ vec3 bounds;
+ float dynamic_range;
+
+ float bias;
+ float normal_bias;
+ bool blend_ambient;
+ uint texture_slot;
+
+ uint pad0;
+ uint pad1;
+ uint pad2;
+ uint mipmaps;
+};
+
+layout(set = 0, binding = 16, std140) uniform VoxelGIs {
+ VoxelGIData data[MAX_VOXEL_GI_INSTANCES];
+}
+voxel_gi_instances;
+
+layout(set = 0, binding = 17) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec2 screen_size;
+ float z_near;
+ float z_far;
+
+ vec4 proj_info;
+
+ vec3 ao_color;
+ uint max_voxel_gi_instances;
+
+ bool high_quality_vct;
+ bool orthogonal;
+ uint pad[2];
+
+ mat3x4 cam_rotation;
+}
+params;
+
+vec2 octahedron_wrap(vec2 v) {
+ vec2 signVal;
+ signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+ signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+ return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+ n.xy = n.xy * 0.5 + 0.5;
+ return n.xy;
+}
+
+vec4 blend_color(vec4 src, vec4 dst) {
+ vec4 res;
+ float sa = 1.0 - src.a;
+ res.a = dst.a * sa + src.a;
+ if (res.a == 0.0) {
+ res.rgb = vec3(0);
+ } else {
+ res.rgb = (dst.rgb * dst.a * sa + src.rgb * src.a) / res.a;
+ }
+ return res;
+}
+
+vec3 reconstruct_position(ivec2 screen_pos) {
+ vec3 pos;
+ pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r;
+
+ pos.z = pos.z * 2.0 - 1.0;
+ if (params.orthogonal) {
+ pos.z = ((pos.z + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+ } else {
+ pos.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - pos.z * (params.z_far - params.z_near));
+ }
+ pos.z = -pos.z;
+
+ pos.xy = vec2(screen_pos) * params.proj_info.xy + params.proj_info.zw;
+ if (!params.orthogonal) {
+ pos.xy *= pos.z;
+ }
+
+ return pos;
+}
+
+void sdfvoxel_gi_process(uint cascade, vec3 cascade_pos, vec3 cam_pos, vec3 cam_normal, vec3 cam_specular_normal, float roughness, out vec3 diffuse_light, out vec3 specular_light) {
+ cascade_pos += cam_normal * sdfgi.normal_bias;
+
+ vec3 base_pos = floor(cascade_pos);
+ //cascade_pos += mix(vec3(0.0),vec3(0.01),lessThan(abs(cascade_pos-base_pos),vec3(0.01))) * cam_normal;
+ ivec3 probe_base_pos = ivec3(base_pos);
+
+ vec4 diffuse_accum = vec4(0.0);
+ vec3 specular_accum;
+
+ ivec3 tex_pos = ivec3(probe_base_pos.xy, int(cascade));
+ tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
+ tex_pos.xy = tex_pos.xy * (SDFGI_OCT_SIZE + 2) + ivec2(1);
+
+ vec3 diffuse_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+
+ vec3 specular_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_specular_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+
+ specular_accum = vec3(0.0);
+
+ vec4 light_accum = vec4(0.0);
+ float weight_accum = 0.0;
+
+ for (uint j = 0; j < 8; j++) {
+ ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
+ ivec3 probe_posi = probe_base_pos;
+ probe_posi += offset;
+
+ // Compute weight
+
+ vec3 probe_pos = vec3(probe_posi);
+ vec3 probe_to_pos = cascade_pos - probe_pos;
+ vec3 probe_dir = normalize(-probe_to_pos);
+
+ vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
+ float weight = trilinear.x * trilinear.y * trilinear.z * max(0.005, dot(cam_normal, probe_dir));
+
+ // Compute lightprobe occlusion
+
+ if (sdfgi.use_occlusion) {
+ ivec3 occ_indexv = abs((sdfgi.cascades[cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+ vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
+
+ vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
+ occ_pos.z += float(cascade);
+ if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
+ occ_pos.x += 1.0;
+ }
+
+ occ_pos *= sdfgi.occlusion_renormalize;
+ float occlusion = dot(textureLod(sampler3D(occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
+
+ weight *= max(occlusion, 0.01);
+ }
+
+ // Compute lightprobe texture position
+
+ vec3 diffuse;
+ vec3 pos_uvw = diffuse_posf;
+ pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+ pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+ diffuse = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb;
+
+ diffuse_accum += vec4(diffuse * weight, weight);
+
+ {
+ vec3 specular = vec3(0.0);
+ vec3 pos_uvw = specular_posf;
+ pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+ pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+ if (roughness < 0.99) {
+ specular = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw + vec3(0, 0, float(sdfgi.max_cascades)), 0.0).rgb;
+ }
+ if (roughness > 0.2) {
+ specular = mix(specular, textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb, (roughness - 0.2) * 1.25);
+ }
+
+ specular_accum += specular * weight;
+ }
+ }
+
+ if (diffuse_accum.a > 0.0) {
+ diffuse_accum.rgb /= diffuse_accum.a;
+ }
+
+ diffuse_light = diffuse_accum.rgb;
+
+ if (diffuse_accum.a > 0.0) {
+ specular_accum /= diffuse_accum.a;
+ }
+
+ specular_light = specular_accum;
+}
+
+void sdfgi_process(vec3 vertex, vec3 normal, vec3 reflection, float roughness, out vec4 ambient_light, out vec4 reflection_light) {
+ //make vertex orientation the world one, but still align to camera
+ vertex.y *= sdfgi.y_mult;
+ normal.y *= sdfgi.y_mult;
+ reflection.y *= sdfgi.y_mult;
+
+ //renormalize
+ normal = normalize(normal);
+ reflection = normalize(reflection);
+
+ vec3 cam_pos = vertex;
+ vec3 cam_normal = normal;
+
+ vec4 light_accum = vec4(0.0);
+ float weight_accum = 0.0;
+
+ vec4 light_blend_accum = vec4(0.0);
+ float weight_blend_accum = 0.0;
+
+ float blend = -1.0;
+
+ // helper constants, compute once
+
+ uint cascade = 0xFFFFFFFF;
+ vec3 cascade_pos;
+ vec3 cascade_normal;
+
+ for (uint i = 0; i < sdfgi.max_cascades; i++) {
+ cascade_pos = (cam_pos - sdfgi.cascades[i].position) * sdfgi.cascades[i].to_probe;
+
+ if (any(lessThan(cascade_pos, vec3(0.0))) || any(greaterThanEqual(cascade_pos, sdfgi.cascade_probe_size))) {
+ continue; //skip cascade
+ }
+
+ cascade = i;
+ break;
+ }
+
+ if (cascade < SDFGI_MAX_CASCADES) {
+ ambient_light = vec4(0, 0, 0, 1);
+ reflection_light = vec4(0, 0, 0, 1);
+
+ float blend;
+ vec3 diffuse, specular;
+ sdfvoxel_gi_process(cascade, cascade_pos, cam_pos, cam_normal, reflection, roughness, diffuse, specular);
+
+ {
+ //process blend
+ float blend_from = (float(sdfgi.probe_axis_size - 1) / 2.0) - 2.5;
+ float blend_to = blend_from + 2.0;
+
+ vec3 inner_pos = cam_pos * sdfgi.cascades[cascade].to_probe;
+
+ float len = length(inner_pos);
+
+ inner_pos = abs(normalize(inner_pos));
+ len *= max(inner_pos.x, max(inner_pos.y, inner_pos.z));
+
+ if (len >= blend_from) {
+ blend = smoothstep(blend_from, blend_to, len);
+ } else {
+ blend = 0.0;
+ }
+ }
+
+ if (blend > 0.0) {
+ //blend
+ if (cascade == sdfgi.max_cascades - 1) {
+ ambient_light.a = 1.0 - blend;
+ reflection_light.a = 1.0 - blend;
+
+ } else {
+ vec3 diffuse2, specular2;
+ cascade_pos = (cam_pos - sdfgi.cascades[cascade + 1].position) * sdfgi.cascades[cascade + 1].to_probe;
+ sdfvoxel_gi_process(cascade + 1, cascade_pos, cam_pos, cam_normal, reflection, roughness, diffuse2, specular2);
+ diffuse = mix(diffuse, diffuse2, blend);
+ specular = mix(specular, specular2, blend);
+ }
+ }
+
+ ambient_light.rgb = diffuse;
+
+ if (roughness < 0.2) {
+ vec3 pos_to_uvw = 1.0 / sdfgi.grid_size;
+ vec4 light_accum = vec4(0.0);
+
+ float blend_size = (sdfgi.grid_size.x / float(sdfgi.probe_axis_size - 1)) * 0.5;
+
+ float radius_sizes[SDFGI_MAX_CASCADES];
+ cascade = 0xFFFF;
+
+ float base_distance = length(cam_pos);
+ for (uint i = 0; i < sdfgi.max_cascades; i++) {
+ radius_sizes[i] = (1.0 / sdfgi.cascades[i].to_cell) * (sdfgi.grid_size.x * 0.5 - blend_size);
+ if (cascade == 0xFFFF && base_distance < radius_sizes[i]) {
+ cascade = i;
+ }
+ }
+
+ cascade = min(cascade, sdfgi.max_cascades - 1);
+
+ float max_distance = radius_sizes[sdfgi.max_cascades - 1];
+ vec3 ray_pos = cam_pos;
+ vec3 ray_dir = reflection;
+
+ {
+ float prev_radius = cascade > 0 ? radius_sizes[cascade - 1] : 0.0;
+ float base_blend = (base_distance - prev_radius) / (radius_sizes[cascade] - prev_radius);
+ float bias = (1.0 + base_blend) * 1.1;
+ vec3 abs_ray_dir = abs(ray_dir);
+ //ray_pos += ray_dir * (bias / sdfgi.cascades[cascade].to_cell); //bias to avoid self occlusion
+ ray_pos += (ray_dir * 1.0 / max(abs_ray_dir.x, max(abs_ray_dir.y, abs_ray_dir.z)) + cam_normal * 1.4) * bias / sdfgi.cascades[cascade].to_cell;
+ }
+ float softness = 0.2 + min(1.0, roughness * 5.0) * 4.0; //approximation to roughness so it does not seem like a hard fade
+ uint i = 0;
+ bool found = false;
+ while (true) {
+ if (length(ray_pos) >= max_distance || light_accum.a > 0.99) {
+ break;
+ }
+ if (!found && i >= cascade && length(ray_pos) < radius_sizes[i]) {
+ uint next_i = min(i + 1, sdfgi.max_cascades - 1);
+ cascade = max(i, cascade); //never go down
+
+ vec3 pos = ray_pos - sdfgi.cascades[i].position;
+ pos *= sdfgi.cascades[i].to_cell * pos_to_uvw;
+
+ float fdistance = textureLod(sampler3D(sdf_cascades[i], linear_sampler), pos, 0.0).r * 255.0 - 1.1;
+
+ vec4 hit_light = vec4(0.0);
+ if (fdistance < softness) {
+ hit_light.rgb = textureLod(sampler3D(light_cascades[i], linear_sampler), pos, 0.0).rgb;
+ hit_light.rgb *= 0.5; //approximation given value read is actually meant for anisotropy
+ hit_light.a = clamp(1.0 - (fdistance / softness), 0.0, 1.0);
+ hit_light.rgb *= hit_light.a;
+ }
+
+ fdistance /= sdfgi.cascades[i].to_cell;
+
+ if (i < (sdfgi.max_cascades - 1)) {
+ pos = ray_pos - sdfgi.cascades[next_i].position;
+ pos *= sdfgi.cascades[next_i].to_cell * pos_to_uvw;
+
+ float fdistance2 = textureLod(sampler3D(sdf_cascades[next_i], linear_sampler), pos, 0.0).r * 255.0 - 1.1;
+
+ vec4 hit_light2 = vec4(0.0);
+ if (fdistance2 < softness) {
+ hit_light2.rgb = textureLod(sampler3D(light_cascades[next_i], linear_sampler), pos, 0.0).rgb;
+ hit_light2.rgb *= 0.5; //approximation given value read is actually meant for anisotropy
+ hit_light2.a = clamp(1.0 - (fdistance2 / softness), 0.0, 1.0);
+ hit_light2.rgb *= hit_light2.a;
+ }
+
+ float prev_radius = i == 0 ? 0.0 : radius_sizes[max(0, i - 1)];
+ float blend = clamp((length(ray_pos) - prev_radius) / (radius_sizes[i] - prev_radius), 0.0, 1.0);
+
+ fdistance2 /= sdfgi.cascades[next_i].to_cell;
+
+ hit_light = mix(hit_light, hit_light2, blend);
+ fdistance = mix(fdistance, fdistance2, blend);
+ }
+
+ light_accum += hit_light;
+ ray_pos += ray_dir * fdistance;
+ found = true;
+ }
+ i++;
+ if (i == sdfgi.max_cascades) {
+ i = 0;
+ found = false;
+ }
+ }
+
+ vec3 light = light_accum.rgb / max(light_accum.a, 0.00001);
+ float alpha = min(1.0, light_accum.a);
+
+ float b = min(1.0, roughness * 5.0);
+
+ float sa = 1.0 - b;
+
+ reflection_light.a = alpha * sa + b;
+ if (reflection_light.a == 0) {
+ specular = vec3(0.0);
+ } else {
+ specular = (light * alpha * sa + specular * b) / reflection_light.a;
+ }
+ }
+
+ reflection_light.rgb = specular;
+
+ ambient_light.rgb *= sdfgi.energy;
+ reflection_light.rgb *= sdfgi.energy;
+ } else {
+ ambient_light = vec4(0);
+ reflection_light = vec4(0);
+ }
+}
+
+//standard voxel cone trace
+vec4 voxel_cone_trace(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
+ float dist = p_bias;
+ vec4 color = vec4(0.0);
+
+ while (dist < max_distance && color.a < 0.95) {
+ float diameter = max(1.0, 2.0 * tan_half_angle * dist);
+ vec3 uvw_pos = (pos + dist * direction) * cell_size;
+ float half_diameter = diameter * 0.5;
+ //check if outside, then break
+ if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + half_diameter * cell_size)))) {
+ break;
+ }
+ vec4 scolor = textureLod(sampler3D(probe, linear_sampler_with_mipmaps), uvw_pos, log2(diameter));
+ float a = (1.0 - color.a);
+ color += a * scolor;
+ dist += half_diameter;
+ }
+
+ return color;
+}
+
+vec4 voxel_cone_trace_45_degrees(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float max_distance, float p_bias) {
+ float dist = p_bias;
+ vec4 color = vec4(0.0);
+ float radius = max(0.5, dist);
+ float lod_level = log2(radius * 2.0);
+
+ while (dist < max_distance && color.a < 0.95) {
+ vec3 uvw_pos = (pos + dist * direction) * cell_size;
+
+ //check if outside, then break
+ if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + radius * cell_size)))) {
+ break;
+ }
+ vec4 scolor = textureLod(sampler3D(probe, linear_sampler_with_mipmaps), uvw_pos, lod_level);
+ lod_level += 1.0;
+
+ float a = (1.0 - color.a);
+ scolor *= a;
+ color += scolor;
+ dist += radius;
+ radius = max(0.5, dist);
+ }
+ return color;
+}
+
+void voxel_gi_compute(uint index, vec3 position, vec3 normal, vec3 ref_vec, mat3 normal_xform, float roughness, inout vec4 out_spec, inout vec4 out_diff, inout float out_blend) {
+ position = (voxel_gi_instances.data[index].xform * vec4(position, 1.0)).xyz;
+ ref_vec = normalize((voxel_gi_instances.data[index].xform * vec4(ref_vec, 0.0)).xyz);
+ normal = normalize((voxel_gi_instances.data[index].xform * vec4(normal, 0.0)).xyz);
+
+ position += normal * voxel_gi_instances.data[index].normal_bias;
+
+ //this causes corrupted pixels, i have no idea why..
+ if (any(bvec2(any(lessThan(position, vec3(0.0))), any(greaterThan(position, voxel_gi_instances.data[index].bounds))))) {
+ return;
+ }
+
+ mat3 dir_xform = mat3(voxel_gi_instances.data[index].xform) * normal_xform;
+
+ vec3 blendv = abs(position / voxel_gi_instances.data[index].bounds * 2.0 - 1.0);
+ float blend = clamp(1.0 - max(blendv.x, max(blendv.y, blendv.z)), 0.0, 1.0);
+ //float blend=1.0;
+
+ float max_distance = length(voxel_gi_instances.data[index].bounds);
+ vec3 cell_size = 1.0 / voxel_gi_instances.data[index].bounds;
+
+ //irradiance
+
+ vec4 light = vec4(0.0);
+
+ if (params.high_quality_vct) {
+ const uint cone_dir_count = 6;
+ vec3 cone_dirs[cone_dir_count] = vec3[](
+ vec3(0.0, 0.0, 1.0),
+ vec3(0.866025, 0.0, 0.5),
+ vec3(0.267617, 0.823639, 0.5),
+ vec3(-0.700629, 0.509037, 0.5),
+ vec3(-0.700629, -0.509037, 0.5),
+ vec3(0.267617, -0.823639, 0.5));
+
+ float cone_weights[cone_dir_count] = float[](0.25, 0.15, 0.15, 0.15, 0.15, 0.15);
+ float cone_angle_tan = 0.577;
+
+ for (uint i = 0; i < cone_dir_count; i++) {
+ vec3 dir = normalize(dir_xform * cone_dirs[i]);
+ light += cone_weights[i] * voxel_cone_trace(voxel_gi_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, voxel_gi_instances.data[index].bias);
+ }
+ } else {
+ const uint cone_dir_count = 4;
+ vec3 cone_dirs[cone_dir_count] = vec3[](
+ vec3(0.707107, 0.0, 0.707107),
+ vec3(0.0, 0.707107, 0.707107),
+ vec3(-0.707107, 0.0, 0.707107),
+ vec3(0.0, -0.707107, 0.707107));
+
+ float cone_weights[cone_dir_count] = float[](0.25, 0.25, 0.25, 0.25);
+ for (int i = 0; i < cone_dir_count; i++) {
+ vec3 dir = normalize(dir_xform * cone_dirs[i]);
+ light += cone_weights[i] * voxel_cone_trace_45_degrees(voxel_gi_textures[index], cell_size, position, dir, max_distance, voxel_gi_instances.data[index].bias);
+ }
+ }
+
+ light.rgb *= voxel_gi_instances.data[index].dynamic_range;
+ if (!voxel_gi_instances.data[index].blend_ambient) {
+ light.a = 1.0;
+ }
+
+ out_diff += light * blend;
+
+ //radiance
+ vec4 irr_light = voxel_cone_trace(voxel_gi_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, voxel_gi_instances.data[index].bias);
+ irr_light.rgb *= voxel_gi_instances.data[index].dynamic_range;
+ if (!voxel_gi_instances.data[index].blend_ambient) {
+ irr_light.a = 1.0;
+ }
+
+ out_spec += irr_light * blend;
+
+ out_blend += blend;
+}
+
+vec4 fetch_normal_and_roughness(ivec2 pos) {
+ vec4 normal_roughness = texelFetch(sampler2D(normal_roughness_buffer, linear_sampler), pos, 0);
+
+ normal_roughness.xyz = normalize(normal_roughness.xyz * 2.0 - 1.0);
+ return normal_roughness;
+}
+
+void process_gi(ivec2 pos, vec3 vertex, inout vec4 ambient_light, inout vec4 reflection_light) {
+ vec4 normal_roughness = fetch_normal_and_roughness(pos);
+
+ vec3 normal = normal_roughness.xyz;
+
+ if (normal.length() > 0.5) {
+ //valid normal, can do GI
+ float roughness = normal_roughness.w;
+ vertex = mat3(params.cam_rotation) * vertex;
+ normal = normalize(mat3(params.cam_rotation) * normal);
+ vec3 reflection = normalize(reflect(normalize(vertex), normal));
+
+#ifdef USE_SDFGI
+ sdfgi_process(vertex, normal, reflection, roughness, ambient_light, reflection_light);
+#endif
+
+#ifdef USE_VOXEL_GI_INSTANCES
+ {
+ uvec2 voxel_gi_tex = texelFetch(usampler2D(voxel_gi_buffer, linear_sampler), pos, 0).rg;
+ roughness *= roughness;
+ //find arbitrary tangent and bitangent, then build a matrix
+ vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, normal));
+ vec3 bitangent = normalize(cross(tangent, normal));
+ mat3 normal_mat = mat3(tangent, bitangent, normal);
+
+ vec4 amb_accum = vec4(0.0);
+ vec4 spec_accum = vec4(0.0);
+ float blend_accum = 0.0;
+
+ for (uint i = 0; i < params.max_voxel_gi_instances; i++) {
+ if (any(equal(uvec2(i), voxel_gi_tex))) {
+ voxel_gi_compute(i, vertex, normal, reflection, normal_mat, roughness, spec_accum, amb_accum, blend_accum);
+ }
+ }
+ if (blend_accum > 0.0) {
+ amb_accum /= blend_accum;
+ spec_accum /= blend_accum;
+ }
+
+#ifdef USE_SDFGI
+ reflection_light = blend_color(spec_accum, reflection_light);
+ ambient_light = blend_color(amb_accum, ambient_light);
+#else
+ reflection_light = spec_accum;
+ ambient_light = amb_accum;
+#endif
+ }
+#endif
+ }
+}
+
+void main() {
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+
+#ifdef MODE_HALF_RES
+ pos <<= 1;
+#endif
+ if (any(greaterThanEqual(pos, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+ vec4 ambient_light = vec4(0.0);
+ vec4 reflection_light = vec4(0.0);
+
+ vec3 vertex = reconstruct_position(pos);
+ vertex.y = -vertex.y;
+
+ process_gi(pos, vertex, ambient_light, reflection_light);
+
+#ifdef MODE_HALF_RES
+ pos >>= 1;
+#endif
+
+ imageStore(ambient_buffer, pos, ambient_light);
+ imageStore(reflection_buffer, pos, reflection_light);
+}
diff --git a/servers/rendering/renderer_rd/shaders/giprobe_write.glsl b/servers/rendering/renderer_rd/shaders/giprobe_write.glsl
new file mode 100644
index 0000000000..5dc2d08a3b
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/giprobe_write.glsl
@@ -0,0 +1,323 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#define NO_CHILDREN 0xFFFFFFFF
+#define GREY_VEC vec3(0.33333, 0.33333, 0.33333)
+
+struct CellChildren {
+ uint children[8];
+};
+
+layout(set = 0, binding = 1, std430) buffer CellChildrenBuffer {
+ CellChildren data[];
+}
+cell_children;
+
+struct CellData {
+ uint position; // xyz 10 bits
+ uint albedo; //rgb albedo
+ uint emission; //rgb normalized with e as multiplier
+ uint normal; //RGB normal encoded
+};
+
+layout(set = 0, binding = 2, std430) buffer CellDataBuffer {
+ CellData data[];
+}
+cell_data;
+
+#define LIGHT_TYPE_DIRECTIONAL 0
+#define LIGHT_TYPE_OMNI 1
+#define LIGHT_TYPE_SPOT 2
+
+#ifdef MODE_COMPUTE_LIGHT
+
+struct Light {
+ uint type;
+ float energy;
+ float radius;
+ float attenuation;
+
+ vec3 color;
+ float cos_spot_angle;
+
+ vec3 position;
+ float inv_spot_attenuation;
+
+ vec3 direction;
+ bool has_shadow;
+};
+
+layout(set = 0, binding = 3, std140) uniform Lights {
+ Light data[MAX_LIGHTS];
+}
+lights;
+
+#endif
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec3 limits;
+ uint stack_size;
+
+ float emission_scale;
+ float propagation;
+ float dynamic_range;
+
+ uint light_count;
+ uint cell_offset;
+ uint cell_count;
+ uint pad[2];
+}
+params;
+
+layout(set = 0, binding = 4, std140) uniform Outputs {
+ vec4 data[];
+}
+output;
+
+#ifdef MODE_COMPUTE_LIGHT
+
+uint raymarch(float distance, float distance_adv, vec3 from, vec3 direction) {
+ uint result = NO_CHILDREN;
+
+ ivec3 size = ivec3(max(max(params.limits.x, params.limits.y), params.limits.z));
+
+ while (distance > -distance_adv) { //use this to avoid precision errors
+ uint cell = 0;
+
+ ivec3 pos = ivec3(from);
+
+ if (all(greaterThanEqual(pos, ivec3(0))) && all(lessThan(pos, size))) {
+ ivec3 ofs = ivec3(0);
+ ivec3 half_size = size / 2;
+
+ for (int i = 0; i < params.stack_size - 1; i++) {
+ bvec3 greater = greaterThanEqual(pos, ofs + half_size);
+
+ ofs += mix(ivec3(0), half_size, greater);
+
+ uint child = 0; //wonder if this can be done faster
+ if (greater.x) {
+ child |= 1;
+ }
+ if (greater.y) {
+ child |= 2;
+ }
+ if (greater.z) {
+ child |= 4;
+ }
+
+ cell = cell_children.data[cell].children[child];
+ if (cell == NO_CHILDREN) {
+ break;
+ }
+
+ half_size >>= ivec3(1);
+ }
+
+ if (cell != NO_CHILDREN) {
+ return cell; //found cell!
+ }
+ }
+
+ from += direction * distance_adv;
+ distance -= distance_adv;
+ }
+
+ return NO_CHILDREN;
+}
+
+bool compute_light_vector(uint light, uint cell, vec3 pos, out float attenuation, out vec3 light_pos) {
+ if (lights.data[light].type == LIGHT_TYPE_DIRECTIONAL) {
+ light_pos = pos - lights.data[light].direction * length(vec3(params.limits));
+ attenuation = 1.0;
+ } else {
+ light_pos = lights.data[light].position;
+ float distance = length(pos - light_pos);
+ if (distance >= lights.data[light].radius) {
+ return false;
+ }
+
+ attenuation = pow(clamp(1.0 - distance / lights.data[light].radius, 0.0001, 1.0), lights.data[light].attenuation);
+
+ if (lights.data[light].type == LIGHT_TYPE_SPOT) {
+ vec3 rel = normalize(pos - light_pos);
+ float cos_spot_angle = lights.data[light].cos_spot_angle;
+ float cos_angle = dot(rel, lights.data[light].direction);
+ if (cos_angle < cos_spot_angle) {
+ return false;
+ }
+
+ float scos = max(cos_angle, cos_spot_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
+ attenuation *= 1.0 - pow(spot_rim, lights.data[light].inv_spot_attenuation);
+ }
+ }
+
+ return true;
+}
+
+float get_normal_advance(vec3 p_normal) {
+ vec3 normal = p_normal;
+ vec3 unorm = abs(normal);
+
+ if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
+ // x code
+ unorm = normal.x > 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0);
+ } else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
+ // y code
+ unorm = normal.y > 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0);
+ } else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
+ // z code
+ unorm = normal.z > 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0);
+ } else {
+ // oh-no we messed up code
+ // has to be
+ unorm = vec3(1.0, 0.0, 0.0);
+ }
+
+ return 1.0 / dot(normal, unorm);
+}
+
+#endif
+
+void main() {
+ uint cell_index = gl_GlobalInvocationID.x;
+ if (cell_index >= params.cell_count) {
+ return;
+ }
+ cell_index += params.cell_offset;
+
+ uvec3 posu = uvec3(cell_data.data[cell_index].position & 0x7FF, (cell_data.data[cell_index].position >> 11) & 0x3FF, cell_data.data[cell_index].position >> 21);
+ vec4 albedo = unpackUnorm4x8(cell_data.data[cell_index].albedo);
+
+#ifdef MODE_COMPUTE_LIGHT
+
+ vec3 pos = vec3(posu) + vec3(0.5);
+
+ vec3 emission = vec3(ivec3(cell_data.data[cell_index].emission & 0x3FF, (cell_data.data[cell_index].emission >> 10) & 0x7FF, cell_data.data[cell_index].emission >> 21)) * params.emission_scale;
+ vec4 normal = unpackSnorm4x8(cell_data.data[cell_index].normal);
+
+#ifdef MODE_ANISOTROPIC
+ vec3 accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
+ const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0));
+#else
+ vec3 accum = vec3(0.0);
+#endif
+
+ for (uint i = 0; i < params.light_count; i++) {
+ float attenuation;
+ vec3 light_pos;
+
+ if (!compute_light_vector(i, cell_index, pos, attenuation, light_pos)) {
+ continue;
+ }
+
+ vec3 light_dir = pos - light_pos;
+ float distance = length(light_dir);
+ light_dir = normalize(light_dir);
+
+ if (length(normal.xyz) > 0.2 && dot(normal.xyz, light_dir) >= 0) {
+ continue; //not facing the light
+ }
+
+ if (lights.data[i].has_shadow) {
+ float distance_adv = get_normal_advance(light_dir);
+
+ distance += distance_adv - mod(distance, distance_adv); //make it reach the center of the box always
+
+ vec3 from = pos - light_dir * distance; //approximate
+ from -= sign(light_dir) * 0.45; //go near the edge towards the light direction to avoid self occlusion
+
+ uint result = raymarch(distance, distance_adv, from, light_dir);
+
+ if (result != cell_index) {
+ continue; //was occluded
+ }
+ }
+
+ vec3 light = lights.data[i].color * albedo.rgb * attenuation * lights.data[i].energy;
+
+#ifdef MODE_ANISOTROPIC
+ for (uint j = 0; j < 6; j++) {
+ accum[j] += max(0.0, dot(accum_dir, -light_dir)) * light + emission;
+ }
+#else
+ if (length(normal.xyz) > 0.2) {
+ accum += max(0.0, dot(normal.xyz, -light_dir)) * light + emission;
+ } else {
+ //all directions
+ accum += light + emission;
+ }
+#endif
+ }
+
+#ifdef MODE_ANISOTROPIC
+
+ output.data[cell_index * 6 + 0] = vec4(accum[0], 0.0);
+ output.data[cell_index * 6 + 1] = vec4(accum[1], 0.0);
+ output.data[cell_index * 6 + 2] = vec4(accum[2], 0.0);
+ output.data[cell_index * 6 + 3] = vec4(accum[3], 0.0);
+ output.data[cell_index * 6 + 4] = vec4(accum[4], 0.0);
+ output.data[cell_index * 6 + 5] = vec4(accum[5], 0.0);
+#else
+ output.data[cell_index] = vec4(accum, 0.0);
+
+#endif
+
+#endif //MODE_COMPUTE_LIGHT
+
+#ifdef MODE_UPDATE_MIPMAPS
+
+ {
+#ifdef MODE_ANISOTROPIC
+ vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
+#else
+ vec3 light_accum = vec3(0.0);
+#endif
+ float count = 0.0;
+ for (uint i = 0; i < 8; i++) {
+ uint child_index = cell_children.data[cell_index].children[i];
+ if (child_index == NO_CHILDREN) {
+ continue;
+ }
+#ifdef MODE_ANISOTROPIC
+ light_accum[1] += output.data[child_index * 6 + 0].rgb;
+ light_accum[2] += output.data[child_index * 6 + 1].rgb;
+ light_accum[3] += output.data[child_index * 6 + 2].rgb;
+ light_accum[4] += output.data[child_index * 6 + 3].rgb;
+ light_accum[5] += output.data[child_index * 6 + 4].rgb;
+ light_accum[6] += output.data[child_index * 6 + 5].rgb;
+
+#else
+ light_accum += output.data[child_index].rgb;
+
+#endif
+
+ count += 1.0;
+ }
+
+ float divisor = mix(8.0, count, params.propagation);
+#ifdef MODE_ANISOTROPIC
+ output.data[cell_index * 6 + 0] = vec4(light_accum[0] / divisor, 0.0);
+ output.data[cell_index * 6 + 1] = vec4(light_accum[1] / divisor, 0.0);
+ output.data[cell_index * 6 + 2] = vec4(light_accum[2] / divisor, 0.0);
+ output.data[cell_index * 6 + 3] = vec4(light_accum[3] / divisor, 0.0);
+ output.data[cell_index * 6 + 4] = vec4(light_accum[4] / divisor, 0.0);
+ output.data[cell_index * 6 + 5] = vec4(light_accum[5] / divisor, 0.0);
+
+#else
+ output.data[cell_index] = vec4(light_accum / divisor, 0.0);
+#endif
+ }
+#endif
+
+#ifdef MODE_WRITE_TEXTURE
+ {
+ }
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/light_data_inc.glsl b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl
new file mode 100644
index 0000000000..fdc7729338
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl
@@ -0,0 +1,87 @@
+#define LIGHT_BAKE_DISABLED 0
+#define LIGHT_BAKE_DYNAMIC 1
+#define LIGHT_BAKE_STATIC 2
+
+struct LightData { //this structure needs to be as packed as possible
+ highp vec3 position;
+ highp float inv_radius;
+
+ mediump vec3 direction;
+ highp float size;
+
+ mediump vec3 color;
+ mediump float attenuation;
+
+ mediump float cone_attenuation;
+ mediump float cone_angle;
+ mediump float specular_amount;
+ bool shadow_enabled;
+
+ highp vec4 atlas_rect; // rect in the shadow atlas
+ highp mat4 shadow_matrix;
+ highp float shadow_bias;
+ highp float shadow_normal_bias;
+ highp float transmittance_bias;
+ highp float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle
+ highp float soft_shadow_scale; // scales the shadow kernel for blurrier shadows
+ uint mask;
+ mediump float shadow_volumetric_fog_fade;
+ uint bake_mode;
+ highp vec4 projector_rect; //projector rect in srgb decal atlas
+};
+
+#define REFLECTION_AMBIENT_DISABLED 0
+#define REFLECTION_AMBIENT_ENVIRONMENT 1
+#define REFLECTION_AMBIENT_COLOR 2
+
+struct ReflectionData {
+ highp vec3 box_extents;
+ mediump float index;
+ highp vec3 box_offset;
+ uint mask;
+ mediump vec3 ambient; // ambient color
+ mediump float intensity;
+ bool exterior;
+ bool box_project;
+ uint ambient_mode;
+ uint pad;
+ //0-8 is intensity,8-9 is ambient, mode
+ highp mat4 local_matrix; // up to here for spot and omni, rest is for directional
+ // notes: for ambientblend, use distance to edge to blend between already existing global environment
+};
+
+struct DirectionalLightData {
+ mediump vec3 direction;
+ mediump float energy;
+ mediump vec3 color;
+ mediump float size;
+ mediump float specular;
+ uint mask;
+ highp float softshadow_angle;
+ highp float soft_shadow_scale;
+ bool blend_splits;
+ bool shadow_enabled;
+ highp float fade_from;
+ highp float fade_to;
+ uvec2 pad;
+ uint bake_mode;
+ mediump float shadow_volumetric_fog_fade;
+ highp vec4 shadow_bias;
+ highp vec4 shadow_normal_bias;
+ highp vec4 shadow_transmittance_bias;
+ highp vec4 shadow_z_range;
+ highp vec4 shadow_range_begin;
+ highp vec4 shadow_split_offsets;
+ highp mat4 shadow_matrix1;
+ highp mat4 shadow_matrix2;
+ highp mat4 shadow_matrix3;
+ highp mat4 shadow_matrix4;
+ mediump vec4 shadow_color1;
+ mediump vec4 shadow_color2;
+ mediump vec4 shadow_color3;
+ mediump vec4 shadow_color4;
+ highp vec2 uv_scale1;
+ highp vec2 uv_scale2;
+ highp vec2 uv_scale3;
+ highp vec2 uv_scale4;
+};
diff --git a/servers/rendering/renderer_rd/shaders/luminance_reduce.glsl b/servers/rendering/renderer_rd/shaders/luminance_reduce.glsl
new file mode 100644
index 0000000000..466442b67a
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/luminance_reduce.glsl
@@ -0,0 +1,82 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define BLOCK_SIZE 8
+
+layout(local_size_x = BLOCK_SIZE, local_size_y = BLOCK_SIZE, local_size_z = 1) in;
+
+shared float tmp_data[BLOCK_SIZE * BLOCK_SIZE];
+
+#ifdef READ_TEXTURE
+
+//use for main texture
+layout(set = 0, binding = 0) uniform sampler2D source_texture;
+
+#else
+
+//use for intermediate textures
+layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D source_luminance;
+
+#endif
+
+layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D dest_luminance;
+
+#ifdef WRITE_LUMINANCE
+layout(set = 2, binding = 0) uniform sampler2D prev_luminance;
+#endif
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ ivec2 source_size;
+ float max_luminance;
+ float min_luminance;
+ float exposure_adjust;
+ float pad[3];
+}
+params;
+
+void main() {
+ uint t = gl_LocalInvocationID.y * BLOCK_SIZE + gl_LocalInvocationID.x;
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+
+ if (any(lessThan(pos, params.source_size))) {
+#ifdef READ_TEXTURE
+ vec3 v = texelFetch(source_texture, pos, 0).rgb;
+ tmp_data[t] = max(v.r, max(v.g, v.b));
+#else
+ tmp_data[t] = imageLoad(source_luminance, pos).r;
+#endif
+ } else {
+ tmp_data[t] = 0.0;
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ uint size = (BLOCK_SIZE * BLOCK_SIZE) >> 1;
+
+ do {
+ if (t < size) {
+ tmp_data[t] += tmp_data[t + size];
+ }
+ groupMemoryBarrier();
+ barrier();
+
+ size >>= 1;
+ } while (size >= 1);
+
+ if (t == 0) {
+ //compute rect size
+ ivec2 rect_size = min(params.source_size - pos, ivec2(BLOCK_SIZE));
+ float avg = tmp_data[0] / float(rect_size.x * rect_size.y);
+ //float avg = tmp_data[0] / float(BLOCK_SIZE*BLOCK_SIZE);
+ pos /= ivec2(BLOCK_SIZE);
+#ifdef WRITE_LUMINANCE
+ float prev_lum = texelFetch(prev_luminance, ivec2(0, 0), 0).r; //1 pixel previous exposure
+ avg = clamp(prev_lum + (avg - prev_lum) * params.exposure_adjust, params.min_luminance, params.max_luminance);
+#endif
+ imageStore(dest_luminance, pos, vec4(avg));
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/luminance_reduce_raster.glsl b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster.glsl
new file mode 100644
index 0000000000..29ebd74a90
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster.glsl
@@ -0,0 +1,74 @@
+/* clang-format off */
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "luminance_reduce_raster_inc.glsl"
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "luminance_reduce_raster_inc.glsl"
+
+layout(location = 0) in vec2 uv_interp;
+/* clang-format on */
+
+layout(set = 0, binding = 0) uniform sampler2D source_exposure;
+
+#ifdef FINAL_PASS
+layout(set = 1, binding = 0) uniform sampler2D prev_luminance;
+#endif
+
+layout(location = 0) out highp float luminance;
+
+void main() {
+ ivec2 dest_pos = ivec2(uv_interp * settings.dest_size);
+ ivec2 src_pos = ivec2(uv_interp * settings.source_size);
+
+ ivec2 next_pos = (dest_pos + ivec2(1)) * settings.source_size / settings.dest_size;
+ next_pos = max(next_pos, src_pos + ivec2(1)); //so it at least reads one pixel
+
+ highp vec3 source_color = vec3(0.0);
+ for (int i = src_pos.x; i < next_pos.x; i++) {
+ for (int j = src_pos.y; j < next_pos.y; j++) {
+ source_color += texelFetch(source_exposure, ivec2(i, j), 0).rgb;
+ }
+ }
+
+ source_color /= float((next_pos.x - src_pos.x) * (next_pos.y - src_pos.y));
+
+#ifdef FIRST_PASS
+ luminance = max(source_color.r, max(source_color.g, source_color.b));
+
+ // This formula should be more "accurate" but gave an overexposed result when testing.
+ // Leaving it here so we can revisit it if we want.
+ // luminance = source_color.r * 0.21 + source_color.g * 0.71 + source_color.b * 0.07;
+#else
+ luminance = source_color.r;
+#endif
+
+#ifdef FINAL_PASS
+ // Obtain our target luminance
+ luminance = clamp(luminance, settings.min_luminance, settings.max_luminance);
+
+ // Now smooth to our transition
+ highp float prev_lum = texelFetch(prev_luminance, ivec2(0, 0), 0).r; //1 pixel previous luminance
+ luminance = prev_lum + (luminance - prev_lum) * clamp(settings.exposure_adjust, 0.0, 1.0);
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl
new file mode 100644
index 0000000000..ed389ffe56
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl
@@ -0,0 +1,11 @@
+
+layout(push_constant, binding = 1, std430) uniform PushConstant {
+ ivec2 source_size;
+ ivec2 dest_size;
+
+ float exposure_adjust;
+ float min_luminance;
+ float max_luminance;
+ float pad;
+}
+settings;
diff --git a/servers/rendering/renderer_rd/shaders/particles.glsl b/servers/rendering/renderer_rd/shaders/particles.glsl
new file mode 100644
index 0000000000..9f8410fd8a
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/particles.glsl
@@ -0,0 +1,632 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+#define SDF_MAX_LENGTH 16384.0
+
+/* SET 0: GLOBAL DATA */
+
+layout(set = 0, binding = 1) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 2, std430) restrict readonly buffer GlobalVariableData {
+ vec4 data[];
+}
+global_variables;
+
+/* Set 1: FRAME AND PARTICLE DATA */
+
+// a frame history is kept for trail deterministic behavior
+
+#define MAX_ATTRACTORS 32
+
+#define ATTRACTOR_TYPE_SPHERE 0
+#define ATTRACTOR_TYPE_BOX 1
+#define ATTRACTOR_TYPE_VECTOR_FIELD 2
+
+struct Attractor {
+ mat4 transform;
+ vec3 extents; //exents or radius
+ uint type;
+ uint texture_index; //texture index for vector field
+ float strength;
+ float attenuation;
+ float directionality;
+};
+
+#define MAX_COLLIDERS 32
+
+#define COLLIDER_TYPE_SPHERE 0
+#define COLLIDER_TYPE_BOX 1
+#define COLLIDER_TYPE_SDF 2
+#define COLLIDER_TYPE_HEIGHT_FIELD 3
+#define COLLIDER_TYPE_2D_SDF 4
+
+struct Collider {
+ mat4 transform;
+ vec3 extents; //exents or radius
+ uint type;
+
+ uint texture_index; //texture index for vector field
+ float scale;
+ uint pad[2];
+};
+
+struct FrameParams {
+ bool emitting;
+ float system_phase;
+ float prev_system_phase;
+ uint cycle;
+
+ float explosiveness;
+ float randomness;
+ float time;
+ float delta;
+
+ uint frame;
+ uint pad0;
+ uint pad1;
+ uint pad2;
+
+ uint random_seed;
+ uint attractor_count;
+ uint collider_count;
+ float particle_size;
+
+ mat4 emission_transform;
+
+ Attractor attractors[MAX_ATTRACTORS];
+ Collider colliders[MAX_COLLIDERS];
+};
+
+layout(set = 1, binding = 0, std430) restrict buffer FrameHistory {
+ FrameParams data[];
+}
+frame_history;
+
+#define PARTICLE_FLAG_ACTIVE uint(1)
+#define PARTICLE_FLAG_STARTED uint(2)
+#define PARTICLE_FLAG_TRAILED uint(4)
+#define PARTICLE_FRAME_MASK uint(0xFFFF)
+#define PARTICLE_FRAME_SHIFT uint(16)
+
+struct ParticleData {
+ mat4 xform;
+ vec3 velocity;
+ uint flags;
+ vec4 color;
+ vec4 custom;
+};
+
+layout(set = 1, binding = 1, std430) restrict buffer Particles {
+ ParticleData data[];
+}
+particles;
+
+#define EMISSION_FLAG_HAS_POSITION 1
+#define EMISSION_FLAG_HAS_ROTATION_SCALE 2
+#define EMISSION_FLAG_HAS_VELOCITY 4
+#define EMISSION_FLAG_HAS_COLOR 8
+#define EMISSION_FLAG_HAS_CUSTOM 16
+
+struct ParticleEmission {
+ mat4 xform;
+ vec3 velocity;
+ uint flags;
+ vec4 color;
+ vec4 custom;
+};
+
+layout(set = 1, binding = 2, std430) restrict buffer SourceEmission {
+ int particle_count;
+ uint pad0;
+ uint pad1;
+ uint pad2;
+ ParticleEmission data[];
+}
+src_particles;
+
+layout(set = 1, binding = 3, std430) restrict buffer DestEmission {
+ int particle_count;
+ int particle_max;
+ uint pad1;
+ uint pad2;
+ ParticleEmission data[];
+}
+dst_particles;
+
+/* SET 2: COLLIDER/ATTRACTOR TEXTURES */
+
+#define MAX_3D_TEXTURES 7
+
+layout(set = 2, binding = 0) uniform texture3D sdf_vec_textures[MAX_3D_TEXTURES];
+layout(set = 2, binding = 1) uniform texture2D height_field_texture;
+
+/* SET 3: MATERIAL */
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = 3, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ float lifetime;
+ bool clear;
+ uint total_particles;
+ uint trail_size;
+ bool use_fractional_delta;
+ bool sub_emitter_mode;
+ bool can_emit;
+ bool trail_pass;
+}
+params;
+
+uint hash(uint x) {
+ x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b);
+ x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b);
+ x = (x >> uint(16)) ^ x;
+ return x;
+}
+
+bool emit_subparticle(mat4 p_xform, vec3 p_velocity, vec4 p_color, vec4 p_custom, uint p_flags) {
+ if (!params.can_emit) {
+ return false;
+ }
+
+ bool valid = false;
+
+ int dst_index = atomicAdd(dst_particles.particle_count, 1);
+
+ if (dst_index >= dst_particles.particle_max) {
+ atomicAdd(dst_particles.particle_count, -1);
+ return false;
+ }
+
+ dst_particles.data[dst_index].xform = p_xform;
+ dst_particles.data[dst_index].velocity = p_velocity;
+ dst_particles.data[dst_index].color = p_color;
+ dst_particles.data[dst_index].custom = p_custom;
+ dst_particles.data[dst_index].flags = p_flags;
+
+ return true;
+}
+
+#GLOBALS
+
+void main() {
+ uint particle = gl_GlobalInvocationID.x;
+
+ if (params.trail_size > 1) {
+ if (params.trail_pass) {
+ particle += (particle / (params.trail_size - 1)) + 1;
+ } else {
+ particle *= params.trail_size;
+ }
+ }
+
+ if (particle >= params.total_particles * params.trail_size) {
+ return; //discard
+ }
+
+ uint index = particle / params.trail_size;
+ uint frame = (particle % params.trail_size);
+
+#define FRAME frame_history.data[frame]
+#define PARTICLE particles.data[particle]
+
+ bool apply_forces = true;
+ bool apply_velocity = true;
+ float local_delta = FRAME.delta;
+
+ float mass = 1.0;
+
+ bool restart = false;
+
+ bool restart_position = false;
+ bool restart_rotation_scale = false;
+ bool restart_velocity = false;
+ bool restart_color = false;
+ bool restart_custom = false;
+
+ if (params.clear) {
+ PARTICLE.color = vec4(1.0);
+ PARTICLE.custom = vec4(0.0);
+ PARTICLE.velocity = vec3(0.0);
+ PARTICLE.flags = 0;
+ PARTICLE.xform = mat4(
+ vec4(1.0, 0.0, 0.0, 0.0),
+ vec4(0.0, 1.0, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+ }
+
+ //clear started flag if set
+
+ if (params.trail_pass) {
+ //trail started
+ uint src_idx = index * params.trail_size;
+ if (bool(particles.data[src_idx].flags & PARTICLE_FLAG_STARTED)) {
+ //save start conditions for trails
+ PARTICLE.color = particles.data[src_idx].color;
+ PARTICLE.custom = particles.data[src_idx].custom;
+ PARTICLE.velocity = particles.data[src_idx].velocity;
+ PARTICLE.flags = PARTICLE_FLAG_TRAILED | ((frame_history.data[0].frame & PARTICLE_FRAME_MASK) << PARTICLE_FRAME_SHIFT); //mark it as trailed, save in which frame it will start
+ PARTICLE.xform = particles.data[src_idx].xform;
+ }
+
+ if (bool(PARTICLE.flags & PARTICLE_FLAG_TRAILED) && ((PARTICLE.flags >> PARTICLE_FRAME_SHIFT) == (FRAME.frame & PARTICLE_FRAME_MASK))) { //check this is trailed and see if it should start now
+ // we just assume that this is the first frame of the particle, the rest is deterministic
+ PARTICLE.flags = PARTICLE_FLAG_ACTIVE | (particles.data[src_idx].flags & (PARTICLE_FRAME_MASK << PARTICLE_FRAME_SHIFT));
+ return; //- this appears like it should be correct, but it seems not to be.. wonder why.
+ }
+ } else {
+ PARTICLE.flags &= ~PARTICLE_FLAG_STARTED;
+ }
+
+ bool collided = false;
+ vec3 collision_normal = vec3(0.0);
+ float collision_depth = 0.0;
+
+ vec3 attractor_force = vec3(0.0);
+
+#if !defined(DISABLE_VELOCITY)
+
+ if (bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE)) {
+ PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta;
+ }
+#endif
+
+ if (!params.trail_pass && params.sub_emitter_mode) {
+ if (!bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE)) {
+ int src_index = atomicAdd(src_particles.particle_count, -1) - 1;
+
+ if (src_index >= 0) {
+ PARTICLE.flags = (PARTICLE_FLAG_ACTIVE | PARTICLE_FLAG_STARTED | (FRAME.cycle << PARTICLE_FRAME_SHIFT));
+ restart = true;
+
+ if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_POSITION)) {
+ PARTICLE.xform[3] = src_particles.data[src_index].xform[3];
+ } else {
+ PARTICLE.xform[3] = vec4(0, 0, 0, 1);
+ restart_position = true;
+ }
+ if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_ROTATION_SCALE)) {
+ PARTICLE.xform[0] = src_particles.data[src_index].xform[0];
+ PARTICLE.xform[1] = src_particles.data[src_index].xform[1];
+ PARTICLE.xform[2] = src_particles.data[src_index].xform[2];
+ } else {
+ PARTICLE.xform[0] = vec4(1, 0, 0, 0);
+ PARTICLE.xform[1] = vec4(0, 1, 0, 0);
+ PARTICLE.xform[2] = vec4(0, 0, 1, 0);
+ restart_rotation_scale = true;
+ }
+ if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_VELOCITY)) {
+ PARTICLE.velocity = src_particles.data[src_index].velocity;
+ } else {
+ PARTICLE.velocity = vec3(0);
+ restart_velocity = true;
+ }
+ if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_COLOR)) {
+ PARTICLE.color = src_particles.data[src_index].color;
+ } else {
+ PARTICLE.color = vec4(1);
+ restart_color = true;
+ }
+
+ if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_CUSTOM)) {
+ PARTICLE.custom = src_particles.data[src_index].custom;
+ } else {
+ PARTICLE.custom = vec4(0);
+ restart_custom = true;
+ }
+ }
+ }
+
+ } else if (FRAME.emitting) {
+ float restart_phase = float(index) / float(params.total_particles);
+
+ if (FRAME.randomness > 0.0) {
+ uint seed = FRAME.cycle;
+ if (restart_phase >= FRAME.system_phase) {
+ seed -= uint(1);
+ }
+ seed *= uint(params.total_particles);
+ seed += uint(index);
+ float random = float(hash(seed) % uint(65536)) / 65536.0;
+ restart_phase += FRAME.randomness * random * 1.0 / float(params.total_particles);
+ }
+
+ restart_phase *= (1.0 - FRAME.explosiveness);
+
+ if (FRAME.system_phase > FRAME.prev_system_phase) {
+ // restart_phase >= prev_system_phase is used so particles emit in the first frame they are processed
+
+ if (restart_phase >= FRAME.prev_system_phase && restart_phase < FRAME.system_phase) {
+ restart = true;
+ if (params.use_fractional_delta) {
+ local_delta = (FRAME.system_phase - restart_phase) * params.lifetime;
+ }
+ }
+
+ } else if (FRAME.delta > 0.0) {
+ if (restart_phase >= FRAME.prev_system_phase) {
+ restart = true;
+ if (params.use_fractional_delta) {
+ local_delta = (1.0 - restart_phase + FRAME.system_phase) * params.lifetime;
+ }
+
+ } else if (restart_phase < FRAME.system_phase) {
+ restart = true;
+ if (params.use_fractional_delta) {
+ local_delta = (FRAME.system_phase - restart_phase) * params.lifetime;
+ }
+ }
+ }
+
+ if (params.trail_pass) {
+ restart = false;
+ }
+
+ if (restart) {
+ PARTICLE.flags = FRAME.emitting ? (PARTICLE_FLAG_ACTIVE | PARTICLE_FLAG_STARTED | (FRAME.cycle << PARTICLE_FRAME_SHIFT)) : 0;
+ restart_position = true;
+ restart_rotation_scale = true;
+ restart_velocity = true;
+ restart_color = true;
+ restart_custom = true;
+ }
+ }
+
+ bool particle_active = bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE);
+
+ uint particle_number = (PARTICLE.flags >> PARTICLE_FRAME_SHIFT) * uint(params.total_particles) + index;
+
+ if (restart && particle_active) {
+#CODE : START
+ }
+
+ if (particle_active) {
+ for (uint i = 0; i < FRAME.attractor_count; i++) {
+ vec3 dir;
+ float amount;
+ vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.attractors[i].transform[3].xyz;
+ vec3 local_pos = rel_vec * mat3(FRAME.attractors[i].transform);
+
+ switch (FRAME.attractors[i].type) {
+ case ATTRACTOR_TYPE_SPHERE: {
+ dir = normalize(rel_vec);
+ float d = length(local_pos) / FRAME.attractors[i].extents.x;
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+ } break;
+ case ATTRACTOR_TYPE_BOX: {
+ dir = normalize(rel_vec);
+
+ vec3 abs_pos = abs(local_pos / FRAME.attractors[i].extents);
+ float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+
+ } break;
+ case ATTRACTOR_TYPE_VECTOR_FIELD: {
+ vec3 uvw_pos = (local_pos / FRAME.attractors[i].extents) * 2.0 - 1.0;
+ if (any(lessThan(uvw_pos, vec3(0.0))) || any(greaterThan(uvw_pos, vec3(1.0)))) {
+ continue;
+ }
+ vec3 s = texture(sampler3D(sdf_vec_textures[FRAME.attractors[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos).xyz;
+ dir = mat3(FRAME.attractors[i].transform) * normalize(s); //revert direction
+ amount = length(s);
+
+ } break;
+ }
+ amount = pow(amount, FRAME.attractors[i].attenuation);
+ dir = normalize(mix(dir, FRAME.attractors[i].transform[2].xyz, FRAME.attractors[i].directionality));
+ attractor_force -= amount * dir * FRAME.attractors[i].strength;
+ }
+
+ float particle_size = FRAME.particle_size;
+
+#ifdef USE_COLLISON_SCALE
+
+ particle_size *= dot(vec3(length(PARTICLE.xform[0].xyz), length(PARTICLE.xform[1].xyz), length(PARTICLE.xform[2].xyz)), vec3(0.33333333333));
+
+#endif
+
+ if (FRAME.collider_count == 1 && FRAME.colliders[0].type == COLLIDER_TYPE_2D_SDF) {
+ //2D collision
+
+ vec2 pos = PARTICLE.xform[3].xy;
+ vec4 to_sdf_x = FRAME.colliders[0].transform[0];
+ vec4 to_sdf_y = FRAME.colliders[0].transform[1];
+ vec2 sdf_pos = vec2(dot(vec4(pos, 0, 1), to_sdf_x), dot(vec4(pos, 0, 1), to_sdf_y));
+
+ vec4 sdf_to_screen = vec4(FRAME.colliders[0].extents, FRAME.colliders[0].scale);
+
+ vec2 uv_pos = sdf_pos * sdf_to_screen.xy + sdf_to_screen.zw;
+
+ if (all(greaterThan(uv_pos, vec2(0.0))) && all(lessThan(uv_pos, vec2(1.0)))) {
+ vec2 pos2 = pos + vec2(0, particle_size);
+ vec2 sdf_pos2 = vec2(dot(vec4(pos2, 0, 1), to_sdf_x), dot(vec4(pos2, 0, 1), to_sdf_y));
+ float sdf_particle_size = distance(sdf_pos, sdf_pos2);
+
+ float d = texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv_pos).r * SDF_MAX_LENGTH;
+
+ d -= sdf_particle_size;
+
+ if (d < 0.0) {
+ const float EPSILON = 0.001;
+ vec2 n = normalize(vec2(
+ texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv_pos + vec2(EPSILON, 0.0)).r - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv_pos - vec2(EPSILON, 0.0)).r,
+ texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv_pos + vec2(0.0, EPSILON)).r - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uv_pos - vec2(0.0, EPSILON)).r));
+
+ collided = true;
+ sdf_pos2 = sdf_pos + n * d;
+ pos2 = vec2(dot(vec4(sdf_pos2, 0, 1), FRAME.colliders[0].transform[2]), dot(vec4(sdf_pos2, 0, 1), FRAME.colliders[0].transform[3]));
+
+ n = pos - pos2;
+
+ collision_normal = normalize(vec3(n, 0.0));
+ collision_depth = length(n);
+ }
+ }
+
+ } else {
+ for (uint i = 0; i < FRAME.collider_count; i++) {
+ vec3 normal;
+ float depth;
+ bool col = false;
+
+ vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.colliders[i].transform[3].xyz;
+ vec3 local_pos = rel_vec * mat3(FRAME.colliders[i].transform);
+
+ switch (FRAME.colliders[i].type) {
+ case COLLIDER_TYPE_SPHERE: {
+ float d = length(rel_vec) - (particle_size + FRAME.colliders[i].extents.x);
+
+ if (d < 0.0) {
+ col = true;
+ depth = -d;
+ normal = normalize(rel_vec);
+ }
+
+ } break;
+ case COLLIDER_TYPE_BOX: {
+ vec3 abs_pos = abs(local_pos);
+ vec3 sgn_pos = sign(local_pos);
+
+ if (any(greaterThan(abs_pos, FRAME.colliders[i].extents))) {
+ //point outside box
+
+ vec3 closest = min(abs_pos, FRAME.colliders[i].extents);
+ vec3 rel = abs_pos - closest;
+ depth = length(rel) - particle_size;
+ if (depth < 0.0) {
+ col = true;
+ normal = mat3(FRAME.colliders[i].transform) * (normalize(rel) * sgn_pos);
+ depth = -depth;
+ }
+ } else {
+ //point inside box
+ vec3 axis_len = FRAME.colliders[i].extents - abs_pos;
+ // there has to be a faster way to do this?
+ if (all(lessThan(axis_len.xx, axis_len.yz))) {
+ normal = vec3(1, 0, 0);
+ } else if (all(lessThan(axis_len.yy, axis_len.xz))) {
+ normal = vec3(0, 1, 0);
+ } else {
+ normal = vec3(0, 0, 1);
+ }
+
+ col = true;
+ depth = dot(normal * axis_len, vec3(1)) + particle_size;
+ normal = mat3(FRAME.colliders[i].transform) * (normal * sgn_pos);
+ }
+
+ } break;
+ case COLLIDER_TYPE_SDF: {
+ vec3 apos = abs(local_pos);
+ float extra_dist = 0.0;
+ if (any(greaterThan(apos, FRAME.colliders[i].extents))) { //outside
+ vec3 mpos = min(apos, FRAME.colliders[i].extents);
+ extra_dist = distance(mpos, apos);
+ }
+
+ if (extra_dist > particle_size) {
+ continue;
+ }
+
+ vec3 uvw_pos = (local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5;
+ float s = texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos).r;
+ s *= FRAME.colliders[i].scale;
+ s += extra_dist;
+ if (s < particle_size) {
+ col = true;
+ depth = particle_size - s;
+ const float EPSILON = 0.001;
+ normal = mat3(FRAME.colliders[i].transform) *
+ normalize(
+ vec3(
+ texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(EPSILON, 0.0, 0.0)).r,
+ texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(0.0, EPSILON, 0.0)).r,
+ texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos - vec3(0.0, 0.0, EPSILON)).r));
+ }
+
+ } break;
+ case COLLIDER_TYPE_HEIGHT_FIELD: {
+ vec3 local_pos_bottom = local_pos;
+ local_pos_bottom.y -= particle_size;
+
+ if (any(greaterThan(abs(local_pos_bottom), FRAME.colliders[i].extents))) {
+ continue;
+ }
+ const float DELTA = 1.0 / 8192.0;
+
+ vec3 uvw_pos = vec3(local_pos_bottom / FRAME.colliders[i].extents) * 0.5 + 0.5;
+
+ float y = 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz).r;
+
+ if (y > uvw_pos.y) {
+ //inside heightfield
+
+ vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents;
+ vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents;
+ vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(sampler2D(height_field_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * FRAME.colliders[i].extents;
+
+ normal = normalize(cross(pos1 - pos2, pos1 - pos3));
+ float local_y = (vec3(local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5).y;
+
+ col = true;
+ depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
+ }
+
+ } break;
+ }
+
+ if (col) {
+ if (!collided) {
+ collided = true;
+ collision_normal = normal;
+ collision_depth = depth;
+ } else {
+ vec3 c = collision_normal * collision_depth;
+ c += normal * max(0.0, depth - dot(normal, c));
+ collision_normal = normalize(c);
+ collision_depth = length(c);
+ }
+ }
+ }
+ }
+ }
+
+ if (particle_active) {
+#CODE : PROCESS
+ }
+
+ PARTICLE.flags &= ~PARTICLE_FLAG_ACTIVE;
+ if (particle_active) {
+ PARTICLE.flags |= PARTICLE_FLAG_ACTIVE;
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/particles_copy.glsl b/servers/rendering/renderer_rd/shaders/particles_copy.glsl
new file mode 100644
index 0000000000..e88e68b511
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/particles_copy.glsl
@@ -0,0 +1,225 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#define PARTICLE_FLAG_ACTIVE uint(1)
+#define PARTICLE_FLAG_STARTED uint(2)
+#define PARTICLE_FLAG_TRAILED uint(4)
+
+struct ParticleData {
+ mat4 xform;
+ vec3 velocity;
+ uint flags;
+ vec4 color;
+ vec4 custom;
+};
+
+layout(set = 0, binding = 1, std430) restrict readonly buffer Particles {
+ ParticleData data[];
+}
+particles;
+
+layout(set = 0, binding = 2, std430) restrict writeonly buffer Transforms {
+ vec4 data[];
+}
+instances;
+
+#ifdef USE_SORT_BUFFER
+
+layout(set = 1, binding = 0, std430) restrict buffer SortBuffer {
+ vec2 data[];
+}
+sort_buffer;
+
+#endif // USE_SORT_BUFFER
+
+layout(set = 2, binding = 0, std430) restrict readonly buffer TrailBindPoses {
+ mat4 data[];
+}
+trail_bind_poses;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec3 sort_direction;
+ uint total_particles;
+
+ uint trail_size;
+ uint trail_total;
+ float frame_delta;
+ float frame_remainder;
+
+ vec3 align_up;
+ uint align_mode;
+
+ bool order_by_lifetime;
+ uint lifetime_split;
+ bool lifetime_reverse;
+ uint pad;
+}
+params;
+
+#define TRANSFORM_ALIGN_DISABLED 0
+#define TRANSFORM_ALIGN_Z_BILLBOARD 1
+#define TRANSFORM_ALIGN_Y_TO_VELOCITY 2
+#define TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY 3
+
+void main() {
+#ifdef MODE_FILL_SORT_BUFFER
+
+ uint particle = gl_GlobalInvocationID.x;
+ if (particle >= params.total_particles) {
+ return; //discard
+ }
+
+ uint src_particle = particle;
+ if (params.trail_size > 1) {
+ src_particle = src_particle * params.trail_size + params.trail_size / 2; //use trail center for sorting
+ }
+ sort_buffer.data[particle].x = dot(params.sort_direction, particles.data[src_particle].xform[3].xyz);
+ sort_buffer.data[particle].y = float(particle);
+#endif
+
+#ifdef MODE_FILL_INSTANCES
+
+ uint particle = gl_GlobalInvocationID.x;
+
+ if (particle >= params.total_particles) {
+ return; //discard
+ }
+
+#ifdef USE_SORT_BUFFER
+
+ if (params.trail_size > 1) {
+ particle = uint(sort_buffer.data[particle / params.trail_size].y) + (particle % params.trail_size);
+ } else {
+ particle = uint(sort_buffer.data[particle].y); //use index from sort buffer
+ }
+#else
+ if (params.order_by_lifetime) {
+ if (params.trail_size > 1) {
+ uint limit = (params.total_particles / params.trail_size) - params.lifetime_split;
+
+ uint base_index = particle / params.trail_size;
+ uint base_offset = particle % params.trail_size;
+
+ if (params.lifetime_reverse) {
+ base_index = (params.total_particles / params.trail_size) - base_index - 1;
+ }
+
+ if (base_index < limit) {
+ base_index = params.lifetime_split + base_index;
+ } else {
+ base_index -= limit;
+ }
+
+ particle = base_index * params.trail_size + base_offset;
+
+ } else {
+ uint limit = params.total_particles - params.lifetime_split;
+
+ if (params.lifetime_reverse) {
+ particle = params.total_particles - particle - 1;
+ }
+
+ if (particle < limit) {
+ particle = params.lifetime_split + particle;
+ } else {
+ particle -= limit;
+ }
+ }
+ }
+#endif // USE_SORT_BUFFER
+
+ mat4 txform;
+
+ if (bool(particles.data[particle].flags & PARTICLE_FLAG_ACTIVE) || bool(particles.data[particle].flags & PARTICLE_FLAG_TRAILED)) {
+ txform = particles.data[particle].xform;
+ if (params.trail_size > 1) {
+ // Since the steps don't fit precisely in the history frames, must do a tiny bit of
+ // interpolation to get them close to their intended location.
+ uint part_ofs = particle % params.trail_size;
+ float natural_ofs = fract((float(part_ofs) / float(params.trail_size)) * float(params.trail_total)) * params.frame_delta;
+
+ txform[3].xyz -= particles.data[particle].velocity * natural_ofs;
+ }
+
+ switch (params.align_mode) {
+ case TRANSFORM_ALIGN_DISABLED: {
+ } break; //nothing
+ case TRANSFORM_ALIGN_Z_BILLBOARD: {
+ mat3 local = mat3(normalize(cross(params.align_up, params.sort_direction)), params.align_up, params.sort_direction);
+ local = local * mat3(txform);
+ txform[0].xyz = local[0];
+ txform[1].xyz = local[1];
+ txform[2].xyz = local[2];
+
+ } break;
+ case TRANSFORM_ALIGN_Y_TO_VELOCITY: {
+ vec3 v = particles.data[particle].velocity;
+ float s = (length(txform[0]) + length(txform[1]) + length(txform[2])) / 3.0;
+ if (length(v) > 0.0) {
+ txform[1].xyz = normalize(v);
+ } else {
+ txform[1].xyz = normalize(txform[1].xyz);
+ }
+
+ txform[0].xyz = normalize(cross(txform[1].xyz, txform[2].xyz));
+ txform[2].xyz = vec3(0.0, 0.0, 1.0) * s;
+ txform[0].xyz *= s;
+ txform[1].xyz *= s;
+ } break;
+ case TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY: {
+ vec3 v = particles.data[particle].velocity;
+ vec3 sv = v - params.sort_direction * dot(params.sort_direction, v); //screen velocity
+ float s = (length(txform[0]) + length(txform[1]) + length(txform[2])) / 3.0;
+
+ if (length(sv) == 0) {
+ sv = params.align_up;
+ }
+
+ sv = normalize(sv);
+
+ txform[0].xyz = normalize(cross(sv, params.sort_direction)) * s;
+ txform[1].xyz = sv * s;
+ txform[2].xyz = params.sort_direction * s;
+
+ } break;
+ }
+
+ txform[3].xyz += particles.data[particle].velocity * params.frame_remainder;
+
+ if (params.trail_size > 1) {
+ uint part_ofs = particle % params.trail_size;
+ txform = txform * trail_bind_poses.data[part_ofs];
+ }
+
+ txform = transpose(txform);
+ } else {
+ txform = mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); //zero scale, becomes invisible
+ }
+
+#ifdef MODE_2D
+
+ uint write_offset = gl_GlobalInvocationID.x * (2 + 1 + 1); //xform + color + custom
+
+ instances.data[write_offset + 0] = txform[0];
+ instances.data[write_offset + 1] = txform[1];
+ instances.data[write_offset + 2] = particles.data[particle].color;
+ instances.data[write_offset + 3] = particles.data[particle].custom;
+
+#else
+
+ uint write_offset = gl_GlobalInvocationID.x * (3 + 1 + 1); //xform + color + custom
+
+ instances.data[write_offset + 0] = txform[0];
+ instances.data[write_offset + 1] = txform[1];
+ instances.data[write_offset + 2] = txform[2];
+ instances.data[write_offset + 3] = particles.data[particle].color;
+ instances.data[write_offset + 4] = particles.data[particle].custom;
+#endif //MODE_2D
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/resolve.glsl b/servers/rendering/renderer_rd/shaders/resolve.glsl
new file mode 100644
index 0000000000..fecf812a8c
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/resolve.glsl
@@ -0,0 +1,236 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#ifdef MODE_RESOLVE_DEPTH
+layout(set = 0, binding = 0) uniform sampler2DMS source_depth;
+layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D dest_depth;
+#endif
+
+#ifdef MODE_RESOLVE_GI
+layout(set = 0, binding = 0) uniform sampler2DMS source_depth;
+layout(set = 0, binding = 1) uniform sampler2DMS source_normal_roughness;
+
+layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D dest_depth;
+layout(rgba8, set = 1, binding = 1) uniform restrict writeonly image2D dest_normal_roughness;
+
+#ifdef VOXEL_GI_RESOLVE
+layout(set = 2, binding = 0) uniform usampler2DMS source_voxel_gi;
+layout(rg8ui, set = 3, binding = 0) uniform restrict writeonly uimage2D dest_voxel_gi;
+#endif
+
+#endif
+
+layout(push_constant, binding = 16, std430) uniform Params {
+ ivec2 screen_size;
+ int sample_count;
+ uint pad;
+}
+params;
+
+void main() {
+ // Pixel being shaded
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(pos, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+#ifdef MODE_RESOLVE_DEPTH
+
+ float depth_avg = 0.0;
+ for (int i = 0; i < params.sample_count; i++) {
+ depth_avg += texelFetch(source_depth, pos, i).r;
+ }
+ depth_avg /= float(params.sample_count);
+ imageStore(dest_depth, pos, vec4(depth_avg));
+
+#endif
+
+#ifdef MODE_RESOLVE_GI
+
+ float best_depth = 1e20;
+ vec4 best_normal_roughness = vec4(0.0);
+#ifdef VOXEL_GI_RESOLVE
+ uvec2 best_voxel_gi;
+#endif
+
+#if 0
+
+ for(int i=0;i<params.sample_count;i++) {
+ float depth = texelFetch(source_depth,pos,i).r;
+ if (depth < best_depth) { //use the depth closest to camera
+ best_depth = depth;
+ best_normal_roughness = texelFetch(source_normal_roughness,pos,i);
+
+#ifdef VOXEL_GI_RESOLVE
+ best_voxel_gi = texelFetch(source_voxel_gi,pos,i).rg;
+#endif
+ }
+ }
+
+#else
+
+#if 1
+
+ vec4 group1;
+ vec4 group2;
+ vec4 group3;
+ vec4 group4;
+ int best_index = 0;
+
+ //2X
+ group1.x = texelFetch(source_depth, pos, 0).r;
+ group1.y = texelFetch(source_depth, pos, 1).r;
+
+ //4X
+ if (params.sample_count >= 4) {
+ group1.z = texelFetch(source_depth, pos, 2).r;
+ group1.w = texelFetch(source_depth, pos, 3).r;
+ }
+ //8X
+ if (params.sample_count >= 8) {
+ group2.x = texelFetch(source_depth, pos, 4).r;
+ group2.y = texelFetch(source_depth, pos, 5).r;
+ group2.z = texelFetch(source_depth, pos, 6).r;
+ group2.w = texelFetch(source_depth, pos, 7).r;
+ }
+ //16X
+ if (params.sample_count >= 16) {
+ group3.x = texelFetch(source_depth, pos, 8).r;
+ group3.y = texelFetch(source_depth, pos, 9).r;
+ group3.z = texelFetch(source_depth, pos, 10).r;
+ group3.w = texelFetch(source_depth, pos, 11).r;
+
+ group4.x = texelFetch(source_depth, pos, 12).r;
+ group4.y = texelFetch(source_depth, pos, 13).r;
+ group4.z = texelFetch(source_depth, pos, 14).r;
+ group4.w = texelFetch(source_depth, pos, 15).r;
+ }
+
+ if (params.sample_count == 2) {
+ best_index = (pos.x & 1) ^ ((pos.y >> 1) & 1); //not much can be done here
+ } else if (params.sample_count == 4) {
+ vec4 freq = vec4(equal(group1, vec4(group1.x)));
+ freq += vec4(equal(group1, vec4(group1.y)));
+ freq += vec4(equal(group1, vec4(group1.z)));
+ freq += vec4(equal(group1, vec4(group1.w)));
+
+ float min_f = freq.x;
+ best_index = 0;
+ if (freq.y < min_f) {
+ best_index = 1;
+ min_f = freq.y;
+ }
+ if (freq.z < min_f) {
+ best_index = 2;
+ min_f = freq.z;
+ }
+ if (freq.w < min_f) {
+ best_index = 3;
+ }
+ } else if (params.sample_count == 8) {
+ vec4 freq0 = vec4(equal(group1, vec4(group1.x)));
+ vec4 freq1 = vec4(equal(group2, vec4(group1.x)));
+ freq0 += vec4(equal(group1, vec4(group1.y)));
+ freq1 += vec4(equal(group2, vec4(group1.y)));
+ freq0 += vec4(equal(group1, vec4(group1.z)));
+ freq1 += vec4(equal(group2, vec4(group1.z)));
+ freq0 += vec4(equal(group1, vec4(group1.w)));
+ freq1 += vec4(equal(group2, vec4(group1.w)));
+ freq0 += vec4(equal(group1, vec4(group2.x)));
+ freq1 += vec4(equal(group2, vec4(group2.x)));
+ freq0 += vec4(equal(group1, vec4(group2.y)));
+ freq1 += vec4(equal(group2, vec4(group2.y)));
+ freq0 += vec4(equal(group1, vec4(group2.z)));
+ freq1 += vec4(equal(group2, vec4(group2.z)));
+ freq0 += vec4(equal(group1, vec4(group2.w)));
+ freq1 += vec4(equal(group2, vec4(group2.w)));
+
+ float min_f0 = freq0.x;
+ int best_index0 = 0;
+ if (freq0.y < min_f0) {
+ best_index0 = 1;
+ min_f0 = freq0.y;
+ }
+ if (freq0.z < min_f0) {
+ best_index0 = 2;
+ min_f0 = freq0.z;
+ }
+ if (freq0.w < min_f0) {
+ best_index0 = 3;
+ min_f0 = freq0.w;
+ }
+
+ float min_f1 = freq1.x;
+ int best_index1 = 4;
+ if (freq1.y < min_f1) {
+ best_index1 = 5;
+ min_f1 = freq1.y;
+ }
+ if (freq1.z < min_f1) {
+ best_index1 = 6;
+ min_f1 = freq1.z;
+ }
+ if (freq1.w < min_f1) {
+ best_index1 = 7;
+ min_f1 = freq1.w;
+ }
+
+ best_index = mix(best_index0, best_index1, min_f0 < min_f1);
+ }
+
+#else
+ float depths[16];
+ int depth_indices[16];
+ int depth_amount[16];
+ int depth_count = 0;
+
+ for (int i = 0; i < params.sample_count; i++) {
+ float depth = texelFetch(source_depth, pos, i).r;
+ int depth_index = -1;
+ for (int j = 0; j < depth_count; j++) {
+ if (abs(depths[j] - depth) < 0.000001) {
+ depth_index = j;
+ break;
+ }
+ }
+
+ if (depth_index == -1) {
+ depths[depth_count] = depth;
+ depth_indices[depth_count] = i;
+ depth_amount[depth_count] = 1;
+ depth_count += 1;
+ } else {
+ depth_amount[depth_index] += 1;
+ }
+ }
+
+ int depth_least = 0xFFFF;
+ int best_index = 0;
+ for (int j = 0; j < depth_count; j++) {
+ if (depth_amount[j] < depth_least) {
+ best_index = depth_indices[j];
+ depth_least = depth_amount[j];
+ }
+ }
+#endif
+ best_depth = texelFetch(source_depth, pos, best_index).r;
+ best_normal_roughness = texelFetch(source_normal_roughness, pos, best_index);
+#ifdef VOXEL_GI_RESOLVE
+ best_voxel_gi = texelFetch(source_voxel_gi, pos, best_index).rg;
+#endif
+
+#endif
+
+ imageStore(dest_depth, pos, vec4(best_depth));
+ imageStore(dest_normal_roughness, pos, vec4(best_normal_roughness));
+#ifdef VOXEL_GI_RESOLVE
+ imageStore(dest_voxel_gi, pos, uvec4(best_voxel_gi, 0, 0));
+#endif
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/roughness_limiter.glsl b/servers/rendering/renderer_rd/shaders/roughness_limiter.glsl
new file mode 100644
index 0000000000..7b964675ca
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/roughness_limiter.glsl
@@ -0,0 +1,70 @@
+#[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_normal;
+layout(r8, set = 1, binding = 0) uniform restrict writeonly image2D dest_roughness;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ ivec2 screen_size;
+ float curve;
+ uint pad;
+}
+params;
+
+#define HALF_PI 1.5707963267948966
+
+void main() {
+ // Pixel being shaded
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThan(pos, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+ vec3 normal_accum = vec3(0.0);
+ float accum = 0.0;
+ for (int i = 0; i <= 1; i++) {
+ for (int j = 0; j <= 1; j++) {
+ normal_accum += normalize(texelFetch(source_normal, pos + ivec2(i, j), 0).xyz * 2.0 - 1.0);
+ accum += 1.0;
+ }
+ }
+
+ normal_accum /= accum;
+
+ float r = length(normal_accum);
+
+ float limit;
+
+ if (r < 1.0) {
+ float threshold = 0.4;
+
+ /*
+ //Formula from Filament, does not make sense to me.
+
+ float r2 = r * r;
+ float kappa = (3.0f * r - r * r2) / (1.0f - r2);
+ float variance = 0.25f / kappa;
+ limit = sqrt(min(2.0f * variance, threshold * threshold));
+ */
+ /*
+ //Formula based on probability distribution graph
+
+ float width = acos(max(0.0,r)); // convert to angle (width)
+ float roughness = pow(width,1.7)*0.854492; //approximate (crappy) formula to convert to roughness
+ limit = min(sqrt(roughness), threshold); //convert to perceptual roughness and apply threshold
+ */
+
+ limit = min(sqrt(pow(acos(max(0.0, r)) / HALF_PI, params.curve)), threshold); //convert to perceptual roughness and apply threshold
+
+ //limit = 0.5;
+ } else {
+ limit = 0.0;
+ }
+
+ imageStore(dest_roughness, pos, vec4(limit));
+}
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl
new file mode 100644
index 0000000000..99714b4504
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl
@@ -0,0 +1,58 @@
+#ifdef ALPHA_HASH_USED
+
+float hash_2d(vec2 p) {
+ return fract(1.0e4 * sin(17.0 * p.x + 0.1 * p.y) *
+ (0.1 + abs(sin(13.0 * p.y + p.x))));
+}
+
+float hash_3d(vec3 p) {
+ return hash_2d(vec2(hash_2d(p.xy), p.z));
+}
+
+float compute_alpha_hash_threshold(vec3 pos, float hash_scale) {
+ vec3 dx = dFdx(pos);
+ vec3 dy = dFdx(pos);
+ float delta_max_sqr = max(length(dx), length(dy));
+ float pix_scale = 1.0 / (hash_scale * delta_max_sqr);
+
+ vec2 pix_scales =
+ vec2(exp2(floor(log2(pix_scale))), exp2(ceil(log2(pix_scale))));
+
+ vec2 a_thresh = vec2(hash_3d(floor(pix_scales.x * pos.xyz)),
+ hash_3d(floor(pix_scales.y * pos.xyz)));
+
+ float lerp_factor = fract(log2(pix_scale));
+
+ float a_interp = (1.0 - lerp_factor) * a_thresh.x + lerp_factor * a_thresh.y;
+
+ float min_lerp = min(lerp_factor, 1.0 - lerp_factor);
+
+ vec3 cases = vec3(a_interp * a_interp / (2.0 * min_lerp * (1.0 - min_lerp)),
+ (a_interp - 0.5 * min_lerp) / (1.0 - min_lerp),
+ 1.0 - ((1.0 - a_interp) * (1.0 - a_interp) /
+ (2.0 * min_lerp * (1.0 - min_lerp))));
+
+ float alpha_hash_threshold =
+ (lerp_factor < (1.0 - min_lerp)) ? ((lerp_factor < min_lerp) ? cases.x : cases.y) : cases.z;
+
+ return clamp(alpha_hash_threshold, 0.0, 1.0);
+}
+
+#endif // ALPHA_HASH_USED
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+
+float calc_mip_level(vec2 texture_coord) {
+ vec2 dx = dFdx(texture_coord);
+ vec2 dy = dFdy(texture_coord);
+ float delta_max_sqr = max(dot(dx, dx), dot(dy, dy));
+ return max(0.0, 0.5 * log2(delta_max_sqr));
+}
+
+float compute_alpha_antialiasing_edge(float input_alpha, vec2 texture_coord, float alpha_edge) {
+ input_alpha *= 1.0 + max(0, calc_mip_level(texture_coord)) * 0.25; // 0.25 mip scale, magic number
+ input_alpha = (input_alpha - alpha_edge) / max(fwidth(input_alpha), 0.0001) + 0.5;
+ return clamp(input_alpha, 0.0, 1.0);
+}
+
+#endif // ALPHA_ANTIALIASING_USED
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
new file mode 100644
index 0000000000..1288cee8b0
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
@@ -0,0 +1,1914 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#include "scene_forward_clustered_inc.glsl"
+
+/* INPUT ATTRIBS */
+
+layout(location = 0) in vec3 vertex_attrib;
+
+//only for pure render depth when normal is not used
+
+#ifdef NORMAL_USED
+layout(location = 1) in vec3 normal_attrib;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 2) in vec4 tangent_attrib;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 3) in vec4 color_attrib;
+#endif
+
+#ifdef UV_USED
+layout(location = 4) in vec2 uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP) || defined(MODE_RENDER_MATERIAL)
+layout(location = 5) in vec2 uv2_attrib;
+#endif
+
+#if defined(CUSTOM0_USED)
+layout(location = 6) in vec4 custom0_attrib;
+#endif
+
+#if defined(CUSTOM1_USED)
+layout(location = 7) in vec4 custom1_attrib;
+#endif
+
+#if defined(CUSTOM2_USED)
+layout(location = 8) in vec4 custom2_attrib;
+#endif
+
+#if defined(CUSTOM3_USED)
+layout(location = 9) in vec4 custom3_attrib;
+#endif
+
+#if defined(BONES_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 10) in uvec4 bone_attrib;
+#endif
+
+#if defined(WEIGHTS_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 11) in vec4 weight_attrib;
+#endif
+
+/* Varyings */
+
+layout(location = 0) out vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) out vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) out vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) out vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) out vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) out vec3 tangent_interp;
+layout(location = 6) out vec3 binormal_interp;
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) out float dp_clip;
+
+#endif
+
+layout(location = 9) out flat uint instance_index;
+
+invariant gl_Position;
+
+#GLOBALS
+
+void main() {
+ vec4 instance_custom = vec4(0.0);
+#if defined(COLOR_USED)
+ color_interp = color_attrib;
+#endif
+
+ instance_index = draw_call.instance_index;
+
+ bool is_multimesh = bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH);
+ if (!is_multimesh) {
+ instance_index += gl_InstanceIndex;
+ }
+
+ mat4 world_matrix = instances.data[instance_index].transform;
+
+ mat3 world_normal_matrix;
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ world_normal_matrix = transpose(inverse(mat3(world_matrix)));
+ } else {
+ world_normal_matrix = mat3(world_matrix);
+ }
+
+ if (is_multimesh) {
+ //multimesh, instances are for it
+
+ mat4 matrix;
+
+#ifdef USE_PARTICLE_TRAILS
+ uint trail_size = (instances.data[instance_index].flags >> INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT) & INSTANCE_FLAGS_PARTICLE_TRAIL_MASK;
+ uint stride = 3 + 1 + 1; //particles always uses this format
+
+ uint offset = trail_size * stride * gl_InstanceIndex;
+
+#ifdef COLOR_USED
+ vec4 pcolor;
+#endif
+ {
+ uint boffset = offset + bone_attrib.x * stride;
+ matrix = mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.x;
+#ifdef COLOR_USED
+ pcolor = transforms.data[boffset + 3] * weight_attrib.x;
+#endif
+ }
+ if (weight_attrib.y > 0.001) {
+ uint boffset = offset + bone_attrib.y * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.y;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.y;
+#endif
+ }
+ if (weight_attrib.z > 0.001) {
+ uint boffset = offset + bone_attrib.z * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.z;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.z;
+#endif
+ }
+ if (weight_attrib.w > 0.001) {
+ uint boffset = offset + bone_attrib.w * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.w;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.w;
+#endif
+ }
+
+ instance_custom = transforms.data[offset + 4];
+
+#ifdef COLOR_USED
+ color_interp *= pcolor;
+#endif
+
+#else
+ uint stride = 0;
+ {
+ //TODO implement a small lookup table for the stride
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ stride += 2;
+ } else {
+ stride += 3;
+ }
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+ stride += 1;
+ }
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ stride += 1;
+ }
+ }
+
+ uint offset = stride * gl_InstanceIndex;
+
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
+ } else {
+ matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], transforms.data[offset + 2], vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 3;
+ }
+
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+#ifdef COLOR_USED
+ color_interp *= transforms.data[offset];
+#endif
+ offset += 1;
+ }
+
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
+ }
+
+#endif
+ //transpose
+ matrix = transpose(matrix);
+ world_matrix = world_matrix * matrix;
+ world_normal_matrix = world_normal_matrix * mat3(matrix);
+ }
+
+ vec3 vertex = vertex_attrib;
+#ifdef NORMAL_USED
+ vec3 normal = normal_attrib * 2.0 - 1.0;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ vec3 tangent = tangent_attrib.xyz * 2.0 - 1.0;
+ float binormalf = tangent_attrib.a * 2.0 - 1.0;
+ vec3 binormal = normalize(cross(normal, tangent) * binormalf);
+#endif
+
+#ifdef UV_USED
+ uv_interp = uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+ uv2_interp = uv2_attrib;
+#endif
+
+#ifdef OVERRIDE_POSITION
+ vec4 position;
+#endif
+
+ mat4 projection_matrix = scene_data.projection_matrix;
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (world_matrix * vec4(vertex, 1.0)).xyz;
+
+ normal = world_normal_matrix * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ tangent = world_normal_matrix * tangent;
+ binormal = world_normal_matrix * binormal;
+
+#endif
+#endif
+
+ float roughness = 1.0;
+
+ mat4 modelview = scene_data.inv_camera_matrix * world_matrix;
+ mat3 modelview_normal = mat3(scene_data.inv_camera_matrix) * world_normal_matrix;
+
+ {
+#CODE : VERTEX
+ }
+
+// using local coordinates (default)
+#if !defined(SKIP_TRANSFORM_USED) && !defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (modelview * vec4(vertex, 1.0)).xyz;
+#ifdef NORMAL_USED
+ normal = modelview_normal * normal;
+#endif
+
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ binormal = modelview_normal * binormal;
+ tangent = modelview_normal * tangent;
+#endif
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (scene_data.inv_camera_matrix * vec4(vertex, 1.0)).xyz;
+ normal = mat3(scene_data.inverse_normal_matrix) * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ binormal = mat3(scene_data.camera_inverse_binormal_matrix) * binormal;
+ tangent = mat3(scene_data.camera_inverse_tangent_matrix) * tangent;
+#endif
+#endif
+
+ vertex_interp = vertex;
+#ifdef NORMAL_USED
+ normal_interp = normal;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ tangent_interp = tangent;
+ binormal_interp = binormal;
+#endif
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_DUAL_PARABOLOID
+
+ vertex_interp.z *= scene_data.dual_paraboloid_side;
+
+ dp_clip = vertex_interp.z; //this attempts to avoid noise caused by objects sent to the other parabolloid side due to bias
+
+ //for dual paraboloid shadow mapping, this is the fastest but least correct way, as it curves straight edges
+
+ vec3 vtx = vertex_interp;
+ float distance = length(vtx);
+ vtx = normalize(vtx);
+ vtx.xy /= 1.0 - vtx.z;
+ vtx.z = (distance / scene_data.z_far);
+ vtx.z = vtx.z * 2.0 - 1.0;
+ vertex_interp = vtx;
+
+#endif
+
+#endif //MODE_RENDER_DEPTH
+
+#ifdef OVERRIDE_POSITION
+ gl_Position = position;
+#else
+ gl_Position = projection_matrix * vec4(vertex_interp, 1.0);
+#endif
+
+#ifdef MODE_RENDER_DEPTH
+ if (scene_data.pancake_shadows) {
+ if (gl_Position.z <= 0.00001) {
+ gl_Position.z = 0.00001;
+ }
+ }
+#endif
+#ifdef MODE_RENDER_MATERIAL
+ if (scene_data.material_uv2_mode) {
+ vec2 uv_offset = unpackHalf2x16(draw_call.uv_offset);
+ gl_Position.xy = (uv2_attrib.xy + uv_offset) * 2.0 - 1.0;
+ gl_Position.z = 0.00001;
+ gl_Position.w = 1.0;
+ }
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Specialization Constants (Toggles) */
+
+layout(constant_id = 0) const bool sc_use_forward_gi = false;
+layout(constant_id = 1) const bool sc_use_light_projector = false;
+layout(constant_id = 2) const bool sc_use_light_soft_shadows = false;
+layout(constant_id = 3) const bool sc_use_directional_soft_shadows = false;
+
+/* Specialization Constants (Values) */
+
+layout(constant_id = 6) const uint sc_soft_shadow_samples = 4;
+layout(constant_id = 7) const uint sc_penumbra_shadow_samples = 4;
+
+layout(constant_id = 8) const uint sc_directional_soft_shadow_samples = 4;
+layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4;
+
+layout(constant_id = 10) const bool sc_decal_use_mipmaps = true;
+layout(constant_id = 11) const bool sc_projector_use_mipmaps = true;
+
+#include "scene_forward_clustered_inc.glsl"
+
+/* Varyings */
+
+layout(location = 0) in vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) in vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) in vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) in vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) in vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) in vec3 tangent_interp;
+layout(location = 6) in vec3 binormal_interp;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) in float dp_clip;
+
+#endif
+
+layout(location = 9) in flat uint instance_index;
+
+//defines to keep compatibility with vertex
+
+#define world_matrix instances.data[instance_index].transform
+#define projection_matrix scene_data.projection_matrix
+
+#if defined(ENABLE_SSS) && defined(ENABLE_TRANSMITTANCE)
+//both required for transmittance to be enabled
+#define LIGHT_TRANSMITTANCE_USED
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#GLOBALS
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_MATERIAL
+
+layout(location = 0) out vec4 albedo_output_buffer;
+layout(location = 1) out vec4 normal_output_buffer;
+layout(location = 2) out vec4 orm_output_buffer;
+layout(location = 3) out vec4 emission_output_buffer;
+layout(location = 4) out float depth_output_buffer;
+
+#endif // MODE_RENDER_MATERIAL
+
+#ifdef MODE_RENDER_NORMAL_ROUGHNESS
+layout(location = 0) out vec4 normal_roughness_output_buffer;
+
+#ifdef MODE_RENDER_VOXEL_GI
+layout(location = 1) out uvec2 voxel_gi_buffer;
+#endif
+
+#endif //MODE_RENDER_NORMAL
+#else // RENDER DEPTH
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+layout(location = 0) out vec4 diffuse_buffer; //diffuse (rgb) and roughness
+layout(location = 1) out vec4 specular_buffer; //specular and SSS (subsurface scatter)
+#else
+
+layout(location = 0) out vec4 frag_color;
+#endif // MODE_MULTIPLE_RENDER_TARGETS
+
+#endif // RENDER DEPTH
+
+#include "scene_forward_aa_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+/* Make a default specular mode SPECULAR_SCHLICK_GGX. */
+#if !defined(SPECULAR_DISABLED) && !defined(SPECULAR_SCHLICK_GGX) && !defined(SPECULAR_BLINN) && !defined(SPECULAR_PHONG) && !defined(SPECULAR_TOON)
+#define SPECULAR_SCHLICK_GGX
+#endif
+
+#include "scene_forward_lights_inc.glsl"
+
+#include "scene_forward_gi_inc.glsl"
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifndef MODE_RENDER_DEPTH
+
+vec4 volumetric_fog_process(vec2 screen_uv, float z) {
+ vec3 fog_pos = vec3(screen_uv, z * scene_data.volumetric_fog_inv_length);
+ if (fog_pos.z < 0.0) {
+ return vec4(0.0);
+ } else if (fog_pos.z < 1.0) {
+ fog_pos.z = pow(fog_pos.z, scene_data.volumetric_fog_detail_spread);
+ }
+
+ return texture(sampler3D(volumetric_fog_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), fog_pos);
+}
+
+vec4 fog_process(vec3 vertex) {
+ vec3 fog_color = scene_data.fog_light_color;
+
+ if (scene_data.fog_aerial_perspective > 0.0) {
+ vec3 sky_fog_color = vec3(0.0);
+ vec3 cube_view = scene_data.radiance_inverse_xform * vertex;
+ // mip_level always reads from the second mipmap and higher so the fog is always slightly blurred
+ float mip_level = mix(1.0 / MAX_ROUGHNESS_LOD, 1.0, 1.0 - (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near));
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+ float lod, blend;
+ blend = modf(mip_level * MAX_ROUGHNESS_LOD, lod);
+ sky_fog_color = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod)).rgb;
+ sky_fog_color = mix(sky_fog_color, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod + 1)).rgb, blend);
+#else
+ sky_fog_color = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), cube_view, mip_level * MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+ fog_color = mix(fog_color, sky_fog_color, scene_data.fog_aerial_perspective);
+ }
+
+ if (scene_data.fog_sun_scatter > 0.001) {
+ vec4 sun_scatter = vec4(0.0);
+ float sun_total = 0.0;
+ vec3 view = normalize(vertex);
+
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ vec3 light_color = directional_lights.data[i].color * directional_lights.data[i].energy;
+ float light_amount = pow(max(dot(view, directional_lights.data[i].direction), 0.0), 8.0);
+ fog_color += light_color * light_amount * scene_data.fog_sun_scatter;
+ }
+ }
+
+ float fog_amount = 1.0 - exp(min(0.0, vertex.z * scene_data.fog_density));
+
+ if (abs(scene_data.fog_height_density) > 0.001) {
+ float y = (scene_data.camera_matrix * vec4(vertex, 1.0)).y;
+
+ float y_dist = scene_data.fog_height - y;
+
+ float vfog_amount = clamp(exp(y_dist * scene_data.fog_height_density), 0.0, 1.0);
+
+ fog_amount = max(vfog_amount, fog_amount);
+ }
+
+ return vec4(fog_color, fog_amount);
+}
+
+void cluster_get_item_range(uint p_offset, out uint item_min, out uint item_max, out uint item_from, out uint item_to) {
+ uint item_min_max = cluster_buffer.data[p_offset];
+ item_min = item_min_max & 0xFFFF;
+ item_max = item_min_max >> 16;
+ ;
+
+ item_from = item_min >> 5;
+ item_to = (item_max == 0) ? 0 : ((item_max - 1) >> 5) + 1; //side effect of how it is stored, as item_max 0 means no elements
+}
+
+uint cluster_get_range_clip_mask(uint i, uint z_min, uint z_max) {
+ int local_min = clamp(int(z_min) - int(i) * 32, 0, 31);
+ int mask_width = min(int(z_max) - int(z_min), 32 - local_min);
+ return bitfieldInsert(uint(0), uint(0xFFFFFFFF), local_min, mask_width);
+}
+
+#endif //!MODE_RENDER DEPTH
+
+void main() {
+#ifdef MODE_DUAL_PARABOLOID
+
+ if (dp_clip > 0.0)
+ discard;
+#endif
+
+ //lay out everything, whathever is unused is optimized away anyway
+ vec3 vertex = vertex_interp;
+ vec3 view = -normalize(vertex_interp);
+ vec3 albedo = vec3(1.0);
+ vec3 backlight = vec3(0.0);
+ vec4 transmittance_color = vec4(0.0, 0.0, 0.0, 1.0);
+ float transmittance_depth = 0.0;
+ float transmittance_boost = 0.0;
+ float metallic = 0.0;
+ float specular = 0.5;
+ vec3 emission = vec3(0.0);
+ float roughness = 1.0;
+ float rim = 0.0;
+ float rim_tint = 0.0;
+ float clearcoat = 0.0;
+ float clearcoat_gloss = 0.0;
+ float anisotropy = 0.0;
+ vec2 anisotropy_flow = vec2(1.0, 0.0);
+ vec4 fog = vec4(0.0);
+#if defined(CUSTOM_RADIANCE_USED)
+ vec4 custom_radiance = vec4(0.0);
+#endif
+#if defined(CUSTOM_IRRADIANCE_USED)
+ vec4 custom_irradiance = vec4(0.0);
+#endif
+
+ float ao = 1.0;
+ float ao_light_affect = 0.0;
+
+ float alpha = 1.0;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ vec3 binormal = normalize(binormal_interp);
+ vec3 tangent = normalize(tangent_interp);
+#else
+ vec3 binormal = vec3(0.0);
+ vec3 tangent = vec3(0.0);
+#endif
+
+#ifdef NORMAL_USED
+ vec3 normal = normalize(normal_interp);
+
+#if defined(DO_SIDE_CHECK)
+ if (!gl_FrontFacing) {
+ normal = -normal;
+ }
+#endif
+
+#endif //NORMAL_USED
+
+#ifdef UV_USED
+ vec2 uv = uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+ vec2 uv2 = uv2_interp;
+#endif
+
+#if defined(COLOR_USED)
+ vec4 color = color_interp;
+#endif
+
+#if defined(NORMAL_MAP_USED)
+
+ vec3 normal_map = vec3(0.5);
+#endif
+
+ float normal_map_depth = 1.0;
+
+ vec2 screen_uv = gl_FragCoord.xy * scene_data.screen_pixel_size + scene_data.screen_pixel_size * 0.5; //account for center
+
+ float sss_strength = 0.0;
+
+#ifdef ALPHA_SCISSOR_USED
+ float alpha_scissor_threshold = 1.0;
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef ALPHA_HASH_USED
+ float alpha_hash_scale = 1.0;
+#endif // ALPHA_HASH_USED
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+ float alpha_antialiasing_edge = 0.0;
+ vec2 alpha_texture_coordinate = vec2(0.0, 0.0);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+ {
+#CODE : FRAGMENT
+ }
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color.a *= sss_strength;
+#endif
+
+#ifndef USE_SHADOW_TO_OPACITY
+
+#ifdef ALPHA_SCISSOR_USED
+ if (alpha < alpha_scissor_threshold) {
+ discard;
+ }
+#endif // ALPHA_SCISSOR_USED
+
+// alpha hash can be used in unison with alpha antialiasing
+#ifdef ALPHA_HASH_USED
+ if (alpha < compute_alpha_hash_threshold(vertex, alpha_hash_scale)) {
+ discard;
+ }
+#endif // ALPHA_HASH_USED
+
+// If we are not edge antialiasing, we need to remove the output alpha channel from scissor and hash
+#if (defined(ALPHA_SCISSOR_USED) || defined(ALPHA_HASH_USED)) && !defined(ALPHA_ANTIALIASING_EDGE_USED)
+ alpha = 1.0;
+#endif
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+// If alpha scissor is used, we must further the edge threshold, otherwise we won't get any edge feather
+#ifdef ALPHA_SCISSOR_USED
+ alpha_antialiasing_edge = clamp(alpha_scissor_threshold + alpha_antialiasing_edge, 0.0, 1.0);
+#endif
+ alpha = compute_alpha_antialiasing_edge(alpha, alpha_texture_coordinate, alpha_antialiasing_edge);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+#ifdef USE_OPAQUE_PREPASS
+ if (alpha < opaque_prepass_threshold) {
+ discard;
+ }
+#endif // USE_OPAQUE_PREPASS
+
+#endif // !USE_SHADOW_TO_OPACITY
+
+#ifdef NORMAL_MAP_USED
+
+ normal_map.xy = normal_map.xy * 2.0 - 1.0;
+ normal_map.z = sqrt(max(0.0, 1.0 - dot(normal_map.xy, normal_map.xy))); //always ignore Z, as it can be RG packed, Z may be pos/neg, etc.
+
+ normal = normalize(mix(normal, tangent * normal_map.x + binormal * normal_map.y + normal * normal_map.z, normal_map_depth));
+
+#endif
+
+#ifdef LIGHT_ANISOTROPY_USED
+
+ if (anisotropy > 0.01) {
+ //rotation matrix
+ mat3 rot = mat3(tangent, binormal, normal);
+ //make local to space
+ tangent = normalize(rot * vec3(anisotropy_flow.x, anisotropy_flow.y, 0.0));
+ binormal = normalize(rot * vec3(-anisotropy_flow.y, anisotropy_flow.x, 0.0));
+ }
+
+#endif
+
+#ifdef ENABLE_CLIP_ALPHA
+ if (albedo.a < 0.99) {
+ //used for doublepass and shadowmapping
+ discard;
+ }
+#endif
+
+ /////////////////////// FOG //////////////////////
+#ifndef MODE_RENDER_DEPTH
+
+#ifndef CUSTOM_FOG_USED
+ // fog must be processed as early as possible and then packed.
+ // to maximize VGPR usage
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+
+ if (scene_data.fog_enabled) {
+ fog = fog_process(vertex);
+ }
+
+ if (scene_data.volumetric_fog_enabled) {
+ vec4 volumetric_fog = volumetric_fog_process(screen_uv, -vertex.z);
+ if (scene_data.fog_enabled) {
+ //must use the full blending equation here to blend fogs
+ vec4 res;
+ float sa = 1.0 - volumetric_fog.a;
+ res.a = fog.a * sa + volumetric_fog.a;
+ if (res.a == 0.0) {
+ res.rgb = vec3(0.0);
+ } else {
+ res.rgb = (fog.rgb * fog.a * sa + volumetric_fog.rgb * volumetric_fog.a) / res.a;
+ }
+ fog = res;
+ } else {
+ fog = volumetric_fog;
+ }
+ }
+#endif //!CUSTOM_FOG_USED
+
+ uint fog_rg = packHalf2x16(fog.rg);
+ uint fog_ba = packHalf2x16(fog.ba);
+
+#endif //!MODE_RENDER_DEPTH
+
+ /////////////////////// DECALS ////////////////////////////////
+
+#ifndef MODE_RENDER_DEPTH
+
+ uvec2 cluster_pos = uvec2(gl_FragCoord.xy) >> scene_data.cluster_shift;
+ uint cluster_offset = (scene_data.cluster_width * cluster_pos.y + cluster_pos.x) * (scene_data.max_cluster_element_count_div_32 + 32);
+
+ uint cluster_z = uint(clamp((-vertex.z / scene_data.z_far) * 32.0, 0.0, 31.0));
+
+ //used for interpolating anything cluster related
+ vec3 vertex_ddx = dFdx(vertex);
+ vec3 vertex_ddy = dFdy(vertex);
+
+ { // process decals
+
+ uint cluster_decal_offset = cluster_offset + scene_data.cluster_type_size * 2;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_decal_offset + scene_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_decal_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+ uint decal_index = 32 * i + bit;
+
+ if (!bool(decals.data[decal_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
+ if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
+ continue; //out of decal
+ }
+
+ float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
+
+ if (decals.data[decal_index].normal_fade > 0.0) {
+ fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+ }
+
+ //we need ddx/ddy for mipmaps, so simulate them
+ vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
+ vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
+
+ if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
+ //has albedo
+ vec4 decal_albedo;
+ if (sc_decal_use_mipmaps) {
+ decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+ } else {
+ decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
+ }
+ decal_albedo *= decals.data[decal_index].modulate;
+ decal_albedo.a *= fade;
+ albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
+
+ if (decals.data[decal_index].normal_rect != vec4(0.0)) {
+ vec3 decal_normal;
+ if (sc_decal_use_mipmaps) {
+ decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
+ } else {
+ decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
+ }
+ decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
+ decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
+ //convert to view space, use xzy because y is up
+ decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
+
+ normal = normalize(mix(normal, decal_normal, decal_albedo.a));
+ }
+
+ if (decals.data[decal_index].orm_rect != vec4(0.0)) {
+ vec3 decal_orm;
+ if (sc_decal_use_mipmaps) {
+ decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
+ } else {
+ decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
+ }
+ ao = mix(ao, decal_orm.r, decal_albedo.a);
+ roughness = mix(roughness, decal_orm.g, decal_albedo.a);
+ metallic = mix(metallic, decal_orm.b, decal_albedo.a);
+ }
+ }
+
+ if (decals.data[decal_index].emission_rect != vec4(0.0)) {
+ //emission is additive, so its independent from albedo
+ if (sc_decal_use_mipmaps) {
+ emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+ } else {
+ emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].emission_energy * fade;
+ }
+ }
+ }
+ }
+ }
+
+ //pack albedo until needed again, saves 2 VGPRs in the meantime
+
+#endif //not render depth
+ /////////////////////// LIGHTING //////////////////////////////
+
+#ifdef NORMAL_USED
+ if (scene_data.roughness_limiter_enabled) {
+ //http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf
+ float roughness2 = roughness * roughness;
+ vec3 dndu = dFdx(normal), dndv = dFdy(normal);
+ float variance = scene_data.roughness_limiter_amount * (dot(dndu, dndu) + dot(dndv, dndv));
+ float kernelRoughness2 = min(2.0 * variance, scene_data.roughness_limiter_limit); //limit effect
+ float filteredRoughness2 = min(1.0, roughness2 + kernelRoughness2);
+ roughness = sqrt(filteredRoughness2);
+ }
+#endif
+ //apply energy conservation
+
+ vec3 specular_light = vec3(0.0, 0.0, 0.0);
+ vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
+ vec3 ambient_light = vec3(0.0, 0.0, 0.0);
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ if (scene_data.use_reflection_cubemap) {
+ vec3 ref_vec = reflect(-view, normal);
+ ref_vec = scene_data.radiance_inverse_xform * ref_vec;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+ float lod, blend;
+ blend = modf(roughness * MAX_ROUGHNESS_LOD, lod);
+ specular_light = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod)).rgb;
+ specular_light = mix(specular_light, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod + 1)).rgb, blend);
+
+#else
+ specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb;
+
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+ float horizon = min(1.0 + dot(ref_vec, normal), 1.0);
+ specular_light *= horizon * horizon;
+ specular_light *= scene_data.ambient_light_color_energy.a;
+ }
+
+#if defined(CUSTOM_RADIANCE_USED)
+ specular_light = mix(specular_light, custom_radiance.rgb, custom_radiance.a);
+#endif
+
+#ifndef USE_LIGHTMAP
+ //lightmap overrides everything
+ if (scene_data.use_ambient_light) {
+ ambient_light = scene_data.ambient_light_color_energy.rgb;
+
+ if (scene_data.use_ambient_cubemap) {
+ vec3 ambient_dir = scene_data.radiance_inverse_xform * normal;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+ vec3 cubemap_ambient = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ambient_dir, MAX_ROUGHNESS_LOD)).rgb;
+#else
+ vec3 cubemap_ambient = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ambient_dir, MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+
+ ambient_light = mix(ambient_light, cubemap_ambient * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix);
+ }
+ }
+#endif // USE_LIGHTMAP
+#if defined(CUSTOM_IRRADIANCE_USED)
+ ambient_light = mix(ambient_light, custom_irradiance.rgb, custom_irradiance.a);
+#endif
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ //radiance
+
+/// GI ///
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef USE_LIGHTMAP
+
+ //lightmap
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE)) { //has lightmap capture
+ uint index = instances.data[instance_index].gi_offset;
+
+ vec3 wnormal = mat3(scene_data.camera_matrix) * normal;
+ const float c1 = 0.429043;
+ const float c2 = 0.511664;
+ const float c3 = 0.743125;
+ const float c4 = 0.886227;
+ const float c5 = 0.247708;
+ ambient_light += (c1 * lightmap_captures.data[index].sh[8].rgb * (wnormal.x * wnormal.x - wnormal.y * wnormal.y) +
+ c3 * lightmap_captures.data[index].sh[6].rgb * wnormal.z * wnormal.z +
+ c4 * lightmap_captures.data[index].sh[0].rgb -
+ c5 * lightmap_captures.data[index].sh[6].rgb +
+ 2.0 * c1 * lightmap_captures.data[index].sh[4].rgb * wnormal.x * wnormal.y +
+ 2.0 * c1 * lightmap_captures.data[index].sh[7].rgb * wnormal.x * wnormal.z +
+ 2.0 * c1 * lightmap_captures.data[index].sh[5].rgb * wnormal.y * wnormal.z +
+ 2.0 * c2 * lightmap_captures.data[index].sh[3].rgb * wnormal.x +
+ 2.0 * c2 * lightmap_captures.data[index].sh[1].rgb * wnormal.y +
+ 2.0 * c2 * lightmap_captures.data[index].sh[2].rgb * wnormal.z);
+
+ } else if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { // has actual lightmap
+ bool uses_sh = bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_SH_LIGHTMAP);
+ uint ofs = instances.data[instance_index].gi_offset & 0xFFFF;
+ vec3 uvw;
+ uvw.xy = uv2 * instances.data[instance_index].lightmap_uv_scale.zw + instances.data[instance_index].lightmap_uv_scale.xy;
+ uvw.z = float((instances.data[instance_index].gi_offset >> 16) & 0xFFFF);
+
+ if (uses_sh) {
+ uvw.z *= 4.0; //SH textures use 4 times more data
+ vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+
+ uint idx = instances.data[instance_index].gi_offset >> 20;
+ vec3 n = normalize(lightmaps.data[idx].normal_xform * normal);
+
+ ambient_light += lm_light_l0 * 0.282095f;
+ ambient_light += lm_light_l1n1 * 0.32573 * n.y;
+ ambient_light += lm_light_l1_0 * 0.32573 * n.z;
+ ambient_light += lm_light_l1p1 * 0.32573 * n.x;
+ if (metallic > 0.01) { // since the more direct bounced light is lost, we can kind of fake it with this trick
+ vec3 r = reflect(normalize(-vertex), normal);
+ specular_light += lm_light_l1n1 * 0.32573 * r.y;
+ specular_light += lm_light_l1_0 * 0.32573 * r.z;
+ specular_light += lm_light_l1p1 * 0.32573 * r.x;
+ }
+
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb;
+ }
+ }
+#else
+
+ if (sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_SDFGI)) { //has lightmap capture
+
+ //make vertex orientation the world one, but still align to camera
+ vec3 cam_pos = mat3(scene_data.camera_matrix) * vertex;
+ vec3 cam_normal = mat3(scene_data.camera_matrix) * normal;
+ vec3 cam_reflection = mat3(scene_data.camera_matrix) * reflect(-view, normal);
+
+ //apply y-mult
+ cam_pos.y *= sdfgi.y_mult;
+ cam_normal.y *= sdfgi.y_mult;
+ cam_normal = normalize(cam_normal);
+ cam_reflection.y *= sdfgi.y_mult;
+ cam_normal = normalize(cam_normal);
+ cam_reflection = normalize(cam_reflection);
+
+ vec4 light_accum = vec4(0.0);
+ float weight_accum = 0.0;
+
+ vec4 light_blend_accum = vec4(0.0);
+ float weight_blend_accum = 0.0;
+
+ float blend = -1.0;
+
+ // helper constants, compute once
+
+ uint cascade = 0xFFFFFFFF;
+ vec3 cascade_pos;
+ vec3 cascade_normal;
+
+ for (uint i = 0; i < sdfgi.max_cascades; i++) {
+ cascade_pos = (cam_pos - sdfgi.cascades[i].position) * sdfgi.cascades[i].to_probe;
+
+ if (any(lessThan(cascade_pos, vec3(0.0))) || any(greaterThanEqual(cascade_pos, sdfgi.cascade_probe_size))) {
+ continue; //skip cascade
+ }
+
+ cascade = i;
+ break;
+ }
+
+ if (cascade < SDFGI_MAX_CASCADES) {
+ bool use_specular = true;
+ float blend;
+ vec3 diffuse, specular;
+ sdfgi_process(cascade, cascade_pos, cam_pos, cam_normal, cam_reflection, use_specular, roughness, diffuse, specular, blend);
+
+ if (blend > 0.0) {
+ //blend
+ if (cascade == sdfgi.max_cascades - 1) {
+ diffuse = mix(diffuse, ambient_light, blend);
+ if (use_specular) {
+ specular = mix(specular, specular_light, blend);
+ }
+ } else {
+ vec3 diffuse2, specular2;
+ float blend2;
+ cascade_pos = (cam_pos - sdfgi.cascades[cascade + 1].position) * sdfgi.cascades[cascade + 1].to_probe;
+ sdfgi_process(cascade + 1, cascade_pos, cam_pos, cam_normal, cam_reflection, use_specular, roughness, diffuse2, specular2, blend2);
+ diffuse = mix(diffuse, diffuse2, blend);
+ if (use_specular) {
+ specular = mix(specular, specular2, blend);
+ }
+ }
+ }
+
+ ambient_light = diffuse;
+ if (use_specular) {
+ specular_light = specular;
+ }
+ }
+ }
+
+ if (sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_VOXEL_GI)) { // process voxel_gi_instances
+
+ uint index1 = instances.data[instance_index].gi_offset & 0xFFFF;
+ vec3 ref_vec = normalize(reflect(normalize(vertex), normal));
+ //find arbitrary tangent and bitangent, then build a matrix
+ vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, normal));
+ vec3 bitangent = normalize(cross(tangent, normal));
+ mat3 normal_mat = mat3(tangent, bitangent, normal);
+
+ vec4 amb_accum = vec4(0.0);
+ vec4 spec_accum = vec4(0.0);
+ voxel_gi_compute(index1, vertex, normal, ref_vec, normal_mat, roughness * roughness, ambient_light, specular_light, spec_accum, amb_accum);
+
+ uint index2 = instances.data[instance_index].gi_offset >> 16;
+
+ if (index2 != 0xFFFF) {
+ voxel_gi_compute(index2, vertex, normal, ref_vec, normal_mat, roughness * roughness, ambient_light, specular_light, spec_accum, amb_accum);
+ }
+
+ if (amb_accum.a > 0.0) {
+ amb_accum.rgb /= amb_accum.a;
+ }
+
+ if (spec_accum.a > 0.0) {
+ spec_accum.rgb /= spec_accum.a;
+ }
+
+ specular_light = spec_accum.rgb;
+ ambient_light = amb_accum.rgb;
+ }
+
+ if (!sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GI_BUFFERS)) { //use GI buffers
+
+ vec2 coord;
+
+ if (scene_data.gi_upscale_for_msaa) {
+ vec2 base_coord = screen_uv;
+ vec2 closest_coord = base_coord;
+ float closest_ang = dot(normal, textureLod(sampler2D(normal_roughness_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), base_coord, 0.0).xyz * 2.0 - 1.0);
+
+ for (int i = 0; i < 4; i++) {
+ const vec2 neighbours[4] = vec2[](vec2(-1, 0), vec2(1, 0), vec2(0, -1), vec2(0, 1));
+ vec2 neighbour_coord = base_coord + neighbours[i] * scene_data.screen_pixel_size;
+ float neighbour_ang = dot(normal, textureLod(sampler2D(normal_roughness_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), neighbour_coord, 0.0).xyz * 2.0 - 1.0);
+ if (neighbour_ang > closest_ang) {
+ closest_ang = neighbour_ang;
+ closest_coord = neighbour_coord;
+ }
+ }
+
+ coord = closest_coord;
+
+ } else {
+ coord = screen_uv;
+ }
+
+ vec4 buffer_ambient = textureLod(sampler2D(ambient_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), coord, 0.0);
+ vec4 buffer_reflection = textureLod(sampler2D(reflection_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), coord, 0.0);
+
+ ambient_light = mix(ambient_light, buffer_ambient.rgb, buffer_ambient.a);
+ specular_light = mix(specular_light, buffer_reflection.rgb, buffer_reflection.a);
+ }
+#endif // !USE_LIGHTMAP
+
+ if (scene_data.ssao_enabled) {
+ float ssao = texture(sampler2D(ao_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), screen_uv).r;
+ ao = min(ao, ssao);
+ ao_light_affect = mix(ao_light_affect, max(ao_light_affect, scene_data.ssao_light_affect), scene_data.ssao_ao_affect);
+ }
+
+ { // process reflections
+
+ vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
+
+ uint cluster_reflection_offset = cluster_offset + scene_data.cluster_type_size * 3;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_reflection_offset + scene_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_reflection_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+ uint reflection_index = 32 * i + bit;
+
+ if (!bool(reflections.data[reflection_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ reflection_process(reflection_index, vertex, normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum);
+ }
+ }
+
+ if (reflection_accum.a > 0.0) {
+ specular_light = reflection_accum.rgb / reflection_accum.a;
+ }
+
+#if !defined(USE_LIGHTMAP)
+ if (ambient_accum.a > 0.0) {
+ ambient_light = ambient_accum.rgb / ambient_accum.a;
+ }
+#endif
+ }
+
+ //finalize ambient light here
+ ambient_light *= albedo.rgb;
+ ambient_light *= ao;
+
+ // convert ao to direct light ao
+ ao = mix(1.0, ao, ao_light_affect);
+
+ //this saves some VGPRs
+ vec3 f0 = F0(metallic, specular, albedo);
+
+ {
+#if defined(DIFFUSE_TOON)
+ //simplify for toon, as
+ specular_light *= specular * metallic * albedo * 2.0;
+#else
+
+ // scales the specular reflections, needs to be computed before lighting happens,
+ // but after environment, GI, and reflection probes are added
+ // Environment brdf approximation (Lazarov 2013)
+ // see https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
+ const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
+ const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
+ vec4 r = roughness * c0 + c1;
+ float ndotv = clamp(dot(normal, view), 0.0, 1.0);
+ float a004 = min(r.x * r.x, exp2(-9.28 * ndotv)) * r.x + r.y;
+ vec2 env = vec2(-1.04, 1.04) * a004 + r.zw;
+
+ specular_light *= env.x * f0 + env.y;
+#endif
+ }
+
+#endif //GI !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#if !defined(MODE_RENDER_DEPTH)
+ //this saves some VGPRs
+ uint orms = packUnorm4x8(vec4(ao, roughness, metallic, specular));
+#endif
+
+// LIGHTING
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ { //directional light
+
+ // Do shadow and lighting in two passes to reduce register pressure
+ uint shadow0 = 0;
+ uint shadow1 = 0;
+
+ for (uint i = 0; i < 8; i++) {
+ if (i >= scene_data.directional_light_count) {
+ break;
+ }
+
+ if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip
+ }
+
+ float shadow = 1.0;
+
+ //version with soft shadows, more expensive
+ if (directional_lights.data[i].shadow_enabled) {
+ if (sc_use_directional_soft_shadows && directional_lights.data[i].softshadow_angle > 0) {
+ float depth_z = -vertex.z;
+
+ vec3 shadow_color = vec3(0.0);
+ vec3 light_dir = directional_lights.data[i].direction;
+
+#define BIAS_FUNC(m_var, m_idx) \
+ m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx]; \
+ vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp)))) * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+ normal_bias -= light_dir * dot(light_dir, normal_bias); \
+ m_var.xyz += normal_bias;
+
+ uint blend_index = 0;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 0)
+
+ vec4 pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+ pssm_coord /= pssm_coord.w;
+
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.x;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
+ shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ blend_index++;
+ }
+
+ if (blend_index < 2 && depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 1)
+
+ vec4 pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_coord /= pssm_coord.w;
+
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.y;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+ float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+
+ if (blend_index == 0) {
+ shadow = s;
+ } else {
+ //blend
+ float blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+ shadow = mix(shadow, s, blend);
+ }
+
+ blend_index++;
+ }
+
+ if (blend_index < 2 && depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 2)
+
+ vec4 pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_coord /= pssm_coord.w;
+
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.z;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+ float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+
+ if (blend_index == 0) {
+ shadow = s;
+ } else {
+ //blend
+ float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+ shadow = mix(shadow, s, blend);
+ }
+
+ blend_index++;
+ }
+
+ if (blend_index < 2) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 3)
+
+ vec4 pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_coord /= pssm_coord.w;
+
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.w;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+ float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+
+ if (blend_index == 0) {
+ shadow = s;
+ } else {
+ //blend
+ float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+ shadow = mix(shadow, s, blend);
+ }
+ }
+
+#undef BIAS_FUNC
+ } else { //no soft shadows
+
+ float depth_z = -vertex.z;
+
+ vec4 pssm_coord;
+ vec3 light_dir = directional_lights.data[i].direction;
+ vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
+
+#define BIAS_FUNC(m_var, m_idx) \
+ m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx]; \
+ vec3 normal_bias = base_normal_bias * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+ normal_bias -= light_dir * dot(light_dir, normal_bias); \
+ m_var.xyz += normal_bias;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 0)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 1)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 2)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+
+ } else {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 3)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ }
+
+ pssm_coord /= pssm_coord.w;
+
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+
+ if (directional_lights.data[i].blend_splits) {
+ float pssm_blend;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 1)
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 2)
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 3)
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+ } else {
+ pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+ }
+
+ pssm_coord /= pssm_coord.w;
+
+ float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ shadow = mix(shadow, shadow2, pssm_blend);
+ }
+
+ shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+
+#undef BIAS_FUNC
+ }
+ } // shadows
+
+ if (i < 4) {
+ shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
+ } else {
+ shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+ }
+ }
+
+ for (uint i = 0; i < 8; i++) {
+ if (i >= scene_data.directional_light_count) {
+ break;
+ }
+
+ if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+ float transmittance_z = transmittance_depth;
+
+ if (directional_lights.data[i].shadow_enabled) {
+ float depth_z = -vertex.z;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 trans_vertex = vec4(vertex - normalize(normal_interp) * directional_lights.data[i].shadow_transmittance_bias.x, 1.0);
+ vec4 trans_coord = directional_lights.data[i].shadow_matrix1 * trans_vertex;
+ trans_coord /= trans_coord.w;
+
+ float shadow_z = textureLod(sampler2D(directional_shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), trans_coord.xy, 0.0).r;
+ shadow_z *= directional_lights.data[i].shadow_z_range.x;
+ float z = trans_coord.z * directional_lights.data[i].shadow_z_range.x;
+
+ transmittance_z = z - shadow_z;
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 trans_vertex = vec4(vertex - normalize(normal_interp) * directional_lights.data[i].shadow_transmittance_bias.y, 1.0);
+ vec4 trans_coord = directional_lights.data[i].shadow_matrix2 * trans_vertex;
+ trans_coord /= trans_coord.w;
+
+ float shadow_z = textureLod(sampler2D(directional_shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), trans_coord.xy, 0.0).r;
+ shadow_z *= directional_lights.data[i].shadow_z_range.y;
+ float z = trans_coord.z * directional_lights.data[i].shadow_z_range.y;
+
+ transmittance_z = z - shadow_z;
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 trans_vertex = vec4(vertex - normalize(normal_interp) * directional_lights.data[i].shadow_transmittance_bias.z, 1.0);
+ vec4 trans_coord = directional_lights.data[i].shadow_matrix3 * trans_vertex;
+ trans_coord /= trans_coord.w;
+
+ float shadow_z = textureLod(sampler2D(directional_shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), trans_coord.xy, 0.0).r;
+ shadow_z *= directional_lights.data[i].shadow_z_range.z;
+ float z = trans_coord.z * directional_lights.data[i].shadow_z_range.z;
+
+ transmittance_z = z - shadow_z;
+
+ } else {
+ vec4 trans_vertex = vec4(vertex - normalize(normal_interp) * directional_lights.data[i].shadow_transmittance_bias.w, 1.0);
+ vec4 trans_coord = directional_lights.data[i].shadow_matrix4 * trans_vertex;
+ trans_coord /= trans_coord.w;
+
+ float shadow_z = textureLod(sampler2D(directional_shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), trans_coord.xy, 0.0).r;
+ shadow_z *= directional_lights.data[i].shadow_z_range.w;
+ float z = trans_coord.z * directional_lights.data[i].shadow_z_range.w;
+
+ transmittance_z = z - shadow_z;
+ }
+ }
+#endif
+
+ float shadow = 1.0;
+
+ if (i < 4) {
+ shadow = float(shadow0 >> (i * 8) & 0xFF) / 255.0;
+ } else {
+ shadow = float(shadow1 >> ((i - 4) * 8) & 0xFF) / 255.0;
+ }
+
+ blur_shadow(shadow);
+
+ float size_A = sc_use_light_soft_shadows ? directional_lights.data[i].size : 0.0;
+
+ light_compute(normal, directional_lights.data[i].direction, normalize(view), size_A, directional_lights.data[i].color * directional_lights.data[i].energy, shadow, f0, orms, 1.0,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+ transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+ rim, rim_tint, albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light,
+ specular_light);
+ }
+ }
+
+ { //omni lights
+
+ uint cluster_omni_offset = cluster_offset;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_omni_offset + scene_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_omni_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+ uint light_index = 32 * i + bit;
+
+ if (!bool(omni_lights.data[light_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ if (omni_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip
+ }
+
+ float shadow = light_process_omni_shadow(light_index, vertex, normal);
+
+ shadow = blur_shadow(shadow);
+
+ light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+ rim,
+ rim_tint,
+ albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light, specular_light);
+ }
+ }
+ }
+
+ { //spot lights
+
+ uint cluster_spot_offset = cluster_offset + scene_data.cluster_type_size;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_spot_offset + scene_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_spot_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+
+ uint light_index = 32 * i + bit;
+
+ if (!bool(spot_lights.data[light_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ if (spot_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip
+ }
+
+ float shadow = light_process_spot_shadow(light_index, vertex, normal);
+
+ shadow = blur_shadow(shadow);
+
+ light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+ rim,
+ rim_tint,
+ albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light, specular_light);
+ }
+ }
+ }
+
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
+
+#if defined(ALPHA_SCISSOR_USED)
+ if (alpha < alpha_scissor) {
+ discard;
+ }
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef USE_OPAQUE_PREPASS
+
+ if (alpha < opaque_prepass_threshold) {
+ discard;
+ }
+
+#endif // USE_OPAQUE_PREPASS
+
+#endif // USE_SHADOW_TO_OPACITY
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_SDF
+
+ {
+ vec3 local_pos = (scene_data.sdf_to_bounds * vec4(vertex, 1.0)).xyz;
+ ivec3 grid_pos = scene_data.sdf_offset + ivec3(local_pos * vec3(scene_data.sdf_size));
+
+ uint albedo16 = 0x1; //solid flag
+ albedo16 |= clamp(uint(albedo.r * 31.0), 0, 31) << 11;
+ albedo16 |= clamp(uint(albedo.g * 31.0), 0, 31) << 6;
+ albedo16 |= clamp(uint(albedo.b * 31.0), 0, 31) << 1;
+
+ imageStore(albedo_volume_grid, grid_pos, uvec4(albedo16));
+
+ uint facing_bits = 0;
+ const vec3 aniso_dir[6] = vec3[](
+ vec3(1, 0, 0),
+ vec3(0, 1, 0),
+ vec3(0, 0, 1),
+ vec3(-1, 0, 0),
+ vec3(0, -1, 0),
+ vec3(0, 0, -1));
+
+ vec3 cam_normal = mat3(scene_data.camera_matrix) * normalize(normal_interp);
+
+ float closest_dist = -1e20;
+
+ for (uint i = 0; i < 6; i++) {
+ float d = dot(cam_normal, aniso_dir[i]);
+ if (d > closest_dist) {
+ closest_dist = d;
+ facing_bits = (1 << i);
+ }
+ }
+
+ imageAtomicOr(geom_facing_grid, grid_pos, facing_bits); //store facing bits
+
+ if (length(emission) > 0.001) {
+ float lumas[6];
+ vec3 light_total = vec3(0);
+
+ for (int i = 0; i < 6; i++) {
+ float strength = max(0.0, dot(cam_normal, aniso_dir[i]));
+ vec3 light = emission * strength;
+ light_total += light;
+ lumas[i] = max(light.r, max(light.g, light.b));
+ }
+
+ float luma_total = max(light_total.r, max(light_total.g, light_total.b));
+
+ uint light_aniso = 0;
+
+ for (int i = 0; i < 6; i++) {
+ light_aniso |= min(31, uint((lumas[i] / luma_total) * 31.0)) << (i * 5);
+ }
+
+ //compress to RGBE9995 to save space
+
+ const float pow2to9 = 512.0f;
+ const float B = 15.0f;
+ const float N = 9.0f;
+ const float LN2 = 0.6931471805599453094172321215;
+
+ float cRed = clamp(light_total.r, 0.0, 65408.0);
+ float cGreen = clamp(light_total.g, 0.0, 65408.0);
+ float cBlue = clamp(light_total.b, 0.0, 65408.0);
+
+ float cMax = max(cRed, max(cGreen, cBlue));
+
+ float expp = max(-B - 1.0f, floor(log(cMax) / LN2)) + 1.0f + B;
+
+ float sMax = floor((cMax / pow(2.0f, expp - B - N)) + 0.5f);
+
+ float exps = expp + 1.0f;
+
+ if (0.0 <= sMax && sMax < pow2to9) {
+ exps = expp;
+ }
+
+ float sRed = floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
+ float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
+ float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
+ //store as 8985 to have 2 extra neighbour bits
+ uint light_rgbe = ((uint(sRed) & 0x1FF) >> 1) | ((uint(sGreen) & 0x1FF) << 8) | (((uint(sBlue) & 0x1FF) >> 1) << 17) | ((uint(exps) & 0x1F) << 25);
+
+ imageStore(emission_grid, grid_pos, uvec4(light_rgbe));
+ imageStore(emission_aniso_grid, grid_pos, uvec4(light_aniso));
+ }
+ }
+
+#endif
+
+#ifdef MODE_RENDER_MATERIAL
+
+ albedo_output_buffer.rgb = albedo;
+ albedo_output_buffer.a = alpha;
+
+ normal_output_buffer.rgb = normal * 0.5 + 0.5;
+ normal_output_buffer.a = 0.0;
+ depth_output_buffer.r = -vertex.z;
+
+ orm_output_buffer.r = ao;
+ orm_output_buffer.g = roughness;
+ orm_output_buffer.b = metallic;
+ orm_output_buffer.a = sss_strength;
+
+ emission_output_buffer.rgb = emission;
+ emission_output_buffer.a = 0.0;
+#endif
+
+#ifdef MODE_RENDER_NORMAL_ROUGHNESS
+ normal_roughness_output_buffer = vec4(normal * 0.5 + 0.5, roughness);
+
+#ifdef MODE_RENDER_VOXEL_GI
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_VOXEL_GI)) { // process voxel_gi_instances
+ uint index1 = instances.data[instance_index].gi_offset & 0xFFFF;
+ uint index2 = instances.data[instance_index].gi_offset >> 16;
+ voxel_gi_buffer.x = index1 & 0xFF;
+ voxel_gi_buffer.y = index2 & 0xFF;
+ } else {
+ voxel_gi_buffer.x = 0xFF;
+ voxel_gi_buffer.y = 0xFF;
+ }
+#endif
+
+#endif //MODE_RENDER_NORMAL_ROUGHNESS
+
+//nothing happens, so a tree-ssa optimizer will result in no fragment shader :)
+#else
+
+ // multiply by albedo
+ diffuse_light *= albedo; // ambient must be multiplied by albedo at the end
+
+ // apply direct light AO
+ ao = unpackUnorm4x8(orms).x;
+ specular_light *= ao;
+ diffuse_light *= ao;
+
+ // apply metallic
+ metallic = unpackUnorm4x8(orms).z;
+ diffuse_light *= 1.0 - metallic;
+ ambient_light *= 1.0 - metallic;
+
+ //restore fog
+ fog = vec4(unpackHalf2x16(fog_rg), unpackHalf2x16(fog_ba));
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+ diffuse_buffer = vec4(albedo.rgb, 0.0);
+ specular_buffer = vec4(0.0);
+
+#else
+
+#ifdef SSS_MODE_SKIN
+ sss_strength = -sss_strength;
+#endif
+ diffuse_buffer = vec4(emission + diffuse_light + ambient_light, sss_strength);
+ specular_buffer = vec4(specular_light, metallic);
+#endif
+
+ diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a);
+ specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a);
+
+#else //MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+ frag_color = vec4(albedo, alpha);
+#else
+ frag_color = vec4(emission + ambient_light + diffuse_light + specular_light, alpha);
+ //frag_color = vec4(1.0);
+#endif //USE_NO_SHADING
+
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+ frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a);
+ ;
+
+#endif //MODE_MULTIPLE_RENDER_TARGETS
+
+#endif //MODE_RENDER_DEPTH
+}
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl
new file mode 100644
index 0000000000..b53bf6a6d4
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl
@@ -0,0 +1,339 @@
+#define M_PI 3.14159265359
+#define ROUGHNESS_MAX_LOD 5
+
+#define MAX_VOXEL_GI_INSTANCES 8
+
+#if defined(has_GL_KHR_shader_subgroup_ballot) && defined(has_GL_KHR_shader_subgroup_arithmetic)
+
+#extension GL_KHR_shader_subgroup_ballot : enable
+#extension GL_KHR_shader_subgroup_arithmetic : enable
+
+#define USE_SUBGROUPS
+
+#endif
+
+#include "cluster_data_inc.glsl"
+#include "decal_data_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) || defined(MODE_RENDER_MATERIAL) || defined(MODE_RENDER_SDF) || defined(MODE_RENDER_NORMAL_ROUGHNESS) || defined(MODE_RENDER_VOXEL_GI) || defined(TANGENT_USED) || defined(NORMAL_MAP_USED)
+#ifndef NORMAL_USED
+#define NORMAL_USED
+#endif
+#endif
+
+layout(push_constant, binding = 0, std430) uniform DrawCall {
+ uint instance_index;
+ uint uv_offset;
+ uint pad0;
+ uint pad1;
+}
+draw_call;
+
+#define SDFGI_MAX_CASCADES 8
+
+/* Set 0: Base Pass (never changes) */
+
+#include "light_data_inc.glsl"
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+layout(set = 0, binding = 1) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 2) uniform sampler shadow_sampler;
+
+layout(set = 0, binding = 3) uniform sampler decal_sampler;
+
+layout(set = 0, binding = 4) uniform sampler light_projector_sampler;
+
+#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5)
+#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6)
+#define INSTANCE_FLAGS_USE_SDFGI (1 << 7)
+#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8)
+#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9)
+#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10)
+#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11)
+#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
+#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA (1 << 15)
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT 16
+//3 bits of stride
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_MASK 0xFF
+
+layout(set = 0, binding = 5, std430) restrict readonly buffer OmniLights {
+ LightData data[];
+}
+omni_lights;
+
+layout(set = 0, binding = 6, std430) restrict readonly buffer SpotLights {
+ LightData data[];
+}
+spot_lights;
+
+layout(set = 0, binding = 7, std430) restrict readonly buffer ReflectionProbeData {
+ ReflectionData data[];
+}
+reflections;
+
+layout(set = 0, binding = 8, std140) uniform DirectionalLights {
+ DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+}
+directional_lights;
+
+#define LIGHTMAP_FLAG_USE_DIRECTION 1
+#define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
+
+struct Lightmap {
+ mat3 normal_xform;
+};
+
+layout(set = 0, binding = 9, std140) restrict readonly buffer Lightmaps {
+ Lightmap data[];
+}
+lightmaps;
+
+struct LightmapCapture {
+ vec4 sh[9];
+};
+
+layout(set = 0, binding = 10, std140) restrict readonly buffer LightmapCaptures {
+ LightmapCapture data[];
+}
+lightmap_captures;
+
+layout(set = 0, binding = 11) uniform texture2D decal_atlas;
+layout(set = 0, binding = 12) uniform texture2D decal_atlas_srgb;
+
+layout(set = 0, binding = 13, std430) restrict readonly buffer Decals {
+ DecalData data[];
+}
+decals;
+
+layout(set = 0, binding = 14, std430) restrict readonly buffer GlobalVariableData {
+ vec4 data[];
+}
+global_variables;
+
+struct SDFVoxelGICascadeData {
+ vec3 position;
+ float to_probe;
+ ivec3 probe_world_offset;
+ float to_cell; // 1/bounds * grid_size
+};
+
+layout(set = 0, binding = 15, std140) uniform SDFGI {
+ vec3 grid_size;
+ uint max_cascades;
+
+ bool use_occlusion;
+ int probe_axis_size;
+ float probe_to_uvw;
+ float normal_bias;
+
+ vec3 lightprobe_tex_pixel_size;
+ float energy;
+
+ vec3 lightprobe_uv_offset;
+ float y_mult;
+
+ vec3 occlusion_clamp;
+ uint pad3;
+
+ vec3 occlusion_renormalize;
+ uint pad4;
+
+ vec3 cascade_probe_size;
+ uint pad5;
+
+ SDFVoxelGICascadeData cascades[SDFGI_MAX_CASCADES];
+}
+sdfgi;
+
+/* Set 1: Render Pass (changes per render pass) */
+
+layout(set = 1, binding = 0, std140) uniform SceneData {
+ mat4 projection_matrix;
+ mat4 inv_projection_matrix;
+
+ mat4 camera_matrix;
+ mat4 inv_camera_matrix;
+
+ vec2 viewport_size;
+ vec2 screen_pixel_size;
+
+ uint cluster_shift;
+ uint cluster_width;
+ uint cluster_type_size;
+ uint max_cluster_element_count_div_32;
+
+ // Use vec4s because std140 doesn't play nice with vec2s, z and w are wasted.
+ vec4 directional_penumbra_shadow_kernel[32];
+ vec4 directional_soft_shadow_kernel[32];
+ vec4 penumbra_shadow_kernel[32];
+ vec4 soft_shadow_kernel[32];
+
+ vec4 ambient_light_color_energy;
+
+ float ambient_color_sky_mix;
+ bool use_ambient_light;
+ bool use_ambient_cubemap;
+ bool use_reflection_cubemap;
+
+ mat3 radiance_inverse_xform;
+
+ vec2 shadow_atlas_pixel_size;
+ vec2 directional_shadow_pixel_size;
+
+ uint directional_light_count;
+ float dual_paraboloid_side;
+ float z_far;
+ float z_near;
+
+ bool ssao_enabled;
+ float ssao_light_affect;
+ float ssao_ao_affect;
+ bool roughness_limiter_enabled;
+
+ float roughness_limiter_amount;
+ float roughness_limiter_limit;
+ uvec2 roughness_limiter_pad;
+
+ vec4 ao_color;
+
+ mat4 sdf_to_bounds;
+
+ ivec3 sdf_offset;
+ bool material_uv2_mode;
+
+ ivec3 sdf_size;
+ bool gi_upscale_for_msaa;
+
+ bool volumetric_fog_enabled;
+ float volumetric_fog_inv_length;
+ float volumetric_fog_detail_spread;
+ uint volumetric_fog_pad;
+
+ bool fog_enabled;
+ float fog_density;
+ float fog_height;
+ float fog_height_density;
+
+ vec3 fog_light_color;
+ float fog_sun_scatter;
+
+ float fog_aerial_perspective;
+
+ float time;
+ float reflection_multiplier; // one normally, zero when rendering reflections
+
+ bool pancake_shadows;
+}
+scene_data;
+
+struct InstanceData {
+ mat4 transform;
+ uint flags;
+ uint instance_uniforms_ofs; //base offset in global buffer for instance variables
+ uint gi_offset; //GI information when using lightmapping (VCT or lightmap index)
+ uint layer_mask;
+ vec4 lightmap_uv_scale;
+};
+
+layout(set = 1, binding = 1, std430) buffer restrict readonly InstanceDataBuffer {
+ InstanceData data[];
+}
+instances;
+
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+layout(set = 1, binding = 2) uniform textureCubeArray radiance_cubemap;
+
+#else
+
+layout(set = 1, binding = 2) uniform textureCube radiance_cubemap;
+
+#endif
+
+layout(set = 1, binding = 3) uniform textureCubeArray reflection_atlas;
+
+layout(set = 1, binding = 4) uniform texture2D shadow_atlas;
+
+layout(set = 1, binding = 5) uniform texture2D directional_shadow_atlas;
+
+layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES];
+
+layout(set = 1, binding = 7) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
+
+layout(set = 1, binding = 8, std430) buffer restrict readonly ClusterBuffer {
+ uint data[];
+}
+cluster_buffer;
+
+#ifdef MODE_RENDER_SDF
+
+layout(r16ui, set = 1, binding = 9) uniform restrict writeonly uimage3D albedo_volume_grid;
+layout(r32ui, set = 1, binding = 10) uniform restrict writeonly uimage3D emission_grid;
+layout(r32ui, set = 1, binding = 11) uniform restrict writeonly uimage3D emission_aniso_grid;
+layout(r32ui, set = 1, binding = 12) uniform restrict uimage3D geom_facing_grid;
+
+//still need to be present for shaders that use it, so remap them to something
+#define depth_buffer shadow_atlas
+#define color_buffer shadow_atlas
+#define normal_roughness_buffer shadow_atlas
+
+#else
+
+layout(set = 1, binding = 9) uniform texture2D depth_buffer;
+layout(set = 1, binding = 10) uniform texture2D color_buffer;
+
+layout(set = 1, binding = 11) uniform texture2D normal_roughness_buffer;
+layout(set = 1, binding = 12) uniform texture2D ao_buffer;
+layout(set = 1, binding = 13) uniform texture2D ambient_buffer;
+layout(set = 1, binding = 14) uniform texture2D reflection_buffer;
+layout(set = 1, binding = 15) uniform texture2DArray sdfgi_lightprobe_texture;
+layout(set = 1, binding = 16) uniform texture3D sdfgi_occlusion_cascades;
+
+struct VoxelGIData {
+ mat4 xform;
+ vec3 bounds;
+ float dynamic_range;
+
+ float bias;
+ float normal_bias;
+ bool blend_ambient;
+ uint texture_slot;
+
+ float anisotropy_strength;
+ float ambient_occlusion;
+ float ambient_occlusion_size;
+ uint mipmaps;
+};
+
+layout(set = 1, binding = 17, std140) uniform VoxelGIs {
+ VoxelGIData data[MAX_VOXEL_GI_INSTANCES];
+}
+voxel_gi_instances;
+
+layout(set = 1, binding = 18) uniform texture3D volumetric_fog_texture;
+
+#endif
+
+/* Set 2 Skeleton & Instancing (can change per item) */
+
+layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms {
+ vec4 data[];
+}
+transforms;
+
+/* Set 3 User Material */
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl
new file mode 100644
index 0000000000..c88bd0a14b
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl
@@ -0,0 +1,242 @@
+// Functions related to gi/sdfgi for our forward renderer
+
+//standard voxel cone trace
+vec4 voxel_cone_trace(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
+ float dist = p_bias;
+ vec4 color = vec4(0.0);
+
+ while (dist < max_distance && color.a < 0.95) {
+ float diameter = max(1.0, 2.0 * tan_half_angle * dist);
+ vec3 uvw_pos = (pos + dist * direction) * cell_size;
+ float half_diameter = diameter * 0.5;
+ //check if outside, then break
+ if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + half_diameter * cell_size)))) {
+ break;
+ }
+ vec4 scolor = textureLod(sampler3D(probe, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uvw_pos, log2(diameter));
+ float a = (1.0 - color.a);
+ color += a * scolor;
+ dist += half_diameter;
+ }
+
+ return color;
+}
+
+vec4 voxel_cone_trace_45_degrees(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
+ float dist = p_bias;
+ vec4 color = vec4(0.0);
+ float radius = max(0.5, tan_half_angle * dist);
+ float lod_level = log2(radius * 2.0);
+
+ while (dist < max_distance && color.a < 0.95) {
+ vec3 uvw_pos = (pos + dist * direction) * cell_size;
+
+ //check if outside, then break
+ if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + radius * cell_size)))) {
+ break;
+ }
+ vec4 scolor = textureLod(sampler3D(probe, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uvw_pos, lod_level);
+ lod_level += 1.0;
+
+ float a = (1.0 - color.a);
+ scolor *= a;
+ color += scolor;
+ dist += radius;
+ radius = max(0.5, tan_half_angle * dist);
+ }
+
+ return color;
+}
+
+void voxel_gi_compute(uint index, vec3 position, vec3 normal, vec3 ref_vec, mat3 normal_xform, float roughness, vec3 ambient, vec3 environment, inout vec4 out_spec, inout vec4 out_diff) {
+ position = (voxel_gi_instances.data[index].xform * vec4(position, 1.0)).xyz;
+ ref_vec = normalize((voxel_gi_instances.data[index].xform * vec4(ref_vec, 0.0)).xyz);
+ normal = normalize((voxel_gi_instances.data[index].xform * vec4(normal, 0.0)).xyz);
+
+ position += normal * voxel_gi_instances.data[index].normal_bias;
+
+ //this causes corrupted pixels, i have no idea why..
+ if (any(bvec2(any(lessThan(position, vec3(0.0))), any(greaterThan(position, voxel_gi_instances.data[index].bounds))))) {
+ return;
+ }
+
+ vec3 blendv = abs(position / voxel_gi_instances.data[index].bounds * 2.0 - 1.0);
+ float blend = clamp(1.0 - max(blendv.x, max(blendv.y, blendv.z)), 0.0, 1.0);
+ //float blend=1.0;
+
+ float max_distance = length(voxel_gi_instances.data[index].bounds);
+ vec3 cell_size = 1.0 / voxel_gi_instances.data[index].bounds;
+
+ //radiance
+
+#define MAX_CONE_DIRS 4
+
+ vec3 cone_dirs[MAX_CONE_DIRS] = vec3[](
+ vec3(0.707107, 0.0, 0.707107),
+ vec3(0.0, 0.707107, 0.707107),
+ vec3(-0.707107, 0.0, 0.707107),
+ vec3(0.0, -0.707107, 0.707107));
+
+ float cone_weights[MAX_CONE_DIRS] = float[](0.25, 0.25, 0.25, 0.25);
+ float cone_angle_tan = 0.98269;
+
+ vec3 light = vec3(0.0);
+
+ for (int i = 0; i < MAX_CONE_DIRS; i++) {
+ vec3 dir = normalize((voxel_gi_instances.data[index].xform * vec4(normal_xform * cone_dirs[i], 0.0)).xyz);
+
+ vec4 cone_light = voxel_cone_trace_45_degrees(voxel_gi_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, voxel_gi_instances.data[index].bias);
+
+ if (voxel_gi_instances.data[index].blend_ambient) {
+ cone_light.rgb = mix(ambient, cone_light.rgb, min(1.0, cone_light.a / 0.95));
+ }
+
+ light += cone_weights[i] * cone_light.rgb;
+ }
+
+ light *= voxel_gi_instances.data[index].dynamic_range;
+ out_diff += vec4(light * blend, blend);
+
+ //irradiance
+ vec4 irr_light = voxel_cone_trace(voxel_gi_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, voxel_gi_instances.data[index].bias);
+ if (voxel_gi_instances.data[index].blend_ambient) {
+ irr_light.rgb = mix(environment, irr_light.rgb, min(1.0, irr_light.a / 0.95));
+ }
+ irr_light.rgb *= voxel_gi_instances.data[index].dynamic_range;
+ //irr_light=vec3(0.0);
+
+ out_spec += vec4(irr_light.rgb * blend, blend);
+}
+
+vec2 octahedron_wrap(vec2 v) {
+ vec2 signVal;
+ signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+ signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+ return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+ n.xy = n.xy * 0.5 + 0.5;
+ return n.xy;
+}
+
+void sdfgi_process(uint cascade, vec3 cascade_pos, vec3 cam_pos, vec3 cam_normal, vec3 cam_specular_normal, bool use_specular, float roughness, out vec3 diffuse_light, out vec3 specular_light, out float blend) {
+ cascade_pos += cam_normal * sdfgi.normal_bias;
+
+ vec3 base_pos = floor(cascade_pos);
+ //cascade_pos += mix(vec3(0.0),vec3(0.01),lessThan(abs(cascade_pos-base_pos),vec3(0.01))) * cam_normal;
+ ivec3 probe_base_pos = ivec3(base_pos);
+
+ vec4 diffuse_accum = vec4(0.0);
+ vec3 specular_accum;
+
+ ivec3 tex_pos = ivec3(probe_base_pos.xy, int(cascade));
+ tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
+ tex_pos.xy = tex_pos.xy * (SDFGI_OCT_SIZE + 2) + ivec2(1);
+
+ vec3 diffuse_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+
+ vec3 specular_posf;
+
+ if (use_specular) {
+ specular_accum = vec3(0.0);
+ specular_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_specular_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+ }
+
+ vec4 light_accum = vec4(0.0);
+ float weight_accum = 0.0;
+
+ for (uint j = 0; j < 8; j++) {
+ ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
+ ivec3 probe_posi = probe_base_pos;
+ probe_posi += offset;
+
+ // Compute weight
+
+ vec3 probe_pos = vec3(probe_posi);
+ vec3 probe_to_pos = cascade_pos - probe_pos;
+ vec3 probe_dir = normalize(-probe_to_pos);
+
+ vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
+ float weight = trilinear.x * trilinear.y * trilinear.z * max(0.005, dot(cam_normal, probe_dir));
+
+ // Compute lightprobe occlusion
+
+ if (sdfgi.use_occlusion) {
+ ivec3 occ_indexv = abs((sdfgi.cascades[cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+ vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
+
+ vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
+ occ_pos.z += float(cascade);
+ if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
+ occ_pos.x += 1.0;
+ }
+
+ occ_pos *= sdfgi.occlusion_renormalize;
+ float occlusion = dot(textureLod(sampler3D(sdfgi_occlusion_cascades, material_samplers[SAMPLER_LINEAR_CLAMP]), occ_pos, 0.0), occ_mask);
+
+ weight *= max(occlusion, 0.01);
+ }
+
+ // Compute lightprobe texture position
+
+ vec3 diffuse;
+ vec3 pos_uvw = diffuse_posf;
+ pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+ pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+ diffuse = textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw, 0.0).rgb;
+
+ diffuse_accum += vec4(diffuse * weight, weight);
+
+ if (use_specular) {
+ vec3 specular = vec3(0.0);
+ vec3 pos_uvw = specular_posf;
+ pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+ pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+ if (roughness < 0.99) {
+ specular = textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw + vec3(0, 0, float(sdfgi.max_cascades)), 0.0).rgb;
+ }
+ if (roughness > 0.5) {
+ specular = mix(specular, textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw, 0.0).rgb, (roughness - 0.5) * 2.0);
+ }
+
+ specular_accum += specular * weight;
+ }
+ }
+
+ if (diffuse_accum.a > 0.0) {
+ diffuse_accum.rgb /= diffuse_accum.a;
+ }
+
+ diffuse_light = diffuse_accum.rgb;
+
+ if (use_specular) {
+ if (diffuse_accum.a > 0.0) {
+ specular_accum /= diffuse_accum.a;
+ }
+
+ specular_light = specular_accum;
+ }
+
+ {
+ //process blend
+ float blend_from = (float(sdfgi.probe_axis_size - 1) / 2.0) - 2.5;
+ float blend_to = blend_from + 2.0;
+
+ vec3 inner_pos = cam_pos * sdfgi.cascades[cascade].to_probe;
+
+ float len = length(inner_pos);
+
+ inner_pos = abs(normalize(inner_pos));
+ len *= max(inner_pos.x, max(inner_pos.y, inner_pos.z));
+
+ if (len >= blend_from) {
+ blend = smoothstep(blend_from, blend_to, len);
+ } else {
+ blend = 0.0;
+ }
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
new file mode 100644
index 0000000000..ef2fde7516
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -0,0 +1,1035 @@
+// Functions related to lighting
+
+// This returns the G_GGX function divided by 2 cos_theta_m, where in practice cos_theta_m is either N.L or N.V.
+// We're dividing this factor off because the overall term we'll end up looks like
+// (see, for example, the first unnumbered equation in B. Burley, "Physically Based Shading at Disney", SIGGRAPH 2012):
+//
+// F(L.V) D(N.H) G(N.L) G(N.V) / (4 N.L N.V)
+//
+// We're basically regouping this as
+//
+// F(L.V) D(N.H) [G(N.L)/(2 N.L)] [G(N.V) / (2 N.V)]
+//
+// and thus, this function implements the [G(N.m)/(2 N.m)] part with m = L or V.
+//
+// The contents of the D and G (G1) functions (GGX) are taken from
+// E. Heitz, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs", J. Comp. Graph. Tech. 3 (2) (2014).
+// Eqns 71-72 and 85-86 (see also Eqns 43 and 80).
+
+float G_GGX_2cos(float cos_theta_m, float alpha) {
+ // Schlick's approximation
+ // C. Schlick, "An Inexpensive BRDF Model for Physically-based Rendering", Computer Graphics Forum. 13 (3): 233 (1994)
+ // Eq. (19), although see Heitz (2014) the about the problems with his derivation.
+ // It nevertheless approximates GGX well with k = alpha/2.
+ float k = 0.5 * alpha;
+ return 0.5 / (cos_theta_m * (1.0 - k) + k);
+
+ // float cos2 = cos_theta_m * cos_theta_m;
+ // float sin2 = (1.0 - cos2);
+ // return 1.0 / (cos_theta_m + sqrt(cos2 + alpha * alpha * sin2));
+}
+
+float D_GGX(float cos_theta_m, float alpha) {
+ float alpha2 = alpha * alpha;
+ float d = 1.0 + (alpha2 - 1.0) * cos_theta_m * cos_theta_m;
+ return alpha2 / (M_PI * d * d);
+}
+
+float G_GGX_anisotropic_2cos(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) {
+ float cos2 = cos_theta_m * cos_theta_m;
+ float sin2 = (1.0 - cos2);
+ float s_x = alpha_x * cos_phi;
+ float s_y = alpha_y * sin_phi;
+ return 1.0 / max(cos_theta_m + sqrt(cos2 + (s_x * s_x + s_y * s_y) * sin2), 0.001);
+}
+
+float D_GGX_anisotropic(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) {
+ float cos2 = cos_theta_m * cos_theta_m;
+ float sin2 = (1.0 - cos2);
+ float r_x = cos_phi / alpha_x;
+ float r_y = sin_phi / alpha_y;
+ float d = cos2 + sin2 * (r_x * r_x + r_y * r_y);
+ return 1.0 / max(M_PI * alpha_x * alpha_y * d * d, 0.001);
+}
+
+float SchlickFresnel(float u) {
+ float m = 1.0 - u;
+ float m2 = m * m;
+ return m2 * m2 * m; // pow(m,5)
+}
+
+float GTR1(float NdotH, float a) {
+ if (a >= 1.0)
+ return 1.0 / M_PI;
+ float a2 = a * a;
+ float t = 1.0 + (a2 - 1.0) * NdotH * NdotH;
+ return (a2 - 1.0) / (M_PI * log(a2) * t);
+}
+
+vec3 F0(float metallic, float specular, vec3 albedo) {
+ float dielectric = 0.16 * specular * specular;
+ // use albedo * metallic as colored specular reflectance at 0 angle for metallic materials;
+ // see https://google.github.io/filament/Filament.md.html
+ return mix(vec3(dielectric), albedo, vec3(metallic));
+}
+
+void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float attenuation, vec3 f0, uint orms, float specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+ vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ vec4 transmittance_color,
+ float transmittance_depth,
+ float transmittance_boost,
+ float transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+ float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ vec3 B, vec3 T, float anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ inout float alpha,
+#endif
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+
+#if defined(LIGHT_CODE_USED)
+ // light is written by the light shader
+
+ vec3 normal = N;
+ vec3 light = L;
+ vec3 view = V;
+
+#CODE : LIGHT
+
+#else
+
+ float NdotL = min(A + dot(N, L), 1.0);
+ float cNdotL = max(NdotL, 0.0); // clamped NdotL
+ float NdotV = dot(N, V);
+ float cNdotV = max(NdotV, 0.0);
+
+#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_BLINN) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+ vec3 H = normalize(V + L);
+#endif
+
+#if defined(SPECULAR_BLINN) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+ float cNdotH = clamp(A + dot(N, H), 0.0, 1.0);
+#endif
+
+#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+ float cLdotH = clamp(A + dot(L, H), 0.0, 1.0);
+#endif
+
+ float metallic = unpackUnorm4x8(orms).z;
+ if (metallic < 1.0) {
+ float roughness = unpackUnorm4x8(orms).y;
+ float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance
+
+#if defined(DIFFUSE_LAMBERT_WRAP)
+ // energy conserving lambert wrap shader
+ diffuse_brdf_NL = max(0.0, (NdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness)));
+#elif defined(DIFFUSE_TOON)
+
+ diffuse_brdf_NL = smoothstep(-roughness, max(roughness, 0.01), NdotL);
+
+#elif defined(DIFFUSE_BURLEY)
+
+ {
+ float FD90_minus_1 = 2.0 * cLdotH * cLdotH * roughness - 0.5;
+ float FdV = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotV);
+ float FdL = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotL);
+ diffuse_brdf_NL = (1.0 / M_PI) * FdV * FdL * cNdotL;
+ /*
+ float energyBias = mix(roughness, 0.0, 0.5);
+ float energyFactor = mix(roughness, 1.0, 1.0 / 1.51);
+ float fd90 = energyBias + 2.0 * VoH * VoH * roughness;
+ float f0 = 1.0;
+ float lightScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotL, 5.0);
+ float viewScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotV, 5.0);
+
+ diffuse_brdf_NL = lightScatter * viewScatter * energyFactor;
+ */
+ }
+#else
+ // lambert
+ diffuse_brdf_NL = cNdotL * (1.0 / M_PI);
+#endif
+
+ diffuse_light += light_color * diffuse_brdf_NL * attenuation;
+
+#if defined(LIGHT_BACKLIGHT_USED)
+ diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation;
+#endif
+
+#if defined(LIGHT_RIM_USED)
+ float rim_light = pow(max(0.0, 1.0 - cNdotV), max(0.0, (1.0 - roughness) * 16.0));
+ diffuse_light += rim_light * rim * mix(vec3(1.0), rim_color, rim_tint) * light_color;
+#endif
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+
+ {
+#ifdef SSS_MODE_SKIN
+ float scale = 8.25 / transmittance_depth;
+ float d = scale * abs(transmittance_z);
+ float dd = -d * d;
+ vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
+ vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
+ vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
+ vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
+ vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
+ vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
+
+ diffuse_light += profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI);
+#else
+
+ float scale = 8.25 / transmittance_depth;
+ float d = scale * abs(transmittance_z);
+ float dd = -d * d;
+ diffuse_light += exp(dd) * transmittance_color.rgb * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI);
+#endif
+ }
+#else
+
+#endif //LIGHT_TRANSMITTANCE_USED
+ }
+
+ float roughness = unpackUnorm4x8(orms).y;
+ if (roughness > 0.0) { // FIXME: roughness == 0 should not disable specular light entirely
+
+ // D
+
+#if defined(SPECULAR_BLINN)
+
+ //normalized blinn
+ float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25;
+ float blinn = pow(cNdotH, shininess);
+ blinn *= (shininess + 2.0) * (1.0 / (8.0 * M_PI));
+
+ specular_light += light_color * attenuation * specular_amount * blinn * f0 * unpackUnorm4x8(orms).w;
+
+#elif defined(SPECULAR_PHONG)
+
+ vec3 R = normalize(-reflect(L, N));
+ float cRdotV = clamp(A + dot(R, V), 0.0, 1.0);
+ float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25;
+ float phong = pow(cRdotV, shininess);
+ phong *= (shininess + 1.0) * (1.0 / (8.0 * M_PI));
+
+ specular_light += light_color * attenuation * specular_amount * phong * f0 * unpackUnorm4x8(orms).w;
+
+#elif defined(SPECULAR_TOON)
+
+ vec3 R = normalize(-reflect(L, N));
+ float RdotV = dot(R, V);
+ float mid = 1.0 - roughness;
+ mid *= mid;
+ float intensity = smoothstep(mid - roughness * 0.5, mid + roughness * 0.5, RdotV) * mid;
+ diffuse_light += light_color * intensity * attenuation * specular_amount; // write to diffuse_light, as in toon shading you generally want no reflection
+
+#elif defined(SPECULAR_DISABLED)
+ // none..
+
+#elif defined(SPECULAR_SCHLICK_GGX)
+ // shlick+ggx as default
+
+#if defined(LIGHT_ANISOTROPY_USED)
+
+ float alpha_ggx = roughness * roughness;
+ float aspect = sqrt(1.0 - anisotropy * 0.9);
+ float ax = alpha_ggx / aspect;
+ float ay = alpha_ggx * aspect;
+ float XdotH = dot(T, H);
+ float YdotH = dot(B, H);
+ float D = D_GGX_anisotropic(cNdotH, ax, ay, XdotH, YdotH);
+ float G = G_GGX_anisotropic_2cos(cNdotL, ax, ay, XdotH, YdotH) * G_GGX_anisotropic_2cos(cNdotV, ax, ay, XdotH, YdotH);
+
+#else
+ float alpha_ggx = roughness * roughness;
+ float D = D_GGX(cNdotH, alpha_ggx);
+ float G = G_GGX_2cos(cNdotL, alpha_ggx) * G_GGX_2cos(cNdotV, alpha_ggx);
+#endif
+ // F
+ float cLdotH5 = SchlickFresnel(cLdotH);
+ vec3 F = mix(vec3(cLdotH5), vec3(1.0), f0);
+
+ vec3 specular_brdf_NL = cNdotL * D * F * G;
+
+ specular_light += specular_brdf_NL * light_color * attenuation * specular_amount;
+#endif
+
+#if defined(LIGHT_CLEARCOAT_USED)
+
+#if !defined(SPECULAR_SCHLICK_GGX)
+ float cLdotH5 = SchlickFresnel(cLdotH);
+#endif
+ float Dr = GTR1(cNdotH, mix(.1, .001, clearcoat_gloss));
+ float Fr = mix(.04, 1.0, cLdotH5);
+ float Gr = G_GGX_2cos(cNdotL, .25) * G_GGX_2cos(cNdotV, .25);
+
+ float clearcoat_specular_brdf_NL = 0.25 * clearcoat * Gr * Fr * Dr * cNdotL;
+
+ specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount;
+#endif
+ }
+
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0));
+#endif
+
+#endif //defined(LIGHT_CODE_USED)
+}
+
+#ifndef USE_NO_SHADOWS
+
+// Interleaved Gradient Noise
+// http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
+float quick_hash(vec2 pos) {
+ const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);
+ return fract(magic.z * fract(dot(pos, magic.xy)));
+}
+
+float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) {
+ vec2 pos = coord.xy;
+ float depth = coord.z;
+
+ //if only one sample is taken, take it from the center
+ if (sc_directional_soft_shadow_samples == 1) {
+ return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
+ }
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ float avg = 0.0;
+
+ for (uint i = 0; i < sc_directional_soft_shadow_samples; i++) {
+ avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data.directional_soft_shadow_kernel[i].xy), depth, 1.0));
+ }
+
+ return avg * (1.0 / float(sc_directional_soft_shadow_samples));
+}
+
+float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord) {
+ vec2 pos = coord.xy;
+ float depth = coord.z;
+
+ //if only one sample is taken, take it from the center
+ if (sc_soft_shadow_samples == 1) {
+ return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
+ }
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ float avg = 0.0;
+
+ for (uint i = 0; i < sc_soft_shadow_samples; i++) {
+ avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data.soft_shadow_kernel[i].xy), depth, 1.0));
+ }
+
+ return avg * (1.0 / float(sc_soft_shadow_samples));
+}
+
+float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec4 uv_rect, vec2 flip_offset, float depth) {
+ //if only one sample is taken, take it from the center
+ if (sc_soft_shadow_samples == 1) {
+ vec2 pos = coord * 0.5 + 0.5;
+ pos = uv_rect.xy + pos * uv_rect.zw;
+ return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
+ }
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ float avg = 0.0;
+ vec2 offset_scale = blur_scale * 2.0 * scene_data.shadow_atlas_pixel_size / uv_rect.zw;
+
+ for (uint i = 0; i < sc_soft_shadow_samples; i++) {
+ vec2 offset = offset_scale * (disk_rotation * scene_data.soft_shadow_kernel[i].xy);
+ vec2 sample_coord = coord + offset;
+
+ float sample_coord_length_sqaured = dot(sample_coord, sample_coord);
+ bool do_flip = sample_coord_length_sqaured > 1.0;
+
+ if (do_flip) {
+ float len = sqrt(sample_coord_length_sqaured);
+ sample_coord = sample_coord * (2.0 / len - 1.0);
+ }
+
+ sample_coord = sample_coord * 0.5 + 0.5;
+ sample_coord = uv_rect.xy + sample_coord * uv_rect.zw;
+
+ if (do_flip) {
+ sample_coord += flip_offset;
+ }
+ avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(sample_coord, depth, 1.0));
+ }
+
+ return avg * (1.0 / float(sc_soft_shadow_samples));
+}
+
+float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale) {
+ //find blocker
+ float blocker_count = 0.0;
+ float blocker_average = 0.0;
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ for (uint i = 0; i < sc_directional_penumbra_shadow_samples; i++) {
+ vec2 suv = pssm_coord.xy + (disk_rotation * scene_data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
+ float d = textureLod(sampler2D(shadow, material_samplers[SAMPLER_LINEAR_CLAMP]), suv, 0.0).r;
+ if (d < pssm_coord.z) {
+ blocker_average += d;
+ blocker_count += 1.0;
+ }
+ }
+
+ if (blocker_count > 0.0) {
+ //blockers found, do soft shadow
+ blocker_average /= blocker_count;
+ float penumbra = (pssm_coord.z - blocker_average) / blocker_average;
+ tex_scale *= penumbra;
+
+ float s = 0.0;
+ for (uint i = 0; i < sc_directional_penumbra_shadow_samples; i++) {
+ vec2 suv = pssm_coord.xy + (disk_rotation * scene_data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
+ s += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(suv, pssm_coord.z, 1.0));
+ }
+
+ return s / float(sc_directional_penumbra_shadow_samples);
+
+ } else {
+ //no blockers found, so no shadow
+ return 1.0;
+ }
+}
+
+#endif //USE_NO_SHADOWS
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
+#ifndef USE_NO_SHADOWS
+ if (omni_lights.data[idx].shadow_enabled) {
+ // there is a shadowmap
+ vec2 texel_size = scene_data.shadow_atlas_pixel_size;
+ vec4 base_uv_rect = omni_lights.data[idx].atlas_rect;
+ base_uv_rect.xy += texel_size;
+ base_uv_rect.zw -= texel_size * 2.0;
+
+ // Omni lights use direction.xy to store to store the offset between the two paraboloid regions
+ vec2 flip_offset = omni_lights.data[idx].direction.xy;
+
+ vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
+
+ float shadow_len = length(local_vert); //need to remember shadow len from here
+ vec3 shadow_dir = normalize(local_vert);
+
+ vec3 local_normal = normalize(mat3(omni_lights.data[idx].shadow_matrix) * normal);
+ vec3 normal_bias = local_normal * omni_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir)));
+
+ float shadow;
+
+ if (sc_use_light_soft_shadows && omni_lights.data[idx].soft_shadow_size > 0.0) {
+ //soft shadow
+
+ //find blocker
+
+ float blocker_count = 0.0;
+ float blocker_average = 0.0;
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ vec3 basis_normal = shadow_dir;
+ vec3 v0 = abs(basis_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, basis_normal));
+ vec3 bitangent = normalize(cross(tangent, basis_normal));
+ float z_norm = shadow_len * omni_lights.data[idx].inv_radius;
+
+ tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
+ bitangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
+
+ for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
+
+ vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
+
+ pos = normalize(pos);
+
+ vec4 uv_rect = base_uv_rect;
+
+ if (pos.z >= 0.0) {
+ uv_rect.xy += flip_offset;
+ }
+
+ pos.z = 1.0 + abs(pos.z);
+ pos.xy /= pos.z;
+
+ pos.xy = pos.xy * 0.5 + 0.5;
+ pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
+
+ float d = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), pos.xy, 0.0).r;
+ if (d < z_norm) {
+ blocker_average += d;
+ blocker_count += 1.0;
+ }
+ }
+
+ if (blocker_count > 0.0) {
+ //blockers found, do soft shadow
+ blocker_average /= blocker_count;
+ float penumbra = (z_norm - blocker_average) / blocker_average;
+ tangent *= penumbra;
+ bitangent *= penumbra;
+
+ z_norm -= omni_lights.data[idx].inv_radius * omni_lights.data[idx].shadow_bias;
+
+ shadow = 0.0;
+ for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
+ vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
+
+ pos = normalize(pos);
+ pos = normalize(pos + normal_bias);
+
+ vec4 uv_rect = base_uv_rect;
+
+ if (pos.z >= 0.0) {
+ uv_rect.xy += flip_offset;
+ }
+
+ pos.z = 1.0 + abs(pos.z);
+ pos.xy /= pos.z;
+
+ pos.xy = pos.xy * 0.5 + 0.5;
+ pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
+ shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(pos.xy, z_norm, 1.0));
+ }
+
+ shadow /= float(sc_penumbra_shadow_samples);
+
+ } else {
+ //no blockers found, so no shadow
+ shadow = 1.0;
+ }
+ } else {
+ vec4 uv_rect = base_uv_rect;
+
+ vec3 shadow_sample = normalize(shadow_dir + normal_bias);
+ if (shadow_sample.z >= 0.0) {
+ uv_rect.xy += flip_offset;
+ flip_offset *= -1.0;
+ }
+
+ shadow_sample.z = 1.0 + abs(shadow_sample.z);
+ vec2 pos = shadow_sample.xy / shadow_sample.z;
+ float depth = shadow_len - omni_lights.data[idx].shadow_bias;
+ depth *= omni_lights.data[idx].inv_radius;
+ shadow = sample_omni_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, flip_offset, depth);
+ }
+
+ return shadow;
+ }
+#endif
+
+ return 1.0;
+}
+
+void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ vec4 transmittance_color,
+ float transmittance_depth,
+ float transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+ float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ vec3 binormal, vec3 tangent, float anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ inout float alpha,
+#endif
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+ vec3 light_rel_vec = omni_lights.data[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float omni_attenuation = get_omni_attenuation(light_length, omni_lights.data[idx].inv_radius, omni_lights.data[idx].attenuation);
+ float light_attenuation = omni_attenuation;
+ vec3 color = omni_lights.data[idx].color;
+
+ float size_A = 0.0;
+
+ if (sc_use_light_soft_shadows && omni_lights.data[idx].size > 0.0) {
+ float t = omni_lights.data[idx].size / max(0.001, light_length);
+ size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
+ }
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+ float transmittance_z = transmittance_depth; //no transmittance by default
+ transmittance_color.a *= light_attenuation;
+ {
+ vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
+
+ //redo shadowmapping, but shrink the model a bit to avoid arctifacts
+ vec4 splane = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * omni_lights.data[idx].transmittance_bias, 1.0));
+
+ float shadow_len = length(splane.xyz);
+ splane.xyz = normalize(splane.xyz);
+
+ if (splane.z >= 0.0) {
+ splane.z += 1.0;
+ clamp_rect.y += clamp_rect.w;
+ } else {
+ splane.z = 1.0 - splane.z;
+ }
+
+ splane.xy /= splane.z;
+
+ splane.xy = splane.xy * 0.5 + 0.5;
+ splane.z = shadow_len * omni_lights.data[idx].inv_radius;
+ splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
+ // splane.xy = clamp(splane.xy,clamp_rect.xy + scene_data.shadow_atlas_pixel_size,clamp_rect.xy + clamp_rect.zw - scene_data.shadow_atlas_pixel_size );
+ splane.w = 1.0; //needed? i think it should be 1 already
+
+ float shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r;
+ transmittance_z = (splane.z - shadow_z) / omni_lights.data[idx].inv_radius;
+ }
+#endif
+
+ if (sc_use_light_projector && omni_lights.data[idx].projector_rect != vec4(0.0)) {
+ vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
+ local_v = normalize(local_v);
+
+ vec4 atlas_rect = omni_lights.data[idx].projector_rect;
+
+ if (local_v.z >= 0.0) {
+ atlas_rect.y += atlas_rect.w;
+ }
+
+ local_v.z = 1.0 + abs(local_v.z);
+
+ local_v.xy /= local_v.z;
+ local_v.xy = local_v.xy * 0.5 + 0.5;
+ vec2 proj_uv = local_v.xy * atlas_rect.zw;
+
+ if (sc_projector_use_mipmaps) {
+ vec2 proj_uv_ddx;
+ vec2 proj_uv_ddy;
+ {
+ vec3 local_v_ddx = (omni_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0)).xyz;
+ local_v_ddx = normalize(local_v_ddx);
+
+ if (local_v_ddx.z >= 0.0) {
+ local_v_ddx.z += 1.0;
+ } else {
+ local_v_ddx.z = 1.0 - local_v_ddx.z;
+ }
+
+ local_v_ddx.xy /= local_v_ddx.z;
+ local_v_ddx.xy = local_v_ddx.xy * 0.5 + 0.5;
+
+ proj_uv_ddx = local_v_ddx.xy * atlas_rect.zw - proj_uv;
+
+ vec3 local_v_ddy = (omni_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddy, 1.0)).xyz;
+ local_v_ddy = normalize(local_v_ddy);
+
+ if (local_v_ddy.z >= 0.0) {
+ local_v_ddy.z += 1.0;
+ } else {
+ local_v_ddy.z = 1.0 - local_v_ddy.z;
+ }
+
+ local_v_ddy.xy /= local_v_ddy.z;
+ local_v_ddy.xy = local_v_ddy.xy * 0.5 + 0.5;
+
+ proj_uv_ddy = local_v_ddy.xy * atlas_rect.zw - proj_uv;
+ }
+
+ vec4 proj = textureGrad(sampler2D(decal_atlas_srgb, light_projector_sampler), proj_uv + atlas_rect.xy, proj_uv_ddx, proj_uv_ddy);
+ color *= proj.rgb * proj.a;
+ } else {
+ vec4 proj = textureLod(sampler2D(decal_atlas_srgb, light_projector_sampler), proj_uv + atlas_rect.xy, 0.0);
+ color *= proj.rgb * proj.a;
+ }
+ }
+
+ light_attenuation *= shadow;
+
+ light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, light_attenuation, f0, orms, omni_lights.data[idx].specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+ transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+ rim * omni_attenuation, rim_tint, rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light,
+ specular_light);
+}
+
+float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
+#ifndef USE_NO_SHADOWS
+ if (spot_lights.data[idx].shadow_enabled) {
+ vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ vec3 spot_dir = spot_lights.data[idx].direction;
+
+ vec3 shadow_dir = light_rel_vec / light_length;
+ vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, shadow_dir)));
+
+ //there is a shadowmap
+ vec4 v = vec4(vertex + normal_bias, 1.0);
+
+ vec4 splane = (spot_lights.data[idx].shadow_matrix * v);
+ splane.z -= spot_lights.data[idx].shadow_bias / (light_length * spot_lights.data[idx].inv_radius);
+ splane /= splane.w;
+
+ float shadow;
+ if (sc_use_light_soft_shadows && spot_lights.data[idx].soft_shadow_size > 0.0) {
+ //soft shadow
+
+ //find blocker
+ float z_norm = dot(spot_dir, -light_rel_vec) * spot_lights.data[idx].inv_radius;
+
+ vec2 shadow_uv = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
+
+ float blocker_count = 0.0;
+ float blocker_average = 0.0;
+
+ mat2 disk_rotation;
+ {
+ float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+ float sr = sin(r);
+ float cr = cos(r);
+ disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+ }
+
+ float uv_size = spot_lights.data[idx].soft_shadow_size * z_norm * spot_lights.data[idx].soft_shadow_scale;
+ vec2 clamp_max = spot_lights.data[idx].atlas_rect.xy + spot_lights.data[idx].atlas_rect.zw;
+ for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size;
+ suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
+ float d = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), suv, 0.0).r;
+ if (d < splane.z) {
+ blocker_average += d;
+ blocker_count += 1.0;
+ }
+ }
+
+ if (blocker_count > 0.0) {
+ //blockers found, do soft shadow
+ blocker_average /= blocker_count;
+ float penumbra = (z_norm - blocker_average) / blocker_average;
+ uv_size *= penumbra;
+
+ shadow = 0.0;
+ for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size;
+ suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
+ shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(suv, splane.z, 1.0));
+ }
+
+ shadow /= float(sc_penumbra_shadow_samples);
+
+ } else {
+ //no blockers found, so no shadow
+ shadow = 1.0;
+ }
+ } else {
+ //hard shadow
+ vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z);
+ shadow = sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv);
+ }
+
+ return shadow;
+ }
+
+#endif //USE_NO_SHADOWS
+
+ return 1.0;
+}
+
+vec2 normal_to_panorama(vec3 n) {
+ n = normalize(n);
+ vec2 panorama_coords = vec2(atan(n.x, n.z), acos(-n.y));
+
+ if (panorama_coords.x < 0.0) {
+ panorama_coords.x += M_PI * 2.0;
+ }
+
+ panorama_coords /= vec2(M_PI * 2.0, M_PI);
+ return panorama_coords;
+}
+
+void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ vec4 transmittance_color,
+ float transmittance_depth,
+ float transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+ float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ vec3 binormal, vec3 tangent, float anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ inout float alpha,
+#endif
+ inout vec3 diffuse_light,
+ inout vec3 specular_light) {
+ vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float spot_attenuation = get_omni_attenuation(light_length, spot_lights.data[idx].inv_radius, spot_lights.data[idx].attenuation);
+ vec3 spot_dir = spot_lights.data[idx].direction;
+ float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_lights.data[idx].cone_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_lights.data[idx].cone_angle));
+ spot_attenuation *= 1.0 - pow(spot_rim, spot_lights.data[idx].cone_attenuation);
+ float light_attenuation = spot_attenuation;
+ vec3 color = spot_lights.data[idx].color;
+ float specular_amount = spot_lights.data[idx].specular_amount;
+
+ float size_A = 0.0;
+
+ if (sc_use_light_soft_shadows && spot_lights.data[idx].size > 0.0) {
+ float t = spot_lights.data[idx].size / max(0.001, light_length);
+ size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
+ }
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+ float transmittance_z = transmittance_depth;
+ transmittance_color.a *= light_attenuation;
+ {
+ vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * spot_lights.data[idx].transmittance_bias, 1.0));
+ splane /= splane.w;
+ splane.xy = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
+
+ float shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r;
+
+ shadow_z = shadow_z * 2.0 - 1.0;
+ float z_far = 1.0 / spot_lights.data[idx].inv_radius;
+ float z_near = 0.01;
+ shadow_z = 2.0 * z_near * z_far / (z_far + z_near - shadow_z * (z_far - z_near));
+
+ //distance to light plane
+ float z = dot(spot_dir, -light_rel_vec);
+ transmittance_z = z - shadow_z;
+ }
+#endif //LIGHT_TRANSMITTANCE_USED
+
+ if (sc_use_light_projector && spot_lights.data[idx].projector_rect != vec4(0.0)) {
+ vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0));
+ splane /= splane.w;
+
+ vec2 proj_uv = normal_to_panorama(splane.xyz) * spot_lights.data[idx].projector_rect.zw;
+
+ if (sc_projector_use_mipmaps) {
+ //ensure we have proper mipmaps
+ vec4 splane_ddx = (spot_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0));
+ splane_ddx /= splane_ddx.w;
+ vec2 proj_uv_ddx = normal_to_panorama(splane_ddx.xyz) * spot_lights.data[idx].projector_rect.zw - proj_uv;
+
+ vec4 splane_ddy = (spot_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddy, 1.0));
+ splane_ddy /= splane_ddy.w;
+ vec2 proj_uv_ddy = normal_to_panorama(splane_ddy.xyz) * spot_lights.data[idx].projector_rect.zw - proj_uv;
+
+ vec4 proj = textureGrad(sampler2D(decal_atlas_srgb, light_projector_sampler), proj_uv + spot_lights.data[idx].projector_rect.xy, proj_uv_ddx, proj_uv_ddy);
+ color *= proj.rgb * proj.a;
+ } else {
+ vec4 proj = textureLod(sampler2D(decal_atlas_srgb, light_projector_sampler), proj_uv + spot_lights.data[idx].projector_rect.xy, 0.0);
+ color *= proj.rgb * proj.a;
+ }
+ }
+ light_attenuation *= shadow;
+
+ light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, light_attenuation, f0, orms, spot_lights.data[idx].specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+ transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+ rim * spot_attenuation, rim_tint, rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light, specular_light);
+}
+
+void reflection_process(uint ref_index, vec3 vertex, vec3 normal, float roughness, vec3 ambient_light, vec3 specular_light, inout vec4 ambient_accum, inout vec4 reflection_accum) {
+ vec3 box_extents = reflections.data[ref_index].box_extents;
+ vec3 local_pos = (reflections.data[ref_index].local_matrix * vec4(vertex, 1.0)).xyz;
+
+ if (any(greaterThan(abs(local_pos), box_extents))) { //out of the reflection box
+ return;
+ }
+
+ vec3 ref_vec = normalize(reflect(vertex, normal));
+
+ vec3 inner_pos = abs(local_pos / box_extents);
+ float blend = max(inner_pos.x, max(inner_pos.y, inner_pos.z));
+ //make blend more rounded
+ blend = mix(length(inner_pos), blend, blend);
+ blend *= blend;
+ blend = max(0.0, 1.0 - blend);
+
+ if (reflections.data[ref_index].intensity > 0.0) { // compute reflection
+
+ vec3 local_ref_vec = (reflections.data[ref_index].local_matrix * vec4(ref_vec, 0.0)).xyz;
+
+ if (reflections.data[ref_index].box_project) { //box project
+
+ vec3 nrdir = normalize(local_ref_vec);
+ vec3 rbmax = (box_extents - local_pos) / nrdir;
+ vec3 rbmin = (-box_extents - local_pos) / nrdir;
+
+ vec3 rbminmax = mix(rbmin, rbmax, greaterThan(nrdir, vec3(0.0, 0.0, 0.0)));
+
+ float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
+ vec3 posonbox = local_pos + nrdir * fa;
+ local_ref_vec = posonbox - reflections.data[ref_index].box_offset;
+ }
+
+ vec4 reflection;
+
+ reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb;
+
+ if (reflections.data[ref_index].exterior) {
+ reflection.rgb = mix(specular_light, reflection.rgb, blend);
+ }
+
+ reflection.rgb *= reflections.data[ref_index].intensity; //intensity
+ reflection.a = blend;
+ reflection.rgb *= reflection.a;
+
+ reflection_accum += reflection;
+ }
+
+ switch (reflections.data[ref_index].ambient_mode) {
+ case REFLECTION_AMBIENT_DISABLED: {
+ //do nothing
+ } break;
+ case REFLECTION_AMBIENT_ENVIRONMENT: {
+ //do nothing
+ vec3 local_amb_vec = (reflections.data[ref_index].local_matrix * vec4(normal, 0.0)).xyz;
+
+ vec4 ambient_out;
+
+ ambient_out.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_amb_vec, reflections.data[ref_index].index), MAX_ROUGHNESS_LOD).rgb;
+ ambient_out.a = blend;
+ if (reflections.data[ref_index].exterior) {
+ ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend);
+ }
+
+ ambient_out.rgb *= ambient_out.a;
+ ambient_accum += ambient_out;
+ } break;
+ case REFLECTION_AMBIENT_COLOR: {
+ vec4 ambient_out;
+ ambient_out.a = blend;
+ ambient_out.rgb = reflections.data[ref_index].ambient;
+ if (reflections.data[ref_index].exterior) {
+ ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend);
+ }
+ ambient_out.rgb *= ambient_out.a;
+ ambient_accum += ambient_out;
+ } break;
+ }
+}
+
+float blur_shadow(float shadow) {
+ return shadow;
+#if 0
+ //disabling for now, will investigate later
+ float interp_shadow = shadow;
+ if (gl_HelperInvocation) {
+ interp_shadow = -4.0; // technically anything below -4 will do but just to make sure
+ }
+
+ uvec2 fc2 = uvec2(gl_FragCoord.xy);
+ interp_shadow -= dFdx(interp_shadow) * (float(fc2.x & 1) - 0.5);
+ interp_shadow -= dFdy(interp_shadow) * (float(fc2.y & 1) - 0.5);
+
+ if (interp_shadow >= 0.0) {
+ shadow = interp_shadow;
+ }
+ return shadow;
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl
new file mode 100644
index 0000000000..39890d25ff
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl
@@ -0,0 +1,1563 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Include our forward mobile UBOs definitions etc. */
+#include "scene_forward_mobile_inc.glsl"
+
+/* INPUT ATTRIBS */
+
+layout(location = 0) in vec3 vertex_attrib;
+
+//only for pure render depth when normal is not used
+
+#ifdef NORMAL_USED
+layout(location = 1) in vec3 normal_attrib;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 2) in vec4 tangent_attrib;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 3) in vec4 color_attrib;
+#endif
+
+#ifdef UV_USED
+layout(location = 4) in vec2 uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP) || defined(MODE_RENDER_MATERIAL)
+layout(location = 5) in vec2 uv2_attrib;
+#endif // MODE_RENDER_MATERIAL
+
+#if defined(CUSTOM0_USED)
+layout(location = 6) in vec4 custom0_attrib;
+#endif
+
+#if defined(CUSTOM1_USED)
+layout(location = 7) in vec4 custom1_attrib;
+#endif
+
+#if defined(CUSTOM2_USED)
+layout(location = 8) in vec4 custom2_attrib;
+#endif
+
+#if defined(CUSTOM3_USED)
+layout(location = 9) in vec4 custom3_attrib;
+#endif
+
+#if defined(BONES_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 10) in uvec4 bone_attrib;
+#endif
+
+#if defined(WEIGHTS_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 11) in vec4 weight_attrib;
+#endif
+
+/* Varyings */
+
+layout(location = 0) highp out vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) mediump out vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) mediump out vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) mediump out vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) mediump out vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) mediump out vec3 tangent_interp;
+layout(location = 6) mediump out vec3 binormal_interp;
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) out highp float dp_clip;
+
+#endif
+
+#ifdef USE_MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#define ViewIndex gl_ViewIndex
+#else
+// !BAS! This needs to become an input once we implement our fallback!
+#define ViewIndex 0
+#endif
+#else
+// Set to zero, not supported in non stereo
+#define ViewIndex 0
+#endif //USE_MULTIVIEW
+
+invariant gl_Position;
+
+#GLOBALS
+
+void main() {
+ vec4 instance_custom = vec4(0.0);
+#if defined(COLOR_USED)
+ color_interp = color_attrib;
+#endif
+
+ bool is_multimesh = bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH);
+
+ mat4 world_matrix = draw_call.transform;
+
+ mat3 world_normal_matrix;
+ if (bool(draw_call.flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ world_normal_matrix = transpose(inverse(mat3(world_matrix)));
+ } else {
+ world_normal_matrix = mat3(world_matrix);
+ }
+
+ if (is_multimesh) {
+ //multimesh, instances are for it
+
+ mat4 matrix;
+
+#ifdef USE_PARTICLE_TRAILS
+ uint trail_size = (draw_call.flags >> INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT) & INSTANCE_FLAGS_PARTICLE_TRAIL_MASK;
+ uint stride = 3 + 1 + 1; //particles always uses this format
+
+ uint offset = trail_size * stride * gl_InstanceIndex;
+
+#ifdef COLOR_USED
+ vec4 pcolor;
+#endif
+ {
+ uint boffset = offset + bone_attrib.x * stride;
+ matrix = mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.x;
+#ifdef COLOR_USED
+ pcolor = transforms.data[boffset + 3] * weight_attrib.x;
+#endif
+ }
+ if (weight_attrib.y > 0.001) {
+ uint boffset = offset + bone_attrib.y * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.y;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.y;
+#endif
+ }
+ if (weight_attrib.z > 0.001) {
+ uint boffset = offset + bone_attrib.z * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.z;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.z;
+#endif
+ }
+ if (weight_attrib.w > 0.001) {
+ uint boffset = offset + bone_attrib.w * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.w;
+#ifdef COLOR_USED
+ pcolor += transforms.data[boffset + 3] * weight_attrib.w;
+#endif
+ }
+
+ instance_custom = transforms.data[offset + 4];
+
+#ifdef COLOR_USED
+ color_interp *= pcolor;
+#endif
+
+#else
+ uint stride = 0;
+ {
+ //TODO implement a small lookup table for the stride
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ stride += 2;
+ } else {
+ stride += 3;
+ }
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+ stride += 1;
+ }
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ stride += 1;
+ }
+ }
+
+ uint offset = stride * gl_InstanceIndex;
+
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
+ } else {
+ matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], transforms.data[offset + 2], vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 3;
+ }
+
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+#ifdef COLOR_USED
+ color_interp *= transforms.data[offset];
+#endif
+ offset += 1;
+ }
+
+ if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
+ }
+
+#endif
+ //transpose
+ matrix = transpose(matrix);
+ world_matrix = world_matrix * matrix;
+ world_normal_matrix = world_normal_matrix * mat3(matrix);
+ }
+
+ vec3 vertex = vertex_attrib;
+#ifdef NORMAL_USED
+ vec3 normal = normal_attrib * 2.0 - 1.0;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ vec3 tangent = tangent_attrib.xyz * 2.0 - 1.0;
+ float binormalf = tangent_attrib.a * 2.0 - 1.0;
+ vec3 binormal = normalize(cross(normal, tangent) * binormalf);
+#endif
+
+#ifdef UV_USED
+ uv_interp = uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+ uv2_interp = uv2_attrib;
+#endif
+
+#ifdef OVERRIDE_POSITION
+ vec4 position;
+#endif
+
+#ifdef USE_MULTIVIEW
+ mat4 projection_matrix = scene_data.projection_matrix_view[ViewIndex];
+ mat4 inv_projection_matrix = scene_data.inv_projection_matrix_view[ViewIndex];
+#else
+ mat4 projection_matrix = scene_data.projection_matrix;
+ mat4 inv_projection_matrix = scene_data.inv_projection_matrix;
+#endif //USE_MULTIVIEW
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (world_matrix * vec4(vertex, 1.0)).xyz;
+
+ normal = world_normal_matrix * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ tangent = world_normal_matrix * tangent;
+ binormal = world_normal_matrix * binormal;
+
+#endif
+#endif
+
+ float roughness = 1.0;
+
+ mat4 modelview = scene_data.inv_camera_matrix * world_matrix;
+ mat3 modelview_normal = mat3(scene_data.inv_camera_matrix) * world_normal_matrix;
+
+ {
+#CODE : VERTEX
+ }
+
+ /* output */
+
+// using local coordinates (default)
+#if !defined(SKIP_TRANSFORM_USED) && !defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (modelview * vec4(vertex, 1.0)).xyz;
+#ifdef NORMAL_USED
+ normal = modelview_normal * normal;
+#endif
+
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ binormal = modelview_normal * binormal;
+ tangent = modelview_normal * tangent;
+#endif
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+ vertex = (scene_data.inv_camera_matrix * vec4(vertex, 1.0)).xyz;
+ normal = mat3(scene_data.inverse_normal_matrix) * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+ binormal = mat3(scene_data.camera_inverse_binormal_matrix) * binormal;
+ tangent = mat3(scene_data.camera_inverse_tangent_matrix) * tangent;
+#endif
+#endif
+
+ vertex_interp = vertex;
+#ifdef NORMAL_USED
+ normal_interp = normal;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ tangent_interp = tangent;
+ binormal_interp = binormal;
+#endif
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_DUAL_PARABOLOID
+
+ vertex_interp.z *= scene_data.dual_paraboloid_side;
+
+ dp_clip = vertex_interp.z; //this attempts to avoid noise caused by objects sent to the other parabolloid side due to bias
+
+ //for dual paraboloid shadow mapping, this is the fastest but least correct way, as it curves straight edges
+
+ vec3 vtx = vertex_interp;
+ float distance = length(vtx);
+ vtx = normalize(vtx);
+ vtx.xy /= 1.0 - vtx.z;
+ vtx.z = (distance / scene_data.z_far);
+ vtx.z = vtx.z * 2.0 - 1.0;
+ vertex_interp = vtx;
+
+#endif
+
+#endif //MODE_RENDER_DEPTH
+
+#ifdef OVERRIDE_POSITION
+ gl_Position = position;
+#else
+ gl_Position = projection_matrix * vec4(vertex_interp, 1.0);
+#endif // OVERRIDE_POSITION
+
+#ifdef MODE_RENDER_DEPTH
+ if (scene_data.pancake_shadows) {
+ if (gl_Position.z <= 0.00001) {
+ gl_Position.z = 0.00001;
+ }
+ }
+#endif // MODE_RENDER_DEPTH
+#ifdef MODE_RENDER_MATERIAL
+ if (scene_data.material_uv2_mode) {
+ vec2 uv_offset = draw_call.lightmap_uv_scale.xy; // we are abusing lightmap_uv_scale here, we shouldn't have a lightmap during a depth pass...
+ gl_Position.xy = (uv2_attrib.xy + uv_offset) * 2.0 - 1.0;
+ gl_Position.z = 0.00001;
+ gl_Position.w = 1.0;
+ }
+#endif // MODE_RENDER_MATERIAL
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Specialization Constants */
+
+#if !defined(MODE_RENDER_DEPTH)
+
+#if !defined(MODE_UNSHADED)
+
+layout(constant_id = 0) const bool sc_use_light_projector = false;
+layout(constant_id = 1) const bool sc_use_light_soft_shadows = false;
+layout(constant_id = 2) const bool sc_use_directional_soft_shadows = false;
+
+layout(constant_id = 3) const uint sc_soft_shadow_samples = 4;
+layout(constant_id = 4) const uint sc_penumbra_shadow_samples = 4;
+
+layout(constant_id = 5) const uint sc_directional_soft_shadow_samples = 4;
+layout(constant_id = 6) const uint sc_directional_penumbra_shadow_samples = 4;
+
+layout(constant_id = 8) const bool sc_projector_use_mipmaps = true;
+
+layout(constant_id = 9) const bool sc_disable_omni_lights = false;
+layout(constant_id = 10) const bool sc_disable_spot_lights = false;
+layout(constant_id = 11) const bool sc_disable_reflection_probes = false;
+layout(constant_id = 12) const bool sc_disable_directional_lights = false;
+
+#endif //!MODE_UNSHADED
+
+layout(constant_id = 7) const bool sc_decal_use_mipmaps = true;
+layout(constant_id = 13) const bool sc_disable_decals = false;
+layout(constant_id = 14) const bool sc_disable_fog = false;
+
+#endif //!MODE_RENDER_DEPTH
+
+/* Include our forward mobile UBOs definitions etc. */
+#include "scene_forward_mobile_inc.glsl"
+
+/* Varyings */
+
+layout(location = 0) highp in vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) mediump in vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) mediump in vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) mediump in vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) mediump in vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) mediump in vec3 tangent_interp;
+layout(location = 6) mediump in vec3 binormal_interp;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) highp in float dp_clip;
+
+#endif
+
+#ifdef USE_MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#define ViewIndex gl_ViewIndex
+#else
+// !BAS! This needs to become an input once we implement our fallback!
+#define ViewIndex 0
+#endif
+#else
+// Set to zero, not supported in non stereo
+#define ViewIndex 0
+#endif //USE_MULTIVIEW
+
+//defines to keep compatibility with vertex
+
+#define world_matrix draw_call.transform
+#ifdef USE_MULTIVIEW
+#define projection_matrix scene_data.projection_matrix_view[ViewIndex]
+#else
+#define projection_matrix scene_data.projection_matrix
+#endif
+
+#if defined(ENABLE_SSS) && defined(ENABLE_TRANSMITTANCE)
+//both required for transmittance to be enabled
+#define LIGHT_TRANSMITTANCE_USED
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#GLOBALS
+
+/* clang-format on */
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_MATERIAL
+
+layout(location = 0) out vec4 albedo_output_buffer;
+layout(location = 1) out vec4 normal_output_buffer;
+layout(location = 2) out vec4 orm_output_buffer;
+layout(location = 3) out vec4 emission_output_buffer;
+layout(location = 4) out float depth_output_buffer;
+
+#endif // MODE_RENDER_MATERIAL
+
+#else // RENDER DEPTH
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+layout(location = 0) out vec4 diffuse_buffer; //diffuse (rgb) and roughness
+layout(location = 1) out vec4 specular_buffer; //specular and SSS (subsurface scatter)
+#else
+
+layout(location = 0) out mediump vec4 frag_color;
+#endif // MODE_MULTIPLE_RENDER_TARGETS
+
+#endif // RENDER DEPTH
+
+#include "scene_forward_aa_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+/* Make a default specular mode SPECULAR_SCHLICK_GGX. */
+#if !defined(SPECULAR_DISABLED) && !defined(SPECULAR_SCHLICK_GGX) && !defined(SPECULAR_BLINN) && !defined(SPECULAR_PHONG) && !defined(SPECULAR_TOON)
+#define SPECULAR_SCHLICK_GGX
+#endif
+
+#include "scene_forward_lights_inc.glsl"
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifndef MODE_RENDER_DEPTH
+
+/*
+ Only supporting normal fog here.
+*/
+
+vec4 fog_process(vec3 vertex) {
+ vec3 fog_color = scene_data.fog_light_color;
+
+ if (scene_data.fog_aerial_perspective > 0.0) {
+ vec3 sky_fog_color = vec3(0.0);
+ vec3 cube_view = scene_data.radiance_inverse_xform * vertex;
+ // mip_level always reads from the second mipmap and higher so the fog is always slightly blurred
+ float mip_level = mix(1.0 / MAX_ROUGHNESS_LOD, 1.0, 1.0 - (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near));
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+ float lod, blend;
+ blend = modf(mip_level * MAX_ROUGHNESS_LOD, lod);
+ sky_fog_color = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod)).rgb;
+ sky_fog_color = mix(sky_fog_color, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod + 1)).rgb, blend);
+#else
+ sky_fog_color = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), cube_view, mip_level * MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+ fog_color = mix(fog_color, sky_fog_color, scene_data.fog_aerial_perspective);
+ }
+
+ if (scene_data.fog_sun_scatter > 0.001) {
+ vec4 sun_scatter = vec4(0.0);
+ float sun_total = 0.0;
+ vec3 view = normalize(vertex);
+
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ vec3 light_color = directional_lights.data[i].color * directional_lights.data[i].energy;
+ float light_amount = pow(max(dot(view, directional_lights.data[i].direction), 0.0), 8.0);
+ fog_color += light_color * light_amount * scene_data.fog_sun_scatter;
+ }
+ }
+
+ float fog_amount = 1.0 - exp(min(0.0, vertex.z * scene_data.fog_density));
+
+ if (abs(scene_data.fog_height_density) > 0.001) {
+ float y = (scene_data.camera_matrix * vec4(vertex, 1.0)).y;
+
+ float y_dist = scene_data.fog_height - y;
+
+ float vfog_amount = clamp(exp(y_dist * scene_data.fog_height_density), 0.0, 1.0);
+
+ fog_amount = max(vfog_amount, fog_amount);
+ }
+
+ return vec4(fog_color, fog_amount);
+}
+
+#endif //!MODE_RENDER DEPTH
+
+void main() {
+#ifdef MODE_DUAL_PARABOLOID
+
+ if (dp_clip > 0.0)
+ discard;
+#endif
+
+ //lay out everything, whathever is unused is optimized away anyway
+ vec3 vertex = vertex_interp;
+ vec3 view = -normalize(vertex_interp);
+ vec3 albedo = vec3(1.0);
+ vec3 backlight = vec3(0.0);
+ vec4 transmittance_color = vec4(0.0);
+ float transmittance_depth = 0.0;
+ float transmittance_boost = 0.0;
+ float metallic = 0.0;
+ float specular = 0.5;
+ vec3 emission = vec3(0.0);
+ float roughness = 1.0;
+ float rim = 0.0;
+ float rim_tint = 0.0;
+ float clearcoat = 0.0;
+ float clearcoat_gloss = 0.0;
+ float anisotropy = 0.0;
+ vec2 anisotropy_flow = vec2(1.0, 0.0);
+ vec4 fog = vec4(0.0);
+#if defined(CUSTOM_RADIANCE_USED)
+ vec4 custom_radiance = vec4(0.0);
+#endif
+#if defined(CUSTOM_IRRADIANCE_USED)
+ vec4 custom_irradiance = vec4(0.0);
+#endif
+
+ float ao = 1.0;
+ float ao_light_affect = 0.0;
+
+ float alpha = 1.0;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+ vec3 binormal = normalize(binormal_interp);
+ vec3 tangent = normalize(tangent_interp);
+#else
+ vec3 binormal = vec3(0.0);
+ vec3 tangent = vec3(0.0);
+#endif
+
+#ifdef NORMAL_USED
+ vec3 normal = normalize(normal_interp);
+
+#if defined(DO_SIDE_CHECK)
+ if (!gl_FrontFacing) {
+ normal = -normal;
+ }
+#endif
+
+#endif //NORMAL_USED
+
+#ifdef UV_USED
+ vec2 uv = uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+ vec2 uv2 = uv2_interp;
+#endif
+
+#if defined(COLOR_USED)
+ vec4 color = color_interp;
+#endif
+
+#if defined(NORMAL_MAP_USED)
+
+ vec3 normal_map = vec3(0.5);
+#endif
+
+ float normal_map_depth = 1.0;
+
+ vec2 screen_uv = gl_FragCoord.xy * scene_data.screen_pixel_size + scene_data.screen_pixel_size * 0.5; //account for center
+
+ float sss_strength = 0.0;
+
+#ifdef ALPHA_SCISSOR_USED
+ float alpha_scissor_threshold = 1.0;
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef ALPHA_HASH_USED
+ float alpha_hash_scale = 1.0;
+#endif // ALPHA_HASH_USED
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+ float alpha_antialiasing_edge = 0.0;
+ vec2 alpha_texture_coordinate = vec2(0.0, 0.0);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+ {
+#CODE : FRAGMENT
+ }
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+#ifdef SSS_MODE_SKIN
+ transmittance_color.a = sss_strength;
+#else
+ transmittance_color.a *= sss_strength;
+#endif
+#endif
+
+#ifndef USE_SHADOW_TO_OPACITY
+
+#ifdef ALPHA_SCISSOR_USED
+ if (alpha < alpha_scissor_threshold) {
+ discard;
+ }
+#endif // ALPHA_SCISSOR_USED
+
+// alpha hash can be used in unison with alpha antialiasing
+#ifdef ALPHA_HASH_USED
+ if (alpha < compute_alpha_hash_threshold(vertex, alpha_hash_scale)) {
+ discard;
+ }
+#endif // ALPHA_HASH_USED
+
+// If we are not edge antialiasing, we need to remove the output alpha channel from scissor and hash
+#if (defined(ALPHA_SCISSOR_USED) || defined(ALPHA_HASH_USED)) && !defined(ALPHA_ANTIALIASING_EDGE_USED)
+ alpha = 1.0;
+#endif
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+// If alpha scissor is used, we must further the edge threshold, otherwise we won't get any edge feather
+#ifdef ALPHA_SCISSOR_USED
+ alpha_antialiasing_edge = clamp(alpha_scissor_threshold + alpha_antialiasing_edge, 0.0, 1.0);
+#endif
+ alpha = compute_alpha_antialiasing_edge(alpha, alpha_texture_coordinate, alpha_antialiasing_edge);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+#ifdef USE_OPAQUE_PREPASS
+ if (alpha < opaque_prepass_threshold) {
+ discard;
+ }
+#endif // USE_OPAQUE_PREPASS
+
+#endif // !USE_SHADOW_TO_OPACITY
+
+#ifdef NORMAL_MAP_USED
+
+ normal_map.xy = normal_map.xy * 2.0 - 1.0;
+ normal_map.z = sqrt(max(0.0, 1.0 - dot(normal_map.xy, normal_map.xy))); //always ignore Z, as it can be RG packed, Z may be pos/neg, etc.
+
+ normal = normalize(mix(normal, tangent * normal_map.x + binormal * normal_map.y + normal * normal_map.z, normal_map_depth));
+
+#endif
+
+#ifdef LIGHT_ANISOTROPY_USED
+
+ if (anisotropy > 0.01) {
+ //rotation matrix
+ mat3 rot = mat3(tangent, binormal, normal);
+ //make local to space
+ tangent = normalize(rot * vec3(anisotropy_flow.x, anisotropy_flow.y, 0.0));
+ binormal = normalize(rot * vec3(-anisotropy_flow.y, anisotropy_flow.x, 0.0));
+ }
+
+#endif
+
+#ifdef ENABLE_CLIP_ALPHA
+ if (albedo.a < 0.99) {
+ //used for doublepass and shadowmapping
+ discard;
+ }
+#endif
+
+ /////////////////////// FOG //////////////////////
+#ifndef MODE_RENDER_DEPTH
+
+#ifndef CUSTOM_FOG_USED
+ // fog must be processed as early as possible and then packed.
+ // to maximize VGPR usage
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+
+ if (!sc_disable_fog && scene_data.fog_enabled) {
+ fog = fog_process(vertex);
+ }
+
+#endif //!CUSTOM_FOG_USED
+
+ uint fog_rg = packHalf2x16(fog.rg);
+ uint fog_ba = packHalf2x16(fog.ba);
+
+#endif //!MODE_RENDER_DEPTH
+
+ /////////////////////// DECALS ////////////////////////////////
+
+#ifndef MODE_RENDER_DEPTH
+
+ vec3 vertex_ddx = dFdx(vertex);
+ vec3 vertex_ddy = dFdy(vertex);
+
+ if (!sc_disable_decals) { //Decals
+ // must implement
+
+ uint decal_indices = draw_call.decals.x;
+ for (uint i = 0; i < 8; i++) {
+ uint decal_index = decal_indices & 0xFF;
+ if (i == 4) {
+ decal_indices = draw_call.decals.y;
+ } else {
+ decal_indices = decal_indices >> 8;
+ }
+
+ if (decal_index == 0xFF) {
+ break;
+ }
+
+ vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
+ if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
+ continue; //out of decal
+ }
+
+ float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
+
+ if (decals.data[decal_index].normal_fade > 0.0) {
+ fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+ }
+
+ //we need ddx/ddy for mipmaps, so simulate them
+ vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
+ vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
+
+ if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
+ //has albedo
+ vec4 decal_albedo;
+ if (sc_decal_use_mipmaps) {
+ decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+ } else {
+ decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
+ }
+ decal_albedo *= decals.data[decal_index].modulate;
+ decal_albedo.a *= fade;
+ albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
+
+ if (decals.data[decal_index].normal_rect != vec4(0.0)) {
+ vec3 decal_normal;
+ if (sc_decal_use_mipmaps) {
+ decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
+ } else {
+ decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
+ }
+ decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
+ decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
+ //convert to view space, use xzy because y is up
+ decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
+
+ normal = normalize(mix(normal, decal_normal, decal_albedo.a));
+ }
+
+ if (decals.data[decal_index].orm_rect != vec4(0.0)) {
+ vec3 decal_orm;
+ if (sc_decal_use_mipmaps) {
+ decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
+ } else {
+ decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
+ }
+ ao = mix(ao, decal_orm.r, decal_albedo.a);
+ roughness = mix(roughness, decal_orm.g, decal_albedo.a);
+ metallic = mix(metallic, decal_orm.b, decal_albedo.a);
+ }
+ }
+
+ if (decals.data[decal_index].emission_rect != vec4(0.0)) {
+ //emission is additive, so its independent from albedo
+ if (sc_decal_use_mipmaps) {
+ emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+ } else {
+ emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].emission_energy * fade;
+ }
+ }
+ }
+ } //Decals
+#endif //!MODE_RENDER_DEPTH
+
+ /////////////////////// LIGHTING //////////////////////////////
+
+#ifdef NORMAL_USED
+ if (scene_data.roughness_limiter_enabled) {
+ //http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf
+ float roughness2 = roughness * roughness;
+ vec3 dndu = dFdx(normal), dndv = dFdy(normal);
+ float variance = scene_data.roughness_limiter_amount * (dot(dndu, dndu) + dot(dndv, dndv));
+ float kernelRoughness2 = min(2.0 * variance, scene_data.roughness_limiter_limit); //limit effect
+ float filteredRoughness2 = min(1.0, roughness2 + kernelRoughness2);
+ roughness = sqrt(filteredRoughness2);
+ }
+#endif // NORMAL_USED
+ //apply energy conservation
+
+ vec3 specular_light = vec3(0.0, 0.0, 0.0);
+ vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
+ vec3 ambient_light = vec3(0.0, 0.0, 0.0);
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ if (scene_data.use_reflection_cubemap) {
+ vec3 ref_vec = reflect(-view, normal);
+ ref_vec = scene_data.radiance_inverse_xform * ref_vec;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+ float lod, blend;
+ blend = modf(roughness * MAX_ROUGHNESS_LOD, lod);
+ specular_light = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod)).rgb;
+ specular_light = mix(specular_light, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod + 1)).rgb, blend);
+
+#else // USE_RADIANCE_CUBEMAP_ARRAY
+ specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb;
+
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+ float horizon = min(1.0 + dot(ref_vec, normal), 1.0);
+ specular_light *= horizon * horizon;
+ specular_light *= scene_data.ambient_light_color_energy.a;
+ }
+
+#if defined(CUSTOM_RADIANCE_USED)
+ specular_light = mix(specular_light, custom_radiance.rgb, custom_radiance.a);
+#endif // CUSTOM_RADIANCE_USED
+
+#ifndef USE_LIGHTMAP
+ //lightmap overrides everything
+ if (scene_data.use_ambient_light) {
+ ambient_light = scene_data.ambient_light_color_energy.rgb;
+
+ if (scene_data.use_ambient_cubemap) {
+ vec3 ambient_dir = scene_data.radiance_inverse_xform * normal;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+ vec3 cubemap_ambient = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ambient_dir, MAX_ROUGHNESS_LOD)).rgb;
+#else
+ vec3 cubemap_ambient = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ambient_dir, MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+
+ ambient_light = mix(ambient_light, cubemap_ambient * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix);
+ }
+ }
+#endif // !USE_LIGHTMAP
+
+#if defined(CUSTOM_IRRADIANCE_USED)
+ ambient_light = mix(specular_light, custom_irradiance.rgb, custom_irradiance.a);
+#endif // CUSTOM_IRRADIANCE_USED
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ //radiance
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef USE_LIGHTMAP
+
+ //lightmap
+ if (bool(draw_call.flags & INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE)) { //has lightmap capture
+ uint index = draw_call.gi_offset;
+
+ vec3 wnormal = mat3(scene_data.camera_matrix) * normal;
+ const float c1 = 0.429043;
+ const float c2 = 0.511664;
+ const float c3 = 0.743125;
+ const float c4 = 0.886227;
+ const float c5 = 0.247708;
+ ambient_light += (c1 * lightmap_captures.data[index].sh[8].rgb * (wnormal.x * wnormal.x - wnormal.y * wnormal.y) +
+ c3 * lightmap_captures.data[index].sh[6].rgb * wnormal.z * wnormal.z +
+ c4 * lightmap_captures.data[index].sh[0].rgb -
+ c5 * lightmap_captures.data[index].sh[6].rgb +
+ 2.0 * c1 * lightmap_captures.data[index].sh[4].rgb * wnormal.x * wnormal.y +
+ 2.0 * c1 * lightmap_captures.data[index].sh[7].rgb * wnormal.x * wnormal.z +
+ 2.0 * c1 * lightmap_captures.data[index].sh[5].rgb * wnormal.y * wnormal.z +
+ 2.0 * c2 * lightmap_captures.data[index].sh[3].rgb * wnormal.x +
+ 2.0 * c2 * lightmap_captures.data[index].sh[1].rgb * wnormal.y +
+ 2.0 * c2 * lightmap_captures.data[index].sh[2].rgb * wnormal.z);
+
+ } else if (bool(draw_call.flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { // has actual lightmap
+ bool uses_sh = bool(draw_call.flags & INSTANCE_FLAGS_USE_SH_LIGHTMAP);
+ uint ofs = draw_call.gi_offset & 0xFFFF;
+ vec3 uvw;
+ uvw.xy = uv2 * draw_call.lightmap_uv_scale.zw + draw_call.lightmap_uv_scale.xy;
+ uvw.z = float((draw_call.gi_offset >> 16) & 0xFFFF);
+
+ if (uses_sh) {
+ uvw.z *= 4.0; //SH textures use 4 times more data
+ vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+
+ uint idx = draw_call.gi_offset >> 20;
+ vec3 n = normalize(lightmaps.data[idx].normal_xform * normal);
+
+ ambient_light += lm_light_l0 * 0.282095f;
+ ambient_light += lm_light_l1n1 * 0.32573 * n.y;
+ ambient_light += lm_light_l1_0 * 0.32573 * n.z;
+ ambient_light += lm_light_l1p1 * 0.32573 * n.x;
+ if (metallic > 0.01) { // since the more direct bounced light is lost, we can kind of fake it with this trick
+ vec3 r = reflect(normalize(-vertex), normal);
+ specular_light += lm_light_l1n1 * 0.32573 * r.y;
+ specular_light += lm_light_l1_0 * 0.32573 * r.z;
+ specular_light += lm_light_l1p1 * 0.32573 * r.x;
+ }
+
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb;
+ }
+ }
+
+ // No GI nor non low end mode...
+
+#endif // USE_LIGHTMAP
+
+ // skipping ssao, do we remove ssao totally?
+
+ if (!sc_disable_reflection_probes) { //Reflection probes
+ vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
+
+ uint reflection_indices = draw_call.reflection_probes.x;
+ for (uint i = 0; i < 8; i++) {
+ uint reflection_index = reflection_indices & 0xFF;
+ if (i == 4) {
+ reflection_indices = draw_call.reflection_probes.y;
+ } else {
+ reflection_indices = reflection_indices >> 8;
+ }
+
+ if (reflection_index == 0xFF) {
+ break;
+ }
+
+ reflection_process(reflection_index, vertex, normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum);
+ }
+
+ if (reflection_accum.a > 0.0) {
+ specular_light = reflection_accum.rgb / reflection_accum.a;
+ }
+ } //Reflection probes
+
+ // finalize ambient light here
+ ambient_light *= albedo.rgb;
+ ambient_light *= ao;
+
+ // convert ao to direct light ao
+ ao = mix(1.0, ao, ao_light_affect);
+
+ //this saves some VGPRs
+ vec3 f0 = F0(metallic, specular, albedo);
+
+ {
+#if defined(DIFFUSE_TOON)
+ //simplify for toon, as
+ specular_light *= specular * metallic * albedo * 2.0;
+#else
+
+ // scales the specular reflections, needs to be computed before lighting happens,
+ // but after environment, GI, and reflection probes are added
+ // Environment brdf approximation (Lazarov 2013)
+ // see https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
+ const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
+ const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
+ vec4 r = roughness * c0 + c1;
+ float ndotv = clamp(dot(normal, view), 0.0, 1.0);
+ float a004 = min(r.x * r.x, exp2(-9.28 * ndotv)) * r.x + r.y;
+ vec2 env = vec2(-1.04, 1.04) * a004 + r.zw;
+
+ specular_light *= env.x * f0 + env.y;
+#endif
+ }
+
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#if !defined(MODE_RENDER_DEPTH)
+ //this saves some VGPRs
+ uint orms = packUnorm4x8(vec4(ao, roughness, metallic, specular));
+#endif
+
+// LIGHTING
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+ if (!sc_disable_directional_lights) { //directional light
+
+ // Do shadow and lighting in two passes to reduce register pressure
+ uint shadow0 = 0;
+ uint shadow1 = 0;
+
+ for (uint i = 0; i < 8; i++) {
+ if (i >= scene_data.directional_light_count) {
+ break;
+ }
+
+ if (!bool(directional_lights.data[i].mask & draw_call.layer_mask)) {
+ continue; //not masked
+ }
+
+ float shadow = 1.0;
+
+ // Directional light shadow code is basically the same as forward clustered at this point in time minus `LIGHT_TRANSMITTANCE_USED` support.
+ // Not sure if there is a reason to change this seeing directional lights are part of our global data
+ // Should think about whether we may want to move this code into an include file or function??
+
+#ifdef USE_SOFT_SHADOWS
+ //version with soft shadows, more expensive
+ if (directional_lights.data[i].shadow_enabled) {
+ float depth_z = -vertex.z;
+
+ vec4 pssm_coord;
+ vec3 shadow_color = vec3(0.0);
+ vec3 light_dir = directional_lights.data[i].direction;
+
+#define BIAS_FUNC(m_var, m_idx) \
+ m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx]; \
+ vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp)))) * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+ normal_bias -= light_dir * dot(light_dir, normal_bias); \
+ m_var.xyz += normal_bias;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 0)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.x;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
+ shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ shadow_color = directional_lights.data[i].shadow_color1.rgb;
+
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 1)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.y;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+ shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ shadow_color = directional_lights.data[i].shadow_color2.rgb;
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 2)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.z;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+ shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ shadow_color = directional_lights.data[i].shadow_color3.rgb;
+
+ } else {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 3)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.w;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+ shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ shadow_color = directional_lights.data[i].shadow_color4.rgb;
+ }
+
+ if (directional_lights.data[i].blend_splits) {
+ vec3 shadow_color_blend = vec3(0.0);
+ float pssm_blend;
+ float shadow2;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 1)
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.y;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+ shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+ shadow_color_blend = directional_lights.data[i].shadow_color2.rgb;
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 2)
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_coord /= pssm_coord.w;
+
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.z;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+ shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+
+ shadow_color_blend = directional_lights.data[i].shadow_color3.rgb;
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 3)
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_coord /= pssm_coord.w;
+ if (directional_lights.data[i].softshadow_angle > 0) {
+ float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+ float range_begin = directional_lights.data[i].shadow_range_begin.w;
+ float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+ vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+ shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+ } else {
+ shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ }
+
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+ shadow_color_blend = directional_lights.data[i].shadow_color4.rgb;
+ } else {
+ pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+ }
+
+ pssm_blend = sqrt(pssm_blend);
+
+ shadow = mix(shadow, shadow2, pssm_blend);
+ shadow_color = mix(shadow_color, shadow_color_blend, pssm_blend);
+ }
+
+ shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+
+#undef BIAS_FUNC
+ }
+#else
+ // Soft shadow disabled version
+
+ if (directional_lights.data[i].shadow_enabled) {
+ float depth_z = -vertex.z;
+
+ vec4 pssm_coord;
+ vec3 light_dir = directional_lights.data[i].direction;
+ vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
+
+#define BIAS_FUNC(m_var, m_idx) \
+ m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx]; \
+ vec3 normal_bias = base_normal_bias * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+ normal_bias -= light_dir * dot(light_dir, normal_bias); \
+ m_var.xyz += normal_bias;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 0)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 1)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 2)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+
+ } else {
+ vec4 v = vec4(vertex, 1.0);
+
+ BIAS_FUNC(v, 3)
+
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ }
+
+ pssm_coord /= pssm_coord.w;
+
+ shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+
+ if (directional_lights.data[i].blend_splits) {
+ float pssm_blend;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 1)
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 2)
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ vec4 v = vec4(vertex, 1.0);
+ BIAS_FUNC(v, 3)
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+ } else {
+ pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+ }
+
+ pssm_coord /= pssm_coord.w;
+
+ float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+ shadow = mix(shadow, shadow2, pssm_blend);
+ }
+
+ shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+
+#undef BIAS_FUNC
+ }
+#endif
+
+ if (i < 4) {
+ shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
+ } else {
+ shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+ }
+ }
+
+ for (uint i = 0; i < 8; i++) {
+ if (i >= scene_data.directional_light_count) {
+ break;
+ }
+
+ if (!bool(directional_lights.data[i].mask & draw_call.layer_mask)) {
+ continue; //not masked
+ }
+
+ // We're not doing light transmittence
+
+ float shadow = 1.0;
+
+ if (i < 4) {
+ shadow = float(shadow0 >> (i * 8) & 0xFF) / 255.0;
+ } else {
+ shadow = float(shadow1 >> ((i - 4) * 8) & 0xFF) / 255.0;
+ }
+
+ blur_shadow(shadow);
+
+ light_compute(normal, directional_lights.data[i].direction, normalize(view), 0.0, directional_lights.data[i].color * directional_lights.data[i].energy, shadow, f0, orms, 1.0,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+/* not supported here
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+ transmittance_z,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+ rim, rim_tint, albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SOFT_SHADOW
+ directional_lights.data[i].size,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light,
+ specular_light);
+ }
+ } //directional light
+
+ if (!sc_disable_omni_lights) { //omni lights
+ uint light_indices = draw_call.omni_lights.x;
+ for (uint i = 0; i < 8; i++) {
+ uint light_index = light_indices & 0xFF;
+ if (i == 4) {
+ light_indices = draw_call.omni_lights.y;
+ } else {
+ light_indices = light_indices >> 8;
+ }
+
+ if (light_index == 0xFF) {
+ break;
+ }
+
+ float shadow = light_process_omni_shadow(light_index, vertex, normal);
+
+ shadow = blur_shadow(shadow);
+
+ light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+/*
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+ rim,
+ rim_tint,
+ albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light, specular_light);
+ }
+ } //omni lights
+
+ if (!sc_disable_spot_lights) { //spot lights
+
+ uint light_indices = draw_call.spot_lights.x;
+ for (uint i = 0; i < 8; i++) {
+ uint light_index = light_indices & 0xFF;
+ if (i == 4) {
+ light_indices = draw_call.spot_lights.y;
+ } else {
+ light_indices = light_indices >> 8;
+ }
+
+ if (light_index == 0xFF) {
+ break;
+ }
+
+ float shadow = light_process_spot_shadow(light_index, vertex, normal);
+
+ shadow = blur_shadow(shadow);
+
+ light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+ backlight,
+#endif
+/*
+#ifdef LIGHT_TRANSMITTANCE_USED
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+ rim,
+ rim_tint,
+ albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+ clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+ tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha,
+#endif
+ diffuse_light, specular_light);
+ }
+ } //spot lights
+
+#ifdef USE_SHADOW_TO_OPACITY
+ alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
+
+#if defined(ALPHA_SCISSOR_USED)
+ if (alpha < alpha_scissor) {
+ discard;
+ }
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef USE_OPAQUE_PREPASS
+
+ if (alpha < opaque_prepass_threshold) {
+ discard;
+ }
+
+#endif // USE_OPAQUE_PREPASS
+
+#endif // USE_SHADOW_TO_OPACITY
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_MATERIAL
+
+ albedo_output_buffer.rgb = albedo;
+ albedo_output_buffer.a = alpha;
+
+ normal_output_buffer.rgb = normal * 0.5 + 0.5;
+ normal_output_buffer.a = 0.0;
+ depth_output_buffer.r = -vertex.z;
+
+ orm_output_buffer.r = ao;
+ orm_output_buffer.g = roughness;
+ orm_output_buffer.b = metallic;
+ orm_output_buffer.a = sss_strength;
+
+ emission_output_buffer.rgb = emission;
+ emission_output_buffer.a = 0.0;
+#endif // MODE_RENDER_MATERIAL
+
+#else // MODE_RENDER_DEPTH
+
+ // multiply by albedo
+ diffuse_light *= albedo; // ambient must be multiplied by albedo at the end
+
+ // apply direct light AO
+ ao = unpackUnorm4x8(orms).x;
+ specular_light *= ao;
+ diffuse_light *= ao;
+
+ // apply metallic
+ metallic = unpackUnorm4x8(orms).z;
+ diffuse_light *= 1.0 - metallic;
+ ambient_light *= 1.0 - metallic;
+
+ //restore fog
+ fog = vec4(unpackHalf2x16(fog_rg), unpackHalf2x16(fog_ba));
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+ diffuse_buffer = vec4(albedo.rgb, 0.0);
+ specular_buffer = vec4(0.0);
+
+#else // MODE_UNSHADED
+
+#ifdef SSS_MODE_SKIN
+ sss_strength = -sss_strength;
+#endif // SSS_MODE_SKIN
+ diffuse_buffer = vec4(emission + diffuse_light + ambient_light, sss_strength);
+ specular_buffer = vec4(specular_light, metallic);
+#endif // MODE_UNSHADED
+
+ diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a);
+ specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a);
+
+#else //MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+ frag_color = vec4(albedo, alpha);
+#else // MODE_UNSHADED
+ frag_color = vec4(emission + ambient_light + diffuse_light + specular_light, alpha);
+ //frag_color = vec4(1.0);
+#endif // MODE_UNSHADED
+
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+ frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a);
+
+#endif //MODE_MULTIPLE_RENDER_TARGETS
+
+#endif //MODE_RENDER_DEPTH
+}
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl
new file mode 100644
index 0000000000..dd8879acb4
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl
@@ -0,0 +1,225 @@
+#define M_PI 3.14159265359
+#define MAX_VIEWS 2
+
+#if defined(USE_MULTIVIEW) && defined(has_VK_KHR_multiview)
+#extension GL_EXT_multiview : enable
+#endif
+
+#include "decal_data_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) || defined(MODE_RENDER_MATERIAL) || defined(TANGENT_USED) || defined(NORMAL_MAP_USED)
+#ifndef NORMAL_USED
+#define NORMAL_USED
+#endif
+#endif
+
+/* don't exceed 128 bytes!! */
+/* put instance data into our push content, not a array */
+layout(push_constant, binding = 0, std430) uniform DrawCall {
+ highp mat4 transform; // 64 - 64
+ uint flags; // 04 - 68
+ uint instance_uniforms_ofs; //base offset in global buffer for instance variables // 04 - 72
+ uint gi_offset; //GI information when using lightmapping (VCT or lightmap index) // 04 - 76
+ uint layer_mask; // 04 - 80
+ highp vec4 lightmap_uv_scale; // 16 - 96 doubles as uv_offset when needed
+
+ uvec2 reflection_probes; // 08 - 104
+ uvec2 omni_lights; // 08 - 112
+ uvec2 spot_lights; // 08 - 120
+ uvec2 decals; // 08 - 128
+}
+draw_call;
+
+/* Set 0: Base Pass (never changes) */
+
+#include "light_data_inc.glsl"
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+layout(set = 0, binding = 1) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 2) uniform sampler shadow_sampler;
+
+layout(set = 0, binding = 3) uniform sampler decal_sampler;
+layout(set = 0, binding = 4) uniform sampler light_projector_sampler;
+
+#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 5)
+#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6)
+#define INSTANCE_FLAGS_USE_SDFGI (1 << 7)
+#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8)
+#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9)
+#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10)
+#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 11)
+#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
+#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA (1 << 15)
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT 16
+//3 bits of stride
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_MASK 0xFF
+
+layout(set = 0, binding = 5, std430) restrict readonly buffer OmniLights {
+ LightData data[];
+}
+omni_lights;
+
+layout(set = 0, binding = 6, std430) restrict readonly buffer SpotLights {
+ LightData data[];
+}
+spot_lights;
+
+layout(set = 0, binding = 7, std430) restrict readonly buffer ReflectionProbeData {
+ ReflectionData data[];
+}
+reflections;
+
+layout(set = 0, binding = 8, std140) uniform DirectionalLights {
+ DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+}
+directional_lights;
+
+#define LIGHTMAP_FLAG_USE_DIRECTION 1
+#define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
+
+struct Lightmap {
+ mediump mat3 normal_xform;
+};
+
+layout(set = 0, binding = 9, std140) restrict readonly buffer Lightmaps {
+ Lightmap data[];
+}
+lightmaps;
+
+struct LightmapCapture {
+ mediump vec4 sh[9];
+};
+
+layout(set = 0, binding = 10, std140) restrict readonly buffer LightmapCaptures {
+ LightmapCapture data[];
+}
+lightmap_captures;
+
+layout(set = 0, binding = 11) uniform mediump texture2D decal_atlas;
+layout(set = 0, binding = 12) uniform mediump texture2D decal_atlas_srgb;
+
+layout(set = 0, binding = 13, std430) restrict readonly buffer Decals {
+ DecalData data[];
+}
+decals;
+
+layout(set = 0, binding = 14, std430) restrict readonly buffer GlobalVariableData {
+ highp vec4 data[];
+}
+global_variables;
+
+/* Set 1: Render Pass (changes per render pass) */
+
+layout(set = 1, binding = 0, std140) uniform SceneData {
+ highp mat4 projection_matrix;
+ highp mat4 inv_projection_matrix;
+ highp mat4 camera_matrix;
+ highp mat4 inv_camera_matrix;
+
+ // only used for multiview
+ highp mat4 projection_matrix_view[MAX_VIEWS];
+ highp mat4 inv_projection_matrix_view[MAX_VIEWS];
+
+ highp vec2 viewport_size;
+ highp vec2 screen_pixel_size;
+
+ // Use vec4s because std140 doesn't play nice with vec2s, z and w are wasted.
+ highp vec4 directional_penumbra_shadow_kernel[32];
+ highp vec4 directional_soft_shadow_kernel[32];
+ highp vec4 penumbra_shadow_kernel[32];
+ highp vec4 soft_shadow_kernel[32];
+
+ mediump vec4 ambient_light_color_energy;
+
+ mediump float ambient_color_sky_mix;
+ bool use_ambient_light;
+ bool use_ambient_cubemap;
+ bool use_reflection_cubemap;
+
+ mediump mat3 radiance_inverse_xform;
+
+ highp vec2 shadow_atlas_pixel_size;
+ highp vec2 directional_shadow_pixel_size;
+
+ uint directional_light_count;
+ mediump float dual_paraboloid_side;
+ highp float z_far;
+ highp float z_near;
+
+ bool ssao_enabled;
+ mediump float ssao_light_affect;
+ mediump float ssao_ao_affect;
+ bool roughness_limiter_enabled;
+
+ mediump float roughness_limiter_amount;
+ mediump float roughness_limiter_limit;
+ uvec2 roughness_limiter_pad;
+
+ mediump vec4 ao_color;
+
+ bool fog_enabled;
+ highp float fog_density;
+ highp float fog_height;
+ highp float fog_height_density;
+
+ mediump vec3 fog_light_color;
+ mediump float fog_sun_scatter;
+
+ mediump float fog_aerial_perspective;
+ bool material_uv2_mode;
+
+ highp float time;
+ mediump float reflection_multiplier; // one normally, zero when rendering reflections
+
+ bool pancake_shadows;
+ uint pad1;
+ uint pad2;
+ uint pad3;
+}
+scene_data;
+
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+layout(set = 1, binding = 2) uniform mediump textureCubeArray radiance_cubemap;
+
+#else
+
+layout(set = 1, binding = 2) uniform mediump textureCube radiance_cubemap;
+
+#endif
+
+layout(set = 1, binding = 3) uniform mediump textureCubeArray reflection_atlas;
+
+layout(set = 1, binding = 4) uniform highp texture2D shadow_atlas;
+
+layout(set = 1, binding = 5) uniform highp texture2D directional_shadow_atlas;
+
+// this needs to change to providing just the lightmap we're using..
+layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES];
+
+layout(set = 1, binding = 9) uniform highp texture2D depth_buffer;
+layout(set = 1, binding = 10) uniform mediump texture2D color_buffer;
+
+/* Set 2 Skeleton & Instancing (can change per item) */
+
+layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms {
+ highp vec4 data[];
+}
+transforms;
+
+/* Set 3 User Material */
diff --git a/servers/rendering/renderer_rd/shaders/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/screen_space_reflection.glsl
new file mode 100644
index 0000000000..78e0a85341
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/screen_space_reflection.glsl
@@ -0,0 +1,246 @@
+#[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, binding = 2, 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;
+
+ mat4 projection;
+}
+params;
+
+vec2 view_to_screen(vec3 view_pos, out float w) {
+ vec4 projected = params.projection * 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
+
+vec3 reconstructCSPosition(vec2 S, float z) {
+ if (params.orthogonal) {
+ return vec3((S.xy * params.proj_info.xy + params.proj_info.zw), z);
+ } else {
+ return vec3((S.xy * params.proj_info.xy + params.proj_info.zw) * z, z);
+ }
+}
+
+void main() {
+ // Pixel being shaded
+ ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
+
+ if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+ vec2 pixel_size = 1.0 / vec2(params.screen_size);
+ vec2 uv = vec2(ssC) * 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 = 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;
+
+ 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;
+ grad = steps_taken / 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);
+ }
+ }
+
+ 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
+
+ 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));
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/screen_space_reflection_filter.glsl b/servers/rendering/renderer_rd/shaders/screen_space_reflection_filter.glsl
new file mode 100644
index 0000000000..62d1cffb0a
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/screen_space_reflection_filter.glsl
@@ -0,0 +1,154 @@
+#[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, binding = 2, std430) uniform Params {
+ vec4 proj_info;
+
+ bool orthogonal;
+ float edge_tolerance;
+ int increment;
+ uint pad;
+
+ ivec2 screen_size;
+ bool vertical;
+ uint steps;
+}
+params;
+
+#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
+
+vec3 reconstructCSPosition(vec2 S, float z) {
+ if (params.orthogonal) {
+ return vec3((S.xy * params.proj_info.xy + params.proj_info.zw), z);
+ } else {
+ return vec3((S.xy * params.proj_info.xy + params.proj_info.zw) * z, z);
+ }
+}
+
+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, 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) + 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, direction, pos, normal, radius);
+ do_filter(accum, accum_radius, divisor, ssC, -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
+}
diff --git a/servers/rendering/renderer_rd/shaders/screen_space_reflection_scale.glsl b/servers/rendering/renderer_rd/shaders/screen_space_reflection_scale.glsl
new file mode 100644
index 0000000000..2328effe7b
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/screen_space_reflection_scale.glsl
@@ -0,0 +1,90 @@
+#[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_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, binding = 1, 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, 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;
+
+ 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 {
+ color = texelFetch(source_ssr, ssC << 1, 0);
+ depth = texelFetch(source_depth, ssC << 1, 0).r;
+ normal = texelFetch(source_normal, ssC << 1, 0);
+
+ 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);
+}
diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_debug.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_debug.glsl
new file mode 100644
index 0000000000..8b58796962
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sdfgi_debug.glsl
@@ -0,0 +1,174 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#define MAX_CASCADES 8
+
+layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
+layout(set = 0, binding = 2) uniform texture3D light_cascades[MAX_CASCADES];
+layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[MAX_CASCADES];
+layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[MAX_CASCADES];
+layout(set = 0, binding = 5) uniform texture3D occlusion_texture;
+
+layout(set = 0, binding = 8) uniform sampler linear_sampler;
+
+struct CascadeData {
+ vec3 offset; //offset of (0,0,0) in world coordinates
+ float to_cell; // 1/bounds * grid_size
+ ivec3 probe_world_offset;
+ uint pad;
+};
+
+layout(set = 0, binding = 9, std140) uniform Cascades {
+ CascadeData data[MAX_CASCADES];
+}
+cascades;
+
+layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image2D screen_buffer;
+
+layout(set = 0, binding = 11) uniform texture2DArray lightprobe_texture;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec3 grid_size;
+ uint max_cascades;
+
+ ivec2 screen_size;
+ bool use_occlusion;
+ float y_mult;
+
+ vec3 cam_extent;
+ int probe_axis_size;
+
+ mat4 cam_transform;
+}
+params;
+
+vec3 linear_to_srgb(vec3 color) {
+ //if going to srgb, clamp from 0 to 1.
+ color = clamp(color, vec3(0.0), vec3(1.0));
+ const vec3 a = vec3(0.055f);
+ return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
+}
+
+vec2 octahedron_wrap(vec2 v) {
+ vec2 signVal;
+ signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+ signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+ return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+ n.xy = n.xy * 0.5 + 0.5;
+ return n.xy;
+}
+
+void main() {
+ // Pixel being shaded
+ ivec2 screen_pos = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(screen_pos, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+ vec3 ray_pos;
+ vec3 ray_dir;
+ {
+ ray_pos = params.cam_transform[3].xyz;
+
+ ray_dir.xy = params.cam_extent.xy * ((vec2(screen_pos) / vec2(params.screen_size)) * 2.0 - 1.0);
+ ray_dir.z = params.cam_extent.z;
+
+ ray_dir = normalize(mat3(params.cam_transform) * ray_dir);
+ }
+
+ ray_pos.y *= params.y_mult;
+ ray_dir.y *= params.y_mult;
+ ray_dir = normalize(ray_dir);
+
+ vec3 pos_to_uvw = 1.0 / params.grid_size;
+
+ vec3 light = vec3(0.0);
+ float blend = 0.0;
+
+#if 1
+ // No interpolation
+
+ vec3 inv_dir = 1.0 / ray_dir;
+
+ float rough = 0.5;
+ bool hit = false;
+
+ for (uint i = 0; i < params.max_cascades; i++) {
+ //convert to local bounds
+ vec3 pos = ray_pos - cascades.data[i].offset;
+ pos *= cascades.data[i].to_cell;
+
+ // Should never happen for debug, since we start mostly at the bounds center,
+ // but add anyway.
+ //if (any(lessThan(pos,vec3(0.0))) || any(greaterThanEqual(pos,params.grid_size))) {
+ // continue; //already past bounds for this cascade, goto next
+ //}
+
+ //find maximum advance distance (until reaching bounds)
+ vec3 t0 = -pos * inv_dir;
+ vec3 t1 = (params.grid_size - pos) * inv_dir;
+ vec3 tmax = max(t0, t1);
+ float max_advance = min(tmax.x, min(tmax.y, tmax.z));
+
+ float advance = 0.0;
+ vec3 uvw;
+ hit = false;
+
+ while (advance < max_advance) {
+ //read how much to advance from SDF
+ uvw = (pos + ray_dir * advance) * pos_to_uvw;
+
+ float distance = texture(sampler3D(sdf_cascades[i], linear_sampler), uvw).r * 255.0 - 1.7;
+
+ if (distance < 0.001) {
+ //consider hit
+ hit = true;
+ break;
+ }
+
+ advance += distance;
+ }
+
+ if (!hit) {
+ pos += ray_dir * min(advance, max_advance);
+ pos /= cascades.data[i].to_cell;
+ pos += cascades.data[i].offset;
+ ray_pos = pos;
+ continue;
+ }
+
+ //compute albedo, emission and normal at hit point
+
+ const float EPSILON = 0.001;
+ vec3 hit_normal = normalize(vec3(
+ texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(EPSILON, 0.0, 0.0)).r,
+ texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(0.0, EPSILON, 0.0)).r,
+ texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(0.0, 0.0, EPSILON)).r));
+
+ vec3 hit_light = texture(sampler3D(light_cascades[i], linear_sampler), uvw).rgb;
+ vec4 aniso0 = texture(sampler3D(aniso0_cascades[i], linear_sampler), uvw);
+ vec3 hit_aniso0 = aniso0.rgb;
+ vec3 hit_aniso1 = vec3(aniso0.a, texture(sampler3D(aniso1_cascades[i], linear_sampler), uvw).rg);
+
+ hit_light *= (dot(max(vec3(0.0), (hit_normal * hit_aniso0)), vec3(1.0)) + dot(max(vec3(0.0), (-hit_normal * hit_aniso1)), vec3(1.0)));
+
+ light = hit_light;
+
+ break;
+ }
+
+#endif
+
+ imageStore(screen_buffer, screen_pos, vec4(linear_to_srgb(light), 1.0));
+}
diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl
new file mode 100644
index 0000000000..0eacbc5363
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl
@@ -0,0 +1,231 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define MAX_CASCADES 8
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ mat4 projection;
+
+ uint band_power;
+ uint sections_in_band;
+ uint band_mask;
+ float section_arc;
+
+ vec3 grid_size;
+ uint cascade;
+
+ uint pad;
+ float y_mult;
+ uint probe_debug_index;
+ int probe_axis_size;
+}
+params;
+
+// http://in4k.untergrund.net/html_articles/hugi_27_-_coding_corner_polaris_sphere_tessellation_101.htm
+
+vec3 get_sphere_vertex(uint p_vertex_id) {
+ float x_angle = float(p_vertex_id & 1u) + (p_vertex_id >> params.band_power);
+
+ float y_angle =
+ float((p_vertex_id & params.band_mask) >> 1) + ((p_vertex_id >> params.band_power) * params.sections_in_band);
+
+ x_angle *= params.section_arc * 0.5f; // remember - 180AA x rot not 360
+ y_angle *= -params.section_arc;
+
+ vec3 point = vec3(sin(x_angle) * sin(y_angle), cos(x_angle), sin(x_angle) * cos(y_angle));
+
+ return point;
+}
+
+#ifdef MODE_PROBES
+
+layout(location = 0) out vec3 normal_interp;
+layout(location = 1) out flat uint probe_index;
+
+#endif
+
+#ifdef MODE_VISIBILITY
+
+layout(location = 0) out float visibility;
+
+#endif
+
+struct CascadeData {
+ vec3 offset; //offset of (0,0,0) in world coordinates
+ float to_cell; // 1/bounds * grid_size
+ ivec3 probe_world_offset;
+ uint pad;
+};
+
+layout(set = 0, binding = 1, std140) uniform Cascades {
+ CascadeData data[MAX_CASCADES];
+}
+cascades;
+
+layout(set = 0, binding = 4) uniform texture3D occlusion_texture;
+layout(set = 0, binding = 3) uniform sampler linear_sampler;
+
+void main() {
+#ifdef MODE_PROBES
+ probe_index = gl_InstanceIndex;
+
+ normal_interp = get_sphere_vertex(gl_VertexIndex);
+
+ vec3 vertex = normal_interp * 0.2;
+
+ float probe_cell_size = float(params.grid_size / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
+
+ ivec3 probe_cell;
+ probe_cell.x = int(probe_index % params.probe_axis_size);
+ probe_cell.y = int(probe_index / (params.probe_axis_size * params.probe_axis_size));
+ probe_cell.z = int((probe_index / params.probe_axis_size) % params.probe_axis_size);
+
+ vertex += (cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size) / vec3(1.0, params.y_mult, 1.0);
+
+ gl_Position = params.projection * vec4(vertex, 1.0);
+#endif
+
+#ifdef MODE_VISIBILITY
+
+ int probe_index = int(params.probe_debug_index);
+
+ vec3 vertex = get_sphere_vertex(gl_VertexIndex) * 0.01;
+
+ float probe_cell_size = float(params.grid_size / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
+
+ ivec3 probe_cell;
+ probe_cell.x = int(probe_index % params.probe_axis_size);
+ probe_cell.y = int((probe_index % (params.probe_axis_size * params.probe_axis_size)) / params.probe_axis_size);
+ probe_cell.z = int(probe_index / (params.probe_axis_size * params.probe_axis_size));
+
+ vertex += (cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size) / vec3(1.0, params.y_mult, 1.0);
+
+ int probe_voxels = int(params.grid_size.x) / int(params.probe_axis_size - 1);
+ int occluder_index = int(gl_InstanceIndex);
+
+ int diameter = probe_voxels * 2;
+ ivec3 occluder_pos;
+ occluder_pos.x = int(occluder_index % diameter);
+ occluder_pos.y = int(occluder_index / (diameter * diameter));
+ occluder_pos.z = int((occluder_index / diameter) % diameter);
+
+ float cell_size = 1.0 / cascades.data[params.cascade].to_cell;
+
+ ivec3 occluder_offset = occluder_pos - ivec3(diameter / 2);
+ vertex += ((vec3(occluder_offset) + vec3(0.5)) * cell_size) / vec3(1.0, params.y_mult, 1.0);
+
+ ivec3 global_cell = probe_cell + cascades.data[params.cascade].probe_world_offset;
+ uint occlusion_layer = 0;
+ if ((global_cell.x & 1) != 0) {
+ occlusion_layer |= 1;
+ }
+ if ((global_cell.y & 1) != 0) {
+ occlusion_layer |= 2;
+ }
+ if ((global_cell.z & 1) != 0) {
+ occlusion_layer |= 4;
+ }
+ ivec3 tex_pos = probe_cell * probe_voxels + occluder_offset;
+
+ const vec4 layer_axis[4] = vec4[](
+ vec4(1, 0, 0, 0),
+ vec4(0, 1, 0, 0),
+ vec4(0, 0, 1, 0),
+ vec4(0, 0, 0, 1));
+
+ tex_pos.z += int(params.cascade) * int(params.grid_size);
+ if (occlusion_layer >= 4) {
+ tex_pos.x += int(params.grid_size.x);
+ occlusion_layer &= 3;
+ }
+
+ visibility = dot(texelFetch(sampler3D(occlusion_texture, linear_sampler), tex_pos, 0), layer_axis[occlusion_layer]);
+
+ gl_Position = params.projection * vec4(vertex, 1.0);
+
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) out vec4 frag_color;
+
+layout(set = 0, binding = 2) uniform texture2DArray lightprobe_texture;
+layout(set = 0, binding = 3) uniform sampler linear_sampler;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ mat4 projection;
+
+ uint band_power;
+ uint sections_in_band;
+ uint band_mask;
+ float section_arc;
+
+ vec3 grid_size;
+ uint cascade;
+
+ uint pad;
+ float y_mult;
+ uint probe_debug_index;
+ int probe_axis_size;
+}
+params;
+
+#ifdef MODE_PROBES
+
+layout(location = 0) in vec3 normal_interp;
+layout(location = 1) in flat uint probe_index;
+
+#endif
+
+#ifdef MODE_VISIBILITY
+layout(location = 0) in float visibility;
+#endif
+
+vec2 octahedron_wrap(vec2 v) {
+ vec2 signVal;
+ signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+ signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+ return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+ n.xy = n.xy * 0.5 + 0.5;
+ return n.xy;
+}
+
+void main() {
+#ifdef MODE_PROBES
+
+ ivec3 tex_pos;
+ tex_pos.x = int(probe_index) % params.probe_axis_size; //x
+ tex_pos.y = int(probe_index) / (params.probe_axis_size * params.probe_axis_size);
+ tex_pos.x += params.probe_axis_size * ((int(probe_index) / params.probe_axis_size) % params.probe_axis_size); //z
+ tex_pos.z = int(params.cascade);
+
+ vec3 tex_pos_ofs = vec3(octahedron_encode(normal_interp) * float(OCT_SIZE), 0.0);
+ vec3 tex_posf = vec3(vec2(tex_pos.xy * (OCT_SIZE + 2) + ivec2(1)), float(tex_pos.z)) + tex_pos_ofs;
+
+ tex_posf.xy /= vec2(ivec2(params.probe_axis_size * params.probe_axis_size * (OCT_SIZE + 2), params.probe_axis_size * (OCT_SIZE + 2)));
+
+ vec4 indirect_light = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), tex_posf, 0.0);
+
+ frag_color = indirect_light;
+
+#endif
+
+#ifdef MODE_VISIBILITY
+
+ frag_color = vec4(vec3(1, visibility, visibility), 1.0);
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_direct_light.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_direct_light.glsl
new file mode 100644
index 0000000000..d6e5c6a92e
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sdfgi_direct_light.glsl
@@ -0,0 +1,508 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#define MAX_CASCADES 8
+
+layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
+layout(set = 0, binding = 2) uniform sampler linear_sampler;
+
+layout(set = 0, binding = 3, std430) restrict readonly buffer DispatchData {
+ uint x;
+ uint y;
+ uint z;
+ uint total_count;
+}
+dispatch_data;
+
+struct ProcessVoxel {
+ uint position; // xyz 7 bit packed, extra 11 bits for neighbors.
+ uint albedo; // rgb bits 0-15 albedo, bits 16-21 are normal bits (set if geometry exists toward that side), extra 11 bits for neighbors.
+ uint light; // rgbe8985 encoded total saved light, extra 2 bits for neighbors.
+ uint light_aniso; // 55555 light anisotropy, extra 2 bits for neighbors.
+ //total neighbours: 26
+};
+
+#ifdef MODE_PROCESS_STATIC
+layout(set = 0, binding = 4, std430) restrict buffer ProcessVoxels {
+#else
+layout(set = 0, binding = 4, std430) restrict buffer readonly ProcessVoxels {
+#endif
+ ProcessVoxel data[];
+}
+process_voxels;
+
+layout(r32ui, set = 0, binding = 5) uniform restrict uimage3D dst_light;
+layout(rgba8, set = 0, binding = 6) uniform restrict image3D dst_aniso0;
+layout(rg8, set = 0, binding = 7) uniform restrict image3D dst_aniso1;
+
+struct CascadeData {
+ vec3 offset; //offset of (0,0,0) in world coordinates
+ float to_cell; // 1/bounds * grid_size
+ ivec3 probe_world_offset;
+ uint pad;
+};
+
+layout(set = 0, binding = 8, std140) uniform Cascades {
+ CascadeData data[MAX_CASCADES];
+}
+cascades;
+
+#define LIGHT_TYPE_DIRECTIONAL 0
+#define LIGHT_TYPE_OMNI 1
+#define LIGHT_TYPE_SPOT 2
+
+struct Light {
+ vec3 color;
+ float energy;
+
+ vec3 direction;
+ bool has_shadow;
+
+ vec3 position;
+ float attenuation;
+
+ uint type;
+ float cos_spot_angle;
+ float inv_spot_attenuation;
+ float radius;
+
+ vec4 shadow_color;
+};
+
+layout(set = 0, binding = 9, std140) buffer restrict readonly Lights {
+ Light data[];
+}
+lights;
+
+layout(set = 0, binding = 10) uniform texture2DArray lightprobe_texture;
+layout(set = 0, binding = 11) uniform texture3D occlusion_texture;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec3 grid_size;
+ uint max_cascades;
+
+ uint cascade;
+ uint light_count;
+ uint process_offset;
+ uint process_increment;
+
+ int probe_axis_size;
+ float bounce_feedback;
+ float y_mult;
+ bool use_occlusion;
+}
+params;
+
+vec2 octahedron_wrap(vec2 v) {
+ vec2 signVal;
+ signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+ signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+ return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+ n.xy = n.xy * 0.5 + 0.5;
+ return n.xy;
+}
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+void main() {
+ uint voxel_index = uint(gl_GlobalInvocationID.x);
+
+ //used for skipping voxels every N frames
+ if (params.process_increment > 1) {
+ voxel_index *= params.process_increment;
+ voxel_index += params.process_offset;
+ }
+
+ if (voxel_index >= dispatch_data.total_count) {
+ return;
+ }
+
+ uint voxel_position = process_voxels.data[voxel_index].position;
+
+ //keep for storing to texture
+ ivec3 positioni = ivec3((uvec3(voxel_position, voxel_position, voxel_position) >> uvec3(0, 7, 14)) & uvec3(0x7F));
+
+ vec3 position = vec3(positioni) + vec3(0.5);
+ position /= cascades.data[params.cascade].to_cell;
+ position += cascades.data[params.cascade].offset;
+
+ uint voxel_albedo = process_voxels.data[voxel_index].albedo;
+
+ vec3 albedo = vec3(uvec3(voxel_albedo >> 10, voxel_albedo >> 5, voxel_albedo) & uvec3(0x1F)) / float(0x1F);
+ vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
+ uint valid_aniso = (voxel_albedo >> 15) & 0x3F;
+
+ const vec3 aniso_dir[6] = vec3[](
+ vec3(1, 0, 0),
+ vec3(0, 1, 0),
+ vec3(0, 0, 1),
+ vec3(-1, 0, 0),
+ vec3(0, -1, 0),
+ vec3(0, 0, -1));
+
+ // Add indirect light first, in order to save computation resources
+#ifdef MODE_PROCESS_DYNAMIC
+ if (params.bounce_feedback > 0.001) {
+ vec3 feedback = (params.bounce_feedback < 1.0) ? (albedo * params.bounce_feedback) : mix(albedo, vec3(1.0), params.bounce_feedback - 1.0);
+ vec3 pos = (vec3(positioni) + vec3(0.5)) * float(params.probe_axis_size - 1) / params.grid_size;
+ ivec3 probe_base_pos = ivec3(pos);
+
+ float weight_accum[6] = float[](0, 0, 0, 0, 0, 0);
+
+ ivec3 tex_pos = ivec3(probe_base_pos.xy, int(params.cascade));
+ tex_pos.x += probe_base_pos.z * int(params.probe_axis_size);
+
+ tex_pos.xy = tex_pos.xy * (OCT_SIZE + 2) + ivec2(1);
+
+ vec3 base_tex_posf = vec3(tex_pos);
+ vec2 tex_pixel_size = 1.0 / vec2(ivec2((OCT_SIZE + 2) * params.probe_axis_size * params.probe_axis_size, (OCT_SIZE + 2) * params.probe_axis_size));
+ vec3 probe_uv_offset = vec3(ivec3(OCT_SIZE + 2, OCT_SIZE + 2, (OCT_SIZE + 2) * params.probe_axis_size)) * tex_pixel_size.xyx;
+
+ for (uint j = 0; j < 8; j++) {
+ ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
+ ivec3 probe_posi = probe_base_pos;
+ probe_posi += offset;
+
+ // Compute weight
+
+ vec3 probe_pos = vec3(probe_posi);
+ vec3 probe_to_pos = pos - probe_pos;
+ vec3 probe_dir = normalize(-probe_to_pos);
+
+ // Compute lightprobe texture position
+
+ vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
+
+ for (uint k = 0; k < 6; k++) {
+ if (bool(valid_aniso & (1 << k))) {
+ vec3 n = aniso_dir[k];
+ float weight = trilinear.x * trilinear.y * trilinear.z * max(0, dot(n, probe_dir));
+
+ if (weight > 0.0 && params.use_occlusion) {
+ ivec3 occ_indexv = abs((cascades.data[params.cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+ vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
+
+ vec3 occ_pos = (vec3(positioni) + aniso_dir[k] + vec3(0.5)) / params.grid_size;
+ occ_pos.z += float(params.cascade);
+ if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
+ occ_pos.x += 1.0;
+ }
+ occ_pos *= vec3(0.5, 1.0, 1.0 / float(params.max_cascades)); //renormalize
+ float occlusion = dot(textureLod(sampler3D(occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
+
+ weight *= occlusion;
+ }
+
+ if (weight > 0.0) {
+ vec3 tex_posf = base_tex_posf + vec3(octahedron_encode(n) * float(OCT_SIZE), 0.0);
+ tex_posf.xy *= tex_pixel_size;
+
+ vec3 pos_uvw = tex_posf;
+ pos_uvw.xy += vec2(offset.xy) * probe_uv_offset.xy;
+ pos_uvw.x += float(offset.z) * probe_uv_offset.z;
+ vec3 indirect_light = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb;
+
+ light_accum[k] += indirect_light * weight;
+ weight_accum[k] += weight;
+ }
+ }
+ }
+ }
+
+ for (uint k = 0; k < 6; k++) {
+ if (weight_accum[k] > 0.0) {
+ light_accum[k] /= weight_accum[k];
+ light_accum[k] *= feedback;
+ }
+ }
+ }
+
+#endif
+
+ {
+ uint rgbe = process_voxels.data[voxel_index].light;
+
+ //read rgbe8985
+ float r = float((rgbe & 0xff) << 1);
+ float g = float((rgbe >> 8) & 0x1ff);
+ float b = float(((rgbe >> 17) & 0xff) << 1);
+ float e = float((rgbe >> 25) & 0x1F);
+ float m = pow(2.0, e - 15.0 - 9.0);
+
+ vec3 l = vec3(r, g, b) * m;
+
+ uint aniso = process_voxels.data[voxel_index].light_aniso;
+ for (uint i = 0; i < 6; i++) {
+ float strength = ((aniso >> (i * 5)) & 0x1F) / float(0x1F);
+ light_accum[i] += l * strength;
+ }
+ }
+
+ // Raytrace light
+
+ vec3 pos_to_uvw = 1.0 / params.grid_size;
+ vec3 uvw_ofs = pos_to_uvw * 0.5;
+
+ for (uint i = 0; i < params.light_count; i++) {
+ float attenuation = 1.0;
+ vec3 direction;
+ float light_distance = 1e20;
+
+ switch (lights.data[i].type) {
+ case LIGHT_TYPE_DIRECTIONAL: {
+ direction = -lights.data[i].direction;
+ } break;
+ case LIGHT_TYPE_OMNI: {
+ vec3 rel_vec = lights.data[i].position - position;
+ direction = normalize(rel_vec);
+ light_distance = length(rel_vec);
+ rel_vec.y /= params.y_mult;
+ attenuation = get_omni_attenuation(light_distance, 1.0 / lights.data[i].radius, lights.data[i].attenuation);
+
+ } break;
+ case LIGHT_TYPE_SPOT: {
+ vec3 rel_vec = lights.data[i].position - position;
+ direction = normalize(rel_vec);
+ light_distance = length(rel_vec);
+ rel_vec.y /= params.y_mult;
+ attenuation = get_omni_attenuation(light_distance, 1.0 / lights.data[i].radius, lights.data[i].attenuation);
+
+ float cos_spot_angle = lights.data[i].cos_spot_angle;
+ float cos_angle = dot(-direction, lights.data[i].direction);
+
+ if (cos_angle < cos_spot_angle) {
+ continue;
+ }
+
+ float scos = max(cos_angle, cos_spot_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
+ attenuation *= 1.0 - pow(spot_rim, lights.data[i].inv_spot_attenuation);
+ } break;
+ }
+
+ if (attenuation < 0.001) {
+ continue;
+ }
+
+ bool hit = false;
+
+ vec3 ray_pos = position;
+ vec3 ray_dir = direction;
+ vec3 inv_dir = 1.0 / ray_dir;
+
+ //this is how to properly bias outgoing rays
+ float cell_size = 1.0 / cascades.data[params.cascade].to_cell;
+ ray_pos += sign(direction) * cell_size * 0.48; // go almost to the box edge but remain inside
+ ray_pos += ray_dir * 0.4 * cell_size; //apply a small bias from there
+
+ for (uint j = params.cascade; j < params.max_cascades; j++) {
+ //convert to local bounds
+ vec3 pos = ray_pos - cascades.data[j].offset;
+ pos *= cascades.data[j].to_cell;
+ float local_distance = light_distance * cascades.data[j].to_cell;
+
+ if (any(lessThan(pos, vec3(0.0))) || any(greaterThanEqual(pos, params.grid_size))) {
+ continue; //already past bounds for this cascade, goto next
+ }
+
+ //find maximum advance distance (until reaching bounds)
+ vec3 t0 = -pos * inv_dir;
+ vec3 t1 = (params.grid_size - pos) * inv_dir;
+ vec3 tmax = max(t0, t1);
+ float max_advance = min(tmax.x, min(tmax.y, tmax.z));
+
+ max_advance = min(local_distance, max_advance);
+
+ float advance = 0.0;
+ float occlusion = 1.0;
+
+ while (advance < max_advance) {
+ //read how much to advance from SDF
+ vec3 uvw = (pos + ray_dir * advance) * pos_to_uvw;
+
+ float distance = texture(sampler3D(sdf_cascades[j], linear_sampler), uvw).r * 255.0 - 1.0;
+ if (distance < 0.001) {
+ //consider hit
+ hit = true;
+ break;
+ }
+
+ occlusion = min(occlusion, distance);
+
+ advance += distance;
+ }
+
+ if (hit) {
+ attenuation *= occlusion;
+ break;
+ }
+
+ if (advance >= local_distance) {
+ break; //past light distance, abandon search
+ }
+ //change ray origin to collision with bounds
+ pos += ray_dir * max_advance;
+ pos /= cascades.data[j].to_cell;
+ pos += cascades.data[j].offset;
+ light_distance -= max_advance / cascades.data[j].to_cell;
+ ray_pos = pos;
+ }
+
+ if (!hit) {
+ vec3 light = albedo * lights.data[i].color.rgb * lights.data[i].energy * attenuation;
+
+ for (int j = 0; j < 6; j++) {
+ if (bool(valid_aniso & (1 << j))) {
+ light_accum[j] += max(0.0, dot(aniso_dir[j], direction)) * light;
+ }
+ }
+ }
+ }
+
+ // Store the light in the light texture
+
+ float lumas[6];
+ vec3 light_total = vec3(0);
+
+ for (int i = 0; i < 6; i++) {
+ light_total += light_accum[i];
+ lumas[i] = max(light_accum[i].r, max(light_accum[i].g, light_accum[i].b));
+ }
+
+ float luma_total = max(light_total.r, max(light_total.g, light_total.b));
+
+ uint light_total_rgbe;
+
+ {
+ //compress to RGBE9995 to save space
+
+ const float pow2to9 = 512.0f;
+ const float B = 15.0f;
+ const float N = 9.0f;
+ const float LN2 = 0.6931471805599453094172321215;
+
+ float cRed = clamp(light_total.r, 0.0, 65408.0);
+ float cGreen = clamp(light_total.g, 0.0, 65408.0);
+ float cBlue = clamp(light_total.b, 0.0, 65408.0);
+
+ float cMax = max(cRed, max(cGreen, cBlue));
+
+ float expp = max(-B - 1.0f, floor(log(cMax) / LN2)) + 1.0f + B;
+
+ float sMax = floor((cMax / pow(2.0f, expp - B - N)) + 0.5f);
+
+ float exps = expp + 1.0f;
+
+ if (0.0 <= sMax && sMax < pow2to9) {
+ exps = expp;
+ }
+
+ float sRed = floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
+ float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
+ float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
+#ifdef MODE_PROCESS_STATIC
+ //since its self-save, use RGBE8985
+ light_total_rgbe = ((uint(sRed) & 0x1FF) >> 1) | ((uint(sGreen) & 0x1FF) << 8) | (((uint(sBlue) & 0x1FF) >> 1) << 17) | ((uint(exps) & 0x1F) << 25);
+
+#else
+ light_total_rgbe = (uint(sRed) & 0x1FF) | ((uint(sGreen) & 0x1FF) << 9) | ((uint(sBlue) & 0x1FF) << 18) | ((uint(exps) & 0x1F) << 27);
+#endif
+ }
+
+#ifdef MODE_PROCESS_DYNAMIC
+
+ vec4 aniso0;
+ aniso0.r = lumas[0] / luma_total;
+ aniso0.g = lumas[1] / luma_total;
+ aniso0.b = lumas[2] / luma_total;
+ aniso0.a = lumas[3] / luma_total;
+
+ vec2 aniso1;
+ aniso1.r = lumas[4] / luma_total;
+ aniso1.g = lumas[5] / luma_total;
+
+ //save to 3D textures
+ imageStore(dst_aniso0, positioni, aniso0);
+ imageStore(dst_aniso1, positioni, vec4(aniso1, 0.0, 0.0));
+ imageStore(dst_light, positioni, uvec4(light_total_rgbe));
+
+ //also fill neighbours, so light interpolation during the indirect pass works
+
+ //recover the neighbour list from the leftover bits
+ uint neighbours = (voxel_albedo >> 21) | ((voxel_position >> 21) << 11) | ((process_voxels.data[voxel_index].light >> 30) << 22) | ((process_voxels.data[voxel_index].light_aniso >> 30) << 24);
+
+ const uint max_neighbours = 26;
+ const ivec3 neighbour_positions[max_neighbours] = ivec3[](
+ ivec3(-1, -1, -1),
+ ivec3(-1, -1, 0),
+ ivec3(-1, -1, 1),
+ ivec3(-1, 0, -1),
+ ivec3(-1, 0, 0),
+ ivec3(-1, 0, 1),
+ ivec3(-1, 1, -1),
+ ivec3(-1, 1, 0),
+ ivec3(-1, 1, 1),
+ ivec3(0, -1, -1),
+ ivec3(0, -1, 0),
+ ivec3(0, -1, 1),
+ ivec3(0, 0, -1),
+ ivec3(0, 0, 1),
+ ivec3(0, 1, -1),
+ ivec3(0, 1, 0),
+ ivec3(0, 1, 1),
+ ivec3(1, -1, -1),
+ ivec3(1, -1, 0),
+ ivec3(1, -1, 1),
+ ivec3(1, 0, -1),
+ ivec3(1, 0, 0),
+ ivec3(1, 0, 1),
+ ivec3(1, 1, -1),
+ ivec3(1, 1, 0),
+ ivec3(1, 1, 1));
+
+ for (uint i = 0; i < max_neighbours; i++) {
+ if (bool(neighbours & (1 << i))) {
+ ivec3 neighbour_pos = positioni + neighbour_positions[i];
+ imageStore(dst_light, neighbour_pos, uvec4(light_total_rgbe));
+ imageStore(dst_aniso0, neighbour_pos, aniso0);
+ imageStore(dst_aniso1, neighbour_pos, vec4(aniso1, 0.0, 0.0));
+ }
+ }
+
+#endif
+
+#ifdef MODE_PROCESS_STATIC
+
+ //save back the anisotropic
+
+ uint light = process_voxels.data[voxel_index].light & (3 << 30);
+ light |= light_total_rgbe;
+ process_voxels.data[voxel_index].light = light; //replace
+
+ uint light_aniso = process_voxels.data[voxel_index].light_aniso & (3 << 30);
+ for (int i = 0; i < 6; i++) {
+ light_aniso |= min(31, uint((lumas[i] / luma_total) * 31.0)) << (i * 5);
+ }
+
+ process_voxels.data[voxel_index].light_aniso = light_aniso;
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_integrate.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_integrate.glsl
new file mode 100644
index 0000000000..eedd28959c
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sdfgi_integrate.glsl
@@ -0,0 +1,612 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#define MAX_CASCADES 8
+
+layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
+layout(set = 0, binding = 2) uniform texture3D light_cascades[MAX_CASCADES];
+layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[MAX_CASCADES];
+layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[MAX_CASCADES];
+
+layout(set = 0, binding = 6) uniform sampler linear_sampler;
+
+struct CascadeData {
+ vec3 offset; //offset of (0,0,0) in world coordinates
+ float to_cell; // 1/bounds * grid_size
+ ivec3 probe_world_offset;
+ uint pad;
+};
+
+layout(set = 0, binding = 7, std140) uniform Cascades {
+ CascadeData data[MAX_CASCADES];
+}
+cascades;
+
+layout(r32ui, set = 0, binding = 8) uniform restrict uimage2DArray lightprobe_texture_data;
+layout(rgba16i, set = 0, binding = 9) uniform restrict iimage2DArray lightprobe_history_texture;
+layout(rgba32i, set = 0, binding = 10) uniform restrict iimage2D lightprobe_average_texture;
+
+//used for scrolling
+layout(rgba16i, set = 0, binding = 11) uniform restrict iimage2DArray lightprobe_history_scroll_texture;
+layout(rgba32i, set = 0, binding = 12) uniform restrict iimage2D lightprobe_average_scroll_texture;
+
+layout(rgba32i, set = 0, binding = 13) uniform restrict iimage2D lightprobe_average_parent_texture;
+
+layout(rgba16f, set = 0, binding = 14) uniform restrict writeonly image2DArray lightprobe_ambient_texture;
+
+#ifdef USE_CUBEMAP_ARRAY
+layout(set = 1, binding = 0) uniform textureCubeArray sky_irradiance;
+#else
+layout(set = 1, binding = 0) uniform textureCube sky_irradiance;
+#endif
+layout(set = 1, binding = 1) uniform sampler linear_sampler_mipmaps;
+
+#define HISTORY_BITS 10
+
+#define SKY_MODE_DISABLED 0
+#define SKY_MODE_COLOR 1
+#define SKY_MODE_SKY 2
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec3 grid_size;
+ uint max_cascades;
+
+ uint probe_axis_size;
+ uint cascade;
+ uint history_index;
+ uint history_size;
+
+ uint ray_count;
+ float ray_bias;
+ ivec2 image_size;
+
+ ivec3 world_offset;
+ uint sky_mode;
+
+ ivec3 scroll;
+ float sky_energy;
+
+ vec3 sky_color;
+ float y_mult;
+
+ bool store_ambient_texture;
+ uint pad[3];
+}
+params;
+
+const float PI = 3.14159265f;
+const float GOLDEN_ANGLE = PI * (3.0 - sqrt(5.0));
+
+vec3 vogel_hemisphere(uint p_index, uint p_count, float p_offset) {
+ float r = sqrt(float(p_index) + 0.5f) / sqrt(float(p_count));
+ float theta = float(p_index) * GOLDEN_ANGLE + p_offset;
+ float y = cos(r * PI * 0.5);
+ float l = sin(r * PI * 0.5);
+ return vec3(l * cos(theta), l * sin(theta), y * (float(p_index & 1) * 2.0 - 1.0));
+}
+
+uvec3 hash3(uvec3 x) {
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = (x >> 16) ^ x;
+ return x;
+}
+
+float hashf3(vec3 co) {
+ return fract(sin(dot(co, vec3(12.9898, 78.233, 137.13451))) * 43758.5453);
+}
+
+vec3 octahedron_encode(vec2 f) {
+ // https://twitter.com/Stubbesaurus/status/937994790553227264
+ f = f * 2.0 - 1.0;
+ vec3 n = vec3(f.x, f.y, 1.0f - abs(f.x) - abs(f.y));
+ float t = clamp(-n.z, 0.0, 1.0);
+ n.x += n.x >= 0 ? -t : t;
+ n.y += n.y >= 0 ? -t : t;
+ return normalize(n);
+}
+
+uint rgbe_encode(vec3 color) {
+ const float pow2to9 = 512.0f;
+ const float B = 15.0f;
+ const float N = 9.0f;
+ const float LN2 = 0.6931471805599453094172321215;
+
+ float cRed = clamp(color.r, 0.0, 65408.0);
+ float cGreen = clamp(color.g, 0.0, 65408.0);
+ float cBlue = clamp(color.b, 0.0, 65408.0);
+
+ float cMax = max(cRed, max(cGreen, cBlue));
+
+ float expp = max(-B - 1.0f, floor(log(cMax) / LN2)) + 1.0f + B;
+
+ float sMax = floor((cMax / pow(2.0f, expp - B - N)) + 0.5f);
+
+ float exps = expp + 1.0f;
+
+ if (0.0 <= sMax && sMax < pow2to9) {
+ exps = expp;
+ }
+
+ float sRed = floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
+ float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
+ float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
+ return (uint(sRed) & 0x1FF) | ((uint(sGreen) & 0x1FF) << 9) | ((uint(sBlue) & 0x1FF) << 18) | ((uint(exps) & 0x1F) << 27);
+}
+
+struct SH {
+#if (SH_SIZE == 16)
+ float c[48];
+#else
+ float c[28];
+#endif
+};
+
+shared SH sh_accum[64]; //8x8
+
+void main() {
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(pos, params.image_size))) { //too large, do nothing
+ return;
+ }
+
+ uint probe_index = gl_LocalInvocationID.x + gl_LocalInvocationID.y * 8;
+
+#ifdef MODE_PROCESS
+
+ float probe_cell_size = float(params.grid_size.x / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
+
+ ivec3 probe_cell;
+ probe_cell.x = pos.x % int(params.probe_axis_size);
+ probe_cell.y = pos.y;
+ probe_cell.z = pos.x / int(params.probe_axis_size);
+
+ vec3 probe_pos = cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size;
+ vec3 pos_to_uvw = 1.0 / params.grid_size;
+
+ for (uint i = 0; i < SH_SIZE * 3; i++) {
+ sh_accum[probe_index].c[i] = 0.0;
+ }
+
+ // quickly ensure each probe has a different "offset" for the vogel function, based on integer world position
+ uvec3 h3 = hash3(uvec3(params.world_offset + probe_cell));
+ float offset = hashf3(vec3(h3 & uvec3(0xFFFFF)));
+
+ //for a more homogeneous hemisphere, alternate based on history frames
+ uint ray_offset = params.history_index;
+ uint ray_mult = params.history_size;
+ uint ray_total = ray_mult * params.ray_count;
+
+ for (uint i = 0; i < params.ray_count; i++) {
+ vec3 ray_dir = vogel_hemisphere(ray_offset + i * ray_mult, ray_total, offset);
+ ray_dir.y *= params.y_mult;
+ ray_dir = normalize(ray_dir);
+
+ //needs to be visible
+ vec3 ray_pos = probe_pos;
+ vec3 inv_dir = 1.0 / ray_dir;
+
+ bool hit = false;
+ uint hit_cascade;
+
+ float bias = params.ray_bias;
+ vec3 abs_ray_dir = abs(ray_dir);
+ ray_pos += ray_dir * 1.0 / max(abs_ray_dir.x, max(abs_ray_dir.y, abs_ray_dir.z)) * bias / cascades.data[params.cascade].to_cell;
+ vec3 uvw;
+
+ for (uint j = params.cascade; j < params.max_cascades; j++) {
+ //convert to local bounds
+ vec3 pos = ray_pos - cascades.data[j].offset;
+ pos *= cascades.data[j].to_cell;
+
+ if (any(lessThan(pos, vec3(0.0))) || any(greaterThanEqual(pos, params.grid_size))) {
+ continue; //already past bounds for this cascade, goto next
+ }
+
+ //find maximum advance distance (until reaching bounds)
+ vec3 t0 = -pos * inv_dir;
+ vec3 t1 = (params.grid_size - pos) * inv_dir;
+ vec3 tmax = max(t0, t1);
+ float max_advance = min(tmax.x, min(tmax.y, tmax.z));
+
+ float advance = 0.0;
+
+ while (advance < max_advance) {
+ //read how much to advance from SDF
+ uvw = (pos + ray_dir * advance) * pos_to_uvw;
+
+ float distance = texture(sampler3D(sdf_cascades[j], linear_sampler), uvw).r * 255.0 - 1.0;
+ if (distance < 0.05) {
+ //consider hit
+ hit = true;
+ break;
+ }
+
+ advance += distance;
+ }
+
+ if (hit) {
+ hit_cascade = j;
+ break;
+ }
+
+ //change ray origin to collision with bounds
+ pos += ray_dir * max_advance;
+ pos /= cascades.data[j].to_cell;
+ pos += cascades.data[j].offset;
+ ray_pos = pos;
+ }
+
+ vec4 light;
+ if (hit) {
+ //avoid reading different texture from different threads
+ for (uint j = params.cascade; j < params.max_cascades; j++) {
+ if (j == hit_cascade) {
+ const float EPSILON = 0.001;
+ vec3 hit_normal = normalize(vec3(
+ texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(EPSILON, 0.0, 0.0)).r,
+ texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(0.0, EPSILON, 0.0)).r,
+ texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(0.0, 0.0, EPSILON)).r));
+
+ vec3 hit_light = texture(sampler3D(light_cascades[hit_cascade], linear_sampler), uvw).rgb;
+ vec4 aniso0 = texture(sampler3D(aniso0_cascades[hit_cascade], linear_sampler), uvw);
+ vec3 hit_aniso0 = aniso0.rgb;
+ vec3 hit_aniso1 = vec3(aniso0.a, texture(sampler3D(aniso1_cascades[hit_cascade], linear_sampler), uvw).rg);
+
+ //one liner magic
+ light.rgb = hit_light * (dot(max(vec3(0.0), (hit_normal * hit_aniso0)), vec3(1.0)) + dot(max(vec3(0.0), (-hit_normal * hit_aniso1)), vec3(1.0)));
+ light.a = 1.0;
+ }
+ }
+
+ } else if (params.sky_mode == SKY_MODE_SKY) {
+#ifdef USE_CUBEMAP_ARRAY
+ light.rgb = textureLod(samplerCubeArray(sky_irradiance, linear_sampler_mipmaps), vec4(ray_dir, 0.0), 2.0).rgb; // Use second mipmap because we don't usually throw a lot of rays, so this compensates.
+#else
+ light.rgb = textureLod(samplerCube(sky_irradiance, linear_sampler_mipmaps), ray_dir, 2.0).rgb; // Use second mipmap because we don't usually throw a lot of rays, so this compensates.
+#endif
+ light.rgb *= params.sky_energy;
+ light.a = 0.0;
+
+ } else if (params.sky_mode == SKY_MODE_COLOR) {
+ light.rgb = params.sky_color;
+ light.rgb *= params.sky_energy;
+ light.a = 0.0;
+ } else {
+ light = vec4(0, 0, 0, 0);
+ }
+
+ vec3 ray_dir2 = ray_dir * ray_dir;
+
+#define SH_ACCUM(m_idx, m_value) \
+ { \
+ vec3 l = light.rgb * (m_value); \
+ sh_accum[probe_index].c[m_idx * 3 + 0] += l.r; \
+ sh_accum[probe_index].c[m_idx * 3 + 1] += l.g; \
+ sh_accum[probe_index].c[m_idx * 3 + 2] += l.b; \
+ }
+ SH_ACCUM(0, 0.282095); //l0
+ SH_ACCUM(1, 0.488603 * ray_dir.y); //l1n1
+ SH_ACCUM(2, 0.488603 * ray_dir.z); //l1n0
+ SH_ACCUM(3, 0.488603 * ray_dir.x); //l1p1
+ SH_ACCUM(4, 1.092548 * ray_dir.x * ray_dir.y); //l2n2
+ SH_ACCUM(5, 1.092548 * ray_dir.y * ray_dir.z); //l2n1
+ SH_ACCUM(6, 0.315392 * (3.0 * ray_dir2.z - 1.0)); //l20
+ SH_ACCUM(7, 1.092548 * ray_dir.x * ray_dir.z); //l2p1
+ SH_ACCUM(8, 0.546274 * (ray_dir2.x - ray_dir2.y)); //l2p2
+#if (SH_SIZE == 16)
+ SH_ACCUM(9, 0.590043 * ray_dir.y * (3.0f * ray_dir2.x - ray_dir2.y));
+ SH_ACCUM(10, 2.890611 * ray_dir.y * ray_dir.x * ray_dir.z);
+ SH_ACCUM(11, 0.646360 * ray_dir.y * (-1.0f + 5.0f * ray_dir2.z));
+ SH_ACCUM(12, 0.373176 * (5.0f * ray_dir2.z * ray_dir.z - 3.0f * ray_dir.z));
+ SH_ACCUM(13, 0.457045 * ray_dir.x * (-1.0f + 5.0f * ray_dir2.z));
+ SH_ACCUM(14, 1.445305 * (ray_dir2.x - ray_dir2.y) * ray_dir.z);
+ SH_ACCUM(15, 0.590043 * ray_dir.x * (ray_dir2.x - 3.0f * ray_dir2.y));
+
+#endif
+ }
+
+ for (uint i = 0; i < SH_SIZE; i++) {
+ // store in history texture
+ ivec3 prev_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(params.history_index));
+ ivec2 average_pos = prev_pos.xy;
+
+ vec4 value = vec4(sh_accum[probe_index].c[i * 3 + 0], sh_accum[probe_index].c[i * 3 + 1], sh_accum[probe_index].c[i * 3 + 2], 1.0) * 4.0 / float(params.ray_count);
+
+ ivec4 ivalue = clamp(ivec4(value * float(1 << HISTORY_BITS)), -32768, 32767); //clamp to 16 bits, so higher values don't break average
+
+ ivec4 prev_value = imageLoad(lightprobe_history_texture, prev_pos);
+ ivec4 average = imageLoad(lightprobe_average_texture, average_pos);
+
+ average -= prev_value;
+ average += ivalue;
+
+ imageStore(lightprobe_history_texture, prev_pos, ivalue);
+ imageStore(lightprobe_average_texture, average_pos, average);
+
+ if (params.store_ambient_texture && i == 0) {
+ ivec3 ambient_pos = ivec3(pos, int(params.cascade));
+ vec4 ambient_light = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
+ ambient_light *= 0.88622; // SHL0
+ imageStore(lightprobe_ambient_texture, ambient_pos, ambient_light);
+ }
+ }
+#endif // MODE PROCESS
+
+#ifdef MODE_STORE
+
+ // converting to octahedral in this step is required because
+ // octahedral is much faster to read from the screen than spherical harmonics,
+ // despite the very slight quality loss
+
+ ivec2 sh_pos = (pos / OCT_SIZE) * ivec2(1, SH_SIZE);
+ ivec2 oct_pos = (pos / OCT_SIZE) * (OCT_SIZE + 2) + ivec2(1);
+ ivec2 local_pos = pos % OCT_SIZE;
+
+ //compute the octahedral normal for this texel
+ vec3 normal = octahedron_encode(vec2(local_pos) / float(OCT_SIZE));
+
+ // read the spherical harmonic
+
+ vec3 normal2 = normal * normal;
+ float c[SH_SIZE] = float[](
+
+ 0.282095, //l0
+ 0.488603 * normal.y, //l1n1
+ 0.488603 * normal.z, //l1n0
+ 0.488603 * normal.x, //l1p1
+ 1.092548 * normal.x * normal.y, //l2n2
+ 1.092548 * normal.y * normal.z, //l2n1
+ 0.315392 * (3.0 * normal2.z - 1.0), //l20
+ 1.092548 * normal.x * normal.z, //l2p1
+ 0.546274 * (normal2.x - normal2.y) //l2p2
+#if (SH_SIZE == 16)
+ ,
+ 0.590043 * normal.y * (3.0f * normal2.x - normal2.y),
+ 2.890611 * normal.y * normal.x * normal.z,
+ 0.646360 * normal.y * (-1.0f + 5.0f * normal2.z),
+ 0.373176 * (5.0f * normal2.z * normal.z - 3.0f * normal.z),
+ 0.457045 * normal.x * (-1.0f + 5.0f * normal2.z),
+ 1.445305 * (normal2.x - normal2.y) * normal.z,
+ 0.590043 * normal.x * (normal2.x - 3.0f * normal2.y)
+
+#endif
+ );
+
+ const float l_mult[SH_SIZE] = float[](
+ 1.0,
+ 2.0 / 3.0,
+ 2.0 / 3.0,
+ 2.0 / 3.0,
+ 1.0 / 4.0,
+ 1.0 / 4.0,
+ 1.0 / 4.0,
+ 1.0 / 4.0,
+ 1.0 / 4.0
+#if (SH_SIZE == 16)
+ , // l4 does not contribute to irradiance
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+#endif
+ );
+
+ vec3 irradiance = vec3(0.0);
+ vec3 radiance = vec3(0.0);
+
+ for (uint i = 0; i < SH_SIZE; i++) {
+ // store in history texture
+ ivec2 average_pos = sh_pos + ivec2(0, i);
+ ivec4 average = imageLoad(lightprobe_average_texture, average_pos);
+
+ vec4 sh = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
+
+ vec3 m = sh.rgb * c[i] * 4.0;
+
+ irradiance += m * l_mult[i];
+ radiance += m;
+ }
+
+ //encode RGBE9995 for the final texture
+
+ uint irradiance_rgbe = rgbe_encode(irradiance);
+ uint radiance_rgbe = rgbe_encode(radiance);
+
+ //store in octahedral map
+
+ ivec3 texture_pos = ivec3(oct_pos, int(params.cascade));
+ ivec3 copy_to[4] = ivec3[](ivec3(-2, -2, -2), ivec3(-2, -2, -2), ivec3(-2, -2, -2), ivec3(-2, -2, -2));
+ copy_to[0] = texture_pos + ivec3(local_pos, 0);
+
+ if (local_pos == ivec2(0, 0)) {
+ copy_to[1] = texture_pos + ivec3(OCT_SIZE - 1, -1, 0);
+ copy_to[2] = texture_pos + ivec3(-1, OCT_SIZE - 1, 0);
+ copy_to[3] = texture_pos + ivec3(OCT_SIZE, OCT_SIZE, 0);
+ } else if (local_pos == ivec2(OCT_SIZE - 1, 0)) {
+ copy_to[1] = texture_pos + ivec3(0, -1, 0);
+ copy_to[2] = texture_pos + ivec3(OCT_SIZE, OCT_SIZE - 1, 0);
+ copy_to[3] = texture_pos + ivec3(-1, OCT_SIZE, 0);
+ } else if (local_pos == ivec2(0, OCT_SIZE - 1)) {
+ copy_to[1] = texture_pos + ivec3(-1, 0, 0);
+ copy_to[2] = texture_pos + ivec3(OCT_SIZE - 1, OCT_SIZE, 0);
+ copy_to[3] = texture_pos + ivec3(OCT_SIZE, -1, 0);
+ } else if (local_pos == ivec2(OCT_SIZE - 1, OCT_SIZE - 1)) {
+ copy_to[1] = texture_pos + ivec3(0, OCT_SIZE, 0);
+ copy_to[2] = texture_pos + ivec3(OCT_SIZE, 0, 0);
+ copy_to[3] = texture_pos + ivec3(-1, -1, 0);
+ } else if (local_pos.y == 0) {
+ copy_to[1] = texture_pos + ivec3(OCT_SIZE - local_pos.x - 1, local_pos.y - 1, 0);
+ } else if (local_pos.x == 0) {
+ copy_to[1] = texture_pos + ivec3(local_pos.x - 1, OCT_SIZE - local_pos.y - 1, 0);
+ } else if (local_pos.y == OCT_SIZE - 1) {
+ copy_to[1] = texture_pos + ivec3(OCT_SIZE - local_pos.x - 1, local_pos.y + 1, 0);
+ } else if (local_pos.x == OCT_SIZE - 1) {
+ copy_to[1] = texture_pos + ivec3(local_pos.x + 1, OCT_SIZE - local_pos.y - 1, 0);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (copy_to[i] == ivec3(-2, -2, -2)) {
+ continue;
+ }
+ imageStore(lightprobe_texture_data, copy_to[i], uvec4(irradiance_rgbe));
+ imageStore(lightprobe_texture_data, copy_to[i] + ivec3(0, 0, int(params.max_cascades)), uvec4(radiance_rgbe));
+ }
+
+#endif
+
+#ifdef MODE_SCROLL
+
+ ivec3 probe_cell;
+ probe_cell.x = pos.x % int(params.probe_axis_size);
+ probe_cell.y = pos.y;
+ probe_cell.z = pos.x / int(params.probe_axis_size);
+
+ ivec3 read_probe = probe_cell - params.scroll;
+
+ if (all(greaterThanEqual(read_probe, ivec3(0))) && all(lessThan(read_probe, ivec3(params.probe_axis_size)))) {
+ // can scroll
+ ivec2 tex_pos;
+ tex_pos = read_probe.xy;
+ tex_pos.x += read_probe.z * int(params.probe_axis_size);
+
+ //scroll
+ for (uint j = 0; j < params.history_size; j++) {
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from history texture
+ ivec3 src_pos = ivec3(tex_pos.x, tex_pos.y * SH_SIZE + i, int(j));
+ ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
+ ivec4 value = imageLoad(lightprobe_history_texture, src_pos);
+ imageStore(lightprobe_history_scroll_texture, dst_pos, value);
+ }
+ }
+
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from average texture
+ ivec2 src_pos = ivec2(tex_pos.x, tex_pos.y * SH_SIZE + i);
+ ivec2 dst_pos = ivec2(pos.x, pos.y * SH_SIZE + i);
+ ivec4 value = imageLoad(lightprobe_average_texture, src_pos);
+ imageStore(lightprobe_average_scroll_texture, dst_pos, value);
+ }
+ } else if (params.cascade < params.max_cascades - 1) {
+ //can't scroll, must look for position in parent cascade
+
+ //to global coords
+ float cell_to_probe = float(params.grid_size.x / float(params.probe_axis_size - 1));
+
+ float probe_cell_size = cell_to_probe / cascades.data[params.cascade].to_cell;
+ vec3 probe_pos = cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size;
+
+ //to parent local coords
+ float probe_cell_size_next = cell_to_probe / cascades.data[params.cascade + 1].to_cell;
+ probe_pos -= cascades.data[params.cascade + 1].offset;
+ probe_pos /= probe_cell_size_next;
+
+ ivec3 probe_posi = ivec3(probe_pos);
+ //add up all light, no need to use occlusion here, since occlusion will do its work afterwards
+
+ vec4 average_light[SH_SIZE] = vec4[](vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0)
+#if (SH_SIZE == 16)
+ ,
+ vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0)
+#endif
+ );
+ float total_weight = 0.0;
+
+ for (int i = 0; i < 8; i++) {
+ ivec3 offset = probe_posi + ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
+
+ vec3 trilinear = vec3(1.0) - abs(probe_pos - vec3(offset));
+ float weight = trilinear.x * trilinear.y * trilinear.z;
+
+ ivec2 tex_pos;
+ tex_pos = offset.xy;
+ tex_pos.x += offset.z * int(params.probe_axis_size);
+
+ for (int j = 0; j < SH_SIZE; j++) {
+ // copy from history texture
+ ivec2 src_pos = ivec2(tex_pos.x, tex_pos.y * SH_SIZE + j);
+ ivec4 average = imageLoad(lightprobe_average_parent_texture, src_pos);
+ vec4 value = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
+ average_light[j] += value * weight;
+ }
+
+ total_weight += weight;
+ }
+
+ if (total_weight > 0.0) {
+ total_weight = 1.0 / total_weight;
+ }
+ //store the averaged values everywhere
+
+ for (int i = 0; i < SH_SIZE; i++) {
+ ivec4 ivalue = clamp(ivec4(average_light[i] * total_weight * float(1 << HISTORY_BITS)), ivec4(-32768), ivec4(32767)); //clamp to 16 bits, so higher values don't break average
+ // copy from history texture
+ ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, 0);
+ for (uint j = 0; j < params.history_size; j++) {
+ dst_pos.z = int(j);
+ imageStore(lightprobe_history_scroll_texture, dst_pos, ivalue);
+ }
+
+ ivalue *= int(params.history_size); //average needs to have all history added up
+ imageStore(lightprobe_average_scroll_texture, dst_pos.xy, ivalue);
+ }
+
+ } else {
+ //scroll at the edge of the highest cascade, just copy what is there,
+ //since its the closest we have anyway
+
+ for (uint j = 0; j < params.history_size; j++) {
+ ivec2 tex_pos;
+ tex_pos = probe_cell.xy;
+ tex_pos.x += probe_cell.z * int(params.probe_axis_size);
+
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from history texture
+ ivec3 src_pos = ivec3(tex_pos.x, tex_pos.y * SH_SIZE + i, int(j));
+ ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
+ ivec4 value = imageLoad(lightprobe_history_texture, dst_pos);
+ imageStore(lightprobe_history_scroll_texture, dst_pos, value);
+ }
+ }
+
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from average texture
+ ivec2 spos = ivec2(pos.x, pos.y * SH_SIZE + i);
+ ivec4 average = imageLoad(lightprobe_average_texture, spos);
+ imageStore(lightprobe_average_scroll_texture, spos, average);
+ }
+ }
+
+#endif
+
+#ifdef MODE_SCROLL_STORE
+
+ //do not update probe texture, as these will be updated later
+
+ for (uint j = 0; j < params.history_size; j++) {
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from history texture
+ ivec3 spos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
+ ivec4 value = imageLoad(lightprobe_history_scroll_texture, spos);
+ imageStore(lightprobe_history_texture, spos, value);
+ }
+ }
+
+ for (int i = 0; i < SH_SIZE; i++) {
+ // copy from average texture
+ ivec2 spos = ivec2(pos.x, pos.y * SH_SIZE + i);
+ ivec4 average = imageLoad(lightprobe_average_scroll_texture, spos);
+ imageStore(lightprobe_average_texture, spos, average);
+ }
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_preprocess.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_preprocess.glsl
new file mode 100644
index 0000000000..4d9fa85a74
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sdfgi_preprocess.glsl
@@ -0,0 +1,1056 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MODE_JUMPFLOOD_OPTIMIZED
+#define GROUP_SIZE 8
+
+layout(local_size_x = GROUP_SIZE, local_size_y = GROUP_SIZE, local_size_z = GROUP_SIZE) in;
+
+#elif defined(MODE_OCCLUSION) || defined(MODE_SCROLL)
+//buffer layout
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#else
+//grid layout
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#endif
+
+#if defined(MODE_INITIALIZE_JUMP_FLOOD) || defined(MODE_INITIALIZE_JUMP_FLOOD_HALF)
+layout(r16ui, set = 0, binding = 1) uniform restrict readonly uimage3D src_color;
+layout(rgba8ui, set = 0, binding = 2) uniform restrict writeonly uimage3D dst_positions;
+#endif
+
+#ifdef MODE_UPSCALE_JUMP_FLOOD
+layout(r16ui, set = 0, binding = 1) uniform restrict readonly uimage3D src_color;
+layout(rgba8ui, set = 0, binding = 2) uniform restrict readonly uimage3D src_positions_half;
+layout(rgba8ui, set = 0, binding = 3) uniform restrict writeonly uimage3D dst_positions;
+#endif
+
+#if defined(MODE_JUMPFLOOD) || defined(MODE_JUMPFLOOD_OPTIMIZED)
+layout(rgba8ui, set = 0, binding = 1) uniform restrict readonly uimage3D src_positions;
+layout(rgba8ui, set = 0, binding = 2) uniform restrict writeonly uimage3D dst_positions;
+#endif
+
+#ifdef MODE_JUMPFLOOD_OPTIMIZED
+
+shared uvec4 group_positions[(GROUP_SIZE + 2) * (GROUP_SIZE + 2) * (GROUP_SIZE + 2)]; //4x4x4 with margins
+
+void group_store(ivec3 p_pos, uvec4 p_value) {
+ uint offset = uint(p_pos.z * (GROUP_SIZE + 2) * (GROUP_SIZE + 2) + p_pos.y * (GROUP_SIZE + 2) + p_pos.x);
+ group_positions[offset] = p_value;
+}
+
+uvec4 group_load(ivec3 p_pos) {
+ uint offset = uint(p_pos.z * (GROUP_SIZE + 2) * (GROUP_SIZE + 2) + p_pos.y * (GROUP_SIZE + 2) + p_pos.x);
+ return group_positions[offset];
+}
+
+#endif
+
+#ifdef MODE_OCCLUSION
+
+layout(r16ui, set = 0, binding = 1) uniform restrict readonly uimage3D src_color;
+layout(r8, set = 0, binding = 2) uniform restrict image3D dst_occlusion[8];
+layout(r32ui, set = 0, binding = 3) uniform restrict readonly uimage3D src_facing;
+
+const uvec2 group_size_offset[11] = uvec2[](uvec2(1, 0), uvec2(3, 1), uvec2(6, 4), uvec2(10, 10), uvec2(15, 20), uvec2(21, 35), uvec2(28, 56), uvec2(36, 84), uvec2(42, 120), uvec2(46, 162), uvec2(48, 208));
+const uint group_pos[256] = uint[](0,
+ 65536, 256, 1,
+ 131072, 65792, 512, 65537, 257, 2,
+ 196608, 131328, 66048, 768, 131073, 65793, 513, 65538, 258, 3,
+ 262144, 196864, 131584, 66304, 1024, 196609, 131329, 66049, 769, 131074, 65794, 514, 65539, 259, 4,
+ 327680, 262400, 197120, 131840, 66560, 1280, 262145, 196865, 131585, 66305, 1025, 196610, 131330, 66050, 770, 131075, 65795, 515, 65540, 260, 5,
+ 393216, 327936, 262656, 197376, 132096, 66816, 1536, 327681, 262401, 197121, 131841, 66561, 1281, 262146, 196866, 131586, 66306, 1026, 196611, 131331, 66051, 771, 131076, 65796, 516, 65541, 261, 6,
+ 458752, 393472, 328192, 262912, 197632, 132352, 67072, 1792, 393217, 327937, 262657, 197377, 132097, 66817, 1537, 327682, 262402, 197122, 131842, 66562, 1282, 262147, 196867, 131587, 66307, 1027, 196612, 131332, 66052, 772, 131077, 65797, 517, 65542, 262, 7,
+ 459008, 393728, 328448, 263168, 197888, 132608, 67328, 458753, 393473, 328193, 262913, 197633, 132353, 67073, 1793, 393218, 327938, 262658, 197378, 132098, 66818, 1538, 327683, 262403, 197123, 131843, 66563, 1283, 262148, 196868, 131588, 66308, 1028, 196613, 131333, 66053, 773, 131078, 65798, 518, 65543, 263,
+ 459264, 393984, 328704, 263424, 198144, 132864, 459009, 393729, 328449, 263169, 197889, 132609, 67329, 458754, 393474, 328194, 262914, 197634, 132354, 67074, 1794, 393219, 327939, 262659, 197379, 132099, 66819, 1539, 327684, 262404, 197124, 131844, 66564, 1284, 262149, 196869, 131589, 66309, 1029, 196614, 131334, 66054, 774, 131079, 65799, 519,
+ 459520, 394240, 328960, 263680, 198400, 459265, 393985, 328705, 263425, 198145, 132865, 459010, 393730, 328450, 263170, 197890, 132610, 67330, 458755, 393475, 328195, 262915, 197635, 132355, 67075, 1795, 393220, 327940, 262660, 197380, 132100, 66820, 1540, 327685, 262405, 197125, 131845, 66565, 1285, 262150, 196870, 131590, 66310, 1030, 196615, 131335, 66055, 775);
+
+shared uint occlusion_facing[((OCCLUSION_SIZE * 2) * (OCCLUSION_SIZE * 2) * (OCCLUSION_SIZE * 2)) / 4];
+
+uint get_facing(ivec3 p_pos) {
+ uint ofs = uint(p_pos.z * OCCLUSION_SIZE * 2 * OCCLUSION_SIZE * 2 + p_pos.y * OCCLUSION_SIZE * 2 + p_pos.x);
+ uint v = occlusion_facing[ofs / 4];
+ return (v >> ((ofs % 4) * 8)) & 0xFF;
+}
+
+#endif
+
+#ifdef MODE_STORE
+
+layout(rgba8ui, set = 0, binding = 1) uniform restrict readonly uimage3D src_positions;
+layout(r16ui, set = 0, binding = 2) uniform restrict readonly uimage3D src_albedo;
+layout(r8, set = 0, binding = 3) uniform restrict readonly image3D src_occlusion[8];
+layout(r32ui, set = 0, binding = 4) uniform restrict readonly uimage3D src_light;
+layout(r32ui, set = 0, binding = 5) uniform restrict readonly uimage3D src_light_aniso;
+layout(r32ui, set = 0, binding = 6) uniform restrict readonly uimage3D src_facing;
+
+layout(r8, set = 0, binding = 7) uniform restrict writeonly image3D dst_sdf;
+layout(r16ui, set = 0, binding = 8) uniform restrict writeonly uimage3D dst_occlusion;
+
+layout(set = 0, binding = 10, std430) restrict buffer DispatchData {
+ uint x;
+ uint y;
+ uint z;
+ uint total_count;
+}
+dispatch_data;
+
+struct ProcessVoxel {
+ uint position; // xyz 7 bit packed, extra 11 bits for neighbors.
+ uint albedo; //rgb bits 0-15 albedo, bits 16-21 are normal bits (set if geometry exists toward that side), extra 11 bits for neibhbours
+ uint light; //rgbe8985 encoded total saved light, extra 2 bits for neighbours
+ uint light_aniso; //55555 light anisotropy, extra 2 bits for neighbours
+ //total neighbours: 26
+};
+
+layout(set = 0, binding = 11, std430) restrict buffer writeonly ProcessVoxels {
+ ProcessVoxel data[];
+}
+dst_process_voxels;
+
+shared ProcessVoxel store_positions[4 * 4 * 4];
+shared uint store_position_count;
+shared uint store_from_index;
+#endif
+
+#ifdef MODE_SCROLL
+
+layout(r16ui, set = 0, binding = 1) uniform restrict writeonly uimage3D dst_albedo;
+layout(r32ui, set = 0, binding = 2) uniform restrict writeonly uimage3D dst_facing;
+layout(r32ui, set = 0, binding = 3) uniform restrict writeonly uimage3D dst_light;
+layout(r32ui, set = 0, binding = 4) uniform restrict writeonly uimage3D dst_light_aniso;
+
+layout(set = 0, binding = 5, std430) restrict buffer readonly DispatchData {
+ uint x;
+ uint y;
+ uint z;
+ uint total_count;
+}
+dispatch_data;
+
+struct ProcessVoxel {
+ uint position; // xyz 7 bit packed, extra 11 bits for neighbors.
+ uint albedo; //rgb bits 0-15 albedo, bits 16-21 are normal bits (set if geometry exists toward that side), extra 11 bits for neibhbours
+ uint light; //rgbe8985 encoded total saved light, extra 2 bits for neighbours
+ uint light_aniso; //55555 light anisotropy, extra 2 bits for neighbours
+ //total neighbours: 26
+};
+
+layout(set = 0, binding = 6, std430) restrict buffer readonly ProcessVoxels {
+ ProcessVoxel data[];
+}
+src_process_voxels;
+
+#endif
+
+#ifdef MODE_SCROLL_OCCLUSION
+
+layout(r8, set = 0, binding = 1) uniform restrict image3D dst_occlusion[8];
+layout(r16ui, set = 0, binding = 2) uniform restrict readonly uimage3D src_occlusion;
+
+#endif
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec3 scroll;
+
+ int grid_size;
+
+ ivec3 probe_offset;
+ int step_size;
+
+ bool half_size;
+ uint occlusion_index;
+ int cascade;
+ uint pad;
+}
+params;
+
+void main() {
+#ifdef MODE_SCROLL
+
+ // Pixel being shaded
+ int index = int(gl_GlobalInvocationID.x);
+ if (index >= dispatch_data.total_count) { //too big
+ return;
+ }
+
+ ivec3 read_pos = (ivec3(src_process_voxels.data[index].position) >> ivec3(0, 7, 14)) & ivec3(0x7F);
+ ivec3 write_pos = read_pos + params.scroll;
+
+ if (any(lessThan(write_pos, ivec3(0))) || any(greaterThanEqual(write_pos, ivec3(params.grid_size)))) {
+ return; // Fits outside the 3D texture, don't do anything.
+ }
+
+ uint albedo = ((src_process_voxels.data[index].albedo & 0x7FFF) << 1) | 1; //add solid bit
+ imageStore(dst_albedo, write_pos, uvec4(albedo));
+
+ uint facing = (src_process_voxels.data[index].albedo >> 15) & 0x3F; //6 anisotropic facing bits
+ imageStore(dst_facing, write_pos, uvec4(facing));
+
+ uint light = src_process_voxels.data[index].light & 0x3fffffff; //30 bits of RGBE8985
+ imageStore(dst_light, write_pos, uvec4(light));
+
+ uint light_aniso = src_process_voxels.data[index].light_aniso & 0x3fffffff; //30 bits of 6 anisotropic 5 bits values
+ imageStore(dst_light_aniso, write_pos, uvec4(light_aniso));
+
+#endif
+
+#ifdef MODE_SCROLL_OCCLUSION
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+ if (any(greaterThanEqual(pos, ivec3(params.grid_size) - abs(params.scroll)))) { //too large, do nothing
+ return;
+ }
+
+ ivec3 read_pos = pos + max(ivec3(0), -params.scroll);
+ ivec3 write_pos = pos + max(ivec3(0), params.scroll);
+
+ read_pos.z += params.cascade * params.grid_size;
+ uint occlusion = imageLoad(src_occlusion, read_pos).r;
+ read_pos.x += params.grid_size;
+ occlusion |= imageLoad(src_occlusion, read_pos).r << 16;
+
+ const uint occlusion_shift[8] = uint[](12, 8, 4, 0, 28, 24, 20, 16);
+
+ for (uint i = 0; i < 8; i++) {
+ float o = float((occlusion >> occlusion_shift[i]) & 0xF) / 15.0;
+ imageStore(dst_occlusion[i], write_pos, vec4(o));
+ }
+
+#endif
+
+#ifdef MODE_INITIALIZE_JUMP_FLOOD
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+
+ uint c = imageLoad(src_color, pos).r;
+ uvec4 v;
+ if (bool(c & 0x1)) {
+ //bit set means this is solid
+ v.xyz = uvec3(pos);
+ v.w = 255; //not zero means used
+ } else {
+ v.xyz = uvec3(0);
+ v.w = 0; // zero means unused
+ }
+
+ imageStore(dst_positions, pos, v);
+#endif
+
+#ifdef MODE_INITIALIZE_JUMP_FLOOD_HALF
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+ ivec3 base_pos = pos * 2;
+
+ //since we store in half size, lets kind of randomize what we store, so
+ //the half size jump flood has a bit better chance to find something
+ uvec4 closest[8];
+ int closest_count = 0;
+
+ for (uint i = 0; i < 8; i++) {
+ ivec3 src_pos = base_pos + ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
+ uint c = imageLoad(src_color, src_pos).r;
+ if (bool(c & 1)) {
+ uvec4 v = uvec4(uvec3(src_pos), 255);
+ closest[closest_count] = v;
+ closest_count++;
+ }
+ }
+
+ if (closest_count == 0) {
+ imageStore(dst_positions, pos, uvec4(0));
+ } else {
+ ivec3 indexv = (pos & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+ int index = (indexv.x | indexv.y | indexv.z) % closest_count;
+ imageStore(dst_positions, pos, closest[index]);
+ }
+
+#endif
+
+#ifdef MODE_JUMPFLOOD
+
+ //regular jumpflood, efficient for large steps, inefficient for small steps
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+
+ vec3 posf = vec3(pos);
+
+ if (params.half_size) {
+ posf = posf * 2.0 + 0.5;
+ }
+
+ uvec4 p = imageLoad(src_positions, pos);
+
+ if (!params.half_size && p == uvec4(uvec3(pos), 255)) {
+ imageStore(dst_positions, pos, p);
+ return; //points to itself and valid, nothing better can be done, just pass
+ }
+
+ float p_dist;
+
+ if (p.w != 0) {
+ p_dist = distance(posf, vec3(p.xyz));
+ } else {
+ p_dist = 0.0; //should not matter
+ }
+
+ const uint offset_count = 26;
+ const ivec3 offsets[offset_count] = ivec3[](
+ ivec3(-1, -1, -1),
+ ivec3(-1, -1, 0),
+ ivec3(-1, -1, 1),
+ ivec3(-1, 0, -1),
+ ivec3(-1, 0, 0),
+ ivec3(-1, 0, 1),
+ ivec3(-1, 1, -1),
+ ivec3(-1, 1, 0),
+ ivec3(-1, 1, 1),
+ ivec3(0, -1, -1),
+ ivec3(0, -1, 0),
+ ivec3(0, -1, 1),
+ ivec3(0, 0, -1),
+ ivec3(0, 0, 1),
+ ivec3(0, 1, -1),
+ ivec3(0, 1, 0),
+ ivec3(0, 1, 1),
+ ivec3(1, -1, -1),
+ ivec3(1, -1, 0),
+ ivec3(1, -1, 1),
+ ivec3(1, 0, -1),
+ ivec3(1, 0, 0),
+ ivec3(1, 0, 1),
+ ivec3(1, 1, -1),
+ ivec3(1, 1, 0),
+ ivec3(1, 1, 1));
+
+ for (uint i = 0; i < offset_count; i++) {
+ ivec3 ofs = pos + offsets[i] * params.step_size;
+ if (any(lessThan(ofs, ivec3(0))) || any(greaterThanEqual(ofs, ivec3(params.grid_size)))) {
+ continue;
+ }
+ uvec4 q = imageLoad(src_positions, ofs);
+
+ if (q.w == 0) {
+ continue; //was not initialized yet, ignore
+ }
+
+ float q_dist = distance(posf, vec3(q.xyz));
+ if (p.w == 0 || q_dist < p_dist) {
+ p = q; //just replace because current is unused
+ p_dist = q_dist;
+ }
+ }
+
+ imageStore(dst_positions, pos, p);
+#endif
+
+#ifdef MODE_JUMPFLOOD_OPTIMIZED
+ //optimized version using shared compute memory
+
+ ivec3 group_offset = ivec3(gl_WorkGroupID.xyz) % params.step_size;
+ ivec3 group_pos = group_offset + (ivec3(gl_WorkGroupID.xyz) / params.step_size) * ivec3(GROUP_SIZE * params.step_size);
+
+ //load data into local group memory
+
+ if (all(lessThan(ivec3(gl_LocalInvocationID.xyz), ivec3((GROUP_SIZE + 2) / 2)))) {
+ //use this thread for loading, this method uses less threads for this but its simpler and less divergent
+ ivec3 base_pos = ivec3(gl_LocalInvocationID.xyz) * 2;
+ for (uint i = 0; i < 8; i++) {
+ ivec3 load_pos = base_pos + ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
+ ivec3 load_global_pos = group_pos + (load_pos - ivec3(1)) * params.step_size;
+ uvec4 q;
+ if (all(greaterThanEqual(load_global_pos, ivec3(0))) && all(lessThan(load_global_pos, ivec3(params.grid_size)))) {
+ q = imageLoad(src_positions, load_global_pos);
+ } else {
+ q = uvec4(0); //unused
+ }
+
+ group_store(load_pos, q);
+ }
+ }
+
+ ivec3 global_pos = group_pos + ivec3(gl_LocalInvocationID.xyz) * params.step_size;
+
+ if (any(lessThan(global_pos, ivec3(0))) || any(greaterThanEqual(global_pos, ivec3(params.grid_size)))) {
+ return; //do nothing else, end here because outside range
+ }
+
+ //sync
+ groupMemoryBarrier();
+ barrier();
+
+ ivec3 local_pos = ivec3(gl_LocalInvocationID.xyz) + ivec3(1);
+
+ const uint offset_count = 27;
+ const ivec3 offsets[offset_count] = ivec3[](
+ ivec3(-1, -1, -1),
+ ivec3(-1, -1, 0),
+ ivec3(-1, -1, 1),
+ ivec3(-1, 0, -1),
+ ivec3(-1, 0, 0),
+ ivec3(-1, 0, 1),
+ ivec3(-1, 1, -1),
+ ivec3(-1, 1, 0),
+ ivec3(-1, 1, 1),
+ ivec3(0, -1, -1),
+ ivec3(0, -1, 0),
+ ivec3(0, -1, 1),
+ ivec3(0, 0, -1),
+ ivec3(0, 0, 0),
+ ivec3(0, 0, 1),
+ ivec3(0, 1, -1),
+ ivec3(0, 1, 0),
+ ivec3(0, 1, 1),
+ ivec3(1, -1, -1),
+ ivec3(1, -1, 0),
+ ivec3(1, -1, 1),
+ ivec3(1, 0, -1),
+ ivec3(1, 0, 0),
+ ivec3(1, 0, 1),
+ ivec3(1, 1, -1),
+ ivec3(1, 1, 0),
+ ivec3(1, 1, 1));
+
+ //only makes sense if point is inside screen
+ uvec4 closest = uvec4(0);
+ float closest_dist = 0.0;
+
+ vec3 posf = vec3(global_pos);
+
+ if (params.half_size) {
+ posf = posf * 2.0 + 0.5;
+ }
+
+ for (uint i = 0; i < offset_count; i++) {
+ uvec4 point = group_load(local_pos + offsets[i]);
+
+ if (point.w == 0) {
+ continue; //was not initialized yet, ignore
+ }
+
+ float dist = distance(posf, vec3(point.xyz));
+ if (closest.w == 0 || dist < closest_dist) {
+ closest = point;
+ closest_dist = dist;
+ }
+ }
+
+ imageStore(dst_positions, global_pos, closest);
+
+#endif
+
+#ifdef MODE_UPSCALE_JUMP_FLOOD
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+
+ uint c = imageLoad(src_color, pos).r;
+ uvec4 v;
+ if (bool(c & 1)) {
+ //bit set means this is solid
+ v.xyz = uvec3(pos);
+ v.w = 255; //not zero means used
+ } else {
+ v = imageLoad(src_positions_half, pos >> 1);
+ float d = length(vec3(ivec3(v.xyz) - pos));
+
+ ivec3 vbase = ivec3(v.xyz - (v.xyz & uvec3(1)));
+
+ //search around if there is a better candidate from the same block
+ for (int i = 0; i < 8; i++) {
+ ivec3 bits = ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
+ ivec3 p = vbase + bits;
+
+ float d2 = length(vec3(p - pos));
+ if (d2 < d) { //check valid distance before test so we avoid a read
+ uint c2 = imageLoad(src_color, p).r;
+ if (bool(c2 & 1)) {
+ v.xyz = uvec3(p);
+ d = d2;
+ }
+ }
+ }
+
+ //could validate better position..
+ }
+
+ imageStore(dst_positions, pos, v);
+
+#endif
+
+#ifdef MODE_OCCLUSION
+
+ uint invocation_idx = uint(gl_LocalInvocationID.x);
+ ivec3 region = ivec3(gl_WorkGroupID);
+
+ ivec3 region_offset = -ivec3(OCCLUSION_SIZE);
+ region_offset += region * OCCLUSION_SIZE * 2;
+ region_offset += params.probe_offset * OCCLUSION_SIZE;
+
+ if (params.scroll != ivec3(0)) {
+ //validate scroll region
+ ivec3 region_offset_to = region_offset + ivec3(OCCLUSION_SIZE * 2);
+ uvec3 scroll_mask = uvec3(notEqual(params.scroll, ivec3(0))); //save which axes acre scrolling
+ ivec3 scroll_from = mix(ivec3(0), ivec3(params.grid_size) + params.scroll, lessThan(params.scroll, ivec3(0)));
+ ivec3 scroll_to = mix(ivec3(params.grid_size), params.scroll, greaterThan(params.scroll, ivec3(0)));
+
+ if ((uvec3(lessThanEqual(region_offset_to, scroll_from)) | uvec3(greaterThanEqual(region_offset, scroll_to))) * scroll_mask == scroll_mask) { //all axes that scroll are out, exit
+ return; //region outside scroll bounds, quit
+ }
+ }
+
+#define OCC_HALF_SIZE (OCCLUSION_SIZE / 2)
+
+ ivec3 local_ofs = ivec3(uvec3(invocation_idx % OCC_HALF_SIZE, (invocation_idx % (OCC_HALF_SIZE * OCC_HALF_SIZE)) / OCC_HALF_SIZE, invocation_idx / (OCC_HALF_SIZE * OCC_HALF_SIZE))) * 4;
+
+ /* for(int i=0;i<64;i++) {
+ ivec3 offset = region_offset + local_ofs + ((ivec3(i) >> ivec3(0,2,4)) & ivec3(3,3,3));
+ uint facig =
+ if (all(greaterThanEqual(offset,ivec3(0))) && all(lessThan(offset,ivec3(params.grid_size)))) {*/
+
+ for (int i = 0; i < 16; i++) { //skip x, so it can be packed
+
+ ivec3 offset = local_ofs + ((ivec3(i * 4) >> ivec3(0, 2, 4)) & ivec3(3, 3, 3));
+
+ uint facing_pack = 0;
+ for (int j = 0; j < 4; j++) {
+ ivec3 foffset = region_offset + offset + ivec3(j, 0, 0);
+ if (all(greaterThanEqual(foffset, ivec3(0))) && all(lessThan(foffset, ivec3(params.grid_size)))) {
+ uint f = imageLoad(src_facing, foffset).r;
+ facing_pack |= f << (j * 8);
+ }
+ }
+
+ occlusion_facing[(offset.z * (OCCLUSION_SIZE * 2 * OCCLUSION_SIZE * 2) + offset.y * (OCCLUSION_SIZE * 2) + offset.x) / 4] = facing_pack;
+ }
+
+ //sync occlusion saved
+ groupMemoryBarrier();
+ barrier();
+
+ //process occlusion
+
+#define OCC_STEPS (OCCLUSION_SIZE * 3 - 2)
+#define OCC_HALF_STEPS (OCC_STEPS / 2)
+
+ for (int step = 0; step < OCC_STEPS; step++) {
+ bool shrink = step >= OCC_HALF_STEPS;
+ int occ_step = shrink ? OCC_HALF_STEPS - (step - OCC_HALF_STEPS) - 1 : step;
+
+ if (invocation_idx < group_size_offset[occ_step].x) {
+ uint pv = group_pos[group_size_offset[occ_step].y + invocation_idx];
+ ivec3 proc_abs = (ivec3(int(pv)) >> ivec3(0, 8, 16)) & ivec3(0xFF);
+
+ if (shrink) {
+ proc_abs = ivec3(OCCLUSION_SIZE) - proc_abs - ivec3(1);
+ }
+
+ for (int i = 0; i < 8; i++) {
+ ivec3 bits = ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
+ ivec3 proc_sign = bits * 2 - 1;
+ ivec3 local_offset = ivec3(OCCLUSION_SIZE) + proc_abs * proc_sign - (ivec3(1) - bits);
+ ivec3 offset = local_offset + region_offset;
+ if (all(greaterThanEqual(offset, ivec3(0))) && all(lessThan(offset, ivec3(params.grid_size)))) {
+ float occ;
+
+ uint facing = get_facing(local_offset);
+
+ if (facing != 0) { //solid
+ occ = 0.0;
+ } else if (step == 0) {
+#if 0
+ occ = 0.0;
+ if (get_facing(local_offset - ivec3(proc_sign.x,0,0))==0) {
+ occ+=1.0;
+ }
+ if (get_facing(local_offset - ivec3(0,proc_sign.y,0))==0) {
+ occ+=1.0;
+ }
+ if (get_facing(local_offset - ivec3(0,0,proc_sign.z))==0) {
+ occ+=1.0;
+ }
+ /*
+ if (get_facing(local_offset - proc_sign)==0) {
+ occ+=1.0;
+ }*/
+
+ occ/=3.0;
+#endif
+ occ = 1.0;
+
+ } else {
+ ivec3 read_dir = -proc_sign;
+
+ ivec3 major_axis;
+ if (proc_abs.x < proc_abs.y) {
+ if (proc_abs.z < proc_abs.y) {
+ major_axis = ivec3(0, 1, 0);
+ } else {
+ major_axis = ivec3(0, 0, 1);
+ }
+ } else {
+ if (proc_abs.z < proc_abs.x) {
+ major_axis = ivec3(1, 0, 0);
+ } else {
+ major_axis = ivec3(0, 0, 1);
+ }
+ }
+
+ float avg = 0.0;
+ occ = 0.0;
+
+ ivec3 read_x = offset + ivec3(read_dir.x, 0, 0) + (proc_abs.x == 0 ? major_axis * read_dir : ivec3(0));
+ ivec3 read_y = offset + ivec3(0, read_dir.y, 0) + (proc_abs.y == 0 ? major_axis * read_dir : ivec3(0));
+ ivec3 read_z = offset + ivec3(0, 0, read_dir.z) + (proc_abs.z == 0 ? major_axis * read_dir : ivec3(0));
+
+ uint facing_x = get_facing(read_x - region_offset);
+ if (facing_x == 0) {
+ if (all(greaterThanEqual(read_x, ivec3(0))) && all(lessThan(read_x, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_x).r;
+ avg += 1.0;
+ }
+ } else {
+ if (proc_abs.x != 0) { //do not occlude from voxels in the opposite octant
+ avg += 1.0;
+ }
+ }
+
+ uint facing_y = get_facing(read_y - region_offset);
+ if (facing_y == 0) {
+ if (all(greaterThanEqual(read_y, ivec3(0))) && all(lessThan(read_y, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_y).r;
+ avg += 1.0;
+ }
+ } else {
+ if (proc_abs.y != 0) {
+ avg += 1.0;
+ }
+ }
+
+ uint facing_z = get_facing(read_z - region_offset);
+ if (facing_z == 0) {
+ if (all(greaterThanEqual(read_z, ivec3(0))) && all(lessThan(read_z, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_z).r;
+ avg += 1.0;
+ }
+ } else {
+ if (proc_abs.z != 0) {
+ avg += 1.0;
+ }
+ }
+
+ if (avg > 0.0) {
+ occ /= avg;
+ }
+ }
+
+ imageStore(dst_occlusion[params.occlusion_index], offset, vec4(occ));
+ }
+ }
+ }
+
+ groupMemoryBarrier();
+ barrier();
+ }
+#if 1
+ //bias solid voxels away
+
+ for (int i = 0; i < 64; i++) {
+ ivec3 local_offset = local_ofs + ((ivec3(i) >> ivec3(0, 2, 4)) & ivec3(3, 3, 3));
+ ivec3 offset = region_offset + local_offset;
+
+ if (all(greaterThanEqual(offset, ivec3(0))) && all(lessThan(offset, ivec3(params.grid_size)))) {
+ uint facing = get_facing(local_offset);
+
+ if (facing != 0) {
+ //only work on solids
+
+ ivec3 proc_pos = local_offset - ivec3(OCCLUSION_SIZE);
+ proc_pos += mix(ivec3(0), ivec3(1), greaterThanEqual(proc_pos, ivec3(0)));
+
+ float avg = 0.0;
+ float occ = 0.0;
+
+ ivec3 read_dir = -sign(proc_pos);
+ ivec3 read_dir_x = ivec3(read_dir.x, 0, 0);
+ ivec3 read_dir_y = ivec3(0, read_dir.y, 0);
+ ivec3 read_dir_z = ivec3(0, 0, read_dir.z);
+ //solid
+#if 0
+
+ uvec3 facing_pos_base = (uvec3(facing) >> uvec3(0,1,2)) & uvec3(1,1,1);
+ uvec3 facing_neg_base = (uvec3(facing) >> uvec3(3,4,5)) & uvec3(1,1,1);
+ uvec3 facing_pos= facing_pos_base &((~facing_neg_base)&uvec3(1,1,1));
+ uvec3 facing_neg= facing_neg_base &((~facing_pos_base)&uvec3(1,1,1));
+#else
+ uvec3 facing_pos = (uvec3(facing) >> uvec3(0, 1, 2)) & uvec3(1, 1, 1);
+ uvec3 facing_neg = (uvec3(facing) >> uvec3(3, 4, 5)) & uvec3(1, 1, 1);
+#endif
+ bvec3 read_valid = bvec3(mix(facing_neg, facing_pos, greaterThan(read_dir, ivec3(0))));
+
+ //sides
+ if (read_valid.x) {
+ ivec3 read_offset = local_offset + read_dir_x;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ if (read_valid.y) {
+ ivec3 read_offset = local_offset + read_dir_y;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ if (read_valid.z) {
+ ivec3 read_offset = local_offset + read_dir_z;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ //adjacents
+
+ if (all(read_valid.yz)) {
+ ivec3 read_offset = local_offset + read_dir_y + read_dir_z;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ if (all(read_valid.xz)) {
+ ivec3 read_offset = local_offset + read_dir_x + read_dir_z;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ if (all(read_valid.xy)) {
+ ivec3 read_offset = local_offset + read_dir_x + read_dir_y;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ //diagonal
+
+ if (all(read_valid)) {
+ ivec3 read_offset = local_offset + read_dir;
+ uint f = get_facing(read_offset);
+ if (f == 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occ += imageLoad(dst_occlusion[params.occlusion_index], read_offset).r;
+ avg += 1.0;
+ }
+ }
+ }
+
+ if (avg > 0.0) {
+ occ /= avg;
+ }
+
+ imageStore(dst_occlusion[params.occlusion_index], offset, vec4(occ));
+ }
+ }
+ }
+
+#endif
+
+#if 1
+ groupMemoryBarrier();
+ barrier();
+
+ for (int i = 0; i < 64; i++) {
+ ivec3 local_offset = local_ofs + ((ivec3(i) >> ivec3(0, 2, 4)) & ivec3(3, 3, 3));
+ ivec3 offset = region_offset + local_offset;
+
+ if (all(greaterThanEqual(offset, ivec3(0))) && all(lessThan(offset, ivec3(params.grid_size)))) {
+ uint facing = get_facing(local_offset);
+
+ if (facing == 0) {
+ ivec3 proc_pos = local_offset - ivec3(OCCLUSION_SIZE);
+ proc_pos += mix(ivec3(0), ivec3(1), greaterThanEqual(proc_pos, ivec3(0)));
+
+ ivec3 proc_abs = abs(proc_pos);
+
+ ivec3 read_dir = sign(proc_pos); //opposite direction
+ ivec3 read_dir_x = ivec3(read_dir.x, 0, 0);
+ ivec3 read_dir_y = ivec3(0, read_dir.y, 0);
+ ivec3 read_dir_z = ivec3(0, 0, read_dir.z);
+ //solid
+ uvec3 read_mask = mix(uvec3(1, 2, 4), uvec3(8, 16, 32), greaterThan(read_dir, ivec3(0))); //match positive with negative normals
+ uvec3 block_mask = mix(uvec3(1, 2, 4), uvec3(8, 16, 32), lessThan(read_dir, ivec3(0))); //match positive with negative normals
+
+ block_mask = uvec3(0);
+
+ float visible = 0.0;
+ float occlude_total = 0.0;
+
+ if (proc_abs.x < OCCLUSION_SIZE) {
+ ivec3 read_offset = local_offset + read_dir_x;
+ uint x_mask = get_facing(read_offset);
+ if (x_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(x_mask & read_mask.x) && !bool(x_mask & block_mask.x)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ if (proc_abs.y < OCCLUSION_SIZE) {
+ ivec3 read_offset = local_offset + read_dir_y;
+ uint y_mask = get_facing(read_offset);
+ if (y_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(y_mask & read_mask.y) && !bool(y_mask & block_mask.y)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ if (proc_abs.z < OCCLUSION_SIZE) {
+ ivec3 read_offset = local_offset + read_dir_z;
+ uint z_mask = get_facing(read_offset);
+ if (z_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(z_mask & read_mask.z) && !bool(z_mask & block_mask.z)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ //if near the cartesian plane, test in opposite direction too
+
+ read_mask = mix(uvec3(1, 2, 4), uvec3(8, 16, 32), lessThan(read_dir, ivec3(0))); //match negative with positive normals
+ block_mask = mix(uvec3(1, 2, 4), uvec3(8, 16, 32), greaterThan(read_dir, ivec3(0))); //match negative with positive normals
+ block_mask = uvec3(0);
+
+ if (proc_abs.x == 1) {
+ ivec3 read_offset = local_offset - read_dir_x;
+ uint x_mask = get_facing(read_offset);
+ if (x_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(x_mask & read_mask.x) && !bool(x_mask & block_mask.x)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ if (proc_abs.y == 1) {
+ ivec3 read_offset = local_offset - read_dir_y;
+ uint y_mask = get_facing(read_offset);
+ if (y_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(y_mask & read_mask.y) && !bool(y_mask & block_mask.y)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ if (proc_abs.z == 1) {
+ ivec3 read_offset = local_offset - read_dir_z;
+ uint z_mask = get_facing(read_offset);
+ if (z_mask != 0) {
+ read_offset += region_offset;
+ if (all(greaterThanEqual(read_offset, ivec3(0))) && all(lessThan(read_offset, ivec3(params.grid_size)))) {
+ occlude_total += 1.0;
+ if (bool(z_mask & read_mask.z) && !bool(z_mask & block_mask.z)) {
+ visible += 1.0;
+ }
+ }
+ }
+ }
+
+ if (occlude_total > 0.0) {
+ float occ = imageLoad(dst_occlusion[params.occlusion_index], offset).r;
+ occ *= visible / occlude_total;
+ imageStore(dst_occlusion[params.occlusion_index], offset, vec4(occ));
+ }
+ }
+ }
+ }
+
+#endif
+
+ /*
+ for(int i=0;i<8;i++) {
+ ivec3 local_offset = local_pos + ((ivec3(i) >> ivec3(2,1,0)) & ivec3(1,1,1)) * OCCLUSION_SIZE;
+ ivec3 offset = local_offset - ivec3(OCCLUSION_SIZE); //looking around probe, so starts negative
+ offset += region * OCCLUSION_SIZE * 2; //offset by region
+ offset += params.probe_offset * OCCLUSION_SIZE; // offset by probe offset
+ if (all(greaterThanEqual(offset,ivec3(0))) && all(lessThan(offset,ivec3(params.grid_size)))) {
+ imageStore(dst_occlusion[params.occlusion_index],offset,vec4( occlusion_data[ to_linear(local_offset) ] ));
+ //imageStore(dst_occlusion[params.occlusion_index],offset,vec4( occlusion_solid[ to_linear(local_offset) ] ));
+ }
+ }
+*/
+
+#endif
+
+#ifdef MODE_STORE
+
+ ivec3 local = ivec3(gl_LocalInvocationID.xyz);
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+ // store SDF
+ uvec4 p = imageLoad(src_positions, pos);
+
+ bool solid = false;
+ float d;
+ if (ivec3(p.xyz) == pos) {
+ //solid block
+ d = 0;
+ solid = true;
+ } else {
+ //distance block
+ d = 1.0 + length(vec3(p.xyz) - vec3(pos));
+ }
+
+ d /= 255.0;
+
+ imageStore(dst_sdf, pos, vec4(d));
+
+ // STORE OCCLUSION
+
+ uint occlusion = 0;
+ const uint occlusion_shift[8] = uint[](12, 8, 4, 0, 28, 24, 20, 16);
+ for (int i = 0; i < 8; i++) {
+ float occ = imageLoad(src_occlusion[i], pos).r;
+ occlusion |= uint(clamp(occ * 15.0, 0.0, 15.0)) << occlusion_shift[i];
+ }
+ {
+ ivec3 occ_pos = pos;
+ occ_pos.z += params.cascade * params.grid_size;
+ imageStore(dst_occlusion, occ_pos, uvec4(occlusion & 0xFFFF));
+ occ_pos.x += params.grid_size;
+ imageStore(dst_occlusion, occ_pos, uvec4(occlusion >> 16));
+ }
+
+ // STORE POSITIONS
+
+ if (local == ivec3(0)) {
+ store_position_count = 0; //base one stores as zero, the others wait
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ if (solid) {
+ uint index = atomicAdd(store_position_count, 1);
+ // At least do the conversion work in parallel
+ store_positions[index].position = uint(pos.x | (pos.y << 7) | (pos.z << 14));
+
+ //see around which voxels point to this one, add them to the list
+ uint bit_index = 0;
+ uint neighbour_bits = 0;
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ for (int k = -1; k <= 1; k++) {
+ if (i == 0 && j == 0 && k == 0) {
+ continue;
+ }
+ ivec3 npos = pos + ivec3(i, j, k);
+ if (all(greaterThanEqual(npos, ivec3(0))) && all(lessThan(npos, ivec3(params.grid_size)))) {
+ p = imageLoad(src_positions, npos);
+ if (ivec3(p.xyz) == pos) {
+ neighbour_bits |= (1 << bit_index);
+ }
+ }
+ bit_index++;
+ }
+ }
+ }
+
+ uint rgb = imageLoad(src_albedo, pos).r;
+ uint facing = imageLoad(src_facing, pos).r;
+
+ store_positions[index].albedo = rgb >> 1; //store as it comes (555) to avoid precision loss (and move away the alpha bit)
+ store_positions[index].albedo |= (facing & 0x3F) << 15; // store facing in bits 15-21
+
+ store_positions[index].albedo |= neighbour_bits << 21; //store lower 11 bits of neighbours with remaining albedo
+ store_positions[index].position |= (neighbour_bits >> 11) << 21; //store 11 bits more of neighbours with position
+
+ store_positions[index].light = imageLoad(src_light, pos).r;
+ store_positions[index].light_aniso = imageLoad(src_light_aniso, pos).r;
+ //add neighbours
+ store_positions[index].light |= (neighbour_bits >> 22) << 30; //store 2 bits more of neighbours with light
+ store_positions[index].light_aniso |= (neighbour_bits >> 24) << 30; //store 2 bits more of neighbours with aniso
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ // global increment only once per group, to reduce pressure
+
+ if (local == ivec3(0) && store_position_count > 0) {
+ store_from_index = atomicAdd(dispatch_data.total_count, store_position_count);
+ uint group_count = (store_from_index + store_position_count - 1) / 64 + 1;
+ atomicMax(dispatch_data.x, group_count);
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ uint read_index = uint(local.z * 4 * 4 + local.y * 4 + local.x);
+ uint write_index = store_from_index + read_index;
+
+ if (read_index < store_position_count) {
+ dst_process_voxels.data[write_index] = store_positions[read_index];
+ }
+
+ if (pos == ivec3(0)) {
+ //this thread clears y and z
+ dispatch_data.y = 1;
+ dispatch_data.z = 1;
+ }
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/skeleton.glsl b/servers/rendering/renderer_rd/shaders/skeleton.glsl
new file mode 100644
index 0000000000..b831005256
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/skeleton.glsl
@@ -0,0 +1,246 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+layout(set = 0, binding = 1, std430) buffer restrict writeonly DstVertexData {
+ uint data[];
+}
+dst_vertices;
+
+layout(set = 0, binding = 2, std430) buffer restrict readonly BlendShapeWeights {
+ float data[];
+}
+blend_shape_weights;
+
+layout(set = 1, binding = 0, std430) buffer restrict readonly SrcVertexData {
+ uint data[];
+}
+src_vertices;
+
+layout(set = 1, binding = 1, std430) buffer restrict readonly BoneWeightData {
+ uint data[];
+}
+src_bone_weights;
+
+layout(set = 1, binding = 2, std430) buffer restrict readonly BlendShapeData {
+ uint data[];
+}
+src_blend_shapes;
+
+layout(set = 2, binding = 0, std430) buffer restrict readonly SkeletonData {
+ vec4 data[];
+}
+bone_transforms;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ bool has_normal;
+ bool has_tangent;
+ bool has_skeleton;
+ bool has_blend_shape;
+
+ uint vertex_count;
+ uint vertex_stride;
+ uint skin_stride;
+ uint skin_weight_offset;
+
+ uint blend_shape_count;
+ bool normalized_blend_shapes;
+ uint pad0;
+ uint pad1;
+}
+params;
+
+vec4 decode_abgr_2_10_10_10(uint base) {
+ uvec4 abgr_2_10_10_10 = (uvec4(base) >> uvec4(0, 10, 20, 30)) & uvec4(0x3FF, 0x3FF, 0x3FF, 0x3);
+ return vec4(abgr_2_10_10_10) / vec4(1023.0, 1023.0, 1023.0, 3.0) * 2.0 - 1.0;
+}
+
+uint encode_abgr_2_10_10_10(vec4 base) {
+ uvec4 abgr_2_10_10_10 = uvec4(clamp(ivec4((base * 0.5 + 0.5) * vec4(1023.0, 1023.0, 1023.0, 3.0)), ivec4(0), ivec4(0x3FF, 0x3FF, 0x3FF, 0x3))) << uvec4(0, 10, 20, 30);
+ return abgr_2_10_10_10.x | abgr_2_10_10_10.y | abgr_2_10_10_10.z | abgr_2_10_10_10.w;
+}
+
+void main() {
+ uint index = gl_GlobalInvocationID.x;
+ if (index >= params.vertex_count) {
+ return;
+ }
+
+ uint src_offset = index * params.vertex_stride;
+
+#ifdef MODE_2D
+ vec2 vertex = uintBitsToFloat(uvec2(src_vertices.data[src_offset + 0], src_vertices.data[src_offset + 1]));
+
+ if (params.has_blend_shape) {
+ float blend_total = 0.0;
+ vec2 blend_vertex = vec2(0.0);
+
+ for (uint i = 0; i < params.blend_shape_count; i++) {
+ float w = blend_shape_weights.data[i];
+ if (abs(w) > 0.0001) {
+ uint base_offset = (params.vertex_count * i + index) * params.vertex_stride;
+
+ blend_vertex += uintBitsToFloat(uvec2(src_blend_shapes.data[base_offset + 0], src_blend_shapes.data[base_offset + 1])) * w;
+
+ base_offset += 2;
+
+ blend_total += w;
+ }
+ }
+
+ if (params.normalized_blend_shapes) {
+ vertex = (1.0 - blend_total) * vertex;
+ }
+
+ vertex += blend_vertex;
+ }
+
+ if (params.has_skeleton) {
+ uint skin_offset = params.skin_stride * index;
+
+ uvec2 bones = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+ uvec2 bones_01 = uvec2(bones.x & 0xFFFF, bones.x >> 16) * 3; //pre-add xform offset
+ uvec2 bones_23 = uvec2(bones.y & 0xFFFF, bones.y >> 16) * 3;
+
+ skin_offset += params.skin_weight_offset;
+
+ uvec2 weights = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+
+ vec2 weights_01 = unpackUnorm2x16(weights.x);
+ vec2 weights_23 = unpackUnorm2x16(weights.y);
+
+ mat4 m = mat4(bone_transforms.data[bones_01.x], bone_transforms.data[bones_01.x + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.x;
+ m += mat4(bone_transforms.data[bones_01.y], bone_transforms.data[bones_01.y + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.y;
+ m += mat4(bone_transforms.data[bones_23.x], bone_transforms.data[bones_23.x + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x;
+ m += mat4(bone_transforms.data[bones_23.y], bone_transforms.data[bones_23.y + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.y;
+
+ //reverse order because its transposed
+ vertex = (vec4(vertex, 0.0, 1.0) * m).xy;
+ }
+#else
+ vec3 vertex;
+ vec3 normal;
+ vec4 tangent;
+
+ vertex = uintBitsToFloat(uvec3(src_vertices.data[src_offset + 0], src_vertices.data[src_offset + 1], src_vertices.data[src_offset + 2]));
+
+ src_offset += 3;
+
+ if (params.has_normal) {
+ normal = decode_abgr_2_10_10_10(src_vertices.data[src_offset]).rgb;
+ src_offset++;
+ }
+
+ if (params.has_tangent) {
+ tangent = decode_abgr_2_10_10_10(src_vertices.data[src_offset]);
+ }
+
+ if (params.has_blend_shape) {
+ float blend_total = 0.0;
+ vec3 blend_vertex = vec3(0.0);
+ vec3 blend_normal = vec3(0.0);
+ vec3 blend_tangent = vec3(0.0);
+
+ for (uint i = 0; i < params.blend_shape_count; i++) {
+ float w = blend_shape_weights.data[i];
+ if (abs(w) > 0.0001) {
+ uint base_offset = (params.vertex_count * i + index) * params.vertex_stride;
+
+ blend_vertex += uintBitsToFloat(uvec3(src_blend_shapes.data[base_offset + 0], src_blend_shapes.data[base_offset + 1], src_blend_shapes.data[base_offset + 2])) * w;
+
+ base_offset += 3;
+
+ if (params.has_normal) {
+ blend_normal += decode_abgr_2_10_10_10(src_blend_shapes.data[base_offset]).rgb * w;
+ base_offset++;
+ }
+
+ if (params.has_tangent) {
+ blend_tangent += decode_abgr_2_10_10_10(src_blend_shapes.data[base_offset]).rgb;
+ }
+
+ blend_total += w;
+ }
+ }
+
+ if (params.normalized_blend_shapes) {
+ vertex = (1.0 - blend_total) * vertex;
+ normal = (1.0 - blend_total) * normal;
+ tangent.rgb = (1.0 - blend_total) * tangent.rgb;
+ }
+
+ vertex += blend_vertex;
+ normal += normalize(normal + blend_normal);
+ tangent.rgb += normalize(tangent.rgb + blend_tangent);
+ }
+
+ if (params.has_skeleton) {
+ uint skin_offset = params.skin_stride * index;
+
+ uvec2 bones = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+ uvec2 bones_01 = uvec2(bones.x & 0xFFFF, bones.x >> 16) * 3; //pre-add xform offset
+ uvec2 bones_23 = uvec2(bones.y & 0xFFFF, bones.y >> 16) * 3;
+
+ skin_offset += params.skin_weight_offset;
+
+ uvec2 weights = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+
+ vec2 weights_01 = unpackUnorm2x16(weights.x);
+ vec2 weights_23 = unpackUnorm2x16(weights.y);
+
+ mat4 m = mat4(bone_transforms.data[bones_01.x], bone_transforms.data[bones_01.x + 1], bone_transforms.data[bones_01.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.x;
+ m += mat4(bone_transforms.data[bones_01.y], bone_transforms.data[bones_01.y + 1], bone_transforms.data[bones_01.y + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.y;
+ m += mat4(bone_transforms.data[bones_23.x], bone_transforms.data[bones_23.x + 1], bone_transforms.data[bones_23.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x;
+ m += mat4(bone_transforms.data[bones_23.y], bone_transforms.data[bones_23.y + 1], bone_transforms.data[bones_23.y + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.y;
+
+ if (params.skin_weight_offset == 4) {
+ //using 8 bones/weights
+ skin_offset = params.skin_stride * index + 2;
+
+ bones = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+ bones_01 = uvec2(bones.x & 0xFFFF, bones.x >> 16) * 3; //pre-add xform offset
+ bones_23 = uvec2(bones.y & 0xFFFF, bones.y >> 16) * 3;
+
+ skin_offset += params.skin_weight_offset;
+
+ weights = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+
+ weights_01 = unpackUnorm2x16(weights.x);
+ weights_23 = unpackUnorm2x16(weights.y);
+
+ m += mat4(bone_transforms.data[bones_01.x], bone_transforms.data[bones_01.x + 1], bone_transforms.data[bones_01.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.x;
+ m += mat4(bone_transforms.data[bones_01.y], bone_transforms.data[bones_01.y + 1], bone_transforms.data[bones_01.y + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.y;
+ m += mat4(bone_transforms.data[bones_23.x], bone_transforms.data[bones_23.x + 1], bone_transforms.data[bones_23.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x;
+ m += mat4(bone_transforms.data[bones_23.y], bone_transforms.data[bones_23.y + 1], bone_transforms.data[bones_23.y + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.y;
+ }
+
+ //reverse order because its transposed
+ vertex = (vec4(vertex, 1.0) * m).xyz;
+ normal = normalize((vec4(normal, 0.0) * m).xyz);
+ tangent.xyz = normalize((vec4(tangent.xyz, 0.0) * m).xyz);
+ }
+
+ uint dst_offset = index * params.vertex_stride;
+
+ uvec3 uvertex = floatBitsToUint(vertex);
+ dst_vertices.data[dst_offset + 0] = uvertex.x;
+ dst_vertices.data[dst_offset + 1] = uvertex.y;
+ dst_vertices.data[dst_offset + 2] = uvertex.z;
+
+ dst_offset += 3;
+
+ if (params.has_normal) {
+ dst_vertices.data[dst_offset] = encode_abgr_2_10_10_10(vec4(normal, 0.0));
+ dst_offset++;
+ }
+
+ if (params.has_tangent) {
+ dst_vertices.data[dst_offset] = encode_abgr_2_10_10_10(tangent);
+ }
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/sky.glsl b/servers/rendering/renderer_rd/shaders/sky.glsl
new file mode 100644
index 0000000000..41c6325bc5
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sky.glsl
@@ -0,0 +1,249 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define MAX_VIEWS 2
+
+#if defined(USE_MULTIVIEW) && defined(has_VK_KHR_multiview)
+#extension GL_EXT_multiview : enable
+#endif
+
+layout(location = 0) out vec2 uv_interp;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ mat3 orientation;
+ vec4 projections[MAX_VIEWS];
+ vec4 position_multiplier;
+ float time;
+}
+params;
+
+void main() {
+ vec2 base_arr[4] = vec2[](vec2(-1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(1.0, -1.0));
+ uv_interp = base_arr[gl_VertexIndex];
+ gl_Position = vec4(uv_interp, 1.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef USE_MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#extension GL_EXT_multiview : enable
+#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
+
+#define M_PI 3.14159265359
+#define MAX_VIEWS 2
+
+layout(location = 0) in vec2 uv_interp;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ mat3 orientation;
+ vec4 projections[MAX_VIEWS];
+ vec4 position_multiplier;
+ float time;
+}
+params;
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+layout(set = 0, binding = 0) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 1, std430) restrict readonly buffer GlobalVariableData {
+ vec4 data[];
+}
+global_variables;
+
+layout(set = 0, binding = 2, std140) uniform SceneData {
+ bool volumetric_fog_enabled;
+ float volumetric_fog_inv_length;
+ float volumetric_fog_detail_spread;
+
+ float fog_aerial_perspective;
+
+ vec3 fog_light_color;
+ float fog_sun_scatter;
+
+ bool fog_enabled;
+ float fog_density;
+
+ float z_far;
+ uint directional_light_count;
+}
+scene_data;
+
+struct DirectionalLightData {
+ vec4 direction_energy;
+ vec4 color_size;
+ bool enabled;
+};
+
+layout(set = 0, binding = 3, std140) uniform DirectionalLights {
+ DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+}
+directional_lights;
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = 1, binding = 0, std140) uniform MaterialUniforms{
+#MATERIAL_UNIFORMS
+} material;
+#endif
+
+layout(set = 2, binding = 0) uniform textureCube radiance;
+#ifdef USE_CUBEMAP_PASS
+layout(set = 2, binding = 1) uniform textureCube half_res;
+layout(set = 2, binding = 2) uniform textureCube quarter_res;
+#else
+layout(set = 2, binding = 1) uniform texture2D half_res;
+layout(set = 2, binding = 2) uniform texture2D quarter_res;
+#endif
+
+layout(set = 3, binding = 0) uniform texture3D volumetric_fog_texture;
+
+#ifdef USE_CUBEMAP_PASS
+#define AT_CUBEMAP_PASS true
+#else
+#define AT_CUBEMAP_PASS false
+#endif
+
+#ifdef USE_HALF_RES_PASS
+#define AT_HALF_RES_PASS true
+#else
+#define AT_HALF_RES_PASS false
+#endif
+
+#ifdef USE_QUARTER_RES_PASS
+#define AT_QUARTER_RES_PASS true
+#else
+#define AT_QUARTER_RES_PASS false
+#endif
+
+#GLOBALS
+
+layout(location = 0) out vec4 frag_color;
+
+vec4 volumetric_fog_process(vec2 screen_uv) {
+ vec3 fog_pos = vec3(screen_uv, 1.0);
+
+ return texture(sampler3D(volumetric_fog_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), fog_pos);
+}
+
+vec4 fog_process(vec3 view, vec3 sky_color) {
+ vec3 fog_color = mix(scene_data.fog_light_color, sky_color, scene_data.fog_aerial_perspective);
+
+ if (scene_data.fog_sun_scatter > 0.001) {
+ vec4 sun_scatter = vec4(0.0);
+ float sun_total = 0.0;
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w;
+ float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0);
+ fog_color += light_color * light_amount * scene_data.fog_sun_scatter;
+ }
+ }
+
+ float fog_amount = clamp(1.0 - exp(-scene_data.z_far * scene_data.fog_density), 0.0, 1.0);
+
+ return vec4(fog_color, fog_amount);
+}
+
+void main() {
+ vec3 cube_normal;
+ cube_normal.z = -1.0;
+ cube_normal.x = (cube_normal.z * (-uv_interp.x - params.projections[ViewIndex].x)) / params.projections[ViewIndex].y;
+ cube_normal.y = -(cube_normal.z * (-uv_interp.y - params.projections[ViewIndex].z)) / params.projections[ViewIndex].w;
+ cube_normal = mat3(params.orientation) * cube_normal;
+ cube_normal.z = -cube_normal.z;
+ cube_normal = normalize(cube_normal);
+
+ vec2 uv = uv_interp * 0.5 + 0.5;
+
+ vec2 panorama_coords = vec2(atan(cube_normal.x, cube_normal.z), acos(cube_normal.y));
+
+ if (panorama_coords.x < 0.0) {
+ panorama_coords.x += M_PI * 2.0;
+ }
+
+ panorama_coords /= vec2(M_PI * 2.0, M_PI);
+
+ vec3 color = vec3(0.0, 0.0, 0.0);
+ float alpha = 1.0; // Only available to subpasses
+ vec4 half_res_color = vec4(1.0);
+ vec4 quarter_res_color = vec4(1.0);
+ vec4 custom_fog = vec4(0.0);
+
+#ifdef USE_CUBEMAP_PASS
+ vec3 inverted_cube_normal = cube_normal;
+ inverted_cube_normal.z *= -1.0;
+#ifdef USES_HALF_RES_COLOR
+ half_res_color = texture(samplerCube(half_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal);
+#endif
+#ifdef USES_QUARTER_RES_COLOR
+ quarter_res_color = texture(samplerCube(quarter_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal);
+#endif
+#else
+#ifdef USES_HALF_RES_COLOR
+ half_res_color = textureLod(sampler2D(half_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0);
+#endif
+#ifdef USES_QUARTER_RES_COLOR
+ quarter_res_color = textureLod(sampler2D(quarter_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0);
+#endif
+#endif
+
+ {
+
+#CODE : SKY
+
+ }
+
+ frag_color.rgb = color * params.position_multiplier.w;
+ frag_color.a = alpha;
+
+#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS)
+
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+ if (scene_data.fog_enabled) {
+ vec4 fog = fog_process(cube_normal, frag_color.rgb);
+ frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a);
+ }
+
+ if (scene_data.volumetric_fog_enabled) {
+ vec4 fog = volumetric_fog_process(uv);
+ frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a);
+ }
+
+ if (custom_fog.a > 0.0) {
+ frag_color.rgb = mix(frag_color.rgb, custom_fog.rgb, custom_fog.a);
+ }
+
+#endif // DISABLE_FOG
+
+ // Blending is disabled for Sky, so alpha doesn't blend
+ // alpha is used for subsurface scattering so make sure it doesn't get applied to Sky
+ if (!AT_CUBEMAP_PASS && !AT_HALF_RES_PASS && !AT_QUARTER_RES_PASS) {
+ frag_color.a = 0.0;
+ }
+}
diff --git a/servers/rendering/renderer_rd/shaders/sort.glsl b/servers/rendering/renderer_rd/shaders/sort.glsl
new file mode 100644
index 0000000000..307e60dc21
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/sort.glsl
@@ -0,0 +1,203 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+// Original version here:
+// https://github.com/GPUOpen-LibrariesAndSDKs/GPUParticles11/blob/master/gpuparticles11/src/Shaders
+
+//
+// Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.
+//
+// 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.
+//
+
+#define SORT_SIZE 512
+#define NUM_THREADS (SORT_SIZE / 2)
+#define INVERSION (16 * 2 + 8 * 3)
+#define ITERATIONS 1
+
+layout(local_size_x = NUM_THREADS, local_size_y = 1, local_size_z = 1) in;
+
+#ifndef MODE_SORT_STEP
+
+shared vec2 g_LDS[SORT_SIZE];
+
+#endif
+
+layout(set = 1, binding = 0, std430) restrict buffer SortBuffer {
+ vec2 data[];
+}
+sort_buffer;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ uint total_elements;
+ uint pad[3];
+ ivec4 job_params;
+}
+params;
+
+void main() {
+#ifdef MODE_SORT_BLOCK
+
+ uvec3 Gid = gl_WorkGroupID;
+ uvec3 DTid = gl_GlobalInvocationID;
+ uvec3 GTid = gl_LocalInvocationID;
+ uint GI = gl_LocalInvocationIndex;
+
+ int GlobalBaseIndex = int((Gid.x * SORT_SIZE) + GTid.x);
+ int LocalBaseIndex = int(GI);
+ int numElementsInThreadGroup = int(min(SORT_SIZE, params.total_elements - (Gid.x * SORT_SIZE)));
+
+ // Load shared data
+
+ int i;
+ for (i = 0; i < 2 * ITERATIONS; ++i) {
+ if (GI + i * NUM_THREADS < numElementsInThreadGroup)
+ g_LDS[LocalBaseIndex + i * NUM_THREADS] = sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS];
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ // Bitonic sort
+ for (int nMergeSize = 2; nMergeSize <= SORT_SIZE; nMergeSize = nMergeSize * 2) {
+ for (int nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) {
+ for (i = 0; i < ITERATIONS; ++i) {
+ int tmp_index = int(GI + NUM_THREADS * i);
+ int index_low = tmp_index & (nMergeSubSize - 1);
+ int index_high = 2 * (tmp_index - index_low);
+ int index = index_high + index_low;
+
+ int nSwapElem = nMergeSubSize == nMergeSize >> 1 ? index_high + (2 * nMergeSubSize - 1) - index_low : index_high + nMergeSubSize + index_low;
+ if (nSwapElem < numElementsInThreadGroup) {
+ vec2 a = g_LDS[index];
+ vec2 b = g_LDS[nSwapElem];
+
+ if (a.x > b.x) {
+ g_LDS[index] = b;
+ g_LDS[nSwapElem] = a;
+ }
+ }
+ groupMemoryBarrier();
+ barrier();
+ }
+ }
+ }
+
+ // Store shared data
+ for (i = 0; i < 2 * ITERATIONS; ++i) {
+ if (GI + i * NUM_THREADS < numElementsInThreadGroup) {
+ sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS];
+ }
+ }
+
+#endif
+
+#ifdef MODE_SORT_STEP
+
+ uvec3 Gid = gl_WorkGroupID;
+ uvec3 GTid = gl_LocalInvocationID;
+
+ ivec4 tgp;
+
+ tgp.x = int(Gid.x) * 256;
+ tgp.y = 0;
+ tgp.z = int(params.total_elements);
+ tgp.w = min(512, max(0, tgp.z - int(Gid.x) * 512));
+
+ uint localID = int(tgp.x) + GTid.x; // calculate threadID within this sortable-array
+
+ uint index_low = localID & (params.job_params.x - 1);
+ uint index_high = 2 * (localID - index_low);
+
+ uint index = tgp.y + index_high + index_low;
+ uint nSwapElem = tgp.y + index_high + params.job_params.y + params.job_params.z * index_low;
+
+ if (nSwapElem < tgp.y + tgp.z) {
+ vec2 a = sort_buffer.data[index];
+ vec2 b = sort_buffer.data[nSwapElem];
+
+ if (a.x > b.x) {
+ sort_buffer.data[index] = b;
+ sort_buffer.data[nSwapElem] = a;
+ }
+ }
+
+#endif
+
+#ifdef MODE_SORT_INNER
+
+ uvec3 Gid = gl_WorkGroupID;
+ uvec3 DTid = gl_GlobalInvocationID;
+ uvec3 GTid = gl_LocalInvocationID;
+ uint GI = gl_LocalInvocationIndex;
+
+ ivec4 tgp;
+
+ tgp.x = int(Gid.x * 256);
+ tgp.y = 0;
+ tgp.z = int(params.total_elements.x);
+ tgp.w = int(min(512, max(0, params.total_elements - Gid.x * 512)));
+
+ int GlobalBaseIndex = int(tgp.y + tgp.x * 2 + GTid.x);
+ int LocalBaseIndex = int(GI);
+ int i;
+
+ // Load shared data
+ for (i = 0; i < 2; ++i) {
+ if (GI + i * NUM_THREADS < tgp.w)
+ g_LDS[LocalBaseIndex + i * NUM_THREADS] = sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS];
+ }
+
+ groupMemoryBarrier();
+ barrier();
+
+ // sort threadgroup shared memory
+ for (int nMergeSubSize = SORT_SIZE >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) {
+ int tmp_index = int(GI);
+ int index_low = tmp_index & (nMergeSubSize - 1);
+ int index_high = 2 * (tmp_index - index_low);
+ int index = index_high + index_low;
+
+ int nSwapElem = index_high + nMergeSubSize + index_low;
+
+ if (nSwapElem < tgp.w) {
+ vec2 a = g_LDS[index];
+ vec2 b = g_LDS[nSwapElem];
+
+ if (a.x > b.x) {
+ g_LDS[index] = b;
+ g_LDS[nSwapElem] = a;
+ }
+ }
+ groupMemoryBarrier();
+ barrier();
+ }
+
+ // Store shared data
+ for (i = 0; i < 2; ++i) {
+ if (GI + i * NUM_THREADS < tgp.w) {
+ sort_buffer.data[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS];
+ }
+ }
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/specular_merge.glsl b/servers/rendering/renderer_rd/shaders/specular_merge.glsl
new file mode 100644
index 0000000000..3579c35cce
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/specular_merge.glsl
@@ -0,0 +1,53 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) out vec2 uv_interp;
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec2 uv_interp;
+
+layout(set = 0, binding = 0) uniform sampler2D specular;
+
+#ifdef MODE_SSR
+
+layout(set = 1, binding = 0) uniform sampler2D ssr;
+
+#endif
+
+#ifdef MODE_MERGE
+
+layout(set = 2, binding = 0) uniform sampler2D diffuse;
+
+#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
+}
diff --git a/servers/rendering/renderer_rd/shaders/ssao.glsl b/servers/rendering/renderer_rd/shaders/ssao.glsl
new file mode 100644
index 0000000000..6e945edfcd
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/ssao.glsl
@@ -0,0 +1,486 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 SSAO_ADAPTIVE_TAP_BASE_COUNT 5
+
+#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_MAX_REF_TAPS 512
+#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, binding = 3, 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
+}
diff --git a/servers/rendering/renderer_rd/shaders/ssao_blur.glsl b/servers/rendering/renderer_rd/shaders/ssao_blur.glsl
new file mode 100644
index 0000000000..d9cd2b4e85
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/ssao_blur.glsl
@@ -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, binding = 1, 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 halfPixel = params.half_screen_pixel_size * 0.5f;
+
+ vec2 uv = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5, 0.5)) * params.half_screen_pixel_size;
+
+ vec2 centre = textureLod(source_ssao, vec2(uv), 0.0).xy;
+
+ vec4 vals;
+ vals.x = textureLod(source_ssao, vec2(uv + vec2(-halfPixel.x * 3, -halfPixel.y)), 0.0).x;
+ vals.y = textureLod(source_ssao, vec2(uv + vec2(+halfPixel.x, -halfPixel.y * 3)), 0.0).x;
+ vals.z = textureLod(source_ssao, vec2(uv + vec2(-halfPixel.x, +halfPixel.y * 3)), 0.0).x;
+ vals.w = textureLod(source_ssao, vec2(uv + vec2(+halfPixel.x * 3, +halfPixel.y)), 0.0).x;
+
+ vec2 sampled = vec2(dot(vals, vec4(0.2)) + centre.x * 0.2, centre.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));
+}
diff --git a/servers/rendering/renderer_rd/shaders/ssao_downsample.glsl b/servers/rendering/renderer_rd/shaders/ssao_downsample.glsl
new file mode 100644
index 0000000000..ee0db6a6f0
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/ssao_downsample.glsl
@@ -0,0 +1,206 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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, binding = 1, 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;
+#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));
+ }
+}
+#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
+}
diff --git a/servers/rendering/renderer_rd/shaders/ssao_importance_map.glsl b/servers/rendering/renderer_rd/shaders/ssao_importance_map.glsl
new file mode 100644
index 0000000000..687fe1e6e2
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/ssao_importance_map.glsl
@@ -0,0 +1,126 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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_ssao;
+#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, binding = 1, 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 avg = 0.0;
+ float minV = 1.0;
+ float maxV = 0.0;
+ for (int i = 0; i < 4; i++) {
+ vec4 vals = textureGather(source_ssao, 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));
+
+ avg += dot(vec4(vals.x, vals.y, vals.z, vals.w), vec4(1.0 / 16.0, 1.0 / 16.0, 1.0 / 16.0, 1.0 / 16.0));
+
+ 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
+}
diff --git a/servers/rendering/renderer_rd/shaders/ssao_interleave.glsl b/servers/rendering/renderer_rd/shaders/ssao_interleave.glsl
new file mode 100644
index 0000000000..0907423d5d
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/ssao_interleave.glsl
@@ -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, binding = 1, 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
+}
diff --git a/servers/rendering/renderer_rd/shaders/subsurface_scattering.glsl b/servers/rendering/renderer_rd/shaders/subsurface_scattering.glsl
new file mode 100644
index 0000000000..9367b641c2
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/subsurface_scattering.glsl
@@ -0,0 +1,189 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#ifdef USE_25_SAMPLES
+const int kernel_size = 13;
+
+const vec2 kernel[kernel_size] = vec2[](
+ vec2(0.530605, 0.0),
+ vec2(0.0211412, 0.0208333),
+ vec2(0.0402784, 0.0833333),
+ vec2(0.0493588, 0.1875),
+ vec2(0.0410172, 0.333333),
+ vec2(0.0263642, 0.520833),
+ vec2(0.017924, 0.75),
+ vec2(0.0128496, 1.02083),
+ vec2(0.0094389, 1.33333),
+ vec2(0.00700976, 1.6875),
+ vec2(0.00500364, 2.08333),
+ vec2(0.00333804, 2.52083),
+ vec2(0.000973794, 3.0));
+
+const vec4 skin_kernel[kernel_size] = vec4[](
+ vec4(0.530605, 0.613514, 0.739601, 0),
+ vec4(0.0211412, 0.0459286, 0.0378196, 0.0208333),
+ vec4(0.0402784, 0.0657244, 0.04631, 0.0833333),
+ vec4(0.0493588, 0.0367726, 0.0219485, 0.1875),
+ vec4(0.0410172, 0.0199899, 0.0118481, 0.333333),
+ vec4(0.0263642, 0.0119715, 0.00684598, 0.520833),
+ vec4(0.017924, 0.00711691, 0.00347194, 0.75),
+ vec4(0.0128496, 0.00356329, 0.00132016, 1.02083),
+ vec4(0.0094389, 0.00139119, 0.000416598, 1.33333),
+ vec4(0.00700976, 0.00049366, 0.000151938, 1.6875),
+ vec4(0.00500364, 0.00020094, 5.28848e-005, 2.08333),
+ vec4(0.00333804, 7.85443e-005, 1.2945e-005, 2.52083),
+ vec4(0.000973794, 1.11862e-005, 9.43437e-007, 3));
+
+#endif //USE_25_SAMPLES
+
+#ifdef USE_17_SAMPLES
+const int kernel_size = 9;
+const vec2 kernel[kernel_size] = vec2[](
+ vec2(0.536343, 0.0),
+ vec2(0.0324462, 0.03125),
+ vec2(0.0582416, 0.125),
+ vec2(0.0571056, 0.28125),
+ vec2(0.0347317, 0.5),
+ vec2(0.0216301, 0.78125),
+ vec2(0.0144609, 1.125),
+ vec2(0.0100386, 1.53125),
+ vec2(0.00317394, 2.0));
+
+const vec4 skin_kernel[kernel_size] = vec4[](
+ vec4(0.536343, 0.624624, 0.748867, 0),
+ vec4(0.0324462, 0.0656718, 0.0532821, 0.03125),
+ vec4(0.0582416, 0.0659959, 0.0411329, 0.125),
+ vec4(0.0571056, 0.0287432, 0.0172844, 0.28125),
+ vec4(0.0347317, 0.0151085, 0.00871983, 0.5),
+ vec4(0.0216301, 0.00794618, 0.00376991, 0.78125),
+ vec4(0.0144609, 0.00317269, 0.00106399, 1.125),
+ vec4(0.0100386, 0.000914679, 0.000275702, 1.53125),
+ vec4(0.00317394, 0.000134823, 3.77269e-005, 2));
+#endif //USE_17_SAMPLES
+
+#ifdef USE_11_SAMPLES
+const int kernel_size = 6;
+const vec2 kernel[kernel_size] = vec2[](
+ vec2(0.560479, 0.0),
+ vec2(0.0771802, 0.08),
+ vec2(0.0821904, 0.32),
+ vec2(0.03639, 0.72),
+ vec2(0.0192831, 1.28),
+ vec2(0.00471691, 2.0));
+
+const vec4 skin_kernel[kernel_size] = vec4[](
+
+ vec4(0.560479, 0.669086, 0.784728, 0),
+ vec4(0.0771802, 0.113491, 0.0793803, 0.08),
+ vec4(0.0821904, 0.0358608, 0.0209261, 0.32),
+ vec4(0.03639, 0.0130999, 0.00643685, 0.72),
+ vec4(0.0192831, 0.00282018, 0.00084214, 1.28),
+ vec4(0.00471691, 0.000184771, 5.07565e-005, 2));
+
+#endif //USE_11_SAMPLES
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ ivec2 screen_size;
+ float camera_z_far;
+ float camera_z_near;
+
+ bool vertical;
+ bool orthogonal;
+ float unit_size;
+ float scale;
+
+ float depth_scale;
+ uint pad[3];
+}
+params;
+
+layout(set = 0, binding = 0) uniform sampler2D source_image;
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2D dest_image;
+layout(set = 2, binding = 0) uniform sampler2D source_depth;
+
+void do_filter(inout vec3 color_accum, inout vec3 divisor, vec2 uv, vec2 step, bool p_skin) {
+ // Accumulate the other samples:
+ for (int i = 1; i < kernel_size; i++) {
+ // Fetch color and depth for current sample:
+ vec2 offset = uv + kernel[i].y * step;
+ vec4 color = texture(source_image, offset);
+
+ if (abs(color.a) < 0.001) {
+ break; //mix no more
+ }
+
+ vec3 w;
+ if (p_skin) {
+ //skin
+ w = skin_kernel[i].rgb;
+ } else {
+ w = vec3(kernel[i].x);
+ }
+
+ color_accum += color.rgb * w;
+ divisor += w;
+ }
+}
+
+void main() {
+ // Pixel being shaded
+ ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
+
+ if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing
+ return;
+ }
+
+ vec2 uv = (vec2(ssC) + 0.5) / vec2(params.screen_size);
+
+ // Fetch color of current pixel:
+ vec4 base_color = texture(source_image, uv);
+ float strength = abs(base_color.a);
+
+ if (strength > 0.0) {
+ vec2 dir = params.vertical ? vec2(0.0, 1.0) : vec2(1.0, 0.0);
+
+ // Fetch linear depth of current pixel:
+ float depth = texture(source_depth, uv).r * 2.0 - 1.0;
+ float depth_scale;
+
+ 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;
+ depth_scale = params.unit_size; //remember depth is negative by default in OpenGL
+ } 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_scale = params.unit_size / depth; //remember depth is negative by default in OpenGL
+ }
+
+ float scale = mix(params.scale, depth_scale, params.depth_scale);
+
+ // Calculate the final step to fetch the surrounding pixels:
+ vec2 step = scale * dir;
+ step *= strength;
+ step /= 3.0;
+ // Accumulate the center sample:
+
+ vec3 divisor;
+ bool skin = bool(base_color.a < 0.0);
+
+ if (skin) {
+ //skin
+ divisor = skin_kernel[0].rgb;
+ } else {
+ divisor = vec3(kernel[0].x);
+ }
+
+ vec3 color = base_color.rgb * divisor;
+
+ do_filter(color, divisor, uv, step, skin);
+ do_filter(color, divisor, uv, -step, skin);
+
+ base_color.rgb = color / divisor;
+ }
+
+ imageStore(dest_image, ssC, base_color);
+}
diff --git a/servers/rendering/renderer_rd/shaders/tonemap.glsl b/servers/rendering/renderer_rd/shaders/tonemap.glsl
new file mode 100644
index 0000000000..3c685c25b9
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/tonemap.glsl
@@ -0,0 +1,435 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#extension GL_EXT_multiview : enable
+#endif
+#endif
+
+layout(location = 0) out vec2 uv_interp;
+
+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));
+ uv_interp = base_arr[gl_VertexIndex];
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MULTIVIEW
+#ifdef has_VK_KHR_multiview
+#extension GL_EXT_multiview : enable
+#define ViewIndex gl_ViewIndex
+#else // has_VK_KHR_multiview
+#define ViewIndex 0
+#endif // has_VK_KHR_multiview
+#endif //MULTIVIEW
+
+layout(location = 0) in vec2 uv_interp;
+
+#ifdef SUBPASS
+layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput input_color;
+#else
+#if MULTIVIEW
+layout(set = 0, binding = 0) uniform sampler2DArray source_color;
+#else
+layout(set = 0, binding = 0) uniform sampler2D source_color;
+#endif
+#endif
+layout(set = 1, binding = 0) uniform sampler2D source_auto_exposure;
+layout(set = 2, binding = 0) uniform sampler2D source_glow;
+#ifdef USE_1D_LUT
+layout(set = 3, binding = 0) uniform sampler2D source_color_correction;
+#else
+layout(set = 3, binding = 0) uniform sampler3D source_color_correction;
+#endif
+
+layout(push_constant, binding = 1, std430) uniform Params {
+ vec3 bcs;
+ bool use_bcs;
+
+ bool use_glow;
+ bool use_auto_exposure;
+ bool use_color_correction;
+ uint tonemapper;
+
+ uvec2 glow_texture_size;
+ float glow_intensity;
+ uint pad3;
+
+ uint glow_mode;
+ float glow_levels[7];
+
+ float exposure;
+ float white;
+ float auto_exposure_grey;
+ uint pad2;
+
+ vec2 pixel_size;
+ bool use_fxaa;
+ bool use_debanding;
+}
+params;
+
+layout(location = 0) out vec4 frag_color;
+
+#ifdef USE_GLOW_FILTER_BICUBIC
+// w0, w1, w2, and w3 are the four cubic B-spline basis functions
+float w0(float a) {
+ return (1.0f / 6.0f) * (a * (a * (-a + 3.0f) - 3.0f) + 1.0f);
+}
+
+float w1(float a) {
+ return (1.0f / 6.0f) * (a * a * (3.0f * a - 6.0f) + 4.0f);
+}
+
+float w2(float a) {
+ return (1.0f / 6.0f) * (a * (a * (-3.0f * a + 3.0f) + 3.0f) + 1.0f);
+}
+
+float w3(float a) {
+ return (1.0f / 6.0f) * (a * a * a);
+}
+
+// g0 and g1 are the two amplitude functions
+float g0(float a) {
+ return w0(a) + w1(a);
+}
+
+float g1(float a) {
+ return w2(a) + w3(a);
+}
+
+// h0 and h1 are the two offset functions
+float h0(float a) {
+ return -1.0f + w1(a) / (w0(a) + w1(a));
+}
+
+float h1(float a) {
+ return 1.0f + w3(a) / (w2(a) + w3(a));
+}
+
+vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
+ float lod = float(p_lod);
+ vec2 tex_size = vec2(params.glow_texture_size >> p_lod);
+ vec2 pixel_size = vec2(1.0f) / tex_size;
+
+ uv = uv * tex_size + vec2(0.5f);
+
+ vec2 iuv = floor(uv);
+ vec2 fuv = fract(uv);
+
+ float g0x = g0(fuv.x);
+ float g1x = g1(fuv.x);
+ float h0x = h0(fuv.x);
+ float h1x = h1(fuv.x);
+ float h0y = h0(fuv.y);
+ float h1y = h1(fuv.y);
+
+ vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5f)) * pixel_size;
+ vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5f)) * pixel_size;
+ vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5f)) * pixel_size;
+ vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5f)) * pixel_size;
+
+ return (g0(fuv.y) * (g0x * textureLod(tex, p0, lod) + g1x * textureLod(tex, p1, lod))) +
+ (g1(fuv.y) * (g0x * textureLod(tex, p2, lod) + g1x * textureLod(tex, p3, lod)));
+}
+
+#define GLOW_TEXTURE_SAMPLE(m_tex, m_uv, m_lod) texture2D_bicubic(m_tex, m_uv, m_lod)
+
+#else
+
+#define GLOW_TEXTURE_SAMPLE(m_tex, m_uv, m_lod) textureLod(m_tex, m_uv, float(m_lod))
+
+#endif
+
+vec3 tonemap_filmic(vec3 color, float white) {
+ // exposure bias: input scale (color *= bias, white *= bias) to make the brightness consistent with other tonemappers
+ // also useful to scale the input to the range that the tonemapper is designed for (some require very high input values)
+ // has no effect on the curve's general shape or visual properties
+ const float exposure_bias = 2.0f;
+ const float A = 0.22f * exposure_bias * exposure_bias; // bias baked into constants for performance
+ const float B = 0.30f * exposure_bias;
+ const float C = 0.10f;
+ const float D = 0.20f;
+ const float E = 0.01f;
+ const float F = 0.30f;
+
+ vec3 color_tonemapped = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;
+ float white_tonemapped = ((white * (A * white + C * B) + D * E) / (white * (A * white + B) + D * F)) - E / F;
+
+ return color_tonemapped / white_tonemapped;
+}
+
+vec3 tonemap_aces(vec3 color, float white) {
+ const float exposure_bias = 0.85f;
+ const float A = 2.51f * exposure_bias * exposure_bias;
+ const float B = 0.03f * exposure_bias;
+ const float C = 2.43f * exposure_bias * exposure_bias;
+ const float D = 0.59f * exposure_bias;
+ const float E = 0.14f;
+
+ vec3 color_tonemapped = (color * (A * color + B)) / (color * (C * color + D) + E);
+ float white_tonemapped = (white * (A * white + B)) / (white * (C * white + D) + E);
+
+ return color_tonemapped / white_tonemapped;
+}
+
+vec3 tonemap_reinhard(vec3 color, float white) {
+ return (white * color + color) / (color * white + white);
+}
+
+vec3 linear_to_srgb(vec3 color) {
+ //if going to srgb, clamp from 0 to 1.
+ color = clamp(color, vec3(0.0), vec3(1.0));
+ const vec3 a = vec3(0.055f);
+ return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
+}
+
+#define TONEMAPPER_LINEAR 0
+#define TONEMAPPER_REINHARD 1
+#define TONEMAPPER_FILMIC 2
+#define TONEMAPPER_ACES 3
+
+vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color
+
+ if (params.tonemapper == TONEMAPPER_LINEAR) {
+ return color;
+ } else if (params.tonemapper == TONEMAPPER_REINHARD) {
+ return tonemap_reinhard(color, white);
+ } else if (params.tonemapper == TONEMAPPER_FILMIC) {
+ return tonemap_filmic(color, white);
+ } else { // TONEMAPPER_ACES
+ return tonemap_aces(color, white);
+ }
+}
+
+vec3 gather_glow(sampler2D tex, vec2 uv) { // sample all selected glow levels
+ vec3 glow = vec3(0.0f);
+
+ if (params.glow_levels[0] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 0).rgb * params.glow_levels[0];
+ }
+
+ if (params.glow_levels[1] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 1).rgb * params.glow_levels[1];
+ }
+
+ if (params.glow_levels[2] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 2).rgb * params.glow_levels[2];
+ }
+
+ if (params.glow_levels[3] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 3).rgb * params.glow_levels[3];
+ }
+
+ if (params.glow_levels[4] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 4).rgb * params.glow_levels[4];
+ }
+
+ if (params.glow_levels[5] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 5).rgb * params.glow_levels[5];
+ }
+
+ if (params.glow_levels[6] > 0.0001) {
+ glow += GLOW_TEXTURE_SAMPLE(tex, uv, 6).rgb * params.glow_levels[6];
+ }
+
+ return glow;
+}
+
+#define GLOW_MODE_ADD 0
+#define GLOW_MODE_SCREEN 1
+#define GLOW_MODE_SOFTLIGHT 2
+#define GLOW_MODE_REPLACE 3
+#define GLOW_MODE_MIX 4
+
+vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blending mode
+ if (params.glow_mode == GLOW_MODE_ADD) {
+ return color + glow;
+ } else if (params.glow_mode == GLOW_MODE_SCREEN) {
+ //need color clamping
+ return max((color + glow) - (color * glow), vec3(0.0));
+ } else if (params.glow_mode == GLOW_MODE_SOFTLIGHT) {
+ //need color clamping
+ glow = glow * vec3(0.5f) + vec3(0.5f);
+
+ color.r = (glow.r <= 0.5f) ? (color.r - (1.0f - 2.0f * glow.r) * color.r * (1.0f - color.r)) : (((glow.r > 0.5f) && (color.r <= 0.25f)) ? (color.r + (2.0f * glow.r - 1.0f) * (4.0f * color.r * (4.0f * color.r + 1.0f) * (color.r - 1.0f) + 7.0f * color.r)) : (color.r + (2.0f * glow.r - 1.0f) * (sqrt(color.r) - color.r)));
+ color.g = (glow.g <= 0.5f) ? (color.g - (1.0f - 2.0f * glow.g) * color.g * (1.0f - color.g)) : (((glow.g > 0.5f) && (color.g <= 0.25f)) ? (color.g + (2.0f * glow.g - 1.0f) * (4.0f * color.g * (4.0f * color.g + 1.0f) * (color.g - 1.0f) + 7.0f * color.g)) : (color.g + (2.0f * glow.g - 1.0f) * (sqrt(color.g) - color.g)));
+ color.b = (glow.b <= 0.5f) ? (color.b - (1.0f - 2.0f * glow.b) * color.b * (1.0f - color.b)) : (((glow.b > 0.5f) && (color.b <= 0.25f)) ? (color.b + (2.0f * glow.b - 1.0f) * (4.0f * color.b * (4.0f * color.b + 1.0f) * (color.b - 1.0f) + 7.0f * color.b)) : (color.b + (2.0f * glow.b - 1.0f) * (sqrt(color.b) - color.b)));
+ return color;
+ } else { //replace
+ return glow;
+ }
+}
+
+vec3 apply_bcs(vec3 color, vec3 bcs) {
+ color = mix(vec3(0.0f), color, bcs.x);
+ color = mix(vec3(0.5f), color, bcs.y);
+ color = mix(vec3(dot(vec3(1.0f), color) * 0.33333f), color, bcs.z);
+
+ return color;
+}
+#ifdef USE_1D_LUT
+vec3 apply_color_correction(vec3 color) {
+ color.r = texture(source_color_correction, vec2(color.r, 0.0f)).r;
+ color.g = texture(source_color_correction, vec2(color.g, 0.0f)).g;
+ color.b = texture(source_color_correction, vec2(color.b, 0.0f)).b;
+ return color;
+}
+#else
+vec3 apply_color_correction(vec3 color) {
+ return textureLod(source_color_correction, color, 0.0).rgb;
+}
+#endif
+
+#ifndef SUBPASS
+vec3 do_fxaa(vec3 color, float exposure, vec2 uv_interp) {
+ const float FXAA_REDUCE_MIN = (1.0 / 128.0);
+ const float FXAA_REDUCE_MUL = (1.0 / 8.0);
+ const float FXAA_SPAN_MAX = 8.0;
+
+#ifdef MULTIVIEW
+ vec3 rgbNW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure;
+ vec3 rgbNE = textureLod(source_color, vec3(uv_interp + vec2(1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure;
+ vec3 rgbSW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure;
+ vec3 rgbSE = textureLod(source_color, vec3(uv_interp + vec2(1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure;
+#else
+ vec3 rgbNW = textureLod(source_color, uv_interp + vec2(-1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure;
+ vec3 rgbNE = textureLod(source_color, uv_interp + vec2(1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure;
+ vec3 rgbSW = textureLod(source_color, uv_interp + vec2(-1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure;
+ vec3 rgbSE = textureLod(source_color, uv_interp + vec2(1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure;
+#endif
+ vec3 rgbM = color;
+ vec3 luma = vec3(0.299, 0.587, 0.114);
+ float lumaNW = dot(rgbNW, luma);
+ float lumaNE = dot(rgbNE, luma);
+ float lumaSW = dot(rgbSW, luma);
+ float lumaSE = dot(rgbSE, luma);
+ float lumaM = dot(rgbM, luma);
+ float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+ float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+ vec2 dir;
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+ float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
+ (0.25 * FXAA_REDUCE_MUL),
+ FXAA_REDUCE_MIN);
+
+ float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
+ dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+ max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+ dir * rcpDirMin)) *
+ params.pixel_size;
+
+#ifdef MULTIVIEW
+ vec3 rgbA = 0.5 * exposure * (textureLod(source_color, vec3(uv_interp + dir * (1.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * (2.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz);
+ vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, vec3(uv_interp + dir * -0.5, ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * 0.5, ViewIndex), 0.0).xyz);
+#else
+ vec3 rgbA = 0.5 * exposure * (textureLod(source_color, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz + textureLod(source_color, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz);
+ vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, uv_interp + dir * -0.5, 0.0).xyz + textureLod(source_color, uv_interp + dir * 0.5, 0.0).xyz);
+#endif
+
+ float lumaB = dot(rgbB, luma);
+ if ((lumaB < lumaMin) || (lumaB > lumaMax)) {
+ return rgbA;
+ } else {
+ return rgbB;
+ }
+}
+#endif // !SUBPASS
+
+// From http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
+// and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom)
+// NOTE: `frag_coord` is in pixels (i.e. not normalized UV).
+vec3 screen_space_dither(vec2 frag_coord) {
+ // Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR.
+ vec3 dither = vec3(dot(vec2(171.0, 231.0), frag_coord));
+ dither.rgb = fract(dither.rgb / vec3(103.0, 71.0, 97.0));
+
+ // Subtract 0.5 to avoid slightly brightening the whole viewport.
+ return (dither.rgb - 0.5) / 255.0;
+}
+
+void main() {
+#ifdef SUBPASS
+ // SUBPASS and MULTIVIEW can be combined but in that case we're already reading from the correct layer
+ vec3 color = subpassLoad(input_color).rgb;
+#elif MULTIVIEW
+ vec3 color = textureLod(source_color, vec3(uv_interp, ViewIndex), 0.0f).rgb;
+#else
+ vec3 color = textureLod(source_color, uv_interp, 0.0f).rgb;
+#endif
+
+ // Exposure
+
+ float exposure = params.exposure;
+
+#ifndef SUBPASS
+ if (params.use_auto_exposure) {
+ exposure *= 1.0 / (texelFetch(source_auto_exposure, ivec2(0, 0), 0).r / params.auto_exposure_grey);
+ }
+#endif
+
+ color *= exposure;
+
+ // Early Tonemap & SRGB Conversion
+#ifndef SUBPASS
+ if (params.use_glow && params.glow_mode == GLOW_MODE_MIX) {
+ vec3 glow = gather_glow(source_glow, uv_interp);
+ color.rgb = mix(color.rgb, glow, params.glow_intensity);
+ }
+
+ if (params.use_fxaa) {
+ color = do_fxaa(color, exposure, uv_interp);
+ }
+#endif
+
+ if (params.use_debanding) {
+ // For best results, debanding should be done before tonemapping.
+ // Otherwise, we're adding noise to an already-quantized image.
+ color += screen_space_dither(gl_FragCoord.xy);
+ }
+
+ // Ensure color values passed to tonemappers are positive.
+ // They can be negative in the case of negative lights, which leads to undesired behavior.
+ color = apply_tonemapping(max(vec3(0.0), color), params.white);
+
+ color = linear_to_srgb(color); // regular linear -> SRGB conversion
+
+#ifndef SUBPASS
+ // Glow
+
+ if (params.use_glow && params.glow_mode != GLOW_MODE_MIX) {
+ vec3 glow = gather_glow(source_glow, uv_interp) * params.glow_intensity;
+
+ // high dynamic range -> SRGB
+ glow = apply_tonemapping(glow, params.white);
+ glow = linear_to_srgb(glow);
+
+ color = apply_glow(color, glow);
+ }
+#endif
+
+ // Additional effects
+
+ if (params.use_bcs) {
+ color = apply_bcs(color, params.bcs);
+ }
+
+ if (params.use_color_correction) {
+ color = apply_color_correction(color);
+ }
+
+ frag_color = vec4(color, 1.0f);
+}
diff --git a/servers/rendering/renderer_rd/shaders/volumetric_fog.glsl b/servers/rendering/renderer_rd/shaders/volumetric_fog.glsl
new file mode 100644
index 0000000000..f2010222e5
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/volumetric_fog.glsl
@@ -0,0 +1,703 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Do not use subgroups here, seems there is not much advantage and causes glitches
+#if defined(has_GL_KHR_shader_subgroup_ballot) && defined(has_GL_KHR_shader_subgroup_arithmetic)
+#extension GL_KHR_shader_subgroup_ballot: enable
+#extension GL_KHR_shader_subgroup_arithmetic: enable
+
+#define USE_SUBGROUPS
+#endif
+*/
+
+#if defined(MODE_FOG) || defined(MODE_FILTER)
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#endif
+
+#if defined(MODE_DENSITY)
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#endif
+
+#include "cluster_data_inc.glsl"
+#include "light_data_inc.glsl"
+
+#define M_PI 3.14159265359
+
+layout(set = 0, binding = 1) uniform texture2D shadow_atlas;
+layout(set = 0, binding = 2) uniform texture2D directional_shadow_atlas;
+
+layout(set = 0, binding = 3, std430) restrict readonly buffer OmniLights {
+ LightData data[];
+}
+omni_lights;
+
+layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights {
+ LightData data[];
+}
+spot_lights;
+
+layout(set = 0, binding = 5, std140) uniform DirectionalLights {
+ DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+}
+directional_lights;
+
+layout(set = 0, binding = 6, std430) buffer restrict readonly ClusterBuffer {
+ uint data[];
+}
+cluster_buffer;
+
+layout(set = 0, binding = 7) uniform sampler linear_sampler;
+
+#ifdef MODE_DENSITY
+layout(rgba16f, set = 0, binding = 8) uniform restrict writeonly image3D density_map;
+layout(rgba16f, set = 0, binding = 9) uniform restrict readonly image3D fog_map; //unused
+#endif
+
+#ifdef MODE_FOG
+layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D density_map;
+layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D fog_map;
+#endif
+
+#ifdef MODE_FILTER
+layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D source_map;
+layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D dest_map;
+#endif
+
+layout(set = 0, binding = 10) uniform sampler shadow_sampler;
+
+#define MAX_VOXEL_GI_INSTANCES 8
+
+struct VoxelGIData {
+ mat4 xform;
+ vec3 bounds;
+ float dynamic_range;
+
+ float bias;
+ float normal_bias;
+ bool blend_ambient;
+ uint texture_slot;
+
+ float anisotropy_strength;
+ float ambient_occlusion;
+ float ambient_occlusion_size;
+ uint mipmaps;
+};
+
+layout(set = 0, binding = 11, std140) uniform VoxelGIs {
+ VoxelGIData data[MAX_VOXEL_GI_INSTANCES];
+}
+voxel_gi_instances;
+
+layout(set = 0, binding = 12) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
+
+layout(set = 0, binding = 13) uniform sampler linear_sampler_with_mipmaps;
+
+#ifdef ENABLE_SDFGI
+
+// SDFGI Integration on set 1
+#define SDFGI_MAX_CASCADES 8
+
+struct SDFVoxelGICascadeData {
+ vec3 position;
+ float to_probe;
+ ivec3 probe_world_offset;
+ float to_cell; // 1/bounds * grid_size
+};
+
+layout(set = 1, binding = 0, std140) uniform SDFGI {
+ vec3 grid_size;
+ uint max_cascades;
+
+ bool use_occlusion;
+ int probe_axis_size;
+ float probe_to_uvw;
+ float normal_bias;
+
+ vec3 lightprobe_tex_pixel_size;
+ float energy;
+
+ vec3 lightprobe_uv_offset;
+ float y_mult;
+
+ vec3 occlusion_clamp;
+ uint pad3;
+
+ vec3 occlusion_renormalize;
+ uint pad4;
+
+ vec3 cascade_probe_size;
+ uint pad5;
+
+ SDFVoxelGICascadeData cascades[SDFGI_MAX_CASCADES];
+}
+sdfgi;
+
+layout(set = 1, binding = 1) uniform texture2DArray sdfgi_ambient_texture;
+
+layout(set = 1, binding = 2) uniform texture3D sdfgi_occlusion_texture;
+
+#endif //SDFGI
+
+layout(set = 0, binding = 14, std140) uniform Params {
+ vec2 fog_frustum_size_begin;
+ vec2 fog_frustum_size_end;
+
+ float fog_frustum_end;
+ float z_near;
+ float z_far;
+ int filter_axis;
+
+ ivec3 fog_volume_size;
+ uint directional_light_count;
+
+ vec3 light_color;
+ float base_density;
+
+ float detail_spread;
+ float gi_inject;
+ uint max_voxel_gi_instances;
+ uint cluster_type_size;
+
+ vec2 screen_size;
+ uint cluster_shift;
+ uint cluster_width;
+
+ uint max_cluster_element_count_div_32;
+ bool use_temporal_reprojection;
+ uint temporal_frame;
+ float temporal_blend;
+
+ mat3x4 cam_rotation;
+ mat4 to_prev_view;
+}
+params;
+
+layout(set = 0, binding = 15) uniform texture3D prev_density_texture;
+
+float get_depth_at_pos(float cell_depth_size, int z) {
+ float d = float(z) * cell_depth_size + cell_depth_size * 0.5; //center of voxels
+ d = pow(d, params.detail_spread);
+ return params.fog_frustum_end * d;
+}
+
+vec3 hash3f(uvec3 x) {
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = (x >> 16) ^ x;
+ return vec3(x & 0xFFFFF) / vec3(float(0xFFFFF));
+}
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+void cluster_get_item_range(uint p_offset, out uint item_min, out uint item_max, out uint item_from, out uint item_to) {
+ uint item_min_max = cluster_buffer.data[p_offset];
+ item_min = item_min_max & 0xFFFF;
+ item_max = item_min_max >> 16;
+ ;
+
+ item_from = item_min >> 5;
+ item_to = (item_max == 0) ? 0 : ((item_max - 1) >> 5) + 1; //side effect of how it is stored, as item_max 0 means no elements
+}
+
+uint cluster_get_range_clip_mask(uint i, uint z_min, uint z_max) {
+ int local_min = clamp(int(z_min) - int(i) * 32, 0, 31);
+ int mask_width = min(int(z_max) - int(z_min), 32 - local_min);
+ return bitfieldInsert(uint(0), uint(0xFFFFFFFF), local_min, mask_width);
+}
+
+#define TEMPORAL_FRAMES 16
+
+const vec3 halton_map[TEMPORAL_FRAMES] = vec3[](
+ vec3(0.5, 0.33333333, 0.2),
+ vec3(0.25, 0.66666667, 0.4),
+ vec3(0.75, 0.11111111, 0.6),
+ vec3(0.125, 0.44444444, 0.8),
+ vec3(0.625, 0.77777778, 0.04),
+ vec3(0.375, 0.22222222, 0.24),
+ vec3(0.875, 0.55555556, 0.44),
+ vec3(0.0625, 0.88888889, 0.64),
+ vec3(0.5625, 0.03703704, 0.84),
+ vec3(0.3125, 0.37037037, 0.08),
+ vec3(0.8125, 0.7037037, 0.28),
+ vec3(0.1875, 0.14814815, 0.48),
+ vec3(0.6875, 0.48148148, 0.68),
+ vec3(0.4375, 0.81481481, 0.88),
+ vec3(0.9375, 0.25925926, 0.12),
+ vec3(0.03125, 0.59259259, 0.32));
+
+void main() {
+ vec3 fog_cell_size = 1.0 / vec3(params.fog_volume_size);
+
+#ifdef MODE_DENSITY
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+ if (any(greaterThanEqual(pos, params.fog_volume_size))) {
+ return; //do not compute
+ }
+
+ vec3 posf = vec3(pos);
+
+ //posf += mix(vec3(0.0),vec3(1.0),0.3) * hash3f(uvec3(pos)) * 2.0 - 1.0;
+
+ vec3 fog_unit_pos = posf * fog_cell_size + fog_cell_size * 0.5; //center of voxels
+
+ uvec2 screen_pos = uvec2(fog_unit_pos.xy * params.screen_size);
+ uvec2 cluster_pos = screen_pos >> params.cluster_shift;
+ uint cluster_offset = (params.cluster_width * cluster_pos.y + cluster_pos.x) * (params.max_cluster_element_count_div_32 + 32);
+ //positions in screen are too spread apart, no hopes for optimizing with subgroups
+
+ fog_unit_pos.z = pow(fog_unit_pos.z, params.detail_spread);
+
+ vec3 view_pos;
+ view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(fog_unit_pos.z));
+ view_pos.z = -params.fog_frustum_end * fog_unit_pos.z;
+ view_pos.y = -view_pos.y;
+
+ vec4 reprojected_density = vec4(0.0);
+ float reproject_amount = 0.0;
+
+ if (params.use_temporal_reprojection) {
+ vec3 prev_view = (params.to_prev_view * vec4(view_pos, 1.0)).xyz;
+ //undo transform into prev view
+ prev_view.y = -prev_view.y;
+ //z back to unit size
+ prev_view.z /= -params.fog_frustum_end;
+ //xy back to unit size
+ prev_view.xy /= mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(prev_view.z));
+ prev_view.xy = prev_view.xy * 0.5 + 0.5;
+ //z back to unspread value
+ prev_view.z = pow(prev_view.z, 1.0 / params.detail_spread);
+
+ if (all(greaterThan(prev_view, vec3(0.0))) && all(lessThan(prev_view, vec3(1.0)))) {
+ //reprojectinon fits
+
+ reprojected_density = textureLod(sampler3D(prev_density_texture, linear_sampler), prev_view, 0.0);
+ reproject_amount = params.temporal_blend;
+
+ // Since we can reproject, now we must jitter the current view pos.
+ // This is done here because cells that can't reproject should not jitter.
+
+ fog_unit_pos = posf * fog_cell_size + fog_cell_size * halton_map[params.temporal_frame]; //center of voxels, offset by halton table
+
+ screen_pos = uvec2(fog_unit_pos.xy * params.screen_size);
+ cluster_pos = screen_pos >> params.cluster_shift;
+ cluster_offset = (params.cluster_width * cluster_pos.y + cluster_pos.x) * (params.max_cluster_element_count_div_32 + 32);
+ //positions in screen are too spread apart, no hopes for optimizing with subgroups
+
+ fog_unit_pos.z = pow(fog_unit_pos.z, params.detail_spread);
+
+ view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(fog_unit_pos.z));
+ view_pos.z = -params.fog_frustum_end * fog_unit_pos.z;
+ view_pos.y = -view_pos.y;
+ }
+ }
+
+ uint cluster_z = uint(clamp((abs(view_pos.z) / params.z_far) * 32.0, 0.0, 31.0));
+
+ vec3 total_light = params.light_color;
+
+ float total_density = params.base_density;
+ float cell_depth_size = abs(view_pos.z - get_depth_at_pos(fog_cell_size.z, pos.z + 1));
+ //compute directional lights
+
+ for (uint i = 0; i < params.directional_light_count; i++) {
+ vec3 shadow_attenuation = vec3(1.0);
+
+ if (directional_lights.data[i].shadow_enabled) {
+ float depth_z = -view_pos.z;
+
+ vec4 pssm_coord;
+ vec3 shadow_color = directional_lights.data[i].shadow_color1.rgb;
+ vec3 light_dir = directional_lights.data[i].direction;
+ vec4 v = vec4(view_pos, 1.0);
+ float z_range;
+
+ if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+ pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+ pssm_coord /= pssm_coord.w;
+ z_range = directional_lights.data[i].shadow_z_range.x;
+
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+ pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+ pssm_coord /= pssm_coord.w;
+ z_range = directional_lights.data[i].shadow_z_range.y;
+
+ } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+ pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+ pssm_coord /= pssm_coord.w;
+ z_range = directional_lights.data[i].shadow_z_range.z;
+
+ } else {
+ pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+ pssm_coord /= pssm_coord.w;
+ z_range = directional_lights.data[i].shadow_z_range.w;
+ }
+
+ float depth = texture(sampler2D(directional_shadow_atlas, linear_sampler), pssm_coord.xy).r;
+ float shadow = exp(min(0.0, (depth - pssm_coord.z)) * z_range * directional_lights.data[i].shadow_volumetric_fog_fade);
+
+ /*
+ //float shadow = textureProj(sampler2DShadow(directional_shadow_atlas,shadow_sampler),pssm_coord);
+ float shadow = 0.0;
+ for(float xi=-1;xi<=1;xi++) {
+ for(float yi=-1;yi<=1;yi++) {
+ vec2 ofs = vec2(xi,yi) * 1.5 * params.directional_shadow_pixel_size;
+ shadow += textureProj(sampler2DShadow(directional_shadow_atlas,shadow_sampler),pssm_coord + vec4(ofs,0.0,0.0));
+ }
+
+ }
+
+ shadow /= 3.0 * 3.0;
+
+*/
+ shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, view_pos.z)); //done with negative values for performance
+
+ shadow_attenuation = mix(shadow_color, vec3(1.0), shadow);
+ }
+
+ total_light += shadow_attenuation * directional_lights.data[i].color * directional_lights.data[i].energy / M_PI;
+ }
+
+ //compute lights from cluster
+
+ { //omni lights
+
+ uint cluster_omni_offset = cluster_offset;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_omni_offset + params.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_omni_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+ uint light_index = 32 * i + bit;
+
+ //if (!bool(omni_omni_lights.data[light_index].mask & draw_call.layer_mask)) {
+ // continue; //not masked
+ //}
+
+ vec3 light_pos = omni_lights.data[light_index].position;
+ float d = distance(omni_lights.data[light_index].position, view_pos);
+ float shadow_attenuation = 1.0;
+
+ if (d * omni_lights.data[light_index].inv_radius < 1.0) {
+ float attenuation = get_omni_attenuation(d, omni_lights.data[light_index].inv_radius, omni_lights.data[light_index].attenuation);
+
+ vec3 light = omni_lights.data[light_index].color / M_PI;
+
+ if (omni_lights.data[light_index].shadow_enabled) {
+ //has shadow
+ vec4 v = vec4(view_pos, 1.0);
+
+ vec4 splane = (omni_lights.data[light_index].shadow_matrix * v);
+ float shadow_len = length(splane.xyz); //need to remember shadow len from here
+
+ splane.xyz = normalize(splane.xyz);
+ vec4 clamp_rect = omni_lights.data[light_index].atlas_rect;
+
+ if (splane.z >= 0.0) {
+ splane.z += 1.0;
+
+ clamp_rect.y += clamp_rect.w;
+
+ } else {
+ splane.z = 1.0 - splane.z;
+ }
+
+ splane.xy /= splane.z;
+
+ splane.xy = splane.xy * 0.5 + 0.5;
+ splane.z = shadow_len * omni_lights.data[light_index].inv_radius;
+ splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
+ splane.w = 1.0; //needed? i think it should be 1 already
+
+ float depth = texture(sampler2D(shadow_atlas, linear_sampler), splane.xy).r;
+
+ shadow_attenuation = exp(min(0.0, (depth - splane.z)) / omni_lights.data[light_index].inv_radius * omni_lights.data[light_index].shadow_volumetric_fog_fade);
+ }
+ total_light += light * attenuation * shadow_attenuation;
+ }
+ }
+ }
+ }
+
+ { //spot lights
+
+ uint cluster_spot_offset = cluster_offset + params.cluster_type_size;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_spot_offset + params.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+#ifdef USE_SUBGROUPS
+ item_from = subgroupBroadcastFirst(subgroupMin(item_from));
+ item_to = subgroupBroadcastFirst(subgroupMax(item_to));
+#endif
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_spot_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+#ifdef USE_SUBGROUPS
+ uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask));
+#else
+ uint merged_mask = mask;
+#endif
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1 << bit);
+#ifdef USE_SUBGROUPS
+ if (((1 << bit) & mask) == 0) { //do not process if not originally here
+ continue;
+ }
+#endif
+
+ //if (!bool(omni_lights.data[light_index].mask & draw_call.layer_mask)) {
+ // continue; //not masked
+ //}
+
+ uint light_index = 32 * i + bit;
+
+ vec3 light_pos = spot_lights.data[light_index].position;
+ vec3 light_rel_vec = spot_lights.data[light_index].position - view_pos;
+ float d = length(light_rel_vec);
+ float shadow_attenuation = 1.0;
+
+ if (d * spot_lights.data[light_index].inv_radius < 1.0) {
+ float attenuation = get_omni_attenuation(d, spot_lights.data[light_index].inv_radius, spot_lights.data[light_index].attenuation);
+
+ vec3 spot_dir = spot_lights.data[light_index].direction;
+ float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_lights.data[light_index].cone_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_lights.data[light_index].cone_angle));
+ attenuation *= 1.0 - pow(spot_rim, spot_lights.data[light_index].cone_attenuation);
+
+ vec3 light = spot_lights.data[light_index].color / M_PI;
+
+ if (spot_lights.data[light_index].shadow_enabled) {
+ //has shadow
+ vec4 v = vec4(view_pos, 1.0);
+
+ vec4 splane = (spot_lights.data[light_index].shadow_matrix * v);
+ splane /= splane.w;
+
+ float depth = texture(sampler2D(shadow_atlas, linear_sampler), splane.xy).r;
+
+ shadow_attenuation = exp(min(0.0, (depth - splane.z)) / spot_lights.data[light_index].inv_radius * spot_lights.data[light_index].shadow_volumetric_fog_fade);
+ }
+
+ total_light += light * attenuation * shadow_attenuation;
+ }
+ }
+ }
+ }
+
+ vec3 world_pos = mat3(params.cam_rotation) * view_pos;
+
+ for (uint i = 0; i < params.max_voxel_gi_instances; i++) {
+ vec3 position = (voxel_gi_instances.data[i].xform * vec4(world_pos, 1.0)).xyz;
+
+ //this causes corrupted pixels, i have no idea why..
+ if (all(bvec2(all(greaterThanEqual(position, vec3(0.0))), all(lessThan(position, voxel_gi_instances.data[i].bounds))))) {
+ position /= voxel_gi_instances.data[i].bounds;
+
+ vec4 light = vec4(0.0);
+ for (uint j = 0; j < voxel_gi_instances.data[i].mipmaps; j++) {
+ vec4 slight = textureLod(sampler3D(voxel_gi_textures[i], linear_sampler_with_mipmaps), position, float(j));
+ float a = (1.0 - light.a);
+ light += a * slight;
+ }
+
+ light.rgb *= voxel_gi_instances.data[i].dynamic_range * params.gi_inject;
+
+ total_light += light.rgb;
+ }
+ }
+
+ //sdfgi
+#ifdef ENABLE_SDFGI
+
+ {
+ float blend = -1.0;
+ vec3 ambient_total = vec3(0.0);
+
+ for (uint i = 0; i < sdfgi.max_cascades; i++) {
+ vec3 cascade_pos = (world_pos - sdfgi.cascades[i].position) * sdfgi.cascades[i].to_probe;
+
+ if (any(lessThan(cascade_pos, vec3(0.0))) || any(greaterThanEqual(cascade_pos, sdfgi.cascade_probe_size))) {
+ continue; //skip cascade
+ }
+
+ vec3 base_pos = floor(cascade_pos);
+ ivec3 probe_base_pos = ivec3(base_pos);
+
+ vec4 ambient_accum = vec4(0.0);
+
+ ivec3 tex_pos = ivec3(probe_base_pos.xy, int(i));
+ tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
+
+ for (uint j = 0; j < 8; j++) {
+ ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
+ ivec3 probe_posi = probe_base_pos;
+ probe_posi += offset;
+
+ // Compute weight
+
+ vec3 probe_pos = vec3(probe_posi);
+ vec3 probe_to_pos = cascade_pos - probe_pos;
+
+ vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
+ float weight = trilinear.x * trilinear.y * trilinear.z;
+
+ // Compute lightprobe occlusion
+
+ if (sdfgi.use_occlusion) {
+ ivec3 occ_indexv = abs((sdfgi.cascades[i].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+ vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
+
+ vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
+ occ_pos.z += float(i);
+ if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
+ occ_pos.x += 1.0;
+ }
+
+ occ_pos *= sdfgi.occlusion_renormalize;
+ float occlusion = dot(textureLod(sampler3D(sdfgi_occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
+
+ weight *= max(occlusion, 0.01);
+ }
+
+ // Compute ambient texture position
+
+ ivec3 uvw = tex_pos;
+ uvw.xy += offset.xy;
+ uvw.x += offset.z * sdfgi.probe_axis_size;
+
+ vec3 ambient = texelFetch(sampler2DArray(sdfgi_ambient_texture, linear_sampler), uvw, 0).rgb;
+
+ ambient_accum.rgb += ambient * weight;
+ ambient_accum.a += weight;
+ }
+
+ if (ambient_accum.a > 0) {
+ ambient_accum.rgb /= ambient_accum.a;
+ }
+ ambient_total = ambient_accum.rgb;
+ break;
+ }
+
+ total_light += ambient_total * params.gi_inject;
+ }
+
+#endif
+
+ vec4 final_density = vec4(total_light, total_density);
+
+ final_density = mix(final_density, reprojected_density, reproject_amount);
+
+ imageStore(density_map, pos, final_density);
+#endif
+
+#ifdef MODE_FOG
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xy, 0);
+
+ if (any(greaterThanEqual(pos, params.fog_volume_size))) {
+ return; //do not compute
+ }
+
+ vec4 fog_accum = vec4(0.0);
+ float prev_z = 0.0;
+
+ float t = 1.0;
+
+ for (int i = 0; i < params.fog_volume_size.z; i++) {
+ //compute fog position
+ ivec3 fog_pos = pos + ivec3(0, 0, i);
+ //get fog value
+ vec4 fog = imageLoad(density_map, fog_pos);
+
+ //get depth at cell pos
+ float z = get_depth_at_pos(fog_cell_size.z, i);
+ //get distance from previous pos
+ float d = abs(prev_z - z);
+ //compute exinction based on beer's
+ float extinction = t * exp(-d * fog.a);
+ //compute alpha based on different of extinctions
+ float alpha = t - extinction;
+ //update extinction
+ t = extinction;
+
+ fog_accum += vec4(fog.rgb * alpha, alpha);
+ prev_z = z;
+
+ vec4 fog_value;
+
+ if (fog_accum.a > 0.0) {
+ fog_value = vec4(fog_accum.rgb / fog_accum.a, 1.0 - t);
+ } else {
+ fog_value = vec4(0.0);
+ }
+
+ imageStore(fog_map, fog_pos, fog_value);
+ }
+
+#endif
+
+#ifdef MODE_FILTER
+
+ ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+
+ const float gauss[7] = float[](0.071303, 0.131514, 0.189879, 0.214607, 0.189879, 0.131514, 0.071303);
+
+ const ivec3 filter_dir[3] = ivec3[](ivec3(1, 0, 0), ivec3(0, 1, 0), ivec3(0, 0, 1));
+ ivec3 offset = filter_dir[params.filter_axis];
+
+ vec4 accum = vec4(0.0);
+ for (int i = -3; i <= 3; i++) {
+ accum += imageLoad(source_map, clamp(pos + offset * i, ivec3(0), params.fog_volume_size - ivec3(1))) * gauss[i + 3];
+ }
+
+ imageStore(dest_map, pos, accum);
+
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/voxel_gi.glsl b/servers/rendering/renderer_rd/shaders/voxel_gi.glsl
new file mode 100644
index 0000000000..49a493cdc7
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/voxel_gi.glsl
@@ -0,0 +1,779 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+#ifdef MODE_DYNAMIC
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+#else
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+#endif
+
+#ifndef MODE_DYNAMIC
+
+#define NO_CHILDREN 0xFFFFFFFF
+#define GREY_VEC vec3(0.33333, 0.33333, 0.33333)
+
+struct CellChildren {
+ uint children[8];
+};
+
+layout(set = 0, binding = 1, std430) buffer CellChildrenBuffer {
+ CellChildren data[];
+}
+cell_children;
+
+struct CellData {
+ uint position; // xyz 10 bits
+ uint albedo; //rgb albedo
+ uint emission; //rgb normalized with e as multiplier
+ uint normal; //RGB normal encoded
+};
+
+layout(set = 0, binding = 2, std430) buffer CellDataBuffer {
+ CellData data[];
+}
+cell_data;
+
+#endif // MODE DYNAMIC
+
+#define LIGHT_TYPE_DIRECTIONAL 0
+#define LIGHT_TYPE_OMNI 1
+#define LIGHT_TYPE_SPOT 2
+
+#if defined(MODE_COMPUTE_LIGHT) || defined(MODE_DYNAMIC_LIGHTING)
+
+struct Light {
+ uint type;
+ float energy;
+ float radius;
+ float attenuation;
+
+ vec3 color;
+ float cos_spot_angle;
+
+ vec3 position;
+ float inv_spot_attenuation;
+
+ vec3 direction;
+ bool has_shadow;
+};
+
+layout(set = 0, binding = 3, std140) uniform Lights {
+ Light data[MAX_LIGHTS];
+}
+lights;
+
+#endif // MODE COMPUTE LIGHT
+
+#ifdef MODE_SECOND_BOUNCE
+
+layout(set = 0, binding = 5) uniform texture3D color_texture;
+
+#ifdef MODE_ANISOTROPIC
+layout(set = 0, binding = 7) uniform texture3D aniso_pos_texture;
+layout(set = 0, binding = 8) uniform texture3D aniso_neg_texture;
+#endif // MODE ANISOTROPIC
+
+#endif // MODE_SECOND_BOUNCE
+
+#ifndef MODE_DYNAMIC
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec3 limits;
+ uint stack_size;
+
+ float emission_scale;
+ float propagation;
+ float dynamic_range;
+
+ uint light_count;
+ uint cell_offset;
+ uint cell_count;
+ float aniso_strength;
+ uint pad;
+}
+params;
+
+layout(set = 0, binding = 4, std430) buffer Outputs {
+ vec4 data[];
+}
+outputs;
+
+#endif // MODE DYNAMIC
+
+layout(set = 0, binding = 9) uniform texture3D texture_sdf;
+layout(set = 0, binding = 10) uniform sampler texture_sampler;
+
+#ifdef MODE_WRITE_TEXTURE
+
+layout(rgba8, set = 0, binding = 5) uniform restrict writeonly image3D color_tex;
+
+#ifdef MODE_ANISOTROPIC
+
+layout(r16ui, set = 0, binding = 6) uniform restrict writeonly uimage3D aniso_pos_tex;
+layout(r16ui, set = 0, binding = 7) uniform restrict writeonly uimage3D aniso_neg_tex;
+
+#endif
+
+#endif
+
+#ifdef MODE_DYNAMIC
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec3 limits;
+ uint light_count; //when not lighting
+ ivec3 x_dir;
+ float z_base;
+ ivec3 y_dir;
+ float z_sign;
+ ivec3 z_dir;
+ float pos_multiplier;
+ ivec2 rect_pos;
+ ivec2 rect_size;
+ ivec2 prev_rect_ofs;
+ ivec2 prev_rect_size;
+ bool flip_x;
+ bool flip_y;
+ float dynamic_range;
+ bool on_mipmap;
+ float propagation;
+ float pad[3];
+}
+params;
+
+#ifdef MODE_DYNAMIC_LIGHTING
+
+layout(rgba8, set = 0, binding = 5) uniform restrict readonly image2D source_albedo;
+layout(rgba8, set = 0, binding = 6) uniform restrict readonly image2D source_normal;
+layout(rgba8, set = 0, binding = 7) uniform restrict readonly image2D source_orm;
+//layout (set=0,binding=8) uniform texture2D source_depth;
+layout(rgba16f, set = 0, binding = 11) uniform restrict image2D emission;
+layout(r32f, set = 0, binding = 12) uniform restrict image2D depth;
+
+#endif
+
+#ifdef MODE_DYNAMIC_SHRINK
+
+layout(rgba16f, set = 0, binding = 5) uniform restrict readonly image2D source_light;
+layout(r32f, set = 0, binding = 6) uniform restrict readonly image2D source_depth;
+
+#ifdef MODE_DYNAMIC_SHRINK_WRITE
+
+layout(rgba16f, set = 0, binding = 7) uniform restrict writeonly image2D light;
+layout(r32f, set = 0, binding = 8) uniform restrict writeonly image2D depth;
+
+#endif // MODE_DYNAMIC_SHRINK_WRITE
+
+#ifdef MODE_DYNAMIC_SHRINK_PLOT
+
+layout(rgba8, set = 0, binding = 11) uniform restrict image3D color_texture;
+
+#ifdef MODE_ANISOTROPIC
+
+layout(r16ui, set = 0, binding = 12) uniform restrict writeonly uimage3D aniso_pos_texture;
+layout(r16ui, set = 0, binding = 13) uniform restrict writeonly uimage3D aniso_neg_texture;
+
+#endif // MODE ANISOTROPIC
+
+#endif //MODE_DYNAMIC_SHRINK_PLOT
+
+#endif // MODE_DYNAMIC_SHRINK
+
+//layout (rgba8,set=0,binding=5) uniform restrict writeonly image3D color_tex;
+
+#endif // MODE DYNAMIC
+
+#if defined(MODE_COMPUTE_LIGHT) || defined(MODE_DYNAMIC_LIGHTING)
+
+float raymarch(float distance, float distance_adv, vec3 from, vec3 direction) {
+ vec3 cell_size = 1.0 / vec3(params.limits);
+ float occlusion = 1.0;
+ while (distance > 0.5) { //use this to avoid precision errors
+ float advance = texture(sampler3D(texture_sdf, texture_sampler), from * cell_size).r * 255.0 - 1.0;
+ if (advance < 0.0) {
+ occlusion = 0.0;
+ break;
+ }
+
+ occlusion = min(advance, occlusion);
+
+ advance = max(distance_adv, advance - mod(advance, distance_adv)); //should always advance in multiples of distance_adv
+
+ from += direction * advance;
+ distance -= advance;
+ }
+
+ return occlusion; //max(0.0,distance);
+}
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+bool compute_light_vector(uint light, vec3 pos, out float attenuation, out vec3 light_pos) {
+ if (lights.data[light].type == LIGHT_TYPE_DIRECTIONAL) {
+ light_pos = pos - lights.data[light].direction * length(vec3(params.limits));
+ attenuation = 1.0;
+
+ } else {
+ light_pos = lights.data[light].position;
+ float distance = length(pos - light_pos);
+ if (distance >= lights.data[light].radius) {
+ return false;
+ }
+
+ attenuation = get_omni_attenuation(distance, 1.0 / lights.data[light].radius, lights.data[light].attenuation);
+
+ if (lights.data[light].type == LIGHT_TYPE_SPOT) {
+ vec3 rel = normalize(pos - light_pos);
+ float cos_spot_angle = lights.data[light].cos_spot_angle;
+ float cos_angle = dot(rel, lights.data[light].direction);
+ if (cos_angle < cos_spot_angle) {
+ return false;
+ }
+
+ float scos = max(cos_angle, cos_spot_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
+ attenuation *= 1.0 - pow(spot_rim, lights.data[light].inv_spot_attenuation);
+ }
+ }
+
+ return true;
+}
+
+float get_normal_advance(vec3 p_normal) {
+ vec3 normal = p_normal;
+ vec3 unorm = abs(normal);
+
+ if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
+ // x code
+ unorm = normal.x > 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0);
+ } else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
+ // y code
+ unorm = normal.y > 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0);
+ } else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
+ // z code
+ unorm = normal.z > 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0);
+ } else {
+ // oh-no we messed up code
+ // has to be
+ unorm = vec3(1.0, 0.0, 0.0);
+ }
+
+ return 1.0 / dot(normal, unorm);
+}
+
+void clip_segment(vec4 plane, vec3 begin, inout vec3 end) {
+ vec3 segment = begin - end;
+ float den = dot(plane.xyz, segment);
+
+ //printf("den is %i\n",den);
+ if (den < 0.0001) {
+ return;
+ }
+
+ float dist = (dot(plane.xyz, begin) - plane.w) / den;
+
+ if (dist < 0.0001 || dist > 1.0001) {
+ return;
+ }
+
+ end = begin + segment * -dist;
+}
+
+bool compute_light_at_pos(uint index, vec3 pos, vec3 normal, inout vec3 light, inout vec3 light_dir) {
+ float attenuation;
+ vec3 light_pos;
+
+ if (!compute_light_vector(index, pos, attenuation, light_pos)) {
+ return false;
+ }
+
+ light_dir = normalize(pos - light_pos);
+
+ if (attenuation < 0.01 || (length(normal) > 0.2 && dot(normal, light_dir) >= 0)) {
+ return false; //not facing the light, or attenuation is near zero
+ }
+
+ if (lights.data[index].has_shadow) {
+ float distance_adv = get_normal_advance(light_dir);
+
+ vec3 to = pos;
+ if (length(normal) > 0.2) {
+ to += normal * distance_adv * 0.51;
+ } else {
+ to -= sign(light_dir) * 0.45; //go near the edge towards the light direction to avoid self occlusion
+ }
+
+ //clip
+ clip_segment(mix(vec4(-1.0, 0.0, 0.0, 0.0), vec4(1.0, 0.0, 0.0, float(params.limits.x - 1)), bvec4(light_dir.x < 0.0)), to, light_pos);
+ clip_segment(mix(vec4(0.0, -1.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, float(params.limits.y - 1)), bvec4(light_dir.y < 0.0)), to, light_pos);
+ clip_segment(mix(vec4(0.0, 0.0, -1.0, 0.0), vec4(0.0, 0.0, 1.0, float(params.limits.z - 1)), bvec4(light_dir.z < 0.0)), to, light_pos);
+
+ float distance = length(to - light_pos);
+ if (distance < 0.1) {
+ return false; // hit
+ }
+
+ distance += distance_adv - mod(distance, distance_adv); //make it reach the center of the box always
+ light_pos = to - light_dir * distance;
+
+ //from -= sign(light_dir)*0.45; //go near the edge towards the light direction to avoid self occlusion
+
+ /*float dist = raymarch(distance,distance_adv,light_pos,light_dir);
+
+ if (dist > distance_adv) {
+ return false;
+ }
+
+ attenuation *= 1.0 - smoothstep(0.1*distance_adv,distance_adv,dist);
+ */
+
+ float occlusion = raymarch(distance, distance_adv, light_pos, light_dir);
+
+ if (occlusion == 0.0) {
+ return false;
+ }
+
+ attenuation *= occlusion; //1.0 - smoothstep(0.1*distance_adv,distance_adv,dist);
+ }
+
+ light = lights.data[index].color * attenuation * lights.data[index].energy;
+ return true;
+}
+
+#endif // MODE COMPUTE LIGHT
+
+void main() {
+#ifndef MODE_DYNAMIC
+
+ uint cell_index = gl_GlobalInvocationID.x;
+ if (cell_index >= params.cell_count) {
+ return;
+ }
+ cell_index += params.cell_offset;
+
+ uvec3 posu = uvec3(cell_data.data[cell_index].position & 0x7FF, (cell_data.data[cell_index].position >> 11) & 0x3FF, cell_data.data[cell_index].position >> 21);
+ vec4 albedo = unpackUnorm4x8(cell_data.data[cell_index].albedo);
+
+#endif
+
+ /////////////////COMPUTE LIGHT///////////////////////////////
+
+#ifdef MODE_COMPUTE_LIGHT
+
+ vec3 pos = vec3(posu) + vec3(0.5);
+
+ vec3 emission = vec3(uvec3(cell_data.data[cell_index].emission & 0x1ff, (cell_data.data[cell_index].emission >> 9) & 0x1ff, (cell_data.data[cell_index].emission >> 18) & 0x1ff)) * pow(2.0, float(cell_data.data[cell_index].emission >> 27) - 15.0 - 9.0);
+ vec3 normal = unpackSnorm4x8(cell_data.data[cell_index].normal).xyz;
+
+#ifdef MODE_ANISOTROPIC
+ vec3 accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
+ const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0));
+#else
+ vec3 accum = vec3(0.0);
+#endif
+
+ for (uint i = 0; i < params.light_count; i++) {
+ vec3 light;
+ vec3 light_dir;
+ if (!compute_light_at_pos(i, pos, normal.xyz, light, light_dir)) {
+ continue;
+ }
+
+ light *= albedo.rgb;
+
+#ifdef MODE_ANISOTROPIC
+ for (uint j = 0; j < 6; j++) {
+ accum[j] += max(0.0, dot(accum_dirs[j], -light_dir)) * light;
+ }
+#else
+ if (length(normal) > 0.2) {
+ accum += max(0.0, dot(normal, -light_dir)) * light;
+ } else {
+ //all directions
+ accum += light;
+ }
+#endif
+ }
+
+#ifdef MODE_ANISOTROPIC
+
+ for (uint i = 0; i < 6; i++) {
+ vec3 light = accum[i];
+ if (length(normal) > 0.2) {
+ light += max(0.0, dot(accum_dirs[i], -normal)) * emission;
+ } else {
+ light += emission;
+ }
+
+ outputs.data[cell_index * 6 + i] = vec4(light, 0.0);
+ }
+
+#else
+ outputs.data[cell_index] = vec4(accum + emission, 0.0);
+
+#endif
+
+#endif //MODE_COMPUTE_LIGHT
+
+ /////////////////SECOND BOUNCE///////////////////////////////
+
+#ifdef MODE_SECOND_BOUNCE
+ vec3 pos = vec3(posu) + vec3(0.5);
+ ivec3 ipos = ivec3(posu);
+ vec4 normal = unpackSnorm4x8(cell_data.data[cell_index].normal);
+
+#ifdef MODE_ANISOTROPIC
+ vec3 accum[6];
+ const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0));
+
+ /*vec3 src_color = texelFetch(sampler3D(color_texture,texture_sampler),ipos,0).rgb * params.dynamic_range;
+ vec3 src_aniso_pos = texelFetch(sampler3D(aniso_pos_texture,texture_sampler),ipos,0).rgb;
+ vec3 src_anisp_neg = texelFetch(sampler3D(anisp_neg_texture,texture_sampler),ipos,0).rgb;
+ accum[0]=src_col * src_aniso_pos.x;
+ accum[1]=src_col * src_aniso_neg.x;
+ accum[2]=src_col * src_aniso_pos.y;
+ accum[3]=src_col * src_aniso_neg.y;
+ accum[4]=src_col * src_aniso_pos.z;
+ accum[5]=src_col * src_aniso_neg.z;*/
+
+ accum[0] = outputs.data[cell_index * 6 + 0].rgb;
+ accum[1] = outputs.data[cell_index * 6 + 1].rgb;
+ accum[2] = outputs.data[cell_index * 6 + 2].rgb;
+ accum[3] = outputs.data[cell_index * 6 + 3].rgb;
+ accum[4] = outputs.data[cell_index * 6 + 4].rgb;
+ accum[5] = outputs.data[cell_index * 6 + 5].rgb;
+
+#else
+ vec3 accum = outputs.data[cell_index].rgb;
+
+#endif
+
+ if (length(normal.xyz) > 0.2) {
+ vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, normal.xyz));
+ vec3 bitangent = normalize(cross(tangent, normal.xyz));
+ mat3 normal_mat = mat3(tangent, bitangent, normal.xyz);
+
+#define MAX_CONE_DIRS 6
+
+ vec3 cone_dirs[MAX_CONE_DIRS] = vec3[](
+ vec3(0.0, 0.0, 1.0),
+ vec3(0.866025, 0.0, 0.5),
+ vec3(0.267617, 0.823639, 0.5),
+ vec3(-0.700629, 0.509037, 0.5),
+ vec3(-0.700629, -0.509037, 0.5),
+ vec3(0.267617, -0.823639, 0.5));
+
+ float cone_weights[MAX_CONE_DIRS] = float[](0.25, 0.15, 0.15, 0.15, 0.15, 0.15);
+ float tan_half_angle = 0.577;
+
+ for (int i = 0; i < MAX_CONE_DIRS; i++) {
+ vec3 direction = normal_mat * cone_dirs[i];
+ vec4 color = vec4(0.0);
+ {
+ float dist = 1.5;
+ float max_distance = length(vec3(params.limits));
+ vec3 cell_size = 1.0 / vec3(params.limits);
+
+#ifdef MODE_ANISOTROPIC
+ vec3 aniso_normal = mix(direction, normal.xyz, params.aniso_strength);
+#endif
+ while (dist < max_distance && color.a < 0.95) {
+ float diameter = max(1.0, 2.0 * tan_half_angle * dist);
+ vec3 uvw_pos = (pos + dist * direction) * cell_size;
+ float half_diameter = diameter * 0.5;
+ //check if outside, then break
+ //if ( any(greaterThan(abs(uvw_pos - 0.5),vec3(0.5f + half_diameter * cell_size)) ) ) {
+ // break;
+ //}
+
+ float log2_diameter = log2(diameter);
+ vec4 scolor = textureLod(sampler3D(color_texture, texture_sampler), uvw_pos, log2_diameter);
+#ifdef MODE_ANISOTROPIC
+
+ vec3 aniso_neg = textureLod(sampler3D(aniso_neg_texture, texture_sampler), uvw_pos, log2_diameter).rgb;
+ vec3 aniso_pos = textureLod(sampler3D(aniso_pos_texture, texture_sampler), uvw_pos, log2_diameter).rgb;
+
+ scolor.rgb *= dot(max(vec3(0.0), (aniso_normal * aniso_pos)), vec3(1.0)) + dot(max(vec3(0.0), (-aniso_normal * aniso_neg)), vec3(1.0));
+#endif
+ float a = (1.0 - color.a);
+ color += a * scolor;
+ dist += half_diameter;
+ }
+ }
+ color *= cone_weights[i] * vec4(albedo.rgb, 1.0) * params.dynamic_range; //restore range
+#ifdef MODE_ANISOTROPIC
+ for (uint j = 0; j < 6; j++) {
+ accum[j] += max(0.0, dot(accum_dirs[j], direction)) * color.rgb;
+ }
+#else
+ accum += color.rgb;
+#endif
+ }
+ }
+
+#ifdef MODE_ANISOTROPIC
+
+ outputs.data[cell_index * 6 + 0] = vec4(accum[0], 0.0);
+ outputs.data[cell_index * 6 + 1] = vec4(accum[1], 0.0);
+ outputs.data[cell_index * 6 + 2] = vec4(accum[2], 0.0);
+ outputs.data[cell_index * 6 + 3] = vec4(accum[3], 0.0);
+ outputs.data[cell_index * 6 + 4] = vec4(accum[4], 0.0);
+ outputs.data[cell_index * 6 + 5] = vec4(accum[5], 0.0);
+#else
+ outputs.data[cell_index] = vec4(accum, 0.0);
+
+#endif
+
+#endif // MODE_SECOND_BOUNCE
+
+ /////////////////UPDATE MIPMAPS///////////////////////////////
+
+#ifdef MODE_UPDATE_MIPMAPS
+
+ {
+#ifdef MODE_ANISOTROPIC
+ vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
+#else
+ vec3 light_accum = vec3(0.0);
+#endif
+ float count = 0.0;
+ for (uint i = 0; i < 8; i++) {
+ uint child_index = cell_children.data[cell_index].children[i];
+ if (child_index == NO_CHILDREN) {
+ continue;
+ }
+#ifdef MODE_ANISOTROPIC
+ light_accum[0] += outputs.data[child_index * 6 + 0].rgb;
+ light_accum[1] += outputs.data[child_index * 6 + 1].rgb;
+ light_accum[2] += outputs.data[child_index * 6 + 2].rgb;
+ light_accum[3] += outputs.data[child_index * 6 + 3].rgb;
+ light_accum[4] += outputs.data[child_index * 6 + 4].rgb;
+ light_accum[5] += outputs.data[child_index * 6 + 5].rgb;
+
+#else
+ light_accum += outputs.data[child_index].rgb;
+
+#endif
+
+ count += 1.0;
+ }
+
+ float divisor = mix(8.0, count, params.propagation);
+#ifdef MODE_ANISOTROPIC
+ outputs.data[cell_index * 6 + 0] = vec4(light_accum[0] / divisor, 0.0);
+ outputs.data[cell_index * 6 + 1] = vec4(light_accum[1] / divisor, 0.0);
+ outputs.data[cell_index * 6 + 2] = vec4(light_accum[2] / divisor, 0.0);
+ outputs.data[cell_index * 6 + 3] = vec4(light_accum[3] / divisor, 0.0);
+ outputs.data[cell_index * 6 + 4] = vec4(light_accum[4] / divisor, 0.0);
+ outputs.data[cell_index * 6 + 5] = vec4(light_accum[5] / divisor, 0.0);
+
+#else
+ outputs.data[cell_index] = vec4(light_accum / divisor, 0.0);
+#endif
+ }
+#endif
+
+ ///////////////////WRITE TEXTURE/////////////////////////////
+
+#ifdef MODE_WRITE_TEXTURE
+ {
+#ifdef MODE_ANISOTROPIC
+ vec3 accum_total = vec3(0.0);
+ accum_total += outputs.data[cell_index * 6 + 0].rgb;
+ accum_total += outputs.data[cell_index * 6 + 1].rgb;
+ accum_total += outputs.data[cell_index * 6 + 2].rgb;
+ accum_total += outputs.data[cell_index * 6 + 3].rgb;
+ accum_total += outputs.data[cell_index * 6 + 4].rgb;
+ accum_total += outputs.data[cell_index * 6 + 5].rgb;
+
+ float accum_total_energy = max(dot(accum_total, GREY_VEC), 0.00001);
+ vec3 iso_positive = vec3(dot(outputs.data[cell_index * 6 + 0].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 2].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 4].rgb, GREY_VEC)) / vec3(accum_total_energy);
+ vec3 iso_negative = vec3(dot(outputs.data[cell_index * 6 + 1].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 3].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 5].rgb, GREY_VEC)) / vec3(accum_total_energy);
+
+ {
+ uint aniso_pos = uint(clamp(iso_positive.b * 31.0, 0.0, 31.0));
+ aniso_pos |= uint(clamp(iso_positive.g * 63.0, 0.0, 63.0)) << 5;
+ aniso_pos |= uint(clamp(iso_positive.r * 31.0, 0.0, 31.0)) << 11;
+ imageStore(aniso_pos_tex, ivec3(posu), uvec4(aniso_pos));
+ }
+
+ {
+ uint aniso_neg = uint(clamp(iso_negative.b * 31.0, 0.0, 31.0));
+ aniso_neg |= uint(clamp(iso_negative.g * 63.0, 0.0, 63.0)) << 5;
+ aniso_neg |= uint(clamp(iso_negative.r * 31.0, 0.0, 31.0)) << 11;
+ imageStore(aniso_neg_tex, ivec3(posu), uvec4(aniso_neg));
+ }
+
+ imageStore(color_tex, ivec3(posu), vec4(accum_total / params.dynamic_range, albedo.a));
+
+#else
+
+ imageStore(color_tex, ivec3(posu), vec4(outputs.data[cell_index].rgb / params.dynamic_range, albedo.a));
+
+#endif
+ }
+#endif
+
+ ///////////////////DYNAMIC LIGHTING/////////////////////////////
+
+#ifdef MODE_DYNAMIC
+
+ ivec2 pos_xy = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(pos_xy, params.rect_size))) {
+ return; //out of bounds
+ }
+
+ ivec2 uv_xy = pos_xy;
+ if (params.flip_x) {
+ uv_xy.x = params.rect_size.x - pos_xy.x - 1;
+ }
+ if (params.flip_y) {
+ uv_xy.y = params.rect_size.y - pos_xy.y - 1;
+ }
+
+#ifdef MODE_DYNAMIC_LIGHTING
+
+ {
+ float z = params.z_base + imageLoad(depth, uv_xy).x * params.z_sign;
+
+ ivec3 pos = params.x_dir * (params.rect_pos.x + pos_xy.x) + params.y_dir * (params.rect_pos.y + pos_xy.y) + abs(params.z_dir) * int(z);
+
+ vec3 normal = imageLoad(source_normal, uv_xy).xyz * 2.0 - 1.0;
+ normal = vec3(params.x_dir) * normal.x * mix(1.0, -1.0, params.flip_x) + vec3(params.y_dir) * normal.y * mix(1.0, -1.0, params.flip_y) - vec3(params.z_dir) * normal.z;
+
+ vec4 albedo = imageLoad(source_albedo, uv_xy);
+
+ //determine the position in space
+
+ vec3 accum = vec3(0.0);
+ for (uint i = 0; i < params.light_count; i++) {
+ vec3 light;
+ vec3 light_dir;
+ if (!compute_light_at_pos(i, vec3(pos) * params.pos_multiplier, normal, light, light_dir)) {
+ continue;
+ }
+
+ light *= albedo.rgb;
+
+ accum += max(0.0, dot(normal, -light_dir)) * light;
+ }
+
+ accum += imageLoad(emission, uv_xy).xyz;
+
+ imageStore(emission, uv_xy, vec4(accum, albedo.a));
+ imageStore(depth, uv_xy, vec4(z));
+ }
+
+#endif // MODE DYNAMIC LIGHTING
+
+#ifdef MODE_DYNAMIC_SHRINK
+
+ {
+ vec4 accum = vec4(0.0);
+ float accum_z = 0.0;
+ float count = 0.0;
+
+ for (int i = 0; i < 4; i++) {
+ ivec2 ofs = pos_xy * 2 + ivec2(i & 1, i >> 1) - params.prev_rect_ofs;
+ if (any(lessThan(ofs, ivec2(0))) || any(greaterThanEqual(ofs, params.prev_rect_size))) {
+ continue;
+ }
+ if (params.flip_x) {
+ ofs.x = params.prev_rect_size.x - ofs.x - 1;
+ }
+ if (params.flip_y) {
+ ofs.y = params.prev_rect_size.y - ofs.y - 1;
+ }
+
+ vec4 light = imageLoad(source_light, ofs);
+ if (light.a == 0.0) { //ignore empty
+ continue;
+ }
+ accum += light;
+ float z = imageLoad(source_depth, ofs).x;
+ accum_z += z * 0.5; //shrink half too
+ count += 1.0;
+ }
+
+ if (params.on_mipmap) {
+ accum.rgb /= mix(8.0, count, params.propagation);
+ accum.a /= 8.0;
+ } else {
+ accum /= 4.0;
+ }
+
+ if (count == 0.0) {
+ accum_z = 0.0; //avoid nan
+ } else {
+ accum_z /= count;
+ }
+
+#ifdef MODE_DYNAMIC_SHRINK_WRITE
+
+ imageStore(light, uv_xy, accum);
+ imageStore(depth, uv_xy, vec4(accum_z));
+#endif
+
+#ifdef MODE_DYNAMIC_SHRINK_PLOT
+
+ if (accum.a < 0.001) {
+ return; //do not blit if alpha is too low
+ }
+
+ ivec3 pos = params.x_dir * (params.rect_pos.x + pos_xy.x) + params.y_dir * (params.rect_pos.y + pos_xy.y) + abs(params.z_dir) * int(accum_z);
+
+ float z_frac = fract(accum_z);
+
+ for (int i = 0; i < 2; i++) {
+ ivec3 pos3d = pos + abs(params.z_dir) * i;
+ if (any(lessThan(pos3d, ivec3(0))) || any(greaterThanEqual(pos3d, params.limits))) {
+ //skip if offlimits
+ continue;
+ }
+ vec4 color_blit = accum * (i == 0 ? 1.0 - z_frac : z_frac);
+ vec4 color = imageLoad(color_texture, pos3d);
+ color.rgb *= params.dynamic_range;
+
+#if 0
+ color.rgb = mix(color.rgb,color_blit.rgb,color_blit.a);
+ color.a+=color_blit.a;
+#else
+
+ float sa = 1.0 - color_blit.a;
+ vec4 result;
+ result.a = color.a * sa + color_blit.a;
+ if (result.a == 0.0) {
+ result = vec4(0.0);
+ } else {
+ result.rgb = (color.rgb * color.a * sa + color_blit.rgb * color_blit.a) / result.a;
+ color = result;
+ }
+
+#endif
+ color.rgb /= params.dynamic_range;
+ imageStore(color_texture, pos3d, color);
+ //imageStore(color_texture,pos3d,vec4(1,1,1,1));
+
+#ifdef MODE_ANISOTROPIC
+ //do not care about anisotropy for dynamic objects, just store full lit in all directions
+ imageStore(aniso_pos_texture, pos3d, uvec4(0xFFFF));
+ imageStore(aniso_neg_texture, pos3d, uvec4(0xFFFF));
+
+#endif // ANISOTROPIC
+ }
+#endif // MODE_DYNAMIC_SHRINK_PLOT
+ }
+#endif
+
+#endif // MODE DYNAMIC
+}
diff --git a/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl b/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl
new file mode 100644
index 0000000000..7d4d72967a
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl
@@ -0,0 +1,229 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+struct CellData {
+ uint position; // xyz 10 bits
+ uint albedo; //rgb albedo
+ uint emission; //rgb normalized with e as multiplier
+ uint normal; //RGB normal encoded
+};
+
+layout(set = 0, binding = 1, std140) buffer CellDataBuffer {
+ CellData data[];
+}
+cell_data;
+
+layout(set = 0, binding = 2) uniform texture3D color_tex;
+
+layout(set = 0, binding = 3) uniform sampler tex_sampler;
+
+#ifdef USE_ANISOTROPY
+layout(set = 0, binding = 4) uniform texture3D aniso_pos_tex;
+layout(set = 0, binding = 5) uniform texture3D aniso_neg_tex;
+#endif
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ mat4 projection;
+ uint cell_offset;
+ float dynamic_range;
+ float alpha;
+ uint level;
+ ivec3 bounds;
+ uint pad;
+}
+params;
+
+layout(location = 0) out vec4 color_interp;
+
+void main() {
+ const vec3 cube_triangles[36] = vec3[](
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, -1.0f, 1.0f),
+ vec3(-1.0f, 1.0f, 1.0f),
+ vec3(1.0f, 1.0f, -1.0f),
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, 1.0f, -1.0f),
+ vec3(1.0f, -1.0f, 1.0f),
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(1.0f, -1.0f, -1.0f),
+ vec3(1.0f, 1.0f, -1.0f),
+ vec3(1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, 1.0f, 1.0f),
+ vec3(-1.0f, 1.0f, -1.0f),
+ vec3(1.0f, -1.0f, 1.0f),
+ vec3(-1.0f, -1.0f, 1.0f),
+ vec3(-1.0f, -1.0f, -1.0f),
+ vec3(-1.0f, 1.0f, 1.0f),
+ vec3(-1.0f, -1.0f, 1.0f),
+ vec3(1.0f, -1.0f, 1.0f),
+ vec3(1.0f, 1.0f, 1.0f),
+ vec3(1.0f, -1.0f, -1.0f),
+ vec3(1.0f, 1.0f, -1.0f),
+ vec3(1.0f, -1.0f, -1.0f),
+ vec3(1.0f, 1.0f, 1.0f),
+ vec3(1.0f, -1.0f, 1.0f),
+ vec3(1.0f, 1.0f, 1.0f),
+ vec3(1.0f, 1.0f, -1.0f),
+ vec3(-1.0f, 1.0f, -1.0f),
+ vec3(1.0f, 1.0f, 1.0f),
+ vec3(-1.0f, 1.0f, -1.0f),
+ vec3(-1.0f, 1.0f, 1.0f),
+ vec3(1.0f, 1.0f, 1.0f),
+ vec3(-1.0f, 1.0f, 1.0f),
+ vec3(1.0f, -1.0f, 1.0f));
+
+ vec3 vertex = cube_triangles[gl_VertexIndex] * 0.5 + 0.5;
+#ifdef MODE_DEBUG_LIGHT_FULL
+ uvec3 posu = uvec3(gl_InstanceIndex % params.bounds.x, (gl_InstanceIndex / params.bounds.x) % params.bounds.y, gl_InstanceIndex / (params.bounds.y * params.bounds.x));
+#else
+ uint cell_index = gl_InstanceIndex + params.cell_offset;
+
+ uvec3 posu = uvec3(cell_data.data[cell_index].position & 0x7FF, (cell_data.data[cell_index].position >> 11) & 0x3FF, cell_data.data[cell_index].position >> 21);
+#endif
+
+#ifdef MODE_DEBUG_EMISSION
+ color_interp.xyz = vec3(uvec3(cell_data.data[cell_index].emission & 0x1ff, (cell_data.data[cell_index].emission >> 9) & 0x1ff, (cell_data.data[cell_index].emission >> 18) & 0x1ff)) * pow(2.0, float(cell_data.data[cell_index].emission >> 27) - 15.0 - 9.0);
+#endif
+
+#ifdef MODE_DEBUG_COLOR
+ color_interp.xyz = unpackUnorm4x8(cell_data.data[cell_index].albedo).xyz;
+#endif
+
+#ifdef MODE_DEBUG_LIGHT
+
+#ifdef USE_ANISOTROPY
+
+#define POS_X 0
+#define POS_Y 1
+#define POS_Z 2
+#define NEG_X 3
+#define NEG_Y 4
+#define NEG_Z 5
+
+ const uint triangle_aniso[12] = uint[](
+ NEG_X,
+ NEG_Z,
+ NEG_Y,
+ NEG_Z,
+ NEG_X,
+ NEG_Y,
+ POS_Z,
+ POS_X,
+ POS_X,
+ POS_Y,
+ POS_Y,
+ POS_Z);
+
+ color_interp.xyz = texelFetch(sampler3D(color_tex, tex_sampler), ivec3(posu), int(params.level)).xyz * params.dynamic_range;
+ vec3 aniso_pos = texelFetch(sampler3D(aniso_pos_tex, tex_sampler), ivec3(posu), int(params.level)).xyz;
+ vec3 aniso_neg = texelFetch(sampler3D(aniso_neg_tex, tex_sampler), ivec3(posu), int(params.level)).xyz;
+ uint side = triangle_aniso[gl_VertexIndex / 3];
+
+ float strength = 0.0;
+ switch (side) {
+ case POS_X:
+ strength = aniso_pos.x;
+ break;
+ case POS_Y:
+ strength = aniso_pos.y;
+ break;
+ case POS_Z:
+ strength = aniso_pos.z;
+ break;
+ case NEG_X:
+ strength = aniso_neg.x;
+ break;
+ case NEG_Y:
+ strength = aniso_neg.y;
+ break;
+ case NEG_Z:
+ strength = aniso_neg.z;
+ break;
+ }
+
+ color_interp.xyz *= strength;
+
+#else
+ color_interp = texelFetch(sampler3D(color_tex, tex_sampler), ivec3(posu), int(params.level));
+ color_interp.xyz *params.dynamic_range;
+
+#endif
+
+#endif
+ float scale = (1 << params.level);
+
+ gl_Position = params.projection * vec4((vec3(posu) + vertex) * scale, 1.0);
+
+#ifdef MODE_DEBUG_LIGHT_FULL
+ if (color_interp.a == 0.0) {
+ gl_Position = vec4(0.0); //force clip and not draw
+ }
+#else
+ color_interp.a = params.alpha;
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec4 color_interp;
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+ frag_color = color_interp;
+
+#ifdef MODE_DEBUG_LIGHT_FULL
+
+ //there really is no alpha, so use dither
+
+ int x = int(gl_FragCoord.x) % 4;
+ int y = int(gl_FragCoord.y) % 4;
+ int index = x + y * 4;
+ float limit = 0.0;
+ if (x < 8) {
+ if (index == 0)
+ limit = 0.0625;
+ if (index == 1)
+ limit = 0.5625;
+ if (index == 2)
+ limit = 0.1875;
+ if (index == 3)
+ limit = 0.6875;
+ if (index == 4)
+ limit = 0.8125;
+ if (index == 5)
+ limit = 0.3125;
+ if (index == 6)
+ limit = 0.9375;
+ if (index == 7)
+ limit = 0.4375;
+ if (index == 8)
+ limit = 0.25;
+ if (index == 9)
+ limit = 0.75;
+ if (index == 10)
+ limit = 0.125;
+ if (index == 11)
+ limit = 0.625;
+ if (index == 12)
+ limit = 1.0;
+ if (index == 13)
+ limit = 0.5;
+ if (index == 14)
+ limit = 0.875;
+ if (index == 15)
+ limit = 0.375;
+ }
+ if (frag_color.a < limit) {
+ discard;
+ }
+#endif
+}
diff --git a/servers/rendering/renderer_rd/shaders/voxel_gi_sdf.glsl b/servers/rendering/renderer_rd/shaders/voxel_gi_sdf.glsl
new file mode 100644
index 0000000000..e20b3f680d
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/voxel_gi_sdf.glsl
@@ -0,0 +1,181 @@
+#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#define MAX_DISTANCE 100000
+
+#define NO_CHILDREN 0xFFFFFFFF
+#define GREY_VEC vec3(0.33333, 0.33333, 0.33333)
+
+struct CellChildren {
+ uint children[8];
+};
+
+layout(set = 0, binding = 1, std430) buffer CellChildrenBuffer {
+ CellChildren data[];
+}
+cell_children;
+
+struct CellData {
+ uint position; // xyz 10 bits
+ uint albedo; //rgb albedo
+ uint emission; //rgb normalized with e as multiplier
+ uint normal; //RGB normal encoded
+};
+
+layout(set = 0, binding = 2, std430) buffer CellDataBuffer {
+ CellData data[];
+}
+cell_data;
+
+layout(r8ui, set = 0, binding = 3) uniform restrict writeonly uimage3D sdf_tex;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ uint offset;
+ uint end;
+ uint pad0;
+ uint pad1;
+}
+params;
+
+void main() {
+ vec3 pos = vec3(gl_GlobalInvocationID);
+ float closest_dist = 100000.0;
+
+ for (uint i = params.offset; i < params.end; i++) {
+ vec3 posu = vec3(uvec3(cell_data.data[i].position & 0x7FF, (cell_data.data[i].position >> 11) & 0x3FF, cell_data.data[i].position >> 21));
+ float dist = length(pos - posu);
+ if (dist < closest_dist) {
+ closest_dist = dist;
+ }
+ }
+
+ uint dist_8;
+
+ if (closest_dist < 0.0001) { // same cell
+ dist_8 = 0; //equals to -1
+ } else {
+ dist_8 = clamp(uint(closest_dist), 0, 254) + 1; //conservative, 0 is 1, so <1 is considered solid
+ }
+
+ imageStore(sdf_tex, ivec3(gl_GlobalInvocationID), uvec4(dist_8));
+ //imageStore(sdf_tex,pos,uvec4(pos*2,0));
+}
+
+#if 0
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec3 limits;
+ uint stack_size;
+}
+params;
+
+float distance_to_aabb(ivec3 pos, ivec3 aabb_pos, ivec3 aabb_size) {
+ vec3 delta = vec3(max(ivec3(0), max(aabb_pos - pos, pos - (aabb_pos + aabb_size - ivec3(1)))));
+ return length(delta);
+}
+
+void main() {
+ ivec3 pos = ivec3(gl_GlobalInvocationID);
+
+ uint stack[10] = uint[](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ uint stack_indices[10] = uint[](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ ivec3 stack_positions[10] = ivec3[](ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0));
+
+ const uint cell_orders[8] = uint[](
+ 0x11f58d1,
+ 0xe2e70a,
+ 0xd47463,
+ 0xbb829c,
+ 0x8d11f5,
+ 0x70ae2e,
+ 0x463d47,
+ 0x29cbb8);
+
+ bool cell_found = false;
+ bool cell_found_exact = false;
+ ivec3 closest_cell_pos;
+ float closest_distance = MAX_DISTANCE;
+ int stack_pos = 0;
+
+ while (true) {
+ uint index = stack_indices[stack_pos] >> 24;
+
+ if (index == 8) {
+ //go up
+ if (stack_pos == 0) {
+ break; //done going through octree
+ }
+ stack_pos--;
+ continue;
+ }
+
+ stack_indices[stack_pos] = (stack_indices[stack_pos] & ((1 << 24) - 1)) | ((index + 1) << 24);
+
+ uint cell_index = (stack_indices[stack_pos] >> (index * 3)) & 0x7;
+ uint child_cell = cell_children.data[stack[stack_pos]].children[cell_index];
+
+ if (child_cell == NO_CHILDREN) {
+ continue;
+ }
+
+ ivec3 child_cell_size = params.limits >> (stack_pos + 1);
+ ivec3 child_cell_pos = stack_positions[stack_pos];
+
+ child_cell_pos += mix(ivec3(0), child_cell_size, bvec3(uvec3(index & 1, index & 2, index & 4) != uvec3(0)));
+
+ bool is_leaf = stack_pos == (params.stack_size - 2);
+
+ if (child_cell_pos == pos && is_leaf) {
+ //we may actually end up in the exact cell.
+ //if this happens, just abort
+ cell_found_exact = true;
+ break;
+ }
+
+ if (cell_found) {
+ //discard by distance
+ float distance = distance_to_aabb(pos, child_cell_pos, child_cell_size);
+ if (distance >= closest_distance) {
+ continue; //pointless, just test next child
+ } else if (is_leaf) {
+ //closer than what we have AND end of stack, save and continue
+ closest_cell_pos = child_cell_pos;
+ closest_distance = distance;
+ continue;
+ }
+ } else if (is_leaf) {
+ //first solid cell we find, save and continue
+ closest_distance = distance_to_aabb(pos, child_cell_pos, child_cell_size);
+ closest_cell_pos = child_cell_pos;
+ cell_found = true;
+ continue;
+ }
+
+ bvec3 direction = greaterThan((pos - (child_cell_pos + (child_cell_size >> 1))), ivec3(0));
+ uint cell_order = 0;
+ cell_order |= mix(0, 1, direction.x);
+ cell_order |= mix(0, 2, direction.y);
+ cell_order |= mix(0, 4, direction.z);
+
+ stack[stack_pos + 1] = child_cell;
+ stack_indices[stack_pos + 1] = cell_orders[cell_order]; //start counting
+ stack_positions[stack_pos + 1] = child_cell_pos;
+ stack_pos++; //go up stack
+ }
+
+ uint dist_8;
+
+ if (cell_found_exact) {
+ dist_8 = 0; //equals to -1
+ } else {
+ float closest_distance = length(vec3(pos - closest_cell_pos));
+ dist_8 = clamp(uint(closest_distance), 0, 254) + 1; //conservative, 0 is 1, so <1 is considered solid
+ }
+
+ imageStore(sdf_tex, pos, uvec4(dist_8));
+}
+#endif