![]() |
|
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! |