Godot Version
4.5
Question
Hey! I’m pretty new to shaders.
I need to perform several different operations on sprites within a single shader. I made the individual parts with the help of ChatGPT and they all work on their own. Then I somehow stitched them together into one shader, struggling for quite a while to make everything run without errors.
Here’s what the shader does:
Deleting black backgrond → Adding Blur → Adding some custom texture → Color manipulating
But I’ve run into a problem — with the shader’s default settings, when all modules are turned off or set to zero, the sprite becomes much more contrasty and loses a lot of brightness. And no amount of color-correction tweaking can bring it back to its original look.
Could you help me fix the shader so that it doesn’t alter the image at all when using the default settings?
Here is my shader:
shader_type canvas_item;
render_mode blend_mix;
// ================= 1) Deleting black backgrond =================
uniform float brightness_threshold : hint_range(0.0, 1.0) = 1.0;
uniform float softness : hint_range(0.0, 0.5) = 0.0;
uniform bool invert = false;
uniform int blur_radius : hint_range(0, 10) = 3;
uniform float blur_strength : hint_range(0.0, 5.0) = 0.0;
uniform float edge_shrink_pixels : hint_range(0.0, 10.0) = 0.0;
uniform float edge_softness : hint_range(0.0, 5.0) = 0.0;
// base texture size in pixels
uniform vec2 texture_size = vec2(512.0, 512.0);
// ================= 2) Adding some custom texture =================
uniform bool overlay_enabled = false;
uniform sampler2D overlay_tex;
uniform vec2 overlay_tiling = vec2(1.0, 1.0);
uniform vec2 overlay_offset = vec2(0.0, 0.0);
uniform float overlay_opacity : hint_range(0.0, 1.0) = 1.0;
uniform float mix_amount : hint_range(0.0, 1.0) = 1.0;
uniform bool clip_to_base_alpha = true;
uniform bool overlay_use_luma = false;
// 0=Multiply,1=Screen,2=Overlay,3=Linear Dodge,4=Linear Burn,
// 5=Color Dodge,6=Color Burn,7=Linear Light,8=Vivid Light,
// 9=Pin Light,10=Hard Mix,11=Soft Light
uniform int blend_mode = 2;
// ================= 3) Colorize=================
uniform float adjust_brightness : hint_range(-1.0, 1.0) = 0.0;
uniform float adjust_contrast : hint_range(0.0, 2.0) = 1.0;
uniform float adjust_saturation : hint_range(0.0, 2.0) = 1.0;
uniform float adjust_hue : hint_range(-1.0, 1.0) = 0.0;
uniform bool force_colorize = false;
const float EPS = 1e-5;
const int MAX_BLUR = 10;
const int MAX_SHRINK = 10;
// ---------- Utilities ----------
float luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }
float alpha_from_brightness(vec4 col) {
float br = luma(col.rgb);
float a_mask = invert
? smoothstep(brightness_threshold, brightness_threshold + softness, 1.0 - br)
: smoothstep(brightness_threshold, brightness_threshold + softness, br);
// a_mask=1 → cut
return col.a * (1.0 - a_mask);
}
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1e-10;
return vec3(abs(q.z + (q.w - q.y)/(6.0d + e)), d/(q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
vec3 rgb = clamp(abs(mod(c.x6.0 + vec3(0.0,4.0,2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z * mix(vec3(1.0), rgb, c.y);
}
// ---------- mixing mods ----------
vec3 screen_f (vec3 b, vec3 o){ return 1.0 - (1.0 - b) * (1.0 - o); }
vec3 overlay_f (vec3 b, vec3 o){ return mix(2.0bo, 1.0-2.0*(1.0-b)(1.0-o), step(0.5, b)); }
vec3 linear_dodge (vec3 b, vec3 o){ return clamp(b + o, 0.0, 1.0); }
vec3 linear_burn (vec3 b, vec3 o){ return clamp(b + o - 1.0, 0.0, 1.0); }
vec3 color_dodge (vec3 b, vec3 o){ return clamp(b / max(1.0 - o, EPS), 0.0, 1.0); }
vec3 color_burn (vec3 b, vec3 o){ return 1.0 - color_dodge(1.0 - b, 1.0 - o); }
vec3 linear_light (vec3 b, vec3 o){ return clamp(b + 2.0o - 1.0, 0.0, 1.0); }
vec3 vivid_light (vec3 b, vec3 o){ vec3 lo=color_burn(b,2.0o); vec3 hi=color_dodge(b,2.0o-1.0); return mix(lo,hi,step(0.5,o)); }
vec3 pin_light (vec3 b, vec3 o){ return mix(min(b,2.0o), max(b,2.0o - 1.0), step(0.5, o)); }
vec3 hard_mix (vec3 b, vec3 o){ return step(0.5, vivid_light(b, o)); }
// Photoshop-like Soft Light
vec3 soft_light (vec3 b, vec3 o){
vec3 a = b - (1.0 - 2.0o) * b * (1.0 - b);
vec3 b2 = b + (2.0o - 1.0) * (sqrt(clamp(b,0.0,1.0)) - b);
return mix(a, b2, step(0.5, o));
}
vec3 apply_mode(int m, vec3 b, vec3 o){
if (m == 0) return b * o;
if (m == 1) return screen_f(b, o);
if (m == 2) return overlay_f(b, o);
if (m == 3) return linear_dodge(b, o);
if (m == 4) return linear_burn(b, o);
if (m == 5) return color_dodge(b, o);
if (m == 6) return color_burn(b, o);
if (m == 7) return linear_light(b, o);
if (m == 8) return vivid_light(b, o);
if (m == 9) return pin_light(b, o);
if (m == 10) return hard_mix(b, o);
return soft_light(b, o); // 11
}
void fragment(){
vec2 px = 1.0 / texture_size;
// ===== 1) Matting =====
vec4 col = texture(TEXTURE, UV) * COLOR;
col.a = alpha_from_brightness(col);
// ===== 1b) delete border =====
int rad = int(edge_shrink_pixels + 0.5);
if (rad > 0) {
rad = min(rad, MAX_SHRINK);
float min_dist = float(rad) + 1.0;
for (int x = -MAX_SHRINK; x <= MAX_SHRINK; x++) {
for (int y = -MAX_SHRINK; y <= MAX_SHRINK; y++) {
if (abs(x) > rad || abs(y) > rad) continue;
vec2 offs = vec2(float(x), float(y)) * px;
vec4 n = texture(TEXTURE, UV + offs) * COLOR;
float a = alpha_from_brightness(n);
if (a < 0.01) {
float d = length(vec2(float(x), float(y)));
min_dist = min(min_dist, d);
}
}
}
float m = smoothstep(edge_shrink_pixels - edge_softness, edge_shrink_pixels, min_dist);
col.a *= m;
}
// ===== 2) Texture =====
vec3 mixed_rgb = col.rgb; // Base texture
if (overlay_enabled && overlay_opacity > 0.0 && mix_amount > 0.0) {
vec2 uv2 = UV * overlay_tiling + overlay_offset;
vec4 over = texture(overlay_tex, uv2);
float over_l = luma(over.rgb);
bool has_overlay = (over.a > 0.001) && (over_l > 0.001 || over_l < 0.999);
if (has_overlay) {
vec3 base_rgb = col.rgb;
vec3 over_rgb = overlay_use_luma ? vec3(over_l) : over.rgb;
float oa = over.a;
if (clip_to_base_alpha) oa *= col.a;
vec3 blended = apply_mode(blend_mode, base_rgb, over_rgb);
float f = clamp(oa * overlay_opacity * mix_amount, 0.0, 1.0);
mixed_rgb = mix(base_rgb, blended, f);
}
}
// ===== 3) Colorize =====
vec3 cc = mixed_rgb;
cc = (cc - 0.5) * adjust_contrast + 0.5;
cc += vec3(adjust_brightness);
vec3 hsv = rgb2hsv(cc);
if (force_colorize && hsv.y < 0.01) {
hsv.x = fract(adjust_hue);
hsv.y = 1.0;
} else {
hsv.x = fract(hsv.x + adjust_hue);
}
hsv.y *= adjust_saturation;
cc = hsv2rgb(hsv);
vec4 final_col = vec4(cc, col.a);
// ===== 4) Post-blur =====
if (blur_radius > 0 && blur_strength > 0.0) {
vec4 acc = vec4(0.0);
float sum_w = 0.0;
int r = min(blur_radius, MAX_BLUR);
for (int x = -MAX_BLUR; x <= MAX_BLUR; x++) {
for (int y = -MAX_BLUR; y <= MAX_BLUR; y++) {
if (abs(x) > r || abs(y) > r) continue;
vec2 offs = vec2(float(x), float(y)) * px * blur_strength;
// 1) matting
vec4 col2 = texture(TEXTURE, UV + offs) * COLOR;
col2.a = alpha_from_brightness(col2);
// 1b) delete border
int rad2 = int(edge_shrink_pixels + 0.5);
if (rad2 > 0) {
rad2 = min(rad2, MAX_SHRINK);
float min_dist2 = float(rad2) + 1.0;
for (int i = -MAX_SHRINK; i <= MAX_SHRINK; i++) {
for (int j = -MAX_SHRINK; j <= MAX_SHRINK; j++) {
if (abs(i) > rad2 || abs(j) > rad2) continue;
vec2 offs2 = vec2(float(i), float(j)) * px;
vec4 n2 = texture(TEXTURE, (UV + offs) + offs2) * COLOR;
float a2 = alpha_from_brightness(n2);
if (a2 < 0.01) {
float d2 = length(vec2(float(i), float(j)));
min_dist2 = min(min_dist2, d2);
}
}
}
float m2 = smoothstep(edge_shrink_pixels - edge_softness, edge_shrink_pixels, min_dist2);
col2.a *= m2;
}
// 2) texture
vec3 mixed2 = col2.rgb;
if (overlay_enabled && overlay_opacity > 0.0 && mix_amount > 0.0) {
vec2 uv2b = (UV + offs) * overlay_tiling + overlay_offset;
vec4 over2 = texture(overlay_tex, uv2b);
float over2_l = luma(over2.rgb);
bool has_overlay2 = (over2.a > 0.001) && (over2_l > 0.001 || over2_l < 0.999);
if (has_overlay2) {
vec3 over2_rgb = overlay_use_luma ? vec3(over2_l) : over2.rgb;
float oa2 = clip_to_base_alpha ? (over2.a * col2.a) : over2.a;
vec3 blended2 = apply_mode(blend_mode, col2.rgb, over2_rgb);
float f2 = clamp(oa2 * overlay_opacity * mix_amount, 0.0, 1.0);
mixed2 = mix(col2.rgb, blended2, f2);
}
}
// 3) colorize
vec3 cc2 = mixed2;
cc2 = (cc2 - 0.5) * adjust_contrast + 0.5;
cc2 += vec3(adjust_brightness);
vec3 hsv2 = rgb2hsv(cc2);
if (force_colorize && hsv2.y < 0.01) {
hsv2.x = fract(adjust_hue);
hsv2.y = 1.0;
} else {
hsv2.x = fract(hsv2.x + adjust_hue);
}
hsv2.y *= adjust_saturation;
cc2 = hsv2rgb(hsv2);
float w = exp(-(float(x*x + y*y)) / (2.0*float(r)*float(r) + 1e-6));
acc += vec4(cc2, col2.a) * w;
sum_w += w;
}
}
COLOR = (sum_w > 0.0) ? (acc / sum_w) : final_col;
} else {
COLOR = final_col;
}
}

