diff options
Diffstat (limited to 'servers/rendering/renderer_rd/shaders')
45 files changed, 14514 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..cb62882deb --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/SCsub @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +Import("env") + +if "RD_GLSL" in env["BUILDERS"]: + env.RD_GLSL("canvas.glsl") + env.RD_GLSL("canvas_occlusion.glsl") + env.RD_GLSL("canvas_sdf.glsl") + env.RD_GLSL("copy.glsl") + env.RD_GLSL("copy_to_fb.glsl") + env.RD_GLSL("cubemap_roughness.glsl") + env.RD_GLSL("cubemap_downsampler.glsl") + env.RD_GLSL("cubemap_filter.glsl") + env.RD_GLSL("scene_forward.glsl") + env.RD_GLSL("sky.glsl") + env.RD_GLSL("tonemap.glsl") + env.RD_GLSL("cube_to_dp.glsl") + env.RD_GLSL("giprobe.glsl") + env.RD_GLSL("giprobe_debug.glsl") + env.RD_GLSL("giprobe_sdf.glsl") + env.RD_GLSL("luminance_reduce.glsl") + env.RD_GLSL("bokeh_dof.glsl") + env.RD_GLSL("ssao.glsl") + env.RD_GLSL("ssao_minify.glsl") + env.RD_GLSL("ssao_blur.glsl") + env.RD_GLSL("roughness_limiter.glsl") + env.RD_GLSL("screen_space_reflection.glsl") + env.RD_GLSL("screen_space_reflection_filter.glsl") + env.RD_GLSL("screen_space_reflection_scale.glsl") + env.RD_GLSL("subsurface_scattering.glsl") + env.RD_GLSL("specular_merge.glsl") + env.RD_GLSL("gi.glsl") + env.RD_GLSL("resolve.glsl") + env.RD_GLSL("sdfgi_preprocess.glsl") + env.RD_GLSL("sdfgi_integrate.glsl") + env.RD_GLSL("sdfgi_direct_light.glsl") + env.RD_GLSL("sdfgi_debug.glsl") + env.RD_GLSL("sdfgi_debug_probes.glsl") + env.RD_GLSL("volumetric_fog.glsl") + env.RD_GLSL("shadow_reduce.glsl") + env.RD_GLSL("particles.glsl") + env.RD_GLSL("particles_copy.glsl") + env.RD_GLSL("sort.glsl") + env.RD_GLSL("skeleton.glsl") 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..63f086a83d --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/bokeh_dof.glsl @@ -0,0 +1,251 @@ +#[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 + +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 + +#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 + +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); +} + +#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/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl new file mode 100644 index 0000000000..7808e7ed52 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/canvas.glsl @@ -0,0 +1,672 @@ +#[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 USE_MATERIAL_UNIFORMS +layout(set = 1, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} material; +#endif + +/* clang-format off */ +VERTEX_SHADER_GLOBALS +/* clang-format on */ + +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; + 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)); + +#if 0 + if (draw_data.flags & FLAGS_INSTANCING_ENABLED) { + uint offset = draw_data.flags & FLAGS_INSTANCING_STRIDE_MASK; + offset *= gl_InstanceIndex; + mat4 instance_xform = mat4( + vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), 0.0, texelFetch(instancing_buffer, offset + 3)), + vec4(texelFetch(instancing_buffer, offset + 4), texelFetch(instancing_buffer, offset + 5), 0.0, texelFetch(instancing_buffer, offset + 7)), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0)); + offset += 8; + if (draw_data.flags & FLAGS_INSTANCING_HAS_COLORS) { + vec4 instance_color; + if (draw_data.flags & FLAGS_INSTANCING_COLOR_8_BIT) { + uint bits = floatBitsToUint(texelFetch(instancing_buffer, offset)); + instance_color = unpackUnorm4x8(bits); + offset += 1; + } else { + instance_color = vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), texelFetch(instancing_buffer, offset + 2), texelFetch(instancing_buffer, offset + 3)); + offset += 4; + } + + color *= instance_color; + } + if (draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA) { + if (draw_data.flags & FLAGS_INSTANCING_CUSTOM_DATA_8_BIT) { + uint bits = floatBitsToUint(texelFetch(instancing_buffer, offset)); + instance_custom = unpackUnorm4x8(bits); + } else { + instance_custom = vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), texelFetch(instancing_buffer, offset + 2), texelFetch(instancing_buffer, offset + 3)); + } + } + } + +#endif + +#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 + { + /* clang-format off */ +VERTEX_SHADER_CODE + /* clang-format on */ + } + +#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 USE_MATERIAL_UNIFORMS +layout(set = 1, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} 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 = d * SDF_MAX_LENGTH - 1.0; + 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; +} + +/* clang-format off */ +FRAGMENT_SHADER_GLOBALS +/* clang-format on */ + +#ifdef LIGHT_SHADER_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); + /* clang-format off */ +LIGHT_SHADER_CODE + /* clang-format on */ + 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_SHADER_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_SHADER_CODE_USED + shadow_color *= 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_depth = 1.0; + +#if defined(NORMALMAP_USED) + vec3 normal_map = vec3(0.0, 0.0, 1.0); + normal_used = true; +#endif + + /* clang-format off */ + +FRAGMENT_SHADER_CODE + + /* clang-format on */ + +#if defined(NORMALMAP_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_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_SHADER_CODE_USED + + vec4 shadow_modulate = vec4(1.0); + light_color = light_compute(light_vertex, direction, normal, light_color, light_color.a, specular_shininess, shadow_modulate, screen_uv, color, uv, 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_SHADER_CODE_USED + , + shadow_modulate +#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_SHADER_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, color, uv, 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_SHADER_CODE_USED + , + shadow_modulate +#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..5c25235c58 --- /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..302ad03b41 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/canvas_sdf.glsl @@ -0,0 +1,135 @@ +#[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, 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(pos, 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; + 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; + } + } + } + } + + 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; + + 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; + float src_dist = length(vec2(src_rel - center)); + if (src_dist < dist) { + dist = src_dist; + rel = src_rel; + } + } + } + + imageStore(dst_process, pos, ivec4(rel, 0, 0)); +#endif + +#ifdef MODE_STORE + + ivec2 rel = imageLoad(src_process, pos).xy; + float d = length(vec2(rel - pos)); + if (d > 0.01) { + d += 1.0; //make it signed + } + d /= SDF_MAX_LENGTH; + d = clamp(d, 0.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; + float d = length(vec2(rel - center)); + + if (d > 0.01) { + d += 1.0; //make it signed + } + d /= SDF_MAX_LENGTH; + d = clamp(d, 0.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..cf7678ea31 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl @@ -0,0 +1,162 @@ + +#define MAX_LIGHTS_PER_ITEM 16 + +#define M_PI 3.14159265359 + +#define SDF_MAX_LENGTH 16384.0 + +#define FLAGS_INSTANCING_STRIDE_MASK 0xF +#define FLAGS_INSTANCING_ENABLED (1 << 4) +#define FLAGS_INSTANCING_HAS_COLORS (1 << 5) +#define FLAGS_INSTANCING_COLOR_8BIT (1 << 6) +#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 7) +#define FLAGS_INSTANCING_CUSTOM_DATA_8_BIT (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..e723468dd8 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl @@ -0,0 +1,95 @@ + +#define CLUSTER_COUNTER_SHIFT 20 +#define CLUSTER_POINTER_MASK ((1 << CLUSTER_COUNTER_SHIFT) - 1) +#define CLUSTER_COUNTER_MASK 0xfff + +struct LightData { //this structure needs to be as packed as possible + vec3 position; + float inv_radius; + vec3 direction; + float size; + uint attenuation_energy; //attenuation + uint color_specular; //rgb color, a specular (8 bit unorm) + uint cone_attenuation_angle; // attenuation and angle, (16bit float) + uint shadow_color_enabled; //shadow rgb color, a>0.5 enabled (8bit unorm) + vec4 atlas_rect; // rect in the shadow atlas + mat4 shadow_matrix; + float shadow_bias; + float shadow_normal_bias; + float transmittance_bias; + float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle + float soft_shadow_scale; // scales the shadow kernel for blurrier shadows + uint mask; + float shadow_volumetric_fog_fade; + uint pad; + 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 { + vec3 box_extents; + float index; + vec3 box_offset; + uint mask; + vec4 params; // intensity, 0, interior , boxproject + vec3 ambient; // ambient color + uint ambient_mode; + 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 { + vec3 direction; + float energy; + vec3 color; + float size; + float specular; + uint mask; + float softshadow_angle; + float soft_shadow_scale; + bool blend_splits; + bool shadow_enabled; + float fade_from; + float fade_to; + uvec3 pad; + float shadow_volumetric_fog_fade; + vec4 shadow_bias; + vec4 shadow_normal_bias; + vec4 shadow_transmittance_bias; + vec4 shadow_z_range; + vec4 shadow_range_begin; + vec4 shadow_split_offsets; + mat4 shadow_matrix1; + mat4 shadow_matrix2; + mat4 shadow_matrix3; + mat4 shadow_matrix4; + vec4 shadow_color1; + vec4 shadow_color2; + vec4 shadow_color3; + vec4 shadow_color4; + vec2 uv_scale1; + vec2 uv_scale2; + vec2 uv_scale3; + vec2 uv_scale4; +}; + +struct DecalData { + mat4 xform; //to decal transform + vec3 inv_extents; + float albedo_mix; + vec4 albedo_rect; + vec4 normal_rect; + vec4 orm_rect; + vec4 emission_rect; + vec4 modulate; + float emission_energy; + uint mask; + float upper_fade; + float lower_fade; + mat3x4 normal_xform; + vec3 normal; + float normal_fade; +}; diff --git a/servers/rendering/renderer_rd/shaders/copy.glsl b/servers/rendering/renderer_rd/shaders/copy.glsl new file mode 100644 index 0000000000..cdd35dfb3f --- /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..9751e13b4e --- /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..54d67db6c6 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl @@ -0,0 +1,69 @@ +#[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 samplerCube source_cube; + +layout(push_constant, binding = 1, std430) uniform Params { + ivec2 screen_size; + ivec2 offset; + float bias; + float z_far; + float z_near; + bool z_flip; +} +params; + +layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D depth_buffer; + +void main() { + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThan(pos, params.screen_size))) { //too large, do nothing + return; + } + + vec2 pixel_size = 1.0 / vec2(params.screen_size); + vec2 uv = (vec2(pos) + 0.5) * pixel_size; + + 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 = normalize(normal); + + normal.y = -normal.y; //needs to be flipped to match projection matrix + if (!params.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; + + imageStore(depth_buffer, pos + params.offset, vec4(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..7f269b7af3 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/cubemap_downsampler.glsl @@ -0,0 +1,191 @@ +// 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; + +layout(push_constant, binding = 1, std430) uniform Params { + uint face_size; +} +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); +} + +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_filter.glsl b/servers/rendering/renderer_rd/shaders/cubemap_filter.glsl new file mode 100644 index 0000000000..987545fb76 --- /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_roughness.glsl b/servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl new file mode 100644 index 0000000000..5cbb00baa4 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/cubemap_roughness.glsl @@ -0,0 +1,142 @@ +#[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; + +layout(push_constant, binding = 1, std430) uniform Params { + uint face_id; + uint sample_count; + float roughness; + bool use_direct_write; + float face_size; +} +params; + +#define M_PI 3.14159265359 + +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)); +} + +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/gi.glsl b/servers/rendering/renderer_rd/shaders/gi.glsl new file mode 100644 index 0000000000..8011dadc72 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/gi.glsl @@ -0,0 +1,663 @@ +#[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 giprobe_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_GI_PROBES 8 + +struct GIProbeData { + 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 = 16, std140) uniform GIProbes { + GIProbeData data[MAX_GI_PROBES]; +} +gi_probes; + +layout(set = 0, binding = 17) uniform texture3D gi_probe_textures[MAX_GI_PROBES]; + +layout(push_constant, binding = 0, std430) uniform Params { + ivec2 screen_size; + float z_near; + float z_far; + + vec4 proj_info; + + uint max_giprobes; + bool high_quality_vct; + bool use_sdfgi; + bool orthogonal; + + vec3 ao_color; + uint pad; + + 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 sdfgi_probe_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; + sdfgi_probe_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; + sdfgi_probe_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 1 + 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 + while (length(ray_pos) < max_distance) { + for (uint i = 0; i < sdfgi.max_cascades; i++) { + if (i >= cascade && length(ray_pos) < radius_sizes[i]) { + 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 distance = texture(sampler3D(sdf_cascades[i], linear_sampler), pos).r * 255.0 - 1.1; + + vec4 hit_light = vec4(0.0); + if (distance < softness) { + hit_light.rgb = texture(sampler3D(light_cascades[i], linear_sampler), pos).rgb; + hit_light.rgb *= 0.5; //approximation given value read is actually meant for anisotropy + hit_light.a = clamp(1.0 - (distance / softness), 0.0, 1.0); + hit_light.rgb *= hit_light.a; + } + + distance /= sdfgi.cascades[i].to_cell; + + if (i < (sdfgi.max_cascades - 1)) { + pos = ray_pos - sdfgi.cascades[i + 1].position; + pos *= sdfgi.cascades[i + 1].to_cell * pos_to_uvw; + + float distance2 = texture(sampler3D(sdf_cascades[i + 1], linear_sampler), pos).r * 255.0 - 1.1; + + vec4 hit_light2 = vec4(0.0); + if (distance2 < softness) { + hit_light2.rgb = texture(sampler3D(light_cascades[i + 1], linear_sampler), pos).rgb; + hit_light2.rgb *= 0.5; //approximation given value read is actually meant for anisotropy + hit_light2.a = clamp(1.0 - (distance2 / softness), 0.0, 1.0); + hit_light2.rgb *= hit_light2.a; + } + + float prev_radius = i == 0 ? 0.0 : radius_sizes[i - 1]; + float blend = clamp((length(ray_pos) - prev_radius) / (radius_sizes[i] - prev_radius), 0.0, 1.0); + + distance2 /= sdfgi.cascades[i + 1].to_cell; + + hit_light = mix(hit_light, hit_light2, blend); + distance = mix(distance, distance2, blend); + } + + light_accum += hit_light; + ray_pos += ray_dir * distance; + break; + } + } + + if (light_accum.a > 0.99) { + break; + } + } + + 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; + } + } + +#endif + + 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 gi_probe_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 = (gi_probes.data[index].xform * vec4(position, 1.0)).xyz; + ref_vec = normalize((gi_probes.data[index].xform * vec4(ref_vec, 0.0)).xyz); + normal = normalize((gi_probes.data[index].xform * vec4(normal, 0.0)).xyz); + + position += normal * gi_probes.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, gi_probes.data[index].bounds))))) { + return; + } + + mat3 dir_xform = mat3(gi_probes.data[index].xform) * normal_xform; + + vec3 blendv = abs(position / gi_probes.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(gi_probes.data[index].bounds); + vec3 cell_size = 1.0 / gi_probes.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(gi_probe_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, gi_probes.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(gi_probe_textures[index], cell_size, position, dir, max_distance, gi_probes.data[index].bias); + } + } + + if (gi_probes.data[index].ambient_occlusion > 0.001) { + float size = 1.0 + gi_probes.data[index].ambient_occlusion_size * 7.0; + + float taps, blend; + blend = modf(size, taps); + float ao = 0.0; + for (float i = 1.0; i <= taps; i++) { + vec3 ofs = (position + normal * (i * 0.5 + 1.0)) * cell_size; + ao += textureLod(sampler3D(gi_probe_textures[index], linear_sampler_with_mipmaps), ofs, i - 1.0).a * i; + } + + if (blend > 0.001) { + vec3 ofs = (position + normal * ((taps + 1.0) * 0.5 + 1.0)) * cell_size; + ao += textureLod(sampler3D(gi_probe_textures[index], linear_sampler_with_mipmaps), ofs, taps).a * (taps + 1.0) * blend; + } + + ao = 1.0 - min(1.0, ao); + + light.rgb = mix(params.ao_color, light.rgb, mix(1.0, ao, gi_probes.data[index].ambient_occlusion)); + } + + light.rgb *= gi_probes.data[index].dynamic_range; + if (!gi_probes.data[index].blend_ambient) { + light.a = 1.0; + } + + out_diff += light * blend; + + //radiance + vec4 irr_light = voxel_cone_trace(gi_probe_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, gi_probes.data[index].bias); + irr_light.rgb *= gi_probes.data[index].dynamic_range; + if (!gi_probes.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 main() { + // Pixel being shaded + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(pos, params.screen_size))) { //too large, do nothing + return; + } + + vec3 vertex = reconstruct_position(pos); + vertex.y = -vertex.y; + + vec4 normal_roughness = fetch_normal_and_roughness(pos); + vec3 normal = normal_roughness.xyz; + + vec4 ambient_light = vec4(0.0), reflection_light = vec4(0.0); + + 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)); + + if (params.use_sdfgi) { + sdfgi_process(vertex, normal, reflection, roughness, ambient_light, reflection_light); + } + + if (params.max_giprobes > 0) { + uvec2 giprobe_tex = texelFetch(usampler2D(giprobe_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_giprobes; i++) { + if (any(equal(uvec2(i), giprobe_tex))) { + gi_probe_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; + } + + if (params.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; + } + } + } + + imageStore(ambient_buffer, pos, ambient_light); + imageStore(reflection_buffer, pos, reflection_light); +} diff --git a/servers/rendering/renderer_rd/shaders/giprobe.glsl b/servers/rendering/renderer_rd/shaders/giprobe.glsl new file mode 100644 index 0000000000..ea4237a45e --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/giprobe.glsl @@ -0,0 +1,768 @@ +#[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 spot_angle_radians; + + vec3 position; + float 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); +} + +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 = 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 angle = acos(dot(rel, lights.data[light].direction)); + if (angle > lights.data[light].spot_angle_radians) { + return false; + } + + float d = clamp(angle / lights.data[light].spot_angle_radians, 0, 1); + attenuation *= pow(1.0 - d, lights.data[light].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/giprobe_debug.glsl b/servers/rendering/renderer_rd/shaders/giprobe_debug.glsl new file mode 100644 index 0000000000..515cc35507 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/giprobe_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/giprobe_sdf.glsl b/servers/rendering/renderer_rd/shaders/giprobe_sdf.glsl new file mode 100644 index 0000000000..5b3dec0ee7 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/giprobe_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 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..9c794f1bcc --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/giprobe_write.glsl @@ -0,0 +1,321 @@ +#[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 spot_angle_radians; + + vec3 position; + float 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 angle = acos(dot(rel, lights.data[light].direction)); + if (angle > lights.data[light].spot_angle_radians) { + return false; + } + + float d = clamp(angle / lights.data[light].spot_angle_radians, 0, 1); + attenuation *= pow(1.0 - d, lights.data[light].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/luminance_reduce.glsl b/servers/rendering/renderer_rd/shaders/luminance_reduce.glsl new file mode 100644 index 0000000000..8a11c35b78 --- /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/particles.glsl b/servers/rendering/renderer_rd/shaders/particles.glsl new file mode 100644 index 0000000000..926c7ef9fc --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/particles.glsl @@ -0,0 +1,549 @@ +#[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 + +/* 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 + +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 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; + +struct ParticleData { + mat4 xform; + vec3 velocity; + bool is_active; + 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 USE_MATERIAL_UNIFORMS +layout(set = 3, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} 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; + uint pad; +} +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_particle(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; +} + +/* clang-format off */ + +COMPUTE_SHADER_GLOBALS + +/* clang-format on */ + +void main() { + uint particle = gl_GlobalInvocationID.x; + + 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.is_active = false; + 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)); + } + + 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 (PARTICLE.is_active) { + PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta; + } +#endif + + /* Process physics if active */ + + if (PARTICLE.is_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 + + 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 (params.sub_emitter_mode) { + if (!PARTICLE.is_active) { + int src_index = atomicAdd(src_particles.particle_count, -1) - 1; + + if (src_index >= 0) { + PARTICLE.is_active = true; + 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; + } + } + } + + uint current_cycle = FRAME.cycle; + + if (FRAME.system_phase < restart_phase) { + current_cycle -= uint(1); + } + + uint particle_number = current_cycle * uint(params.total_particles) + particle; + + if (restart) { + PARTICLE.is_active = FRAME.emitting; + restart_position = true; + restart_rotation_scale = true; + restart_velocity = true; + restart_color = true; + restart_custom = true; + } + } + + if (PARTICLE.is_active) { + /* clang-format off */ + +COMPUTE_SHADER_CODE + + /* clang-format on */ + } +} 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..6c782b6045 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/particles_copy.glsl @@ -0,0 +1,82 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +struct ParticleData { + mat4 xform; + vec3 velocity; + bool is_active; + 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(push_constant, binding = 0, std430) uniform Params { + vec3 sort_direction; + uint total_particles; +} +params; + +void main() { +#ifdef MODE_FILL_SORT_BUFFER + + uint particle = gl_GlobalInvocationID.x; + if (particle >= params.total_particles) { + return; //discard + } + + sort_buffer.data[particle].x = dot(params.sort_direction, particles.data[particle].xform[3].xyz); + sort_buffer.data[particle].y = float(particle); +#endif + +#ifdef MODE_FILL_INSTANCES + + uint particle = gl_GlobalInvocationID.x; + uint write_offset = gl_GlobalInvocationID.x * (3 + 1 + 1); //xform + color + custom + + if (particle >= params.total_particles) { + return; //discard + } + +#ifdef USE_SORT_BUFFER + particle = uint(sort_buffer.data[particle].y); //use index from sort buffer +#endif + + mat4 txform; + + if (particles.data[particle].is_active) { + txform = transpose(particles.data[particle].xform); + } else { + txform = mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); //zero scale, becomes invisible + } + + 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 +} diff --git a/servers/rendering/renderer_rd/shaders/resolve.glsl b/servers/rendering/renderer_rd/shaders/resolve.glsl new file mode 100644 index 0000000000..9429a66dc9 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/resolve.glsl @@ -0,0 +1,110 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +#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 GIPROBE_RESOLVE +layout(set = 2, binding = 0) uniform usampler2DMS source_giprobe; +layout(rg8ui, set = 3, binding = 0) uniform restrict writeonly uimage2D dest_giprobe; +#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_GI + + float best_depth = 1e20; + vec4 best_normal_roughness = vec4(0.0); +#ifdef GIPROBE_RESOLVE + uvec2 best_giprobe; +#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 GIPROBE_RESOLVE + best_giprobe = texelFetch(source_giprobe,pos,i).rg; +#endif + } + } + +#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]; + } + } + + best_depth = texelFetch(source_depth, pos, best_index).r; + best_normal_roughness = texelFetch(source_normal_roughness, pos, best_index); +#ifdef GIPROBE_RESOLVE + best_giprobe = texelFetch(source_giprobe, pos, best_index).rg; +#endif + +#endif + + imageStore(dest_depth, pos, vec4(best_depth)); + imageStore(dest_normal_roughness, pos, vec4(best_normal_roughness)); +#ifdef GIPROBE_RESOLVE + imageStore(dest_giprobe, pos, uvec4(best_giprobe, 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..464895928a --- /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.glsl b/servers/rendering/renderer_rd/shaders/scene_forward.glsl new file mode 100644 index 0000000000..5b01cb1f82 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/scene_forward.glsl @@ -0,0 +1,2905 @@ +#[vertex] + +#version 450 + +VERSION_DEFINES + +#include "scene_forward_inc.glsl" + +/* INPUT ATTRIBS */ + +layout(location = 0) in vec3 vertex_attrib; +layout(location = 1) in vec3 normal_attrib; +#if defined(TANGENT_USED) || defined(NORMALMAP_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 + +layout(location = 4) in vec2 uv_attrib; + +#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) +layout(location = 10) in uvec4 bone_attrib; +#endif + +#if defined(WEIGHTS_USED) +layout(location = 11) in vec4 weight_attrib; +#endif + +/* Varyings */ + +layout(location = 0) out vec3 vertex_interp; +layout(location = 1) out vec3 normal_interp; + +#if defined(COLOR_USED) +layout(location = 2) out vec4 color_interp; +#endif + +layout(location = 3) out vec2 uv_interp; + +#if defined(UV2_USED) || defined(USE_LIGHTMAP) +layout(location = 4) out vec2 uv2_interp; +#endif + +#if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED) +layout(location = 5) out vec3 tangent_interp; +layout(location = 6) out vec3 binormal_interp; +#endif + +#ifdef USE_MATERIAL_UNIFORMS +layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} material; +#endif + +/* clang-format off */ + +VERTEX_SHADER_GLOBALS + +/* clang-format on */ + +invariant gl_Position; + +layout(location = 7) flat out uint instance_index; + +#ifdef MODE_DUAL_PARABOLOID + +layout(location = 8) out float dp_clip; + +#endif + +void main() { + instance_index = draw_call.instance_index; + vec4 instance_custom = vec4(0.0); +#if defined(COLOR_USED) + color_interp = color_attrib; +#endif + + mat4 world_matrix = instances.data[instance_index].transform; + mat3 world_normal_matrix = mat3(instances.data[instance_index].normal_transform); + + if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH)) { + //multimesh, instances are for it + uint offset = (instances.data[instance_index].flags >> INSTANCE_FLAGS_MULTIMESH_STRIDE_SHIFT) & INSTANCE_FLAGS_MULTIMESH_STRIDE_MASK; + offset *= gl_InstanceIndex; + + mat4 matrix; + 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]; + } + + //transpose + matrix = transpose(matrix); + world_matrix = world_matrix * matrix; + world_normal_matrix = world_normal_matrix * mat3(matrix); + + } else { + //not a multimesh, instances are for multiple draw calls + instance_index += gl_InstanceIndex; + } + + vec3 vertex = vertex_attrib; + vec3 normal = normal_attrib * 2.0 - 1.0; + +#if defined(TANGENT_USED) || defined(NORMALMAP_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 + +#if 0 + if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_SKELETON)) { + //multimesh, instances are for it + + uvec2 bones_01 = uvec2(bone_attrib.x & 0xFFFF, bone_attrib.x >> 16) * 3; + uvec2 bones_23 = uvec2(bone_attrib.y & 0xFFFF, bone_attrib.y >> 16) * 3; + vec2 weights_01 = unpackUnorm2x16(bone_attrib.z); + vec2 weights_23 = unpackUnorm2x16(bone_attrib.w); + + mat4 m = mat4(transforms.data[bones_01.x], transforms.data[bones_01.x + 1], transforms.data[bones_01.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.x; + m += mat4(transforms.data[bones_01.y], transforms.data[bones_01.y + 1], transforms.data[bones_01.y + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.y; + m += mat4(transforms.data[bones_23.x], transforms.data[bones_23.x + 1], transforms.data[bones_23.x + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x; + m += mat4(transforms.data[bones_23.y], transforms.data[bones_23.y + 1], 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 = (vec4(normal, 0.0) * m).xyz; + +#if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED) + + tangent = (vec4(tangent, 0.0) * m).xyz; + binormal = (vec4(binormal, 0.0) * m).xyz; +#endif + } +#endif + uv_interp = uv_attrib; + +#if defined(UV2_USED) || defined(USE_LIGHTMAP) + uv2_interp = uv2_attrib; +#endif + +#ifdef USE_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(NORMALMAP_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; + + { + /* clang-format off */ + +VERTEX_SHADER_CODE + + /* clang-format on */ + } + +// using local coordinates (default) +#if !defined(SKIP_TRANSFORM_USED) && !defined(VERTEX_WORLD_COORDS_USED) + + vertex = (modelview * vec4(vertex, 1.0)).xyz; + normal = modelview_normal * normal; +#endif + +#if defined(TANGENT_USED) || defined(NORMALMAP_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(NORMALMAP_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; + normal_interp = normal; + +#if defined(TANGENT_USED) || defined(NORMALMAP_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; + normal_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 USE_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) { + gl_Position.xy = (uv2_attrib.xy + draw_call.bake_uv2_offset) * 2.0 - 1.0; + gl_Position.z = 0.00001; + gl_Position.w = 1.0; + } +#endif +} + +#[fragment] + +#version 450 + +VERSION_DEFINES + +#include "scene_forward_inc.glsl" + +/* Varyings */ + +layout(location = 0) in vec3 vertex_interp; +layout(location = 1) in vec3 normal_interp; + +#if defined(COLOR_USED) +layout(location = 2) in vec4 color_interp; +#endif + +layout(location = 3) in vec2 uv_interp; + +#if defined(UV2_USED) || defined(USE_LIGHTMAP) +layout(location = 4) in vec2 uv2_interp; +#endif + +#if defined(TANGENT_USED) || defined(NORMALMAP_USED) || defined(LIGHT_ANISOTROPY_USED) +layout(location = 5) in vec3 tangent_interp; +layout(location = 6) in vec3 binormal_interp; +#endif + +layout(location = 7) flat in uint instance_index; + +#ifdef MODE_DUAL_PARABOLOID + +layout(location = 8) in float dp_clip; + +#endif + +//defines to keep compatibility with vertex + +#define world_matrix instances.data[instance_index].transform +#define world_normal_matrix instances.data[instance_index].normal_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 USE_MATERIAL_UNIFORMS +layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ +MATERIAL_UNIFORMS + /* clang-format on */ +} material; +#endif + +/* clang-format off */ + +FRAGMENT_SHADER_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 + +#ifdef MODE_RENDER_NORMAL_ROUGHNESS +layout(location = 0) out vec4 normal_roughness_output_buffer; + +#ifdef MODE_RENDER_GIPROBE +layout(location = 1) out uvec2 giprobe_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 + +#endif // RENDER DEPTH + +#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 + +// 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). + +#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) + +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 shadow_attenuation, vec3 diffuse_color, float roughness, float metallic, float specular, float specular_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + vec4 transmittance_color, + float transmittance_depth, + float transmittance_curve, + float transmittance_boost, + float transmittance_z, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#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(USE_LIGHT_SHADER_CODE) + // light is written by the light shader + + vec3 normal = N; + vec3 albedo = diffuse_color; + vec3 light = L; + vec3 view = V; + + /* clang-format off */ + +LIGHT_SHADER_CODE + + /* clang-format on */ + +#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 + + if (metallic < 1.0) { +#if defined(DIFFUSE_OREN_NAYAR) + vec3 diffuse_brdf_NL; +#else + float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance +#endif + +#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_OREN_NAYAR) + + { + // see http://mimosa-pudica.net/improved-oren-nayar.html + float LdotV = dot(L, V); + + float s = LdotV - NdotL * NdotV; + float t = mix(1.0, max(NdotL, NdotV), step(0.0, s)); + + float sigma2 = roughness * roughness; // TODO: this needs checking + vec3 A = 1.0 + sigma2 * (-0.5 / (sigma2 + 0.33) + 0.17 * diffuse_color / (sigma2 + 0.13)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + diffuse_brdf_NL = cNdotL * (A + vec3(B) * s / t) * (1.0 / M_PI); + } + +#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_color * shadow_attenuation * diffuse_brdf_NL * attenuation; + +#if defined(LIGHT_BACKLIGHT_USED) + diffuse_light += light_color * diffuse_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), diffuse_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 * diffuse_color * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI) * attenuation; + } +#else + + if (transmittance_depth > 0.0) { + float fade = clamp(abs(transmittance_z / transmittance_depth), 0.0, 1.0); + + fade = pow(max(0.0, 1.0 - fade), transmittance_curve); + fade *= clamp(transmittance_boost - NdotL, 0.0, 1.0); + + diffuse_light += diffuse_color * transmittance_color.rgb * light_color * (1.0 / M_PI) * transmittance_color.a * fade * attenuation; + } + +#endif //SSS_MODE_SKIN + +#endif //LIGHT_TRANSMITTANCE_USED + } + + 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) * cNdotL; + blinn *= (shininess + 8.0) * (1.0 / (8.0 * M_PI)); + float intensity = blinn; + + specular_light += light_color * shadow_attenuation * intensity * specular_blob_intensity * attenuation; + +#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 + 8.0) * (1.0 / (8.0 * M_PI)); + float intensity = (phong) / max(4.0 * cNdotV * cNdotL, 0.75); + + specular_light += light_color * shadow_attenuation * intensity * specular_blob_intensity * attenuation; + +#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 * shadow_attenuation * intensity * specular_blob_intensity * attenuation; // 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 + vec3 f0 = F0(metallic, specular, diffuse_color); + 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 * shadow_attenuation * specular_blob_intensity * attenuation; +#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 * shadow_attenuation * specular_blob_intensity * attenuation; +#endif + } + +#ifdef USE_SHADOW_TO_OPACITY + alpha = min(alpha, clamp(1.0 - length(shadow_attenuation * attenuation), 0.0, 1.0)); +#endif + +#endif //defined(USE_LIGHT_SHADER_CODE) +} + +#ifndef USE_NO_SHADOWS + +// Produces cheap white noise, optimized for window-space +// Comes from: https://www.shadertoy.com/view/4djSRW +// Copyright: Dave Hoskins, MIT License +float quick_hash(vec2 pos) { + vec3 p3 = fract(vec3(pos.xyx) * .1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +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 (scene_data.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 < scene_data.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(scene_data.directional_soft_shadow_samples)); +} + +float sample_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 (scene_data.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 < scene_data.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(scene_data.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 < scene_data.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 < scene_data.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(scene_data.directional_penumbra_shadow_samples); + + } else { + //no blockers found, so no shadow + return 1.0; + } +} + +#endif //USE_NO_SHADOWS + +void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 albedo, float roughness, float metallic, float specular, float p_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + vec4 transmittance_color, + float transmittance_depth, + float transmittance_curve, + float transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#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 = lights.data[idx].position - vertex; + float light_length = length(light_rel_vec); + float normalized_distance = light_length * lights.data[idx].inv_radius; + vec2 attenuation_energy = unpackHalf2x16(lights.data[idx].attenuation_energy); + float omni_attenuation = pow(max(1.0 - normalized_distance, 0.0), attenuation_energy.x); + float light_attenuation = omni_attenuation; + vec3 shadow_attenuation = vec3(1.0); + vec4 color_specular = unpackUnorm4x8(lights.data[idx].color_specular); + color_specular.rgb *= attenuation_energy.y; + float size_A = 0.0; + + if (lights.data[idx].size > 0.0) { + float t = 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 +#endif + +#ifndef USE_NO_SHADOWS + vec4 shadow_color_enabled = unpackUnorm4x8(lights.data[idx].shadow_color_enabled); + if (shadow_color_enabled.w > 0.5) { + // there is a shadowmap + + vec4 v = vec4(vertex, 1.0); + + vec4 splane = (lights.data[idx].shadow_matrix * v); + float shadow_len = length(splane.xyz); //need to remember shadow len from here + + { + vec3 nofs = normal_interp * lights.data[idx].shadow_normal_bias / lights.data[idx].inv_radius; + nofs *= (1.0 - max(0.0, dot(normalize(light_rel_vec), normalize(normal_interp)))); + v.xyz += nofs; + splane = (lights.data[idx].shadow_matrix * v); + } + + float shadow; + + if (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 normal = normalize(splane.xyz); + 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)); + float z_norm = shadow_len * lights.data[idx].inv_radius; + + tangent *= lights.data[idx].soft_shadow_size * lights.data[idx].soft_shadow_scale; + bitangent *= lights.data[idx].soft_shadow_size * lights.data[idx].soft_shadow_scale; + + for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) { + vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; + + vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + + pos = normalize(pos); + vec4 uv_rect = lights.data[idx].atlas_rect; + + if (pos.z >= 0.0) { + pos.z += 1.0; + uv_rect.y += uv_rect.w; + } else { + pos.z = 1.0 - 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 -= lights.data[idx].inv_radius * lights.data[idx].shadow_bias; + + shadow = 0.0; + for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) { + vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; + vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + + pos = normalize(pos); + vec4 uv_rect = lights.data[idx].atlas_rect; + + if (pos.z >= 0.0) { + pos.z += 1.0; + uv_rect.y += uv_rect.w; + } else { + pos.z = 1.0 - 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(scene_data.penumbra_shadow_samples); + + } else { + //no blockers found, so no shadow + shadow = 1.0; + } + } else { + splane.xyz = normalize(splane.xyz); + vec4 clamp_rect = lights.data[idx].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 - lights.data[idx].shadow_bias) * lights.data[idx].inv_radius; + splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw; + splane.w = 1.0; //needed? i think it should be 1 already + shadow = sample_pcf_shadow(shadow_atlas, lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, splane); + } + +#ifdef LIGHT_TRANSMITTANCE_USED + { + vec4 clamp_rect = lights.data[idx].atlas_rect; + + //redo shadowmapping, but shrink the model a bit to avoid arctifacts + splane = (lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * lights.data[idx].transmittance_bias, 1.0)); + + shadow_len = length(splane.xyz); + splane = normalize(splane.xyz); + + if (splane.z >= 0.0) { + splane.z += 1.0; + + } else { + splane.z = 1.0 - splane.z; + } + + splane.xy /= splane.z; + splane.xy = splane.xy * 0.5 + 0.5; + splane.z = shadow_len * lights.data[idx].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 shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r; + transmittance_z = (splane.z - shadow_z) / lights.data[idx].inv_radius; + } +#endif + + vec3 no_shadow = vec3(1.0); + + if (lights.data[idx].projector_rect != vec4(0.0)) { + vec3 local_v = (lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz; + local_v = normalize(local_v); + + vec4 atlas_rect = lights.data[idx].projector_rect; + + if (local_v.z >= 0.0) { + local_v.z += 1.0; + atlas_rect.y += atlas_rect.w; + + } else { + local_v.z = 1.0 - 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; + + vec2 proj_uv_ddx; + vec2 proj_uv_ddy; + { + vec3 local_v_ddx = (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 = (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, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), proj_uv + atlas_rect.xy, proj_uv_ddx, proj_uv_ddy); + no_shadow = mix(no_shadow, proj.rgb, proj.a); + } + + shadow_attenuation = mix(shadow_color_enabled.rgb, no_shadow, shadow); + } +#endif //USE_NO_SHADOWS + + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color_specular.rgb, light_attenuation, shadow_attenuation, albedo, roughness, metallic, specular, color_specular.a * p_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_curve, + transmittance_boost, + transmittance_z, +#endif +#ifdef LIGHT_RIM_USED + rim * omni_attenuation, rim_tint, +#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 light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 albedo, float roughness, float metallic, float specular, float p_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + vec4 transmittance_color, + float transmittance_depth, + float transmittance_curve, + float transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#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 = lights.data[idx].position - vertex; + float light_length = length(light_rel_vec); + float normalized_distance = light_length * lights.data[idx].inv_radius; + vec2 attenuation_energy = unpackHalf2x16(lights.data[idx].attenuation_energy); + float spot_attenuation = pow(max(1.0 - normalized_distance, 0.001), attenuation_energy.x); + vec3 spot_dir = lights.data[idx].direction; + vec2 spot_att_angle = unpackHalf2x16(lights.data[idx].cone_attenuation_angle); + float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_att_angle.y); + float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_att_angle.y)); + spot_attenuation *= 1.0 - pow(spot_rim, spot_att_angle.x); + float light_attenuation = spot_attenuation; + vec3 shadow_attenuation = vec3(1.0); + vec4 color_specular = unpackUnorm4x8(lights.data[idx].color_specular); + color_specular.rgb *= attenuation_energy.y; + + float size_A = 0.0; + + if (lights.data[idx].size > 0.0) { + float t = lights.data[idx].size / max(0.001, light_length); + size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t)); + } +/* + if (lights.data[idx].atlas_rect!=vec4(0.0)) { + //use projector texture + } + */ +#ifdef LIGHT_TRANSMITTANCE_USED + float transmittance_z = transmittance_depth; +#endif + +#ifndef USE_NO_SHADOWS + vec4 shadow_color_enabled = unpackUnorm4x8(lights.data[idx].shadow_color_enabled); + if (shadow_color_enabled.w > 0.5) { + //there is a shadowmap + vec4 v = vec4(vertex, 1.0); + + v.xyz -= spot_dir * lights.data[idx].shadow_bias; + + float z_norm = dot(spot_dir, -light_rel_vec) * lights.data[idx].inv_radius; + + float depth_bias_scale = 1.0 / (max(0.0001, z_norm)); //the closer to the light origin, the more you have to offset to reach 1px in the map + vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(spot_dir, -normalize(normal_interp)))) * lights.data[idx].shadow_normal_bias * depth_bias_scale; + normal_bias -= spot_dir * dot(spot_dir, normal_bias); //only XY, no Z + v.xyz += normal_bias; + + //adjust with bias + z_norm = dot(spot_dir, v.xyz - lights.data[idx].position) * lights.data[idx].inv_radius; + + float shadow; + + vec4 splane = (lights.data[idx].shadow_matrix * v); + splane /= splane.w; + + if (lights.data[idx].soft_shadow_size > 0.0) { + //soft shadow + + //find blocker + + vec2 shadow_uv = splane.xy * lights.data[idx].atlas_rect.zw + 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 = lights.data[idx].soft_shadow_size * z_norm * lights.data[idx].soft_shadow_scale; + vec2 clamp_max = lights.data[idx].atlas_rect.xy + lights.data[idx].atlas_rect.zw; + for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) { + vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size; + suv = clamp(suv, 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 < 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; + uv_size *= penumbra; + + shadow = 0.0; + for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) { + vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size; + suv = clamp(suv, lights.data[idx].atlas_rect.xy, clamp_max); + shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(suv, z_norm, 1.0)); + } + + shadow /= float(scene_data.penumbra_shadow_samples); + + } else { + //no blockers found, so no shadow + shadow = 1.0; + } + + } else { + //hard shadow + vec4 shadow_uv = vec4(splane.xy * lights.data[idx].atlas_rect.zw + lights.data[idx].atlas_rect.xy, z_norm, 1.0); + + shadow = sample_pcf_shadow(shadow_atlas, lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv); + } + + vec3 no_shadow = vec3(1.0); + + if (lights.data[idx].projector_rect != vec4(0.0)) { + splane = (lights.data[idx].shadow_matrix * vec4(vertex, 1.0)); + splane /= splane.w; + + vec2 proj_uv = splane.xy * lights.data[idx].projector_rect.zw; + + //ensure we have proper mipmaps + vec4 splane_ddx = (lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0)); + splane_ddx /= splane_ddx.w; + vec2 proj_uv_ddx = splane_ddx.xy * lights.data[idx].projector_rect.zw - proj_uv; + + vec4 splane_ddy = (lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddy, 1.0)); + splane_ddy /= splane_ddy.w; + vec2 proj_uv_ddy = splane_ddy.xy * lights.data[idx].projector_rect.zw - proj_uv; + + vec4 proj = textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), proj_uv + lights.data[idx].projector_rect.xy, proj_uv_ddx, proj_uv_ddy); + no_shadow = mix(no_shadow, proj.rgb, proj.a); + } + + shadow_attenuation = mix(shadow_color_enabled.rgb, no_shadow, shadow); + +#ifdef LIGHT_TRANSMITTANCE_USED + { + splane = (lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * lights.data[idx].transmittance_bias, 1.0)); + splane /= splane.w; + splane.xy = splane.xy * lights.data[idx].atlas_rect.zw + lights.data[idx].atlas_rect.xy; + + float shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r; + //reconstruct depth + shadow_z /= lights.data[idx].inv_radius; + //distance to light plane + float z = dot(spot_dir, -light_rel_vec); + transmittance_z = z - shadow_z; + } +#endif //LIGHT_TRANSMITTANCE_USED + } + +#endif //USE_NO_SHADOWS + + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color_specular.rgb, light_attenuation, shadow_attenuation, albedo, roughness, metallic, specular, color_specular.a * p_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_curve, + transmittance_boost, + transmittance_z, +#endif +#ifdef LIGHT_RIM_USED + rim * spot_attenuation, rim_tint, +#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].params.x > 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].params.w > 0.5) { //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].params.z < 0.5) { + reflection.rgb = mix(specular_light, reflection.rgb, blend); + } + + reflection.rgb *= reflections.data[ref_index].params.x; + 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].params.z < 0.5) { //interior + 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].params.z < 0.5) { + ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend); + } + ambient_out.rgb *= ambient_out.a; + ambient_accum += ambient_out; + } break; + } +} + +#ifdef USE_FORWARD_GI + +//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 gi_probe_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 = (gi_probes.data[index].xform * vec4(position, 1.0)).xyz; + ref_vec = normalize((gi_probes.data[index].xform * vec4(ref_vec, 0.0)).xyz); + normal = normalize((gi_probes.data[index].xform * vec4(normal, 0.0)).xyz); + + position += normal * gi_probes.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, gi_probes.data[index].bounds))))) { + return; + } + + vec3 blendv = abs(position / gi_probes.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(gi_probes.data[index].bounds); + vec3 cell_size = 1.0 / gi_probes.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((gi_probes.data[index].xform * vec4(normal_xform * cone_dirs[i], 0.0)).xyz); + + vec4 cone_light = voxel_cone_trace_45_degrees(gi_probe_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, gi_probes.data[index].bias); + + if (gi_probes.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 *= gi_probes.data[index].dynamic_range; + out_diff += vec4(light * blend, blend); + + //irradiance + vec4 irr_light = voxel_cone_trace(gi_probe_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, gi_probes.data[index].bias); + if (gi_probes.data[index].blend_ambient) { + irr_light.rgb = mix(environment, irr_light.rgb, min(1.0, irr_light.a / 0.95)); + } + irr_light.rgb *= gi_probes.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; + } + } +} + +#endif //USE_FORWARD_GI + +#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) + +#ifndef MODE_RENDER_DEPTH + +#ifndef LOW_END_MODE + +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); +} +#endif + +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(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 + +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_curve = 1.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); +#if defined(CUSTOM_FOG_USED) + vec4 custom_fog = vec4(0.0); +#endif +#if defined(CUSTOM_RADIANCE_USED) + vec4 custom_radiance = vec4(0.0); +#endif +#if defined(CUSTOM_IRRADIANCE_USED) + vec4 custom_irradiance = vec4(0.0); +#endif + +#if defined(AO_USED) + float ao = 1.0; + float ao_light_affect = 0.0; +#endif + + float alpha = 1.0; + +#if defined(TANGENT_USED) || defined(NORMALMAP_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 + vec3 normal = normalize(normal_interp); + +#if defined(DO_SIDE_CHECK) + if (!gl_FrontFacing) { + normal = -normal; + } +#endif + + vec2 uv = uv_interp; + +#if defined(UV2_USED) || defined(USE_LIGHTMAP) + vec2 uv2 = uv2_interp; +#endif + +#if defined(COLOR_USED) + vec4 color = color_interp; +#endif + +#if defined(NORMALMAP_USED) + + vec3 normalmap = vec3(0.5); +#endif + + float normaldepth = 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 + + { + /* clang-format off */ + +FRAGMENT_SHADER_CODE + + /* clang-format on */ + } + +#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 wont 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 NORMALMAP_USED + + normalmap.xy = normalmap.xy * 2.0 - 1.0; + normalmap.z = sqrt(max(0.0, 1.0 - dot(normalmap.xy, normalmap.xy))); //always ignore Z, as it can be RG packed, Z may be pos/neg, etc. + + normal = normalize(mix(normal, tangent * normalmap.x + binormal * normalmap.y + normal * normalmap.z, normaldepth)); + +#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 + /////////////////////// DECALS //////////////////////////////// + +#ifndef MODE_RENDER_DEPTH + + uvec4 cluster_cell = texture(usampler3D(cluster_texture, material_samplers[SAMPLER_NEAREST_CLAMP]), vec3(screen_uv, (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near))); + //used for interpolating anything cluster related + vec3 vertex_ddx = dFdx(vertex); + vec3 vertex_ddy = dFdy(vertex); + + { // process decals + + uint decal_count = cluster_cell.w >> CLUSTER_COUNTER_SHIFT; + uint decal_pointer = cluster_cell.w & CLUSTER_POINTER_MASK; + + //do outside for performance and avoiding arctifacts + + for (uint i = 0; i < decal_count; i++) { + uint decal_index = cluster_data.indices[decal_pointer + i]; + 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 + } + + //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; + + 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); + } + + if (decals.data[decal_index].albedo_rect != vec4(0.0)) { + //has albedo + vec4 decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), 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); + 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 = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), 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; + 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 = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), 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; +#if defined(AO_USED) + ao = mix(ao, decal_orm.r, decal_albedo.a); +#endif + 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 + emission += textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), 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; + } + } + } + +#endif //not render depth + /////////////////////// LIGHTING ////////////////////////////// + + 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 = dFdx(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); + } + //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 + 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(specular_light, custom_irradiance.rgb, custom_irradiance.a); +#endif +#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) + + //radiance + + float specular_blob_intensity = 1.0; + +#if defined(SPECULAR_TOON) + specular_blob_intensity *= specular * 2.0; +#endif + +#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 & 0xFFF; + 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 >> 12) & 0xFF); + + 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; + } + } +#elif defined(USE_FORWARD_GI) + + if (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 (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GIPROBE)) { // process giprobes + + 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); + gi_probe_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) { + gi_probe_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; + } +#elif !defined(LOW_END_MODE) + + if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GI_BUFFERS)) { //use GI buffers + + ivec2 coord; + + if (scene_data.gi_upscale_for_msaa) { + ivec2 base_coord = ivec2(gl_FragCoord.xy); + ivec2 closest_coord = base_coord; + float closest_ang = dot(normal, texelFetch(sampler2D(normal_roughness_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), base_coord, 0).xyz * 2.0 - 1.0); + + for (int i = 0; i < 4; i++) { + const ivec2 neighbours[4] = ivec2[](ivec2(-1, 0), ivec2(1, 0), ivec2(0, -1), ivec2(0, 1)); + ivec2 neighbour_coord = base_coord + neighbours[i]; + float neighbour_ang = dot(normal, texelFetch(sampler2D(normal_roughness_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), neighbour_coord, 0).xyz * 2.0 - 1.0); + if (neighbour_ang > closest_ang) { + closest_ang = neighbour_ang; + closest_coord = neighbour_coord; + } + } + + coord = closest_coord; + + } else { + coord = ivec2(gl_FragCoord.xy); + } + + vec4 buffer_ambient = texelFetch(sampler2D(ambient_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), coord, 0); + vec4 buffer_reflection = texelFetch(sampler2D(reflection_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), coord, 0); + + ambient_light = mix(ambient_light, buffer_ambient.rgb, buffer_ambient.a); + specular_light = mix(specular_light, buffer_reflection.rgb, buffer_reflection.a); + } +#endif + + { // 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 reflection_probe_count = cluster_cell.z >> CLUSTER_COUNTER_SHIFT; + uint reflection_probe_pointer = cluster_cell.z & CLUSTER_POINTER_MASK; + + for (uint i = 0; i < reflection_probe_count; i++) { + uint ref_index = cluster_data.indices[reflection_probe_pointer + i]; + reflection_process(ref_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 + } + + { +#if defined(DIFFUSE_TOON) + //simplify for toon, as + specular_light *= specular * metallic * albedo * 2.0; +#else + + // scales the specular reflections, needs to be 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; + + vec3 f0 = F0(metallic, specular, albedo); + specular_light *= env.x * f0 + env.y; +#endif + } + + { //directional light + + for (uint i = 0; i < scene_data.directional_light_count; i++) { + if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) { + continue; //not masked + } + + vec3 shadow_attenuation = vec3(1.0); + +#ifdef LIGHT_TRANSMITTANCE_USED + float transmittance_z = transmittance_depth; +#endif + + 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; + + float shadow = 0.0; + + 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; + +#ifdef LIGHT_TRANSMITTANCE_USED + { + 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; + } +#endif + } 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; +#ifdef LIGHT_TRANSMITTANCE_USED + { + 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; + } +#endif + } 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; +#ifdef LIGHT_TRANSMITTANCE_USED + { + 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; + } +#endif + + } 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; + +#ifdef LIGHT_TRANSMITTANCE_USED + { + 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 + } + + 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 + + shadow_attenuation = mix(shadow_color, vec3(1.0), shadow); + +#undef BIAS_FUNC + } + + light_compute(normal, directional_lights.data[i].direction, normalize(view), directional_lights.data[i].size, directional_lights.data[i].color * directional_lights.data[i].energy, 1.0, shadow_attenuation, albedo, roughness, metallic, specular, directional_lights.data[i].specular * specular_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_curve, + transmittance_boost, + transmittance_z, +#endif +#ifdef LIGHT_RIM_USED + rim, rim_tint, +#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 omni_light_count = cluster_cell.x >> CLUSTER_COUNTER_SHIFT; + uint omni_light_pointer = cluster_cell.x & CLUSTER_POINTER_MASK; + + for (uint i = 0; i < omni_light_count; i++) { + uint light_index = cluster_data.indices[omni_light_pointer + i]; + + if (!bool(lights.data[light_index].mask & instances.data[instance_index].layer_mask)) { + continue; //not masked + } + + light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, albedo, roughness, metallic, specular, specular_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_curve, + transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + rim, + rim_tint, +#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 spot_light_count = cluster_cell.y >> CLUSTER_COUNTER_SHIFT; + uint spot_light_pointer = cluster_cell.y & CLUSTER_POINTER_MASK; + + for (uint i = 0; i < spot_light_count; i++) { + uint light_index = cluster_data.indices[spot_light_pointer + i]; + + if (!bool(lights.data[light_index].mask & instances.data[instance_index].layer_mask)) { + continue; //not masked + } + + light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, albedo, roughness, metallic, specular, specular_blob_intensity, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_curve, + transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + rim, + rim_tint, +#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; + +#if defined(AO_USED) + orm_output_buffer.r = ao; +#else + orm_output_buffer.r = 0.0; +#endif + 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_GIPROBE + if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GIPROBE)) { // process giprobes + uint index1 = instances.data[instance_index].gi_offset & 0xFFFF; + uint index2 = instances.data[instance_index].gi_offset >> 16; + giprobe_buffer.x = index1 & 0xFF; + giprobe_buffer.y = index2 & 0xFF; + } else { + giprobe_buffer.x = 0xFF; + giprobe_buffer.y = 0xFF; + } +#endif + +#endif //MODE_RENDER_NORMAL + +//nothing happens, so a tree-ssa optimizer will result in no fragment shader :) +#else + + specular_light *= scene_data.reflection_multiplier; + ambient_light *= albedo; //ambient must be multiplied by albedo at the end + +//ambient occlusion +#if defined(AO_USED) + +#ifndef LOW_END_MODE + if (scene_data.ssao_enabled && scene_data.ssao_ao_affect > 0.0) { + float ssao = texture(sampler2D(ao_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), screen_uv).r; + ao = mix(ao, min(ao, ssao), scene_data.ssao_ao_affect); + ao_light_affect = mix(ao_light_affect, max(ao_light_affect, scene_data.ssao_light_affect), scene_data.ssao_ao_affect); + } +#endif //LOW_END_MODE + + ambient_light = mix(scene_data.ao_color.rgb, ambient_light, ao); + ao_light_affect = mix(1.0, ao, ao_light_affect); + specular_light = mix(scene_data.ao_color.rgb, specular_light, ao_light_affect); + diffuse_light = mix(scene_data.ao_color.rgb, diffuse_light, ao_light_affect); +#else + +#ifndef LOW_END_MODE + if (scene_data.ssao_enabled) { + float ao = texture(sampler2D(ao_buffer, material_samplers[SAMPLER_LINEAR_CLAMP]), screen_uv).r; + ambient_light = mix(scene_data.ao_color.rgb, ambient_light, ao); + float ao_light_affect = mix(1.0, ao, scene_data.ssao_light_affect); + specular_light = mix(scene_data.ao_color.rgb, specular_light, ao_light_affect); + diffuse_light = mix(scene_data.ao_color.rgb, diffuse_light, ao_light_affect); + } +#endif //LOW_END_MODE + +#endif // AO_USED + + // base color remapping + diffuse_light *= 1.0 - metallic; // TODO: avoid all diffuse and ambient light calculations when metallic == 1 up to this point + ambient_light *= 1.0 - metallic; + +#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 + + // 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(vertex); + diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a); + specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a); + } + +#ifndef LOW_END_MODE + if (scene_data.volumetric_fog_enabled) { + vec4 fog = volumetric_fog_process(screen_uv, -vertex.z); + diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a); + specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a); + } +#endif // LOW_END_MODE + +#if defined(CUSTOM_FOG_USED) + diffuse_buffer.rgb = mix(diffuse_buffer.rgb, custom_fog.rgb, custom_fog.a); + specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), custom_fog.a); +#endif //CUSTOM_FOG_USED + +#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. + if (scene_data.fog_enabled) { + vec4 fog = fog_process(vertex); + frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + } +#ifndef LOW_END_MODE + if (scene_data.volumetric_fog_enabled) { + vec4 fog = volumetric_fog_process(screen_uv, -vertex.z); + frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + } +#endif + +#if defined(CUSTOM_FOG_USED) + frag_color.rgb = mix(frag_color.rgb, custom_fog.rgb, custom_fog.a); +#endif //CUSTOM_FOG_USED + +#endif //MODE_MULTIPLE_RENDER_TARGETS + +#endif //MODE_RENDER_DEPTH +} diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_inc.glsl new file mode 100644 index 0000000000..d18581c1b3 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/scene_forward_inc.glsl @@ -0,0 +1,332 @@ +#define M_PI 3.14159265359 +#define ROUGHNESS_MAX_LOD 5 + +#define MAX_GI_PROBES 8 + +#include "cluster_data_inc.glsl" + +layout(push_constant, binding = 0, std430) uniform DrawCall { + uint instance_index; + uint pad; //16 bits minimum size + vec2 bake_uv2_offset; //used for bake to uv2, ignored otherwise +} +draw_call; + +/* Set 0 Scene data that never changes, ever */ + +#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; + +#define SDFGI_MAX_CASCADES 8 + +layout(set = 0, binding = 3, std140) uniform SceneData { + mat4 projection_matrix; + mat4 inv_projection_matrix; + + mat4 camera_matrix; + mat4 inv_camera_matrix; + + vec2 viewport_size; + vec2 screen_pixel_size; + + //use vec4s because std140 doesnt 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]; + + uint directional_penumbra_shadow_samples; + uint directional_soft_shadow_samples; + uint penumbra_shadow_samples; + uint soft_shadow_samples; + + 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; + +#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_GIPROBE (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_MULTIMESH_STRIDE_SHIFT 16 +//3 bits of stride +#define INSTANCE_FLAGS_MULTIMESH_STRIDE_MASK 0x7 + +#define INSTANCE_FLAGS_SKELETON (1 << 19) + +struct InstanceData { + mat4 transform; + mat4 normal_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 = 0, binding = 4, std430) restrict readonly buffer Instances { + InstanceData data[]; +} +instances; + +layout(set = 0, binding = 5, std430) restrict readonly buffer Lights { + LightData data[]; +} +lights; + +layout(set = 0, binding = 6) buffer restrict readonly ReflectionProbeData { + ReflectionData data[]; +} +reflections; + +layout(set = 0, binding = 7, 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 = 10, std140) restrict readonly buffer Lightmaps { + Lightmap data[]; +} +lightmaps; + +layout(set = 0, binding = 11) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES]; + +struct LightmapCapture { + vec4 sh[9]; +}; + +layout(set = 0, binding = 12, std140) restrict readonly buffer LightmapCaptures { + LightmapCapture data[]; +} +lightmap_captures; + +layout(set = 0, binding = 13) uniform texture2D decal_atlas; +layout(set = 0, binding = 14) uniform texture2D decal_atlas_srgb; + +layout(set = 0, binding = 15, std430) restrict readonly buffer Decals { + DecalData data[]; +} +decals; + +layout(set = 0, binding = 16) uniform utexture3D cluster_texture; + +layout(set = 0, binding = 17, std430) restrict readonly buffer ClusterData { + uint indices[]; +} +cluster_data; + +layout(set = 0, binding = 18) uniform texture2D directional_shadow_atlas; + +layout(set = 0, binding = 19, std430) restrict readonly buffer GlobalVariableData { + vec4 data[]; +} +global_variables; + +#ifndef LOW_END_MODE + +struct SDFGIProbeCascadeData { + vec3 position; + float to_probe; + ivec3 probe_world_offset; + float to_cell; // 1/bounds * grid_size +}; + +layout(set = 0, binding = 20, 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; + + SDFGIProbeCascadeData cascades[SDFGI_MAX_CASCADES]; +} +sdfgi; + +#endif //LOW_END_MODE + +// decal atlas + +/* Set 1, Radiance */ + +#ifdef USE_RADIANCE_CUBEMAP_ARRAY + +layout(set = 1, binding = 0) uniform textureCubeArray radiance_cubemap; + +#else + +layout(set = 1, binding = 0) uniform textureCube radiance_cubemap; + +#endif + +/* Set 2, Reflection and Shadow Atlases (view dependent) */ + +layout(set = 1, binding = 1) uniform textureCubeArray reflection_atlas; + +layout(set = 1, binding = 2) uniform texture2D shadow_atlas; + +#ifndef LOW_END_MODE +layout(set = 1, binding = 3) uniform texture3D gi_probe_textures[MAX_GI_PROBES]; +#endif + +/* Set 3, Render Buffers */ + +#ifdef MODE_RENDER_SDF + +layout(r16ui, set = 1, binding = 4) uniform restrict writeonly uimage3D albedo_volume_grid; +layout(r32ui, set = 1, binding = 5) uniform restrict writeonly uimage3D emission_grid; +layout(r32ui, set = 1, binding = 6) uniform restrict writeonly uimage3D emission_aniso_grid; +layout(r32ui, set = 1, binding = 7) 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 = 4) uniform texture2D depth_buffer; +layout(set = 1, binding = 5) uniform texture2D color_buffer; + +#ifndef LOW_END_MODE + +layout(set = 1, binding = 6) uniform texture2D normal_roughness_buffer; +layout(set = 1, binding = 7) uniform texture2D ao_buffer; +layout(set = 1, binding = 8) uniform texture2D ambient_buffer; +layout(set = 1, binding = 9) uniform texture2D reflection_buffer; +layout(set = 1, binding = 10) uniform texture2DArray sdfgi_lightprobe_texture; +layout(set = 1, binding = 11) uniform texture3D sdfgi_occlusion_cascades; + +struct GIProbeData { + 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 = 12, std140) uniform GIProbes { + GIProbeData data[MAX_GI_PROBES]; +} +gi_probes; + +layout(set = 1, binding = 13) uniform texture3D volumetric_fog_texture; + +#endif // LOW_END_MODE + +#endif + +/* Set 4 Skeleton & Instancing (Multimesh) */ + +layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms { + vec4 data[]; +} +transforms; + +/* Set 5 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..06dc4b13de --- /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..a5afe74cb2 --- /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..218605a962 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/screen_space_reflection_scale.glsl @@ -0,0 +1,87 @@ +#[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; + vec3 normal; + + if (params.filtered) { + color = vec4(0.0); + depth = 0.0; + normal = vec3(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; + normal += texelFetch(source_normal, ofs, 0).xyz * 2.0 - 1.0; + + 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 = normalize(normal / 4.0) * 0.5 + 0.5; + } else { + color = texelFetch(source_ssr, ssC << 1, 0); + depth = texelFetch(source_depth, ssC << 1, 0).r; + normal = texelFetch(source_normal, ssC << 1, 0).xyz; + + 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, vec4(normal, 0.0)); +} 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..813ea29fa1 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/sdfgi_debug.glsl @@ -0,0 +1,275 @@ +#[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 + 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))); + + if (blend > 0.0) { + light = mix(light, hit_light, blend); + blend = 0.0; + } else { + light = hit_light; + + //process blend + float blend_from = (float(params.probe_axis_size - 1) / 2.0) - 2.5; + float blend_to = blend_from + 2.0; + + vec3 cam_pos = params.cam_transform[3].xyz - cascades.data[i].offset; + cam_pos *= cascades.data[i].to_cell; + + pos += ray_dir * min(advance, max_advance); + vec3 inner_pos = pos - cam_pos; + + inner_pos = inner_pos * float(params.probe_axis_size - 1) / params.grid_size.x; + + 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); + + pos /= cascades.data[i].to_cell; + pos += cascades.data[i].offset; + ray_pos = pos; + hit = false; //continue trace for blend + + continue; + } + } + + break; + } + + light = mix(light, vec3(0.0), blend); + +#else + + vec3 inv_dir = 1.0 / ray_dir; + + bool hit = false; + vec4 light_accum = vec4(0.0); + + float blend_size = (params.grid_size.x / float(params.probe_axis_size - 1)) * 0.5; + + float radius_sizes[MAX_CASCADES]; + for (uint i = 0; i < params.max_cascades; i++) { + radius_sizes[i] = (1.0 / cascades.data[i].to_cell) * (params.grid_size.x * 0.5 - blend_size); + } + + float max_distance = radius_sizes[params.max_cascades - 1]; + float advance = 0; + while (advance < max_distance) { + for (uint i = 0; i < params.max_cascades; i++) { + if (advance < radius_sizes[i]) { + vec3 pos = (ray_pos + ray_dir * advance) - cascades.data[i].offset; + pos *= cascades.data[i].to_cell * pos_to_uvw; + + float distance = texture(sampler3D(sdf_cascades[i], linear_sampler), pos).r * 255.0 - 1.0; + + vec4 hit_light = vec4(0.0); + if (distance < 1.0) { + hit_light.a = max(0.0, 1.0 - distance); + hit_light.rgb = texture(sampler3D(light_cascades[i], linear_sampler), pos).rgb; + hit_light.rgb *= hit_light.a; + } + + distance /= cascades.data[i].to_cell; + + if (i < (params.max_cascades - 1)) { + pos = (ray_pos + ray_dir * advance) - cascades.data[i + 1].offset; + pos *= cascades.data[i + 1].to_cell * pos_to_uvw; + + float distance2 = texture(sampler3D(sdf_cascades[i + 1], linear_sampler), pos).r * 255.0 - 1.0; + + vec4 hit_light2 = vec4(0.0); + if (distance2 < 1.0) { + hit_light2.a = max(0.0, 1.0 - distance2); + hit_light2.rgb = texture(sampler3D(light_cascades[i + 1], linear_sampler), pos).rgb; + hit_light2.rgb *= hit_light2.a; + } + + float prev_radius = i == 0 ? 0.0 : radius_sizes[i - 1]; + float blend = (advance - prev_radius) / (radius_sizes[i] - prev_radius); + + distance2 /= cascades.data[i + 1].to_cell; + + hit_light = mix(hit_light, hit_light2, blend); + distance = mix(distance, distance2, blend); + } + + light_accum += hit_light; + advance += distance; + break; + } + } + + if (light_accum.a > 0.98) { + break; + } + } + + light = light_accum.rgb / light_accum.a; + +#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..08da283dad --- /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..61e4bf5e18 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/sdfgi_direct_light.glsl @@ -0,0 +1,472 @@ +#[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 neigbours + 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 +}; + +#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 spot_angle; + float 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(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; + bool multibounce; + float y_mult; + uint pad; +} +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; +} + +void main() { + uint voxel_index = uint(gl_GlobalInvocationID.x); + + //used for skipping voxels every N frames + voxel_index = params.process_offset + voxel_index * params.process_increment; + + 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]; + + uint valid_aniso = (voxel_albedo >> 15) & 0x3F; + + { + 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; + } + } + + 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)); + + // 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 = pow(clamp(1.0 - length(rel_vec) / lights.data[i].radius, 0.0, 1.0), 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 = pow(clamp(1.0 - length(rel_vec) / lights.data[i].radius, 0.0, 1.0), lights.data[i].attenuation); + + float angle = acos(dot(normalize(rel_vec), -lights.data[i].direction)); + if (angle > lights.data[i].spot_angle) { + attenuation = 0.0; + } else { + float d = clamp(angle / lights.data[i].spot_angle, 0, 1); + attenuation *= pow(1.0 - d, lights.data[i].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; + } + } + } + } + + // Add indirect light + + if (params.multibounce) { + vec3 pos = (vec3(positioni) + vec3(0.5)) * float(params.probe_axis_size - 1) / params.grid_size; + ivec3 probe_base_pos = ivec3(pos); + + vec4 probe_accum[6] = vec4[](vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); + 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 = (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.005, dot(n, probe_dir)); + + 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; + vec4 indirect_light = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0); + + probe_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] += probe_accum[k].rgb * albedo / weight_accum[k]; + } + } + } + + // 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_fields.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_fields.glsl new file mode 100644 index 0000000000..69d8824d8a --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/sdfgi_fields.glsl @@ -0,0 +1,182 @@ +/* clang-format off */ +[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = OCT_RES, local_size_y = OCT_RES, local_size_z = 1) in; + +/* clang-format on */ + +#define MAX_CASCADES 8 + +layout(rgba16f, set = 0, binding = 1) uniform restrict image2DArray irradiance_texture; +layout(rg16f, set = 0, binding = 2) uniform restrict image2DArray depth_texture; + +layout(rgba32ui, set = 0, binding = 3) uniform restrict uimage2DArray irradiance_history_texture; +layout(rg32ui, set = 0, binding = 4) uniform restrict uimage2DArray depth_history_texture; + +struct CascadeData { + vec3 offset; //offset of (0,0,0) in world coordinates + float to_cell; // 1/bounds * grid_size +}; + +layout(set = 0, binding = 5, std140) uniform Cascades { + CascadeData data[MAX_CASCADES]; +} +cascades; + +#define DEPTH_HISTORY_BITS 24 +#define IRRADIANCE_HISTORY_BITS 16 + +layout(push_constant, binding = 0, std430) uniform Params { + vec3 grid_size; + uint max_cascades; + + uint probe_axis_size; + uint cascade; + uint history_size; + uint pad0; + + ivec3 scroll; //scroll in probes + uint pad1; +} +params; + +void main() { + ivec2 local = ivec2(gl_LocalInvocationID.xy); + ivec2 probe = ivec2(gl_WorkGroupID.xy); + + ivec3 probe_cell; + probe_cell.x = probe.x % int(params.probe_axis_size); + probe_cell.y = probe.y; + probe_cell.z = probe.x / int(params.probe_axis_size); + +#ifdef MODE_SCROLL_BEGIN + + ivec3 read_cell = probe_cell - params.scroll; + + uint src_layer = (params.history_size + 1) * params.cascade; + uint dst_layer = (params.history_size + 1) * params.max_cascades; + + for (uint i = 0; i <= params.history_size; i++) { + ivec3 write_pos = ivec3(probe * OCT_RES + local, int(i)); + + if (any(lessThan(read_pos, ivec3(0))) || any(greaterThanEqual(read_pos, ivec3(params.probe_axis_size)))) { + // nowhere to read from for scrolling, try finding the value from upper probes + +#ifdef MODE_IRRADIANCE + imageStore(irradiance_history_texture, write_pos, uvec4(0)); +#endif +#ifdef MODE_DEPTH + imageStore(depth_history_texture, write_pos, uvec4(0)); +#endif + } else { + ivec3 read_pos; + read_pos.xy = read_cell.xy; + read_pos.x += read_cell.z * params.probe_axis_size; + read_pos.xy = read_pos.xy * OCT_RES + local; + read_pos.z = int(i); + +#ifdef MODE_IRRADIANCE + uvec4 value = imageLoad(irradiance_history_texture, read_pos); + imageStore(irradiance_history_texture, write_pos, value); +#endif +#ifdef MODE_DEPTH + uvec2 value = imageLoad(depth_history_texture, read_pos); + imageStore(depth_history_texture, write_pos, value); +#endif + } + } + +#endif // MODE_SCROLL_BEGIN + +#ifdef MODE_SCROLL_END + + uint src_layer = (params.history_size + 1) * params.max_cascades; + uint dst_layer = (params.history_size + 1) * params.cascade; + + for (uint i = 0; i <= params.history_size; i++) { + ivec3 pos = ivec3(probe * OCT_RES + local, int(i)); + +#ifdef MODE_IRRADIANCE + uvec4 value = imageLoad(irradiance_history_texture, read_pos); + imageStore(irradiance_history_texture, write_pos, value); +#endif +#ifdef MODE_DEPTH + uvec2 value = imageLoad(depth_history_texture, read_pos); + imageStore(depth_history_texture, write_pos, value); +#endif + } + +#endif //MODE_SCROLL_END + +#ifdef MODE_STORE + + uint src_layer = (params.history_size + 1) * params.cascade + params.history_size; + ivec3 read_pos = ivec3(probe * OCT_RES + local, int(src_layer)); + + ivec3 write_pos = ivec3(probe * (OCT_RES + 2) + ivec2(1), int(params.cascade)); + + ivec3 copy_to[4] = ivec3[](write_pos, ivec3(-2, -2, -2), ivec3(-2, -2, -2), ivec3(-2, -2, -2)); + +#ifdef MODE_IRRADIANCE + uvec4 average = imageLoad(irradiance_history_texture, read_pos); + vec4 light_accum = vec4(average / params.history_size) / float(1 << IRRADIANCE_HISTORY_BITS); + +#endif +#ifdef MODE_DEPTH + uvec2 value = imageLoad(depth_history_texture, read_pos); + vec2 depth_accum = vec4(average / params.history_size) / float(1 << IRRADIANCE_HISTORY_BITS); + + float probe_cell_size = float(params.grid_size / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell; + float max_depth = length(params.grid_size / cascades.data[params.max_cascades - 1].to_cell); + max_depth /= probe_cell_size; + + depth_value = (vec2(average / params.history_size) / float(1 << DEPTH_HISTORY_BITS)) * vec2(max_depth, max_depth * max_depth); + +#endif + + /* Fill the border if required */ + + if (local == ivec2(0, 0)) { + copy_to[1] = texture_pos + ivec3(OCT_RES - 1, -1, 0); + copy_to[2] = texture_pos + ivec3(-1, OCT_RES - 1, 0); + copy_to[3] = texture_pos + ivec3(OCT_RES, OCT_RES, 0); + } else if (local == ivec2(OCT_RES - 1, 0)) { + copy_to[1] = texture_pos + ivec3(0, -1, 0); + copy_to[2] = texture_pos + ivec3(OCT_RES, OCT_RES - 1, 0); + copy_to[3] = texture_pos + ivec3(-1, OCT_RES, 0); + } else if (local == ivec2(0, OCT_RES - 1)) { + copy_to[1] = texture_pos + ivec3(-1, 0, 0); + copy_to[2] = texture_pos + ivec3(OCT_RES - 1, OCT_RES, 0); + copy_to[3] = texture_pos + ivec3(OCT_RES, -1, 0); + } else if (local == ivec2(OCT_RES - 1, OCT_RES - 1)) { + copy_to[1] = texture_pos + ivec3(0, OCT_RES, 0); + copy_to[2] = texture_pos + ivec3(OCT_RES, 0, 0); + copy_to[3] = texture_pos + ivec3(-1, -1, 0); + } else if (local.y == 0) { + copy_to[1] = texture_pos + ivec3(OCT_RES - local.x - 1, local.y - 1, 0); + } else if (local.x == 0) { + copy_to[1] = texture_pos + ivec3(local.x - 1, OCT_RES - local.y - 1, 0); + } else if (local.y == OCT_RES - 1) { + copy_to[1] = texture_pos + ivec3(OCT_RES - local.x - 1, local.y + 1, 0); + } else if (local.x == OCT_RES - 1) { + copy_to[1] = texture_pos + ivec3(local.x + 1, OCT_RES - local.y - 1, 0); + } + + for (int i = 0; i < 4; i++) { + if (copy_to[i] == ivec3(-2, -2, -2)) { + continue; + } +#ifdef MODE_IRRADIANCE + imageStore(irradiance_texture, copy_to[i], light_accum); +#endif +#ifdef MODE_DEPTH + imageStore(depth_texture, copy_to[i], vec4(depth_value, 0.0, 0.0)); +#endif + } + +#endif // MODE_STORE +} 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..d516ab22c3 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/sdfgi_integrate.glsl @@ -0,0 +1,617 @@ +#[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; + +layout(set = 1, binding = 0) uniform textureCube sky_irradiance; + +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); +} + +void main() { + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(pos, params.image_size))) { //too large, do nothing + return; + } + +#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; + + vec4 probe_sh_accum[SH_SIZE] = vec4[]( + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0) +#if (SH_SIZE == 16) + , + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0), + vec4(0.0) +#endif + ); + + // 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; + vec3 hit_normal; + vec3 hit_light; + vec3 hit_aniso0; + vec3 hit_aniso1; + + 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; + + 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; + + vec3 uvw; + + 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.001) { + //consider hit + hit = true; + break; + } + + advance += distance; + } + + if (hit) { + const float EPSILON = 0.001; + hit_normal = normalize(vec3( + texture(sampler3D(sdf_cascades[j], linear_sampler), uvw + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_cascades[j], linear_sampler), uvw - vec3(EPSILON, 0.0, 0.0)).r, + texture(sampler3D(sdf_cascades[j], linear_sampler), uvw + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_cascades[j], linear_sampler), uvw - vec3(0.0, EPSILON, 0.0)).r, + texture(sampler3D(sdf_cascades[j], linear_sampler), uvw + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_cascades[j], linear_sampler), uvw - vec3(0.0, 0.0, EPSILON)).r)); + + hit_light = texture(sampler3D(light_cascades[j], linear_sampler), uvw).rgb; + vec4 aniso0 = texture(sampler3D(aniso0_cascades[j], linear_sampler), uvw); + hit_aniso0 = aniso0.rgb; + hit_aniso1 = vec3(aniso0.a, texture(sampler3D(aniso1_cascades[j], linear_sampler), uvw).rg); + + 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) { + //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) { + light.rgb = textureLod(samplerCube(sky_irradiance, linear_sampler_mipmaps), ray_dir, 2.0).rgb; //use second mipmap because we dont usually throw a lot of rays, so this compensates + 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; + float c[SH_SIZE] = float[]( + + 0.282095, //l0 + 0.488603 * ray_dir.y, //l1n1 + 0.488603 * ray_dir.z, //l1n0 + 0.488603 * ray_dir.x, //l1p1 + 1.092548 * ray_dir.x * ray_dir.y, //l2n2 + 1.092548 * ray_dir.y * ray_dir.z, //l2n1 + 0.315392 * (3.0 * ray_dir2.z - 1.0), //l20 + 1.092548 * ray_dir.x * ray_dir.z, //l2p1 + 0.546274 * (ray_dir2.x - ray_dir2.y) //l2p2 +#if (SH_SIZE == 16) + , + 0.590043 * ray_dir.y * (3.0f * ray_dir2.x - ray_dir2.y), + 2.890611 * ray_dir.y * ray_dir.x * ray_dir.z, + 0.646360 * ray_dir.y * (-1.0f + 5.0f * ray_dir2.z), + 0.373176 * (5.0f * ray_dir2.z * ray_dir.z - 3.0f * ray_dir.z), + 0.457045 * ray_dir.x * (-1.0f + 5.0f * ray_dir2.z), + 1.445305 * (ray_dir2.x - ray_dir2.y) * ray_dir.z, + 0.590043 * ray_dir.x * (ray_dir2.x - 3.0f * ray_dir2.y) + +#endif + ); + + for (uint j = 0; j < SH_SIZE; j++) { + probe_sh_accum[j] += light * c[j]; + } + } + + 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 = probe_sh_accum[i] * 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; + + //fill the spherical harmonic + vec4 sh[SH_SIZE]; + + 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); + + sh[i] = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS); + } + + //compute the octahedral normal for this texel + vec3 normal = octahedron_encode(vec2(local_pos) / float(OCT_SIZE)); + /* + // read the spherical harmonic + 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; + vec4 light = (c1 * sh[8] * (normal.x * normal.x - normal.y * normal.y) + + c3 * sh[6] * normal.z * normal.z + + c4 * sh[0] - + c5 * sh[6] + + 2.0 * c1 * sh[4] * normal.x * normal.y + + 2.0 * c1 * sh[7] * normal.x * normal.z + + 2.0 * c1 * sh[5] * normal.y * normal.z + + 2.0 * c2 * sh[3] * normal.x + + 2.0 * c2 * sh[1] * normal.y + + 2.0 * c2 * sh[2] * normal.z); +*/ + 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++) { + vec3 m = sh[i].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 probe_cell_size = float(params.grid_size.x / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell; + vec3 probe_pos = cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size; + + //to parent local coords + probe_pos -= cascades.data[params.cascade + 1].offset; + probe_pos *= cascades.data[params.cascade + 1].to_cell; + probe_pos = probe_pos * float(params.probe_axis_size - 1) / float(params.grid_size.x); + + 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 { + // clear and let it re-raytrace, only for the last cascade, which happens very un-often + //scroll + for (uint j = 0; j < params.history_size; j++) { + for (int i = 0; i < SH_SIZE; i++) { + // copy from history texture + ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j)); + imageStore(lightprobe_history_scroll_texture, dst_pos, ivec4(0)); + } + } + + for (int i = 0; i < SH_SIZE; i++) { + // copy from average texture + ivec2 dst_pos = ivec2(pos.x, pos.y * SH_SIZE + i); + imageStore(lightprobe_average_scroll_texture, dst_pos, ivec4(0)); + } + } + +#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..916c60ac89 --- /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 neigbours + 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 neigbours + 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, dont 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/shadow_reduce.glsl b/servers/rendering/renderer_rd/shaders/shadow_reduce.glsl new file mode 100644 index 0000000000..29443ae7db --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/shadow_reduce.glsl @@ -0,0 +1,105 @@ +#[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_REDUCE + +shared float tmp_data[BLOCK_SIZE * BLOCK_SIZE]; +const uint swizzle_table[BLOCK_SIZE] = uint[](0, 4, 2, 6, 1, 5, 3, 7); +const uint unswizzle_table[BLOCK_SIZE] = uint[](0, 0, 0, 1, 0, 2, 1, 3); + +#endif + +layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D source_depth; +layout(r32f, set = 0, binding = 1) uniform restrict writeonly image2D dst_depth; + +layout(push_constant, binding = 1, std430) uniform Params { + ivec2 source_size; + ivec2 source_offset; + uint min_size; + uint gaussian_kernel_version; + ivec2 filter_dir; +} +params; + +void main() { +#ifdef MODE_REDUCE + + uvec2 pos = gl_LocalInvocationID.xy; + + ivec2 image_offset = params.source_offset; + ivec2 image_pos = image_offset + ivec2(gl_GlobalInvocationID.xy); + uint dst_t = swizzle_table[pos.y] * BLOCK_SIZE + swizzle_table[pos.x]; + tmp_data[dst_t] = imageLoad(source_depth, min(image_pos, params.source_size - ivec2(1))).r; + ivec2 image_size = params.source_size; + + uint t = pos.y * BLOCK_SIZE + pos.x; + + //neighbours + uint size = BLOCK_SIZE; + + do { + groupMemoryBarrier(); + barrier(); + + size >>= 1; + image_size >>= 1; + image_offset >>= 1; + + if (all(lessThan(pos, uvec2(size)))) { + uint nx = t + size; + uint ny = t + (BLOCK_SIZE * size); + uint nxy = ny + size; + + tmp_data[t] += tmp_data[nx]; + tmp_data[t] += tmp_data[ny]; + tmp_data[t] += tmp_data[nxy]; + tmp_data[t] /= 4.0; + } + + } while (size > params.min_size); + + if (all(lessThan(pos, uvec2(size)))) { + image_pos = ivec2(unswizzle_table[size + pos.x], unswizzle_table[size + pos.y]); + image_pos += image_offset + ivec2(gl_WorkGroupID.xy) * int(size); + + image_size = max(ivec2(1), image_size); //in case image size became 0 + + if (all(lessThan(image_pos, uvec2(image_size)))) { + imageStore(dst_depth, image_pos, vec4(tmp_data[t])); + } + } +#endif + +#ifdef MODE_FILTER + + ivec2 image_pos = params.source_offset + ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(image_pos, params.source_size))) { + return; + } + + ivec2 clamp_min = ivec2(params.source_offset); + ivec2 clamp_max = ivec2(params.source_size) - 1; + + //gaussian kernel, size 9, sigma 4 + const int kernel_size = 9; + const float gaussian_kernel[kernel_size * 3] = float[]( + 0.000229, 0.005977, 0.060598, 0.241732, 0.382928, 0.241732, 0.060598, 0.005977, 0.000229, + 0.028532, 0.067234, 0.124009, 0.179044, 0.20236, 0.179044, 0.124009, 0.067234, 0.028532, + 0.081812, 0.101701, 0.118804, 0.130417, 0.134535, 0.130417, 0.118804, 0.101701, 0.081812); + float accum = 0.0; + for (int i = 0; i < kernel_size; i++) { + ivec2 ofs = clamp(image_pos + params.filter_dir * (i - kernel_size / 2), clamp_min, clamp_max); + accum += imageLoad(source_depth, ofs).r * gaussian_kernel[params.gaussian_kernel_version + i]; + } + + imageStore(dst_depth, image_pos, vec4(accum)); + +#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..b19f5a9ad3 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/skeleton.glsl @@ -0,0 +1,199 @@ +#[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])); +#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 (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..6c985e1f5c --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/sky.glsl @@ -0,0 +1,250 @@ +#[vertex] + +#version 450 + +VERSION_DEFINES + +layout(location = 0) out vec2 uv_interp; + +layout(push_constant, binding = 1, std430) uniform Params { + mat3 orientation; + vec4 proj; + 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 + +#define M_PI 3.14159265359 + +layout(location = 0) in vec2 uv_interp; + +layout(push_constant, binding = 1, std430) uniform Params { + mat3 orientation; + vec4 proj; + vec4 position_multiplier; + float time; //TODO consider adding vec2 screen res, and float radiance size +} +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 USE_MATERIAL_UNIFORMS +layout(set = 1, binding = 0, std140) uniform MaterialUniforms{ + /* clang-format off */ + +MATERIAL_UNIFORMS + + /* clang-format on */ +} 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 + +/* clang-format off */ + +FRAGMENT_SHADER_GLOBALS + +/* clang-format on */ + +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.proj.x)) / params.proj.y; + cube_normal.y = -(cube_normal.z * (-uv_interp.y - params.proj.z)) / params.proj.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 + +// unused, just here to make our compiler happy, make sure we don't execute any light code the user adds in.. +#ifndef REALLYINCLUDETHIS + { + /* clang-format off */ + +LIGHT_SHADER_CODE + + /* clang-format on */ + } +#endif + { + /* clang-format off */ + +FRAGMENT_SHADER_CODE + + /* clang-format on */ + } + + 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..e5ebb9c64b --- /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..0b8f406213 --- /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..346338181a --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/ssao.glsl @@ -0,0 +1,249 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +#define TWO_PI 6.283185307179586476925286766559 + +#ifdef SSAO_QUALITY_HIGH +#define NUM_SAMPLES (20) +#endif + +#ifdef SSAO_QUALITY_ULTRA +#define NUM_SAMPLES (48) +#endif + +#ifdef SSAO_QUALITY_LOW +#define NUM_SAMPLES (8) +#endif + +#if !defined(SSAO_QUALITY_LOW) && !defined(SSAO_QUALITY_HIGH) && !defined(SSAO_QUALITY_ULTRA) +#define NUM_SAMPLES (12) +#endif + +// If using depth mip levels, the log of the maximum pixel offset before we need to switch to a lower +// miplevel to maintain reasonable spatial locality in the cache +// If this number is too small (< 3), too many taps will land in the same pixel, and we'll get bad variance that manifests as flashing. +// If it is too high (> 5), we'll get bad performance because we're not using the MIP levels effectively +#define LOG_MAX_OFFSET (3) + +// This must be less than or equal to the MAX_MIP_LEVEL defined in SSAO.cpp +#define MAX_MIP_LEVEL (4) + +// This is the number of turns around the circle that the spiral pattern makes. This should be prime to prevent +// taps from lining up. This particular choice was tuned for NUM_SAMPLES == 9 + +const int ROTATIONS[] = int[]( + 1, 1, 2, 3, 2, 5, 2, 3, 2, + 3, 3, 5, 5, 3, 4, 7, 5, 5, 7, + 9, 8, 5, 5, 7, 7, 7, 8, 5, 8, + 11, 12, 7, 10, 13, 8, 11, 8, 7, 14, + 11, 11, 13, 12, 13, 19, 17, 13, 11, 18, + 19, 11, 11, 14, 17, 21, 15, 16, 17, 18, + 13, 17, 11, 17, 19, 18, 25, 18, 19, 19, + 29, 21, 19, 27, 31, 29, 21, 18, 17, 29, + 31, 31, 23, 18, 25, 26, 25, 23, 19, 34, + 19, 27, 21, 25, 39, 29, 17, 21, 27); + +//#define NUM_SPIRAL_TURNS (7) +const int NUM_SPIRAL_TURNS = ROTATIONS[NUM_SAMPLES - 1]; + +layout(set = 0, binding = 0) uniform sampler2D source_depth_mipmaps; +layout(r8, set = 1, binding = 0) uniform restrict writeonly image2D dest_image; + +#ifndef USE_HALF_SIZE +layout(set = 2, binding = 0) uniform sampler2D source_depth; +#endif + +layout(set = 3, binding = 0) uniform sampler2D source_normal; + +layout(push_constant, binding = 1, std430) uniform Params { + ivec2 screen_size; + float z_far; + float z_near; + + bool orthogonal; + float intensity_div_r6; + float radius; + float bias; + + vec4 proj_info; + vec2 pixel_size; + float proj_scale; + uint pad; +} +params; + +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); + } +} + +vec3 getPosition(ivec2 ssP) { + vec3 P; +#ifdef USE_HALF_SIZE + P.z = texelFetch(source_depth_mipmaps, ssP, 0).r; + P.z = -P.z; +#else + P.z = texelFetch(source_depth, ssP, 0).r; + + P.z = P.z * 2.0 - 1.0; + if (params.orthogonal) { + P.z = ((P.z + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; + } else { + P.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - P.z * (params.z_far - params.z_near)); + } + P.z = -P.z; +#endif + // Offset to pixel center + P = reconstructCSPosition(vec2(ssP) + vec2(0.5), P.z); + return P; +} + +/** Returns a unit vector and a screen-space radius for the tap on a unit disk (the caller should scale by the actual disk radius) */ +vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR) { + // Radius relative to ssR + float alpha = (float(sampleNumber) + 0.5) * (1.0 / float(NUM_SAMPLES)); + float angle = alpha * (float(NUM_SPIRAL_TURNS) * 6.28) + spinAngle; + + ssR = alpha; + return vec2(cos(angle), sin(angle)); +} + +/** Read the camera-space position of the point at screen-space pixel ssP + unitOffset * ssR. Assumes length(unitOffset) == 1 */ +vec3 getOffsetPosition(ivec2 ssP, float ssR) { + // Derivation: + // mipLevel = floor(log(ssR / MAX_OFFSET)); + + int mipLevel = clamp(int(floor(log2(ssR))) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL); + + vec3 P; + + // We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map. + // Manually clamp to the texture size because texelFetch bypasses the texture unit + ivec2 mipP = clamp(ssP >> mipLevel, ivec2(0), (params.screen_size >> mipLevel) - ivec2(1)); + +#ifdef USE_HALF_SIZE + P.z = texelFetch(source_depth_mipmaps, mipP, mipLevel).r; + P.z = -P.z; +#else + if (mipLevel < 1) { + //read from depth buffer + P.z = texelFetch(source_depth, mipP, 0).r; + P.z = P.z * 2.0 - 1.0; + if (params.orthogonal) { + P.z = ((P.z + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; + } else { + P.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - P.z * (params.z_far - params.z_near)); + } + P.z = -P.z; + + } else { + //read from mipmaps + P.z = texelFetch(source_depth_mipmaps, mipP, mipLevel - 1).r; + P.z = -P.z; + } +#endif + + // Offset to pixel center + P = reconstructCSPosition(vec2(ssP) + vec2(0.5), P.z); + + return P; +} + +/** Compute the occlusion due to sample with index \a i about the pixel at \a ssC that corresponds + to camera-space point \a C with unit normal \a n_C, using maximum screen-space sampling radius \a ssDiskRadius + + Note that units of H() in the HPG12 paper are meters, not + unitless. The whole falloff/sampling function is therefore + unitless. In this implementation, we factor out (9 / radius). + + Four versions of the falloff function are implemented below +*/ +float sampleAO(in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssDiskRadius, in float p_radius, in int tapIndex, in float randomPatternRotationAngle) { + // Offset on the unit disk, spun for this pixel + float ssR; + vec2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR); + ssR *= ssDiskRadius; + + ivec2 ssP = ivec2(ssR * unitOffset) + ssC; + + if (any(lessThan(ssP, ivec2(0))) || any(greaterThanEqual(ssP, params.screen_size))) { + return 0.0; + } + + // The occluding point in camera space + vec3 Q = getOffsetPosition(ssP, ssR); + + vec3 v = Q - C; + + float vv = dot(v, v); + float vn = dot(v, n_C); + + const float epsilon = 0.01; + float radius2 = p_radius * p_radius; + + // A: From the HPG12 paper + // Note large epsilon to avoid overdarkening within cracks + //return float(vv < radius2) * max((vn - bias) / (epsilon + vv), 0.0) * radius2 * 0.6; + + // B: Smoother transition to zero (lowers contrast, smoothing out corners). [Recommended] + float f = max(radius2 - vv, 0.0); + return f * f * f * max((vn - params.bias) / (epsilon + vv), 0.0); + + // C: Medium contrast (which looks better at high radii), no division. Note that the + // contribution still falls off with radius^2, but we've adjusted the rate in a way that is + // more computationally efficient and happens to be aesthetically pleasing. + // return 4.0 * max(1.0 - vv * invRadius2, 0.0) * max(vn - bias, 0.0); + + // D: Low contrast, no division operation + // return 2.0 * float(vv < radius * radius) * max(vn - bias, 0.0); +} + +void main() { + // Pixel being shaded + ivec2 ssC = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing + return; + } + + // World space point being shaded + vec3 C = getPosition(ssC); + +#ifdef USE_HALF_SIZE + vec3 n_C = texelFetch(source_normal, ssC << 1, 0).xyz * 2.0 - 1.0; +#else + vec3 n_C = texelFetch(source_normal, ssC, 0).xyz * 2.0 - 1.0; +#endif + n_C = normalize(n_C); + n_C.y = -n_C.y; //because this code reads flipped + + // Hash function used in the HPG12 AlchemyAO paper + float randomPatternRotationAngle = mod(float((3 * ssC.x ^ ssC.y + ssC.x * ssC.y) * 10), TWO_PI); + + // Reconstruct normals from positions. These will lead to 1-pixel black lines + // at depth discontinuities, however the blur will wipe those out so they are not visible + // in the final image. + + // Choose the screen-space sample radius + // proportional to the projected area of the sphere + + float ssDiskRadius = -params.proj_scale * params.radius; + if (!params.orthogonal) { + ssDiskRadius = -params.proj_scale * params.radius / C.z; + } + float sum = 0.0; + for (int i = 0; i < NUM_SAMPLES; ++i) { + sum += sampleAO(ssC, C, n_C, ssDiskRadius, params.radius, i, randomPatternRotationAngle); + } + + float A = max(0.0, 1.0 - sum * params.intensity_div_r6 * (5.0 / float(NUM_SAMPLES))); + + imageStore(dest_image, ssC, vec4(A)); +} 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..3e63e3cb59 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/ssao_blur.glsl @@ -0,0 +1,153 @@ +#[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(set = 1, binding = 0) uniform sampler2D source_depth; +#ifdef MODE_UPSCALE +layout(set = 2, binding = 0) uniform sampler2D source_depth_mipmaps; +#endif + +layout(r8, set = 3, binding = 0) uniform restrict writeonly image2D dest_image; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Tunable Parameters: + +layout(push_constant, binding = 1, std430) uniform Params { + float edge_sharpness; /** Increase to make depth edges crisper. Decrease to reduce flicker. */ + int filter_scale; + float z_far; + float z_near; + bool orthogonal; + uint pad0; + uint pad1; + uint pad2; + ivec2 axis; /** (1, 0) or (0, 1) */ + ivec2 screen_size; +} +params; + +/** Filter radius in pixels. This will be multiplied by SCALE. */ +#define R (4) + +////////////////////////////////////////////////////////////////////////////////////////////// + +// Gaussian coefficients +const float gaussian[R + 1] = + //float[](0.356642, 0.239400, 0.072410, 0.009869); + //float[](0.398943, 0.241971, 0.053991, 0.004432, 0.000134); // stddev = 1.0 + float[](0.153170, 0.144893, 0.122649, 0.092902, 0.062970); // stddev = 2.0 +//float[](0.111220, 0.107798, 0.098151, 0.083953, 0.067458, 0.050920, 0.036108); // stddev = 3.0 + +void main() { + // Pixel being shaded + ivec2 ssC = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(ssC, params.screen_size))) { //too large, do nothing + return; + } + +#ifdef MODE_UPSCALE + + //closest one should be the same pixel, but check nearby just in case + float depth = texelFetch(source_depth, ssC, 0).r; + + depth = depth * 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)); + } + + vec2 pixel_size = 1.0 / vec2(params.screen_size); + vec2 closest_uv = vec2(ssC) * pixel_size + pixel_size * 0.5; + vec2 from_uv = closest_uv; + vec2 ps2 = pixel_size; // * 2.0; + + float closest_depth = abs(textureLod(source_depth_mipmaps, closest_uv, 0.0).r - depth); + + vec2 offsets[4] = vec2[](vec2(ps2.x, 0), vec2(-ps2.x, 0), vec2(0, ps2.y), vec2(0, -ps2.y)); + for (int i = 0; i < 4; i++) { + vec2 neighbour = from_uv + offsets[i]; + float neighbour_depth = abs(textureLod(source_depth_mipmaps, neighbour, 0.0).r - depth); + if (neighbour_depth < closest_depth) { + closest_uv = neighbour; + closest_depth = neighbour_depth; + } + } + + float visibility = textureLod(source_ssao, closest_uv, 0.0).r; + imageStore(dest_image, ssC, vec4(visibility)); +#else + + float depth = texelFetch(source_depth, ssC, 0).r; + +#ifdef MODE_FULL_SIZE + depth = depth * 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)); + } + +#endif + float depth_divide = 1.0 / params.z_far; + + //depth *= depth_divide; + + /* + if (depth > params.z_far * 0.999) { + discard; //skybox + } + */ + + float sum = texelFetch(source_ssao, ssC, 0).r; + + // Base weight for depth falloff. Increase this for more blurriness, + // decrease it for better edge discrimination + float BASE = gaussian[0]; + float totalWeight = BASE; + sum *= totalWeight; + + ivec2 clamp_limit = params.screen_size - ivec2(1); + + for (int r = -R; r <= R; ++r) { + // We already handled the zero case above. This loop should be unrolled and the static branch optimized out, + // so the IF statement has no runtime cost + if (r != 0) { + ivec2 ppos = ssC + params.axis * (r * params.filter_scale); + float value = texelFetch(source_ssao, clamp(ppos, ivec2(0), clamp_limit), 0).r; + ivec2 rpos = clamp(ppos, ivec2(0), clamp_limit); + + float temp_depth = texelFetch(source_depth, rpos, 0).r; +#ifdef MODE_FULL_SIZE + temp_depth = temp_depth * 2.0 - 1.0; + if (params.orthogonal) { + temp_depth = ((temp_depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; + } else { + temp_depth = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near - temp_depth * (params.z_far - params.z_near)); + } + //temp_depth *= depth_divide; +#endif + // spatial domain: offset gaussian tap + float weight = 0.3 + gaussian[abs(r)]; + //weight *= max(0.0, dot(temp_normal, normal)); + + // range domain (the "bilateral" weight). As depth difference increases, decrease weight. + weight *= max(0.0, 1.0 - params.edge_sharpness * abs(temp_depth - depth)); + + sum += value * weight; + totalWeight += weight; + } + } + + const float epsilon = 0.0001; + float visibility = sum / (totalWeight + epsilon); + + imageStore(dest_image, ssC, vec4(visibility)); +#endif +} diff --git a/servers/rendering/renderer_rd/shaders/ssao_minify.glsl b/servers/rendering/renderer_rd/shaders/ssao_minify.glsl new file mode 100644 index 0000000000..263fca386f --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/ssao_minify.glsl @@ -0,0 +1,45 @@ +#[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; + ivec2 source_size; + bool orthogonal; + uint pad; +} +params; + +#ifdef MINIFY_START +layout(set = 0, binding = 0) uniform sampler2D source_texture; +#else +layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D source_image; +#endif +layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D dest_image; + +void main() { + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + + if (any(greaterThan(pos, params.source_size >> 1))) { //too large, do nothing + return; + } + +#ifdef MINIFY_START + float depth = texelFetch(source_texture, pos << 1, 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)); + } +#else + float depth = imageLoad(source_image, pos << 1).r; +#endif + + imageStore(dest_image, pos, vec4(depth)); +} 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..88a953562f --- /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..7de91fd541 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/tonemap.glsl @@ -0,0 +1,386 @@ +#[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 source_color; +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) { + // Ensure color values are positive. + // They can be negative in the case of negative lights, which leads to undesired behavior. + color = max(vec3(0.0), color); + + 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 { //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 + +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; + + 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; + 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; + + 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); + + float lumaB = dot(rgbB, luma); + if ((lumaB < lumaMin) || (lumaB > lumaMax)) { + return rgbA; + } else { + return rgbB; + } +} + +// 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() { + vec3 color = textureLod(source_color, uv_interp, 0.0f).rgb; + + // Exposure + + float exposure = params.exposure; + + if (params.use_auto_exposure) { + exposure *= 1.0 / (texelFetch(source_auto_exposure, ivec2(0, 0), 0).r / params.auto_exposure_grey); + } + + color *= exposure; + + // Early Tonemap & SRGB Conversion + + 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); + } + 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); + } + color = apply_tonemapping(color, params.white); + + color = linear_to_srgb(color); // regular linear -> SRGB conversion + + // 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); + } + + // 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..13b162f0c9 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/volumetric_fog.glsl @@ -0,0 +1,530 @@ +#[compute] + +#version 450 + +VERSION_DEFINES + +#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" + +#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 Lights { + LightData data[]; +} +lights; + +layout(set = 0, binding = 4, std140) uniform DirectionalLights { + DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; +} +directional_lights; + +layout(set = 0, binding = 5) uniform utexture3D cluster_texture; + +layout(set = 0, binding = 6, std430) restrict readonly buffer ClusterData { + uint indices[]; +} +cluster_data; + +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_GI_PROBES 8 + +struct GIProbeData { + 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 GIProbes { + GIProbeData data[MAX_GI_PROBES]; +} +gi_probes; + +layout(set = 0, binding = 12) uniform texture3D gi_probe_textures[MAX_GI_PROBES]; + +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 SDFGIProbeCascadeData { + 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; + + SDFGIProbeCascadeData 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(push_constant, binding = 0, std430) 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_gi_probes; + uint pad; + + mat3x4 cam_rotation; +} +params; + +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)); +} + +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 + 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; + + 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 + + vec3 cluster_pos; + cluster_pos.xy = fog_unit_pos.xy; + cluster_pos.z = clamp((abs(view_pos.z) - params.z_near) / (params.z_far - params.z_near), 0.0, 1.0); + + uvec4 cluster_cell = texture(usampler3D(cluster_texture, linear_sampler), cluster_pos); + + uint omni_light_count = cluster_cell.x >> CLUSTER_COUNTER_SHIFT; + uint omni_light_pointer = cluster_cell.x & CLUSTER_POINTER_MASK; + + for (uint i = 0; i < omni_light_count; i++) { + uint light_index = cluster_data.indices[omni_light_pointer + i]; + + vec3 light_pos = lights.data[i].position; + float d = distance(lights.data[i].position, view_pos) * lights.data[i].inv_radius; + vec3 shadow_attenuation = vec3(1.0); + + if (d < 1.0) { + vec2 attenuation_energy = unpackHalf2x16(lights.data[i].attenuation_energy); + vec4 color_specular = unpackUnorm4x8(lights.data[i].color_specular); + + float attenuation = pow(max(1.0 - d, 0.0), attenuation_energy.x); + + vec3 light = attenuation_energy.y * color_specular.rgb / M_PI; + + vec4 shadow_color_enabled = unpackUnorm4x8(lights.data[i].shadow_color_enabled); + + if (shadow_color_enabled.a > 0.5) { + //has shadow + vec4 v = vec4(view_pos, 1.0); + + vec4 splane = (lights.data[i].shadow_matrix * v); + float shadow_len = length(splane.xyz); //need to remember shadow len from here + + splane.xyz = normalize(splane.xyz); + vec4 clamp_rect = lights.data[i].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 * lights.data[i].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; + float shadow = exp(min(0.0, (depth - splane.z)) / lights.data[i].inv_radius * lights.data[i].shadow_volumetric_fog_fade); + + shadow_attenuation = mix(shadow_color_enabled.rgb, vec3(1.0), shadow); + } + total_light += light * attenuation * shadow_attenuation; + } + } + + uint spot_light_count = cluster_cell.y >> CLUSTER_COUNTER_SHIFT; + uint spot_light_pointer = cluster_cell.y & CLUSTER_POINTER_MASK; + + for (uint i = 0; i < spot_light_count; i++) { + uint light_index = cluster_data.indices[spot_light_pointer + i]; + + vec3 light_pos = lights.data[i].position; + vec3 light_rel_vec = lights.data[i].position - view_pos; + float d = length(light_rel_vec) * lights.data[i].inv_radius; + vec3 shadow_attenuation = vec3(1.0); + + if (d < 1.0) { + vec2 attenuation_energy = unpackHalf2x16(lights.data[i].attenuation_energy); + vec4 color_specular = unpackUnorm4x8(lights.data[i].color_specular); + + float attenuation = pow(max(1.0 - d, 0.0), attenuation_energy.x); + + vec3 spot_dir = lights.data[i].direction; + vec2 spot_att_angle = unpackHalf2x16(lights.data[i].cone_attenuation_angle); + float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_att_angle.y); + float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_att_angle.y)); + attenuation *= 1.0 - pow(spot_rim, spot_att_angle.x); + + vec3 light = attenuation_energy.y * color_specular.rgb / M_PI; + + vec4 shadow_color_enabled = unpackUnorm4x8(lights.data[i].shadow_color_enabled); + + if (shadow_color_enabled.a > 0.5) { + //has shadow + vec4 v = vec4(view_pos, 1.0); + + vec4 splane = (lights.data[i].shadow_matrix * v); + splane /= splane.w; + + float depth = texture(sampler2D(shadow_atlas, linear_sampler), splane.xy).r; + float shadow = exp(min(0.0, (depth - splane.z)) / lights.data[i].inv_radius * lights.data[i].shadow_volumetric_fog_fade); + + shadow_attenuation = mix(shadow_color_enabled.rgb, vec3(1.0), shadow); + } + + total_light += light * attenuation * shadow_attenuation; + } + } + + vec3 world_pos = mat3(params.cam_rotation) * view_pos; + + for (uint i = 0; i < params.max_gi_probes; i++) { + vec3 position = (gi_probes.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, gi_probes.data[i].bounds))))) { + position /= gi_probes.data[i].bounds; + + vec4 light = vec4(0.0); + for (uint j = 0; j < gi_probes.data[i].mipmaps; j++) { + vec4 slight = textureLod(sampler3D(gi_probe_textures[i], linear_sampler_with_mipmaps), position, float(j)); + float a = (1.0 - light.a); + light += a * slight; + } + + light.rgb *= gi_probes.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 + + imageStore(density_map, pos, vec4(total_light, total_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 +} |