Created
July 16, 2025 15:36
-
-
Save MagnusThor/fd485bad97cddf3a52f91500103f2558 to your computer and use it in GitHub Desktop.
This shader raymarches a stylized landscape with fractal terrain, dynamic lake shading, procedural clouds, and post-processing effects like god rays, lens flares, and sun glow. It uses screen-space techniques to simulate lighting, reflections, and atmosphere, creating a rich, animated scene with depth and color variation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct Uniforms { | |
| resolution: vec3<f32>, | |
| time: f32 | |
| }; | |
| struct VertexOutput { | |
| @builtin(position) pos: vec4<f32>, | |
| @location(0) uv: vec2<f32> | |
| }; | |
| @group(0) @binding(0) var<uniform> uniforms: Uniforms; | |
| @group(0) @binding(1) var linearSampler: sampler; | |
| fn fractalNoise(P: vec2<f32>, U: vec3<f32>) -> f32 { | |
| var p_var = P; | |
| var total = 0.0; | |
| var amp = 1.0; | |
| var deriv = vec2<f32>(0.0); | |
| let rot = mat2x2( | |
| cos(1.0 + 11.0 * U.z), -sin(1.0 + 11.0 * U.z), | |
| sin(1.0 + 11.0 * U.z), cos(1.0 + 11.0 * U.z) | |
| ); | |
| loop { | |
| if amp < 0.01 { | |
| break; | |
| } | |
| let o = cos(p_var.xyyx + 19.0 * U.xzxz); | |
| let q = vec3<f32>(o.y, o.x, o.w); | |
| deriv += q.xy; | |
| total -= amp * (1.0 + q.z) / (1.0 + 3.0 * dot(deriv, deriv)); | |
| p_var = rot * p_var * 2.0; | |
| amp *= 0.55; | |
| } | |
| return total; | |
| } | |
| fn radialBlurFast(fragCoord: vec2<f32>, sunPos: vec2<f32>, sceneColor: vec3<f32>) -> vec3<f32> { | |
| let samples = 20; | |
| let decay = 0.95; | |
| let exposure = 0.12; | |
| let density = 1.85; | |
| let weight = 1.3; | |
| var illuminationDecay = 1.0; | |
| var result = vec3<f32>(0.0); | |
| let deltaTex = (sunPos - fragCoord) / f32(samples) * density; | |
| var coord = fragCoord; | |
| for (var i = 0; i < samples; i = i + 1) { | |
| coord = coord + deltaTex; | |
| let sunStrength = smoothstep(150.0, 50.0, length(coord - sunPos)); | |
| let sampleBrightness = sunStrength * length(sceneColor); | |
| let scalar_product = sampleBrightness * illuminationDecay * weight; | |
| result = fma(sceneColor, vec3<f32>(scalar_product), result); | |
| illuminationDecay *= decay; | |
| } | |
| return result * exposure; | |
| } | |
| fn sunDot(uv: vec2<f32>, center: vec2<f32>, radius: f32, color: vec3<f32>) -> vec3<f32> { | |
| let d = distance(uv, center); | |
| let glow = exp(-pow(d / radius, 2.5)); | |
| return color * glow; | |
| } | |
| fn doLensGlint(uv: vec2<f32>, c: vec2<f32>, r: f32, w: f32) -> f32 { | |
| let l: f32 = length(uv - c); | |
| return smoothstep(0., w * r, l) * (1. - smoothstep(w * r, r, l)); | |
| } | |
| fn lensFlare(fragCoord: vec2<f32>, sunPos: vec2<f32>, res: vec2<f32>, time: f32) -> vec3<f32> { | |
| let dir = sunPos - fragCoord; | |
| let dist = length(dir); | |
| let normDir = dir / dist; // Normalized direction from fragCoord to sunPos | |
| var flare = vec3<f32>(0.0); | |
| // Main central glow/halo (unchanged - good base) | |
| let haloRadius = 200.0; | |
| let halo = pow(max(0.0, 1.0 - length(fragCoord - sunPos) / haloRadius), 4.0); | |
| flare += vec3<f32>(1.0, 0.75, 0.5) * halo * 0.7; | |
| // --- Tiny Ghosting Elements (dots) --- | |
| let numGhosts = 5; | |
| for (var i = 0; i < numGhosts; i = i + 1) { | |
| let ghostOffsetFactor = 0.03 + f32(i) * 0.015; | |
| let dynamicOffsetAmount = sin(time * (0.2 + f32(i) * 0.02)) * 3.0 + 5.0; | |
| let ghostPos = sunPos + normDir * dynamicOffsetAmount * ghostOffsetFactor; | |
| let ghostDist = distance(fragCoord, ghostPos); | |
| let ghostRadius = 1.0 + sin(time * 0.08 + f32(i) * 0.3) * 0.5; | |
| let ghostIntensity = exp(-pow(ghostDist / ghostRadius, 3.0)); | |
| let baseColor = vec3<f32>(1.0, 0.9 - 0.15 * f32(i), 0.7 - 0.1 * f32(i)); | |
| flare += baseColor * ghostIntensity * (0.02 + sin(time * 0.07 + f32(i)) * 0.002); | |
| } | |
| let numGlints = 4; // Number of small glint rings | |
| for (var i = 0; i < numGlints; i = i + 1) { | |
| // Glint position: subtle offset along the sun-to-fragment direction, with slow animation | |
| let glintOffsetFactor = 0.04 + f32(i) * 0.02; | |
| let glintDynamicOffset = cos(time * (0.15 + f32(i) * 0.02)) * 5.0; | |
| let glintPos = sunPos + normDir * (glintOffsetFactor * 100.0 + glintDynamicOffset); | |
| // Glint radius: tiny, with subtle breathing | |
| let glintRadius = 8.0 + sin(time * (0.1 + f32(i) * 0.01)) * 3.0; | |
| // --- MODIFIED: Glint width (w parameter in doLensGlint) --- | |
| // Increased 'w' to make the rings thicker and more visible. | |
| // This creates a more substantial "circle" rather than a thin line. | |
| let glintThicknessFactor = 0.5 + f32(i) * 0.05; // Base 0.5, increasing to 0.65 for last glint | |
| let glintIntensity = doLensGlint(fragCoord, glintPos, glintRadius, glintThicknessFactor); | |
| // Chromatic aberration effect: separate colors slightly for each glint | |
| var glintColor: vec3<f32>; | |
| if (i == 0) { glintColor = vec3<f32>(1.0, 0.2, 0.2); } // Reddish | |
| else if (i == 1) { glintColor = vec3<f32>(0.2, 1.0, 0.2); } // Greenish | |
| else if (i == 2) { glintColor = vec3<f32>(0.2, 0.2, 1.0); } // Blueish | |
| else { glintColor = vec3<f32>(1.0, 1.0, 0.5); } // Yellowish for the last one | |
| // --- MODIFIED: Overall intensity of the glints --- | |
| // Increased from 0.03 to 0.1 for better visibility, adjust as needed. | |
| flare += glintColor * glintIntensity * 0.1; | |
| } | |
| // Streaks (unchanged - they add a different, distinct effect) | |
| let streakAngle = atan2(normDir.y, normDir.x); | |
| let streaks = abs(sin(6.0 * streakAngle)) * pow(1.0 - dist / length(res), 4.0); | |
| flare += vec3<f32>(1.0, 0.85, 0.65) * streaks * 0.15; | |
| return flare; | |
| } | |
| fn ACESFilm(x: vec3<f32>) -> vec3<f32> { | |
| let a = 2.51; | |
| let b = 0.03; | |
| let c = 2.43; | |
| let d = 0.59; | |
| let e = 0.14; | |
| return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3<f32>(0.0), vec3<f32>(1.0)); | |
| } | |
| fn noise(p: vec2<f32>) -> f32 { | |
| let i = floor(p); | |
| let f = fract(p); | |
| let a = hash(i); | |
| let b = hash(i + vec2(1.0, 0.0)); | |
| let c = hash(i + vec2(0.0, 1.0)); | |
| let d = hash(i + vec2(1.0, 1.0)); | |
| let u = f * f * (3.0 - 2.0 * f); // smoothstep | |
| return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); | |
| } | |
| fn hash2(p: vec2<f32>) -> vec2<f32> { | |
| let a = dot(p, vec2<f32>(127.1, 311.7)); | |
| let b = dot(p, vec2<f32>(269.5, 183.3)); | |
| return fract(sin(vec2<f32>(a, b)) * 43758.5453); | |
| } | |
| fn hash(p: vec2<f32>) -> f32 { | |
| return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); | |
| } | |
| fn fbm(p: vec2<f32>) -> f32 { | |
| var value = 0.0; | |
| var amplitude = 0.5; | |
| var frequency = 1.0; | |
| let gain = 0.5; | |
| let lacunarity = 2.0; | |
| for (var i = 0; i < 5; i++) { | |
| value += amplitude * noise(p * frequency); | |
| frequency *= lacunarity; | |
| amplitude *= gain; | |
| } | |
| return value; | |
| } | |
| fn hash3(p: vec3<f32>) -> f32 { | |
| return fract(sin(dot(p, vec3<f32>(127.1, 311.7, 74.7))) * 43758.5453123); | |
| } | |
| fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> { | |
| let one_minus_cosTheta_pow5 = pow(1.0 - cosTheta, 5.0); | |
| let term_B_vec3 = vec3<f32>(one_minus_cosTheta_pow5); | |
| return fma(vec3<f32>(1.0) - F0, term_B_vec3, F0); | |
| } | |
| fn mainImage(invocation_id: vec2<f32>) -> vec4<f32> { | |
| let R = uniforms.resolution.xy; | |
| let fragCoord = invocation_id.xy; | |
| let time = uniforms.time; | |
| let C = fragCoord.xy; | |
| var O = vec3<f32>(0.0); | |
| var d = 9.0; | |
| var z = 0.0; | |
| let maxSteps = 64; | |
| let epsilon = 1e-3; | |
| let U = vec3<f32>(1.0, 3.0, 0.0); | |
| var orbitTrap = 1e20; | |
| for (var i = 0; i < maxSteps && d > epsilon; i++) { | |
| let speed = 2.5; | |
| let t = time * speed; | |
| let dir = normalize(vec3<f32>(C - 0.5 * R, R.y)); | |
| let p = fma(dir, vec3<f32>(z), vec3<f32>(0.0, 0.0, t)); | |
| let P = 0.23 * p.xz; | |
| let s = p.y + 4.0; | |
| d = abs(s) + 0.6; | |
| d += fractalNoise(P, U); | |
| orbitTrap = min(orbitTrap, d); | |
| let lake_uv_offset_x = sin(P.x * 5.0 + time); | |
| let lake_uv_offset_y = cos(P.y * 5.0 - time); | |
| let lake_uv = fma(vec2<f32>(lake_uv_offset_x, lake_uv_offset_y), vec2<f32>(0.05), P); | |
| var lake_color = vec3<f32>( | |
| fma(0.15, sin(lake_uv.x * 4.0 + time * 0.5), 0.1), | |
| fma(0.2, cos(lake_uv.y * 6.0 - time * 0.3), 0.3), | |
| fma(0.15, sin(length(lake_uv) * 8.0 + time), 0.5) | |
| ); | |
| let shallow = clamp(1.0 - smoothstep(-0.5, 0.0, s), 0.0, 1.0); | |
| lake_color = fma(vec3<f32>(-0.15, -0.1, -0.1), vec3<f32>(shallow), lake_color); | |
| let distFade = exp(-0.02 * z); | |
| let rockA = vec3<f32>(0.3, 0.2, 0.18); | |
| let rockB = vec3<f32>(0.4, 0.25, 0.15); | |
| let rockMix = mix(rockA, rockB, fractalNoise(P * 1.5, U)); | |
| let mountain_color_scalar_term = distFade * (1.0 + 2.0 * d); | |
| let mountain_color = rockMix * vec3<f32>(mountain_color_scalar_term); | |
| let grassAmount = smoothstep(0.1, 0.5, s) * smoothstep(80.0, 15.0, z); | |
| let grassColor_base = vec3<f32>(0.15, 0.4, 0.1); | |
| let grassColor_noise_scalar = 0.05 * fractalNoise(P * 10.0, U); | |
| let grassColor_total_scalar = grassAmount * 2.0; | |
| let grassColor = fma(vec3<f32>(grassColor_noise_scalar), vec3<f32>(grassColor_total_scalar), | |
| grassColor_base * vec3<f32>(grassColor_total_scalar)); | |
| let mountainWithGrass = mix(mountain_color, grassColor, grassAmount); | |
| let inSky = s > 0.0; | |
| let highUp = z > 100.0; | |
| let scatter = mix(vec3<f32>(1.1, 0.15, 0.25), vec3<f32>(0.3, 0.5, 0.7), C.y / R.y); | |
| let shaded = mix(lake_color, mix(mountainWithGrass, scatter, f32(highUp)), f32(inSky)); | |
| O += shaded; | |
| z += d; | |
| } | |
| let skyBlend = C.y / R.y; | |
| let baseSkyColor = mix(vec3<f32>(0.02, 0.03, 0.08), vec3<f32>(0.15, 0.2, 0.3), skyBlend); | |
| var color = mix(baseSkyColor, tanh(O / 100.0), 0.36); | |
| let sunWorldDir = normalize(vec3<f32>(-0.1, 0.8, 0.1)); | |
| let depthParallax = 0.04 * skyBlend; | |
| let cloudDriftSpeed1 = 0.05; // Faster layer, drifts right | |
| let cloudDriftSpeed2 = 0.07; // Slower layer, drifts left | |
| let cloudDriftSpeed3 = 0.010; // Medium layer, drifts slightly up-right | |
| let cloud_uv_offset1 = vec2<f32>(time * cloudDriftSpeed1, 0.0); | |
| let cloud_uv_offset2 = vec2<f32>(-time * cloudDriftSpeed2, time * 0.005); | |
| let cloud_uv_offset3 = vec2<f32>(time * cloudDriftSpeed3, time * cloudDriftSpeed3 * 0.5); | |
| let cloud_uv = C * 0.008 + cloud_uv_offset1; | |
| let cloud_uv2 = C * 0.004 + vec2<f32>(10.0, 10.0) + cloud_uv_offset2; | |
| let cloud_uv3 = C * 0.012 + vec2<f32>(20.0, -5.0) + cloud_uv_offset3; | |
| let uv1 = cloud_uv + vec2<f32>( | |
| fbm(cloud_uv * 2.0 + depthParallax), | |
| -fbm(cloud_uv * 1.5 - depthParallax) | |
| ); | |
| let uv2 = cloud_uv2 + vec2<f32>( | |
| fbm(cloud_uv2 * 1.5 + depthParallax * 0.8), | |
| -fbm(cloud_uv2 * 1.0 - depthParallax * 0.8) | |
| ); | |
| // New third layer UV distortion | |
| let uv3 = cloud_uv3 + vec2<f32>( | |
| fbm(cloud_uv3 * 2.5 + depthParallax * 1.2), | |
| -fbm(cloud_uv3 * 1.8 - depthParallax * 1.2) | |
| ); | |
| let verticalFade = smoothstep(0.25, 0.65, C.y / R.y); | |
| let cloud_mask1 = verticalFade * | |
| smoothstep(0.3, 0.7, pow(fbm(uv1 * 1.1), 1.5)) * | |
| smoothstep(0.3, 1.0, skyBlend) * | |
| smoothstep(0.4, 0.75, skyBlend); | |
| let cloud_mask2 = verticalFade * | |
| smoothstep(0.25, 0.6, pow(fbm(uv2 * 1.0), 2.0)) * | |
| smoothstep(0.3, 1.0, skyBlend); | |
| // New third cloud mask | |
| let cloud_mask3 = verticalFade * | |
| smoothstep(0.2, 0.5, pow(fbm(uv3 * 1.3), 1.8)) * | |
| smoothstep(0.25, 0.9, skyBlend); // A bit lower/more visible | |
| let sunLight = clamp(dot(vec3<f32>(0.0, 1.5, 0.0), sunWorldDir), 0.0, 1.0); | |
| let cloudColor = mix(vec3<f32>(1.05, 1.05, 1.0), vec3<f32>(1.3, 1.25, 1.0), 0.4 * sunLight) * | |
| mix(0.5, 1.0, sunLight); | |
| color = mix(color, cloudColor, 0.3 * cloud_mask1); | |
| color = mix(color, cloudColor * 0.6, 0.2 * cloud_mask2); | |
| // Blend in third cloud layer | |
| color = mix(color, cloudColor * 0.8, 0.15 * cloud_mask3); | |
| let reflectedSunWorldDir = normalize(vec3<f32>(sunWorldDir.x, -sunWorldDir.y, sunWorldDir.z)); | |
| let reflected_sun_screen_pos = R * 0.5 + reflectedSunWorldDir.xy * R * 0.45; | |
| let sunDist = distance(C, reflected_sun_screen_pos); | |
| let godrayMask = smoothstep(R.x * 0.3, R.x * 0.1, sunDist); | |
| let godrayColor = vec3<f32>(1.0, 0.85, 0.7) * godrayMask * 4.0; | |
| let viewDir = normalize(vec3<f32>(C - 0.5 * R, R.y)); | |
| let cosTheta = dot(viewDir, vec3<f32>(0.0, 1.0, 0.0)); | |
| let F0 = vec3<f32>(0.08); | |
| let fresnel = fresnelSchlick(clamp(1.0 - cosTheta, 0.0, 1.0), F0).r; | |
| let horizon_y = R.y * 0.45; | |
| let fade_range = R.y * 0.05; | |
| let reflection_visibility = smoothstep(horizon_y - fade_range, horizon_y + fade_range, C.y); | |
| let reflFade = smoothstep(R.y * 0.35, R.y * 0.45, C.y) * fresnel * 2.0; | |
| color += godrayColor * reflFade * reflection_visibility; | |
| let sun_screen_pos = R * 0.5 + sunWorldDir.xy * R * 0.45; | |
| let godray_pull_factor = 0.3; | |
| let screen_center = R * 0.5; | |
| let godray_origin_offset = (screen_center - sun_screen_pos) * godray_pull_factor; | |
| let angled_godray_pos = sun_screen_pos + godray_origin_offset; | |
| let sunGlow = sunDot(C, sun_screen_pos, 60.0, vec3<f32>(4.5, 3.5, 2.8)); | |
| let flare = lensFlare(C, sun_screen_pos, R,uniforms.time*0.1) * 1.5; | |
| let godrays = radialBlurFast(C, angled_godray_pos, color) * 1.5; | |
| color += godrays + flare + sunGlow; | |
| let lightDir = normalize(sunWorldDir); | |
| let terrainShade = clamp(dot(vec3<f32>(0.0, 1.0, 0.0), lightDir), 0.0, 1.0); | |
| color *= mix(0.6, 1.0, terrainShade); | |
| let fog = exp(-0.0002 * length(C - sun_screen_pos)); | |
| color = mix(baseSkyColor * 1.2, color, fog); | |
| let vignette = smoothstep(1.5, 0.0, length((C - 0.5 * R) / R)); | |
| color *= vignette; | |
| return vec4<f32>(ACESFilm(color + sunGlow), 1.0); | |
| } | |
| @fragment | |
| fn main_fragment(in: VertexOutput) -> @location(0) vec4<f32> { | |
| return mainImage(in.pos.xy); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment