Very simple but powerful sunset effect using gradients (and a checkerboard for the ground).
If you're new to shaders and ray-tracing concepts - this is a good starting point - as the implementation is less than 50 lines (without all the comments).
Complete Code for the Sexy Sunset
The code has been given lots of comments so you can make out what it does!
@group(0) @binding(0) var mySampler: sampler; @group(0) @binding(1) var myTexture: texture_2d<f32>; @group(0) @binding(2) var <uniform> mytimer : f32;
/* skyColor combines various optical and atmospheric effects to create a realistic sky shader.
1. Atmospheric Scattering - The reddish tones simulate the scattering of shorter wavelengths (blue/green) near the horizon. The bluish tones simulate Rayleigh scattering, which dominates at higher altitudes. 2. Fog - Below the horizon (yd), the fog effect gradually blends the scene with gray to simulate distance. 3 Sunlight - A diffuse glow gives the sun a soft appearance. A specular highlight creates the bright central spot of the sun. 4. Exponential Decays - These simulate the attenuation of light due to atmospheric density. */ fn skyColor(rd: vec3<f32>) -> vec3<f32> { // Define the direction of the sun in the sky, slightly offset above the horizon. // Normalized to ensure it is a unit vector. let sundir = normalize(vec3<f32>(0.0, 0.1, 1.0));
// `yd` represents the downward component of the ray's y-axis direction, // clamped to the range [negative infinity, 0.0]. // This is used later to simulate fog and atmospheric effects. let yd = min(rd.y, 0.0);
// `rd_y_clamped` is the upward component of the ray's y-axis direction, // clamped to the range [0.0, positive infinity]. // This is used for atmospheric color calculations above the horizon. let rd_y_clamped = max(rd.y, 0.0);
// Initialize the base color as black (no contribution yet). var col = vec3<f32>(0.0);
// Add a reddish hue to the sky, primarily contributing to the lower atmosphere. // This simulates the red and green scatter near the horizon. col += vec3<f32>( 0.4, // Red channel 0.4 - exp(-rd_y_clamped * 20.0) * 0.15, // Green channel fades with altitude 0.0 // No contribution to the blue channel ) * exp(-rd_y_clamped * 9.0); // Exponential decay simulating atmospheric absorption.
// Add a bluish hue to the sky, simulating Rayleigh scattering in the upper atmosphere. col += vec3<f32>( 0.3, // Light contribution to red 0.5, // Medium contribution to green 0.6 // Strong contribution to blue ) * (1.0 - exp(-rd_y_clamped * 8.0)) // Smooth transition effect as rd_y increases. * exp(-rd_y_clamped * 0.9); // Exponential decay for realism.
// Simulate fog by mixing the sky color with a flat gray (vec3(0.3)). // The blending factor depends on the downward component of the ray (`yd`). col = mix( col * 1.2, // Enhance sky brightness slightly before mixing. vec3<f32>(0.3), // Fog color (gray). 1.0 - exp(yd * 100.0) // Fog intensity increases below the horizon. );
// Add sunlight contributions: // 1. A warm, diffuse glow around the sun. col += vec3<f32>(1.0, 0.8, 0.55) // Sun color (soft yellow-orange). * pow(max(dot(rd, sundir), 0.0), 15.0) // Sun's diffuse intensity (15th power for sharpness). * 0.6; // Scale down the intensity for balance.
// 2. A sharp specular highlight for the sun itself, with very high power for sharpness. col += pow(max(dot(rd, sundir), 0.0), 150.0) // Specular highlight intensity (150th power for sharp focus). * 0.15; // Scale down the specular contribution.
// Return the final computed color for the sky. return col; }
fn mymod(x:f32, y:f32) -> f32 { return ( x - y * floor(x/y) ); }
fn checker(p: vec2<f32>) -> f32 { // Compute the floor of the input vector let floored_p = floor(p);
// Apply modulus operation to wrap the values within 0-2 let mod_p = vec2<f32>( mymod(floored_p.x,2.0), mymod(floored_p.y,2.0) );
// Sum the components of the vector let sum = mod_p.x + mod_p.y;
// Compute the final modulus let checker_value = mymod(sum, 2.0);
// Return based on the result of the modulus if (checker_value < 1.0) { return 0.25; } return 0.1; }
/* SHADER ENTRY POINT */ @fragment fn main(@location(0) uvs: vec2<f32>) -> @location(0) vec4<f32> { // Define the screen resolution. This is a constant that determines the size of the render output. // It's used to adjust the aspect ratio and for various calculations in the shader. let resolution = vec2<f32>(512.0, 512.0);
// UV coordinates. These are normalized to [0, 1] across the screen space. // Convert normalized UVs into a range of [-1, 1], effectively creating clip space coordinates. // This is a common step in rendering pipelines to account for camera space calculations. let v = vec2<f32>(-1.0) + 2.0 * uvs;
// Compute the screen's aspect ratio, which is the ratio of width to height. // This is used to scale the x-axis so that the image doesn't appear stretched or squished. let aspect_ratio = resolution.x / resolution.y;
// Construct the direction vector `dir` for a ray originating from the camera. // The x-component is scaled by the aspect ratio, the y-component is offset for a slight tilt, // and the z-component points forward into the scene. let dir = normalize(vec3<f32>(v.x * aspect_ratio, v.y + 0.5, 1.5));
// Compute the color of the checkerboard pattern based on the ray direction. // The pattern is scaled by dividing the xz components of `dir` by `dir.y`. // A time-based offset (`-mytimer * 2.0`) adds animation to the checkerboard pattern. let checker_col = vec3<f32>(checker(dir.xz / dir.y * 0.5 + vec2<f32>(0.0, -mytimer * 2.0)));
// Compute the reflection vector for the ray `dir` relative to a flat surface with a normal of (0, 1, 0). // This simulates light bouncing off the ground plane into the scene. let reflected_dir = reflect(dir, vec3<f32>(0.0, 1.0, 0.0));
// Combine the checkerboard color and the sky color reflected in the ground. // The reflection is scaled down by 0.3 to reduce its intensity. var col = checker_col + skyColor(reflected_dir) * 0.3;
// Add atmospheric blending by interpolating (`mix`) between the current color (`col`) // and the sky color along the ray direction (`dir`). // The blending factor is controlled by an exponential decay function, simulating atmospheric density. col = mix(col, skyColor(dir), exp(-max(-v.y * 9.0 - 4.8, 0.0)));
// Apply a vignette effect to darken the corners of the image. // The effect is strongest where the UV coordinates are farthest from the center. // A smooth transition is achieved using a power function. let vignette = 0.7 + 0.3 * pow(uvs.x * uvs.y * (1.0 - uvs.x) * (1.0 - uvs.y) * 16.0, 0.1); col *= vignette;
// Output the final color as a `vec4`, where the alpha value is set to 1.0 for full opacity. return vec4<f32>(col, 1.0); }
Things to Try
• Animate the position of the sun - so it either moves up or down or across the sky over time
• Animate the colors (so they gradually become darker and lighter)
• Try changing the ground - instead of a checkerboard something else (other patterns)
• Add user interface (pass parameters via uniform structure) - so you can interactively tinker with the various parameters to create all sorts of sky types
• If you've not noticed - their isn't any randomness in the effect - just gradients - so mixing in a bit of noise can add a new aspect of realism
• Try adding in some simple blobs for clouds (so the sky isn't so clear)
• Add in a second sun! Star Wars or Riddick type worlds....
• Add in a second pass 'blur' to create a heat phase
Resources & Links
• Full Implementation (Source Code and Interactive Demo) LINK
Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.