shader_type spatial; // Base texture uniform sampler2D base_texture : source_color, filter_nearest, repeat_enable; // === WEATHERING CONTROLS === // Gradient (rising damp) uniform float gradient_strength : hint_range(0.0, 1.0) = 0.6; uniform float gradient_exponent : hint_range(1.0, 5.0) = 3.0; uniform float gradient_noise_warp : hint_range(0.0, 1.0) = 0.4; // Hue shifting uniform float shadow_cool_shift : hint_range(0.0, 0.15) = 0.06; uniform float highlight_warm_shift : hint_range(0.0, 0.15) = 0.04; // Surface grime (noise overlay) uniform float grime_strength : hint_range(0.0, 1.0) = 0.3; uniform float grime_scale : hint_range(0.5, 10.0) = 3.0; // Noise scale for large weathering patterns uniform float weather_scale : hint_range(0.1, 5.0) = 1.2; // Stain blotches uniform float stain_strength : hint_range(0.0, 1.0) = 0.35; uniform float stain_scale : hint_range(0.5, 5.0) = 2.0; // World-space vertical mapping (replaces UV.y which tiles in Quake-style UVs) uniform float wall_bottom : hint_range(-10.0, 50.0) = 2.5; uniform float wall_height : hint_range(0.1, 50.0) = 3.0; // Overall intensity multiplier uniform float weathering_intensity : hint_range(0.0, 2.0) = 1.0; // ============================================================ // Noise functions (no external texture needed) // ============================================================ vec2 hash22(vec2 p) { p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); } float noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f); return mix(mix(dot(hash22(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)), dot(hash22(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x), mix(dot(hash22(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)), dot(hash22(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x), u.y); } float fbm(vec2 p, int octaves) { float value = 0.0; float amplitude = 0.5; float frequency = 1.0; for (int i = 0; i < octaves; i++) { value += amplitude * noise(p * frequency); frequency *= 2.0; amplitude *= 0.5; } return value; } // ============================================================ // RGB <-> HSV conversion // ============================================================ 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 = 1.0e-10; return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } // ============================================================ // Main fragment // ============================================================ void fragment() { // Sample base texture vec4 tex = texture(base_texture, UV); vec3 color = tex.rgb; // World position for noise - makes each wall unique vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; vec2 wp = world_pos.xz; // World-space vertical gradient (0 = top, 1 = bottom) float v_pos = 1.0 - clamp((world_pos.y - wall_bottom) / wall_height, 0.0, 1.0); // ---- COARSE WEATHERING NOISE ---- float weather_noise = fbm(wp * weather_scale, 3) * 0.5 + 0.5; // ---- FINE SURFACE GRIME ---- float grime_noise = fbm(wp * grime_scale + vec2(50.0, 50.0), 3); // ---- STAIN BLOTCHES ---- float stain_noise = fbm(wp * stain_scale + vec2(100.0, 200.0), 4); stain_noise = smoothstep(0.15, 0.45, stain_noise); // Convert to HSV vec3 hsv = rgb2hsv(color); // ---- GRADIENT: RISING DAMP ---- float gradient = pow(v_pos, gradient_exponent); float warped_gradient = gradient * mix(1.0, weather_noise * 1.6, gradient_noise_warp); warped_gradient = clamp(warped_gradient, 0.0, 1.0); float grad_effect = warped_gradient * gradient_strength * weathering_intensity; hsv.z -= grad_effect * 0.35; hsv.x += grad_effect * shadow_cool_shift; hsv.y += grad_effect * 0.12; // ---- HUE SHIFTING ---- float luminance = hsv.z; float cool_warm = (luminance - 0.5) * 2.0; hsv.x += mix(shadow_cool_shift, -highlight_warm_shift, cool_warm * 0.5 + 0.5) * weathering_intensity; // ---- SURFACE GRIME ---- float grime_effect = grime_noise * grime_strength * weathering_intensity * 0.08; hsv.z -= grime_effect; hsv.y += abs(grime_effect) * 0.5; // ---- STAIN BLOTCHES ---- float stain_effect = stain_noise * stain_strength * weathering_intensity; hsv.z -= stain_effect * 0.1; hsv.x += stain_effect * 0.02; // Clamp HSV hsv.x = fract(hsv.x); hsv.y = clamp(hsv.y, 0.0, 1.0); hsv.z = clamp(hsv.z, 0.0, 1.0); // Back to RGB color = hsv2rgb(hsv); ALBEDO = color; }