104 lines
3.2 KiB
Plaintext
104 lines
3.2 KiB
Plaintext
shader_type canvas_item;
|
|
|
|
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap, repeat_disable;
|
|
|
|
// --- Kuwahara (painterly smoothing) ---
|
|
uniform int kuwahara_radius : hint_range(1, 10) = 4;
|
|
|
|
// --- Edge / line art ---
|
|
uniform float edge_strength : hint_range(0.0, 4.0) = 1.2;
|
|
uniform float edge_threshold : hint_range(0.0, 1.0) = 0.12;
|
|
uniform vec3 edge_color : source_color = vec3(0.05, 0.04, 0.03);
|
|
|
|
// --- Brush / canvas texture overlay ---
|
|
uniform sampler2D brush_tex : filter_linear_mipmap, repeat_enable;
|
|
uniform vec2 brush_scale = vec2(18.0, 18.0);
|
|
uniform float brush_strength : hint_range(0.0, 1.0) = 0.35;
|
|
|
|
// --- Final color tweaks ---
|
|
uniform float saturation : hint_range(0.0, 2.0) = 1.1;
|
|
|
|
float luminance(vec3 c) {
|
|
return dot(c, vec3(0.299, 0.587, 0.114));
|
|
}
|
|
|
|
// Classic 4-quadrant Kuwahara filter
|
|
vec3 kuwahara(sampler2D tex, vec2 uv, vec2 px, int r) {
|
|
vec3 mean[4];
|
|
float variance[4];
|
|
|
|
vec2 signs[4];
|
|
signs[0] = vec2(-1.0, -1.0);
|
|
signs[1] = vec2(1.0, -1.0);
|
|
signs[2] = vec2(-1.0, 1.0);
|
|
signs[3] = vec2(1.0, 1.0);
|
|
|
|
for (int q = 0; q < 4; q++) {
|
|
vec3 sum = vec3(0.0);
|
|
vec3 sum_sq = vec3(0.0);
|
|
float count = 0.0;
|
|
for (int j = 0; j <= r; j++) {
|
|
for (int i = 0; i <= r; i++) {
|
|
vec2 offset = vec2(float(i), float(j)) * signs[q] * px;
|
|
vec3 c = texture(tex, uv + offset).rgb;
|
|
sum += c;
|
|
sum_sq += c * c;
|
|
count += 1.0;
|
|
}
|
|
}
|
|
vec3 m = sum / count;
|
|
vec3 v = sum_sq / count - m * m;
|
|
mean[q] = m;
|
|
variance[q] = v.r + v.g + v.b;
|
|
}
|
|
|
|
float min_var = variance[0];
|
|
vec3 result = mean[0];
|
|
for (int q = 1; q < 4; q++) {
|
|
if (variance[q] < min_var) {
|
|
min_var = variance[q];
|
|
result = mean[q];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Sobel edge detection on luminance
|
|
float sobel_edge(sampler2D tex, vec2 uv, vec2 px) {
|
|
float tl = luminance(texture(tex, uv + vec2(-px.x, -px.y)).rgb);
|
|
float t = luminance(texture(tex, uv + vec2(0.0, -px.y)).rgb);
|
|
float tr = luminance(texture(tex, uv + vec2(px.x, -px.y)).rgb);
|
|
float l = luminance(texture(tex, uv + vec2(-px.x, 0.0)).rgb);
|
|
float r = luminance(texture(tex, uv + vec2(px.x, 0.0)).rgb);
|
|
float bl = luminance(texture(tex, uv + vec2(-px.x, px.y)).rgb);
|
|
float b = luminance(texture(tex, uv + vec2(0.0, px.y)).rgb);
|
|
float br = luminance(texture(tex, uv + vec2(px.x, px.y)).rgb);
|
|
|
|
float gx = -tl - 2.0 * l - bl + tr + 2.0 * r + br;
|
|
float gy = -tl - 2.0 * t - tr + bl + 2.0 * b + br;
|
|
return sqrt(gx * gx + gy * gy);
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 uv = SCREEN_UV;
|
|
vec2 px = SCREEN_PIXEL_SIZE;
|
|
|
|
// STEP 1: painterly color smoothing
|
|
vec3 painted = kuwahara(SCREEN_TEXTURE, uv, px, kuwahara_radius);
|
|
|
|
// STEP 2: line art from the smoothed image (cleaner lines than raw noise)
|
|
float edge = sobel_edge(SCREEN_TEXTURE, uv, px);
|
|
edge = smoothstep(edge_threshold, edge_threshold + 0.3, edge) * edge_strength;
|
|
vec3 with_edges = mix(painted, edge_color, clamp(edge, 0.0, 1.0));
|
|
|
|
// STEP 3: brush / canvas texture overlay (fake stroke + paper grain)
|
|
vec3 brush = texture(brush_tex, uv * brush_scale).rgb;
|
|
vec3 with_brush = with_edges * mix(vec3(1.0), brush, brush_strength);
|
|
|
|
// STEP 4: slight saturation boost, like paint pigment richness
|
|
float l = luminance(with_brush);
|
|
vec3 final_color = mix(vec3(l), with_brush, saturation);
|
|
|
|
COLOR = vec4(final_color, 1.0);
|
|
}
|