2021-07-20 21:40:16 +10:00
/* clang-format off */
#[vertex]
#version 450
#VERSION_DEFINES
#include "blur_raster_inc.glsl"
layout(location = 0) out vec2 uv_interp;
/* clang-format on */
void main() {
2025-08-26 11:31:22 -07:00
// old code, ARM driver bug on Mali-GXXx GPUs and Vulkan API 1.3.xxx
// https://github.com/godotengine/godot/pull/92817#issuecomment-2168625982
//vec2 base_arr[3] = vec2[](vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0));
//gl_Position = vec4(base_arr[gl_VertexIndex], 0.0, 1.0);
//uv_interp = clamp(gl_Position.xy, vec2(0.0, 0.0), vec2(1.0, 1.0)) * 2.0; // saturate(x) * 2.0
vec2 vertex_base;
if (gl_VertexIndex == 0) {
vertex_base = vec2(-1.0, -1.0);
} else if (gl_VertexIndex == 1) {
vertex_base = vec2(-1.0, 3.0);
} else {
vertex_base = vec2(3.0, -1.0);
}
gl_Position = vec4(vertex_base, 0.0, 1.0);
uv_interp = clamp(vertex_base, vec2(0.0, 0.0), vec2(1.0, 1.0)) * 2.0; // saturate(x) * 2.0
2021-07-20 21:40:16 +10:00
}
/* clang-format off */
#[fragment]
#version 450
#VERSION_DEFINES
#include "blur_raster_inc.glsl"
layout(location = 0) in vec2 uv_interp;
/* clang-format on */
layout(set = 0, binding = 0) uniform sampler2D source_color;
2025-09-01 14:43:37 -07:00
#ifdef MODE_GLOW_UPSAMPLE
// When upsampling this is original downsampled texture, not the blended upsampled texture.
layout(set = 1, binding = 0) uniform sampler2D blend_color;
layout(constant_id = 0) const bool use_debanding = false;
layout(constant_id = 1) const bool use_blend_color = false;
// From https://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).
// This dithering must be applied after encoding changes (linear/nonlinear) have been applied
// as the final step before quantization from floating point to integer values.
vec3 screen_space_dither(vec2 frag_coord, float bit_alignment_diviser) {
// Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR.
// Removed the time component to avoid passing time into this shader.
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.
// Use a dither strength of 100% rather than the 37.5% suggested by the original source.
return (dither.rgb - 0.5) / bit_alignment_diviser;
}
2021-07-20 21:40:16 +10:00
#endif
layout(location = 0) out vec4 frag_color;
2025-09-01 14:43:37 -07:00
#ifdef MODE_GLOW_DOWNSAMPLE
// https://www.shadertoy.com/view/mdsyDf
vec4 BloomDownKernel4(sampler2D Tex, vec2 uv0) {
vec2 RcpSrcTexRes = blur.source_pixel_size;
vec2 tc = (uv0 * 2.0 + 1.0) * RcpSrcTexRes;
float la = 1.0 / 4.0;
vec2 o = (0.5 + la) * RcpSrcTexRes;
vec4 c = vec4(0.0);
c += textureLod(Tex, tc + vec2(-1.0, -1.0) * o, 0.0) * 0.25;
c += textureLod(Tex, tc + vec2(1.0, -1.0) * o, 0.0) * 0.25;
c += textureLod(Tex, tc + vec2(-1.0, 1.0) * o, 0.0) * 0.25;
c += textureLod(Tex, tc + vec2(1.0, 1.0) * o, 0.0) * 0.25;
return c;
}
#endif
#ifdef MODE_GLOW_UPSAMPLE
// https://www.shadertoy.com/view/mdsyDf
vec4 BloomUpKernel4(sampler2D Tex, vec2 uv0) {
vec2 RcpSrcTexRes = blur.source_pixel_size;
vec2 uv = uv0 * 0.5 + 0.5;
vec2 uvI = floor(uv);
vec2 uvF = uv - uvI;
vec2 tc = uvI * RcpSrcTexRes.xy;
// optimal stop-band
float lw = 0.357386;
float la = 25.0 / 32.0; // 0.78125 ~ 0.779627;
float lb = 3.0 / 64.0; // 0.046875 ~ 0.0493871;
vec2 l = vec2(-1.5 + la, 0.5 + lb);
vec2 lx = uvF.x == 0.0 ? l.xy : -l.yx;
vec2 ly = uvF.y == 0.0 ? l.xy : -l.yx;
lx *= RcpSrcTexRes.xx;
ly *= RcpSrcTexRes.yy;
vec4 c00 = textureLod(Tex, tc + vec2(lx.x, ly.x), 0.0);
vec4 c10 = textureLod(Tex, tc + vec2(lx.y, ly.x), 0.0);
vec4 c01 = textureLod(Tex, tc + vec2(lx.x, ly.y), 0.0);
vec4 c11 = textureLod(Tex, tc + vec2(lx.y, ly.y), 0.0);
vec2 w = abs(uvF * 2.0 - lw);
vec4 cx0 = c00 * (1.0 - w.x) + (c10 * w.x);
vec4 cx1 = c01 * (1.0 - w.x) + (c11 * w.x);
vec4 cxy = cx0 * (1.0 - w.y) + (cx1 * w.y);
return cxy;
}
#endif // MODE_GLOW_UPSAMPLE
2021-07-20 21:40:16 +10:00
void main() {
2021-07-26 21:31:15 +10:00
// We do not apply our color scale for our mobile renderer here, we'll leave our colors at half brightness and apply scale in the tonemap raster.
2021-07-20 21:40:16 +10:00
#ifdef MODE_MIPMAP
2025-09-01 14:43:37 -07:00
vec2 pix_size = blur.dest_pixel_size;
2021-07-20 21:40:16 +10:00
vec4 color = texture(source_color, uv_interp + vec2(-0.5, -0.5) * pix_size);
color += texture(source_color, uv_interp + vec2(0.5, -0.5) * pix_size);
color += texture(source_color, uv_interp + vec2(0.5, 0.5) * pix_size);
color += texture(source_color, uv_interp + vec2(-0.5, 0.5) * pix_size);
frag_color = color / 4.0;
#endif
#ifdef MODE_GAUSSIAN_BLUR
2022-11-10 12:07:54 -08:00
// For Gaussian Blur we use 13 taps in a single pass instead of 12 taps over 2 passes.
// This minimizes the number of times we change framebuffers which is very important for mobile.
// Source: http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
2025-09-01 14:43:37 -07:00
vec4 A = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(-1.0, -1.0));
vec4 B = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(0.0, -1.0));
vec4 C = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(1.0, -1.0));
vec4 D = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(-0.5, -0.5));
vec4 E = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(0.5, -0.5));
vec4 F = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(-1.0, 0.0));
2022-11-10 12:07:54 -08:00
vec4 G = texture(source_color, uv_interp);
2025-09-01 14:43:37 -07:00
vec4 H = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(1.0, 0.0));
vec4 I = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(-0.5, 0.5));
vec4 J = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(0.5, 0.5));
vec4 K = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(-1.0, 1.0));
vec4 L = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(0.0, 1.0));
vec4 M = texture(source_color, uv_interp + blur.dest_pixel_size * vec2(1.0, 1.0));
2022-11-10 12:07:54 -08:00
float base_weight = 0.5 / 4.0;
float lesser_weight = 0.125 / 4.0;
frag_color = (D + E + I + J) * base_weight;
frag_color += (A + B + G + F) * lesser_weight;
frag_color += (B + C + H + G) * lesser_weight;
frag_color += (F + G + L + K) * lesser_weight;
frag_color += (G + H + M + L) * lesser_weight;
2021-07-20 21:40:16 +10:00
#endif
2025-09-01 14:43:37 -07:00
#ifdef MODE_GLOW_GATHER
// First step, go straight to quarter resolution.
// Don't apply blur, but include thresholding.
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
vec2 block_pos = floor(gl_FragCoord.xy) * 4.0;
vec2 end = max(1.0 / blur.source_pixel_size - vec2(4.0), vec2(0.0));
block_pos = clamp(block_pos, vec2(0.0), end);
2022-04-29 17:10:54 +10:00
2025-09-01 14:43:37 -07:00
// We skipped a level, so gather 16 closest samples now.
2022-04-29 17:10:54 +10:00
2025-09-01 14:43:37 -07:00
vec4 color = textureLod(source_color, (block_pos + vec2(1.0, 1.0)) * blur.source_pixel_size, 0.0);
color += textureLod(source_color, (block_pos + vec2(1.0, 3.0)) * blur.source_pixel_size, 0.0);
color += textureLod(source_color, (block_pos + vec2(3.0, 1.0)) * blur.source_pixel_size, 0.0);
color += textureLod(source_color, (block_pos + vec2(3.0, 3.0)) * blur.source_pixel_size, 0.0);
frag_color = color * 0.25;
2022-04-29 17:10:54 +10:00
2025-09-01 14:43:37 -07:00
// Apply strength a second time since it usually gets added at each level.
frag_color *= blur.glow_strength;
frag_color *= blur.glow_strength;
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
// In the first pass bring back to correct color range else we're applying the wrong threshold
// in subsequent passes we can use it as is as we'd just be undoing it right after.
frag_color *= blur.luminance_multiplier;
frag_color *= blur.glow_exposure;
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
float luminance = max(frag_color.r, max(frag_color.g, frag_color.b));
float feedback = max(smoothstep(blur.glow_hdr_threshold, blur.glow_hdr_threshold + blur.glow_hdr_scale, luminance), blur.glow_bloom);
2022-04-29 17:10:54 +10:00
2025-09-01 14:43:37 -07:00
frag_color = min(frag_color * feedback, vec4(blur.glow_luminance_cap)) / blur.luminance_multiplier;
#endif // MODE_GLOW_GATHER_WIDE
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
#ifdef MODE_GLOW_DOWNSAMPLE
// Regular downsample, apply a simple blur.
frag_color = BloomDownKernel4(source_color, floor(gl_FragCoord.xy));
frag_color *= blur.glow_strength;
#endif // MODE_GLOW_DOWNSAMPLE
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
#ifdef MODE_GLOW_UPSAMPLE
2021-07-20 21:40:16 +10:00
2025-09-01 14:43:37 -07:00
frag_color = BloomUpKernel4(source_color, floor(gl_FragCoord.xy)) * blur.glow_strength; // "glow_strength" here is actually the glow level. It is always 1.0, except for the first upsample where we need to apply the level to two textures at once.
if (use_blend_color) {
vec2 uv = floor(gl_FragCoord.xy) + 0.5;
frag_color += textureLod(blend_color, uv * blur.dest_pixel_size, 0.0) * blur.glow_level;
2021-07-20 21:40:16 +10:00
}
2025-09-01 14:43:37 -07:00
if (use_debanding) {
frag_color.rgb += screen_space_dither(gl_FragCoord.xy, 1023.0);
}
#endif // MODE_GLOW_UPSAMPLE
2021-07-20 21:40:16 +10:00
2021-07-29 17:43:17 +10:00
#ifdef MODE_COPY
vec4 color = textureLod(source_color, uv_interp, 0.0);
frag_color = color;
2021-07-20 21:40:16 +10:00
#endif
}