Unofficial Hotscreen Community
Gaussian Blur Shader - Printable Version

+- Unofficial Hotscreen Community (https://hotscreen.dominated.dev)
+-- Forum: HotScreen (https://hotscreen.dominated.dev/forumdisplay.php?fid=6)
+--- Forum: Mods (https://hotscreen.dominated.dev/forumdisplay.php?fid=12)
+--- Thread: Gaussian Blur Shader (/showthread.php?tid=68)



Gaussian Blur Shader - lamba5da - 03-21-2026

I didn't like how built-in blur looked like at high values
   
So I generated a shader for another variant of adjustable Gaussian Blur

Adjustables through code:
uniform float blur_radius : hint_range(0.0, 50.0) - The radius of the blur in pixels. Higher = more blur.
uniform float blur_intensity : hint_range(0.1, 5.0) - Higher values make the blur softer/more spread out for the same radius
There are little more, but I'm not sure what they do
...

Installation:

1. Just put .filter file in your CUSTOM_DATA directory
2. To add follow in Hotscreen: Add a Filter - Custom filters - Blur_shader


If you don't want to download anything, you can just copy it's code:
1. Follow in Hotscreen: Add a Filter - Mods - Shader effect
2. Press "Expand code window" and delete all the code
3. Paste this, and then press "Apply shader code":

shader_type canvas_item;

// --- Adjustable Parameters ---
// The radius of the blur in pixels. Higher = more blur but slower.
uniform float blur_radius : hint_range(0.0, 50.0) = 17.0;
// The intensity (sigma) of the gaussian distribution.
// Higher values make the blur softer/more spread out for the same radius.
uniform float blur_intensity : hint_range(0.1, 5.0) = 1.0;

// always put this to get if the border must be smoothed
uniform int use_smooth;

// this allows to sample the current screen correctly
global uniform int ScreenRotation;
global uniform sampler2D CurrentScreenTexture;

vec4 sampleCurrentScreen(vec2 uv, vec2 screen_pixel_size) {
vec2 rotated_uv = uv * (1.0-screen_pixel_size);
if (ScreenRotation == 2) {
rotated_uv = vec2(uv.y, 1.0 - uv.x);
} else if (ScreenRotation == 3) {
rotated_uv = vec2(uv.x, uv.y);
} else if (ScreenRotation == 4) {
rotated_uv = vec2(1.0 - uv.y, uv.x);
}
return texture(CurrentScreenTexture, rotated_uv).bgra;
}

// Helper to calculate Gaussian weight
float gaussian(float x, float sigma) {
return exp(-(x * x) / (2.0 * sigma * sigma));
}

void fragment() {
vec2 uv = SCREEN_UV;

// Normalize texel size to be consistent across resolutions (based on 1920 width reference)
float coherent_texel = 1.0 / 1920.0;
vec2 texel = vec2(coherent_texel, coherent_texel * SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x);

// Calculate Sigma based on radius and user intensity adjustment
// Standard Gaussian relation: sigma ≈ radius / 3.0 covers 99% of the curve
float sigma = max(0.1, (blur_radius / 3.0) * blur_intensity);

// Determine how many pixels to sample on either side of the center
// We clamp to prevent performance spikes, maxing out at roughly 32 samples per axis
int range = int(ceil(sigma * 3.0));
range = clamp(range, 1, 32);

vec4 accum = vec4(0.0);
float total_weight = 0.0;

// --- Horizontal Pass ---
for (int i = -range; i <= range; i++) {
float x_offset = float(i) * texel.x;
float weight = gaussian(float(i), sigma);

vec2 offsetUV = uv + vec2(x_offset, 0.0);
accum += sampleCurrentScreen(offsetUV, SCREEN_PIXEL_SIZE) * weight;
total_weight += weight;
}

// Normalize horizontal result
accum /= total_weight;

// --- Vertical Pass ---
// To do a true 2D Gaussian separable blur, we take the result of the horizontal pass
// and blur it vertically. Since we can't store intermediate textures easily in one pass,
// we simulate this by accumulating the vertical samples of the *already horizontally blurred* logic?
// NO: In a single pass fragment shader, we cannot actually do two distinct passes without a backbuffer.
//
// OPTIMIZATION FOR SINGLE PASS:
// A true separable blur requires two draws. In a single custom shader slot like this,
// doing a full 2D Gaussian kernel (sampling X then Y for every pixel) is O(R^2) and very slow.
//
// ALTERNATIVE APPROACH FOR SINGLE PASS:
// We will perform a standard 2D Gaussian Kernel sampling (Radial Gaussian).
// It is less efficient than separable but looks correct and fits the single-pass constraint.
// We reset accum and sample in a grid/circle pattern.

accum = vec4(0.0);
total_weight = 0.0;

// Sample in a square grid, discarding corners outside the radius for efficiency
for (int y = -range; y <= range; y++) {
for (int x = -range; x <= range; x++) {
float dist_sq = float(x*x + y*y);

// Optimization: Skip pixels outside the effective radius circle
if (dist_sq > float(range * range)) continue;

float dist = sqrt(dist_sq);
float weight = gaussian(dist, sigma);

vec2 offsetUV = uv + vec2(float(x) * texel.x, float(y) * texel.y);
accum += sampleCurrentScreen(offsetUV, SCREEN_PIXEL_SIZE) * weight;
total_weight += weight;
}
}

accum /= total_weight;

vec4 final_color = accum;

// Always put this code to correctly manage the transparency if smooth blending enabled
if (use_smooth == 1) {
final_color.a *= texture(TEXTURE, UV).r;
}
final_color.a *= COLOR.a;

COLOR = final_color;
}


RE: Gaussian Blur Shader - juseyo - 03-21-2026

I don't like the built-in blur shader either, thanks for sharing this!