www.xbdev.net
xbdev - software development
Wednesday January 15, 2025
Home | Contact | Support | WebGPU Graphics and Compute ...
     
 

WebGPU/WGSL Tutorials and Articles

Graphics and Compute ...

 


Pixel Tunnel - Minecraft Look - Pixelated Squares


This article will go through the steps of constructing an infinite square tunnel with a minecraft-like feel (pixelated colors). We'll go through multiple simplified code samples to show what each part does - with screenshots.



The texture coordinates as the color.
The texture coordinates as the color.


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     return 
vec4<f32>( input_coords0.01.0 );
}



Normlize the UV coordinates goes from -1.0 to 1.0.
Normlize the UV coordinates goes from -1.0 to 1.0.


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
return vec4<f32>( normalized_coords0.01.0 );
}



Abs coordinates - limit to only positive values - 1-0-1 - sort of mirrors one side.
Abs coordinates - limit to only positive values - 1-0-1 - sort of mirrors one side.


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
return vec4<f32>( abs_coords0.01.0 );
}



Random squares using the quantized coordinates.
Random squares using the quantized coordinates.


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
var quantized_coords floor10.0 abs_coords );
    
    var 
illum random2quantized_coords );
                                 
    return 
vec4<f32>( vec3(illum), 1.0 );
}



Random colors instead of grayscale.
Random colors instead of grayscale.


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
var quantized_coords floor10.0 abs_coords );
    
    var 
rgb vec3random2quantized_coords 1.0 ),
                    
random2quantized_coords 0.7 ),
                    
random2quantized_coords 0.3 ) );
                                 
    return 
vec4<f32>( rgb1.0 );
}



Color gradient using selected colors instead of just randomness.
Color gradient using selected colors instead of just randomness.


fn gradient_color(valuef32) -> vec3<f32> {
    
// Clamp the input value to the range [0.0, 1.0]
    
let t clamp(value0.01.0);

    
// Define gradient colors
    
let colors = array<vec3<f32>, 5>(
        
vec3<f32>(1.01.01.0),      // White
        
vec3<f32>(0.750.750.75),  // Gray
        
vec3<f32>(0.870.720.53),  // Light Brown
        
vec3<f32>(0.650.450.25),  // Brown
        
vec3<f32>(0.450.270.07)   // Dark Brown
    
);

    
// Determine segment and local interpolation factor
    
let segment 4.0// Map t to range [0, 4]
    
let index floor(segment); // Segment index
    
let local_t fract(segment); // Local interpolation factor

    // Interpolate between two consecutive colors
    
return mix(colors[i32(index)], colors[i32(index 1)], local_t);
}


@
fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
     
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
var quantized_coords floor10.0 abs_coords );
    
    var 
rv random2quantized_coords );
    
    var 
rgb gradient_colorrv );
                                 
    return 
vec4<f32>( rgb1.0 );
}


Other gradient colors - repeating values to emphasis particular colors.


Mix in a bit of green with more white and gray.
Mix in a bit of green with more white and gray.


const numGradients:i32 8;
fn 
gradient_color(valuef32) -> vec3<f32> {
    
// Clamp the input value to the range [0.0, 1.0]
    
let t clamp(value0.01.0);

    
// Define gradient colors
    
let colors = array<vec3<f32>, numGradients>(
        
vec3<f32>(1.01.01.0),      // White
        
vec3<f32>(1.0),  
        
vec3<f32>(0.750.750.75), 
        
vec3<f32>(0.750.750.75),  // Gray
        
vec3<f32>(0.750.750.75),  // Gray
        
vec3<f32>(0.370.520.33),  
        
vec3<f32>(0.650.450.25),  // Brown
        
vec3<f32>(0.450.270.07)   // Dark Brown
    
);

    
// Determine segment and local interpolation factor
    
let segment * ( f32(numGradients) - 1.0 ); // Map t to range [0, numGradients]
    
let index floor(segment); // Segment index
    
let local_t fract(segment); // Local interpolation factor

    // Interpolate between two consecutive colors
    
return mix(colors[i32(index)], colors[i32(index 1)], local_t);
}



Towards Infinity


Simple example of taking a screen full of squares
floor(..)
function and the uv coordinates - then the vertical texture coordinate that goes from 0 to 1 - which is used for the
depth
(divide it by the distance so as it goes further away it gets smaller).

We need to make sur ew use the normalized coordinates with the origin in the middle of the screen so that is the far distance point.


Shows a grid of squares so they
Shows a grid of squares so they're scaled using the vertical value - as the value increases they get smaller (distance).


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords 512.0;
    
    var 
vertical input_coords.y// 0 to 1

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.1;   
    var 
block_size lo + (hi lo) * (vertical);

    
// Calculate normalized coordinates -1 to 1 - we want to divide by the 
    // normalize (0 in the middle) - so when we divide the value it goes towards
    // the middle and not the left (if 0,0 is on the left side).  
    // Useful to know if you want to move the target point!
    
var normalized_coords = (2.0 pixel_coords resolution.xy) / resolution.y;
    
    var 
quantized_coords floor10.0 normalized_coords block_size );
                                 
    
// Generate a random color based on the quantized coordinates
    
var rr random2(quantized_coords);
    
    return 
vec4<f32>( vec3(rr), 1.0);
}


If we modify the code so instead of just going from 0 to 1 for the vertical - we use the normalized value that goes from -1 to 1 - we pass it through an
abs(..)
function - so it remains positive - which gives us 1.0->0.0->1.0 for the texture coordinate.

var vertical abs(normalized_coords.y); // 1->0->1


Gives us the following output:


Normalized vertical coordinates instead of just a vertical value of 0 to 1 - we use 1-0-1. Works but it
Normalized vertical coordinates instead of just a vertical value of 0 to 1 - we use 1-0-1. Works but it's the wrong way around!


This is the full code - same as before - except the modified line:
@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords 512.0;
    
    
// Calculate normalized coordinates -1 to 1 - we want to divide by the 
    // normalize (0 in the middle) - so when we divide the value it goes towards
    // the middle and not the left (if 0,0 is on the left side).  
    // Useful to know if you want to move the target point!
    
var normalized_coords = (2.0 pixel_coords resolution.xy) / resolution.y;
    
    var 
vertical 1.0 abs(normalized_coords.x); // 1->0->1

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.3;   
    var 
block_size lo + (hi lo) * (vertical);

    var 
quantized_coords floor10.0 normalized_coords block_size );
                                 
    
// Generate a random color based on the quantized coordinates
    
var rr random2(quantized_coords);
    
    return 
vec4<f32>( vec3(rr), 1.0);
}


It is working - but we need to fix the values; so instead of 1-0-1 we use 0-1-0 to get the correct output.


The following will invert the coordinates and give us a better result:

var vertical 1.0 abs(normalized_coords.y); // 0->1->0



Vertical top and bottom fade away towards the middle of the screen (into the distance).
Vertical top and bottom fade away towards the middle of the screen (into the distance).



Simply a matter of changing the
.y
to
.x
to see the same thing for the horizontal.

var vertical 1.0 abs(normalized_coords.x); // 1->0->1



Squares fade away on the horizontal - by checking the width instead of the height for the texture coordinates.
Squares fade away on the horizontal - by checking the width instead of the height for the texture coordinates.


The big step after this - is to have both the horizontal and vertical! To create a tunnel effect.

We'll check which is the biggest value - the horizontal or vertical and use this to divide as the distance.

@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords 512.0;
    
    
// Calculate normalized coordinates -1 to 1 - we want to divide by the 
    // normalize (0 in the middle) - so when we divide the value it goes towards
    // the middle and not the left (if 0,0 is on the left side).  
    // Useful to know if you want to move the target point!
    
var normalized_coords = (2.0 pixel_coords resolution.xy) / resolution.y;
    
    var 
delta 1.0 abs(normalized_coords); // 1->0->1
    
var distance delta.y;
    if ( 
delta.delta.) { distance delta.x; }

    
// map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.3;   
    var 
block_size lo + (hi lo) * (distance);

    var 
quantized_coords floor10.0 normalized_coords block_size );
                                 
    
// Generate a random color based on the quantized coordinates
    
var rr random2(quantized_coords);
    
    return 
vec4<f32>( vec3(rr), 1.0);
}



Squares fade away into the centre of the screen (origin point).
Squares fade away into the centre of the screen (origin point).



If you'll notice the squares seems stretched - and not correct, so we fix this by modifying the calculation.

@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;
    
    
// Calculate normalized coordinates -1 to 1 - we want to divide by the 
    // normalize (0 in the middle) - so when we divide the value it goes towards
    // the middle and not the left (if 0,0 is on the left side).  
    // Useful to know if you want to move the target point!
    
var normalized_coords = (2.0 input_coords 1.0);// -1.0 to 1.0
    
    
var vertical vec2(1.0,0.0) - abs(normalized_coords); // 1->0->1 - **only offset one**

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.2;   
    var 
block_size lo + (hi lo) * (vertical);
    
    var 
quantized_coords floor10.0 * (vertical) / block_size.);
                                 
    
// Generate a random color based on the quantized coordinates
    
var rr random2(quantized_coords);
    
    return 
vec4<f32>( vec3(rr), 1.0);
}



Horizontal squares mapped to disapear into the distance in the centre.
Horizontal squares mapped to disapear into the distance in the centre.


Do both the horizont and vertical, check the side using
if (abs_coords.y > abs_coords.x)
.

@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;
    
    var 
normalized_coords = (2.0 input_coords 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords ); // 1->0->1
    
    
var vertical   vec2(1.0,0.0) - abs_coords// 1->0->1 - **only offset one**
    
var horizontal vec2(0.0,1.0) - abs_coords// 1->0->1 - **only offset one**

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.2;   
    var 
block_size lo + (hi lo) * ( vec2(1.0,1.0) - abs_coords );
    
    var 
quantized_coords floor10.0 * (vertical) / block_size.);
    
    if (
abs_coords.abs_coords.x) {
        
quantized_coords floor10.0 * (horizontal) / block_size.);
    }
    
    
// Generate a random color based on the quantized coordinates
    
var rr random2(quantized_coords);
    
    return 
vec4<f32>( vec3(rr), 1.0);
}



Scale the squares so they shrink away into the distance (at the middle).
Scale the squares so they shrink away into the distance (at the middle).



Mix a bit of black in with the color - so it fades away to black in the distance - we can use the mix (lerp) function
rr = mix( 0.0, rr, pow(length(abs_coords),2) )
as shown in the following:


@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;
    
    var 
normalized_coords = (2.0 input_coords 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords ); // 1->0->1
    

    
var vertical   vec2(1.0,0.0) - abs_coords// 1->0->1 - **only offset one**
    
var horizontal vec2(0.0,1.0) - abs_coords// 1->0->1 - **only offset one**

    
    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.0;   
    var 
block_size lo + (hi lo) * ( vec2(1.0,1.0) - abs_coords );
    
    var 
quantized_coords floor10.0 * (vertical) / block_size.);
    
    if (
abs_coords.abs_coords.x) {
        
quantized_coords floor10.0 * (horizontal) / block_size.);
    }
    
    
// Generate a random color based on the quantized coordinates
    
var rr random2quantized_coords );
    
    
rr mix0.0rrpow(length(abs_coords),2) );
    
    return 
vec4<f32>( vec3(rr), 1.0);
}



Mix in a bit of black so the squares further away fade to black.
Mix in a bit of black so the squares further away fade to black.



Animation - Moving Along Tunnel


Mix in a bit of offset with the random number calculation - to create the illusion that we're moving through the pixelated tunnel.

@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;
    
    
let tt mytimer;
    
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
var vertical   vec2(1.0,0.0) - abs_coords// 1->0->1 - **only offset one**
    
var horizontal vec2(0.0,1.0) - abs_coords// 1->0->1 - **only offset one**

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.0;   
    var 
block_size lo + (hi lo) * ( vec2(1.0,1.0) - abs_coords );
    
    var 
quantized_coords floor10.0 * (vertical) / block_size.10.0*vec2(tt,0.0) );
    
    if (
abs_coords.abs_coords.x) {
        
quantized_coords floor10.0 * (horizontal) / block_size.10.0*vec2(0.0,tt) );
    }
    
    
// Generate a random color based on the quantized coordinates
    
var rr random2quantized_coords );
    
    
rr mix0.0rrpow(length(abs_coords),2) );
    
    return 
vec4<f32>( vec3(rr), 1.0);
}



Add mytimer uniform value to the coordinates to create a scolling effect - looks like we
Add mytimer uniform value to the coordinates to create a scolling effect - looks like we're constantly going down the tunnel.



Tinker with Colors


While grayscale does look good - adding a bit of color can make things more interesting - and we'll set the ground to brown - so make some of the squares brown.

@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;
    
    
let tt mytimer;
    
    var 
normalized_coords = (2.0 * (input_coords) - 1.0); // -1.0 to 1.0
    
    
var abs_coords absnormalized_coords  ); // 1->0->1
    
    
var vertical   vec2(1.0,0.0) - abs_coords// 1->0->1 - **only offset one**
    
var horizontal vec2(0.0,1.0) - abs_coords// 1->0->1 - **only offset one**

    // map the 0->1 to lo->hi value
    
let lo 1.0;  
    
let hi 0.0;   
    var 
block_size lo + (hi lo) * ( vec2(1.0,1.0) - abs_coords );
    
    var 
quantized_coords floor10.0 * (vertical) / block_size.10.0*vec2(tt,0.0) );
    
    if (
abs_coords.abs_coords.x) {
        
quantized_coords floor10.0 * (horizontal) / block_size.10.0*vec2(0.0,tt) );
    }
    
    
// Generate a random color based on the quantized coordinates
    
var rr random2quantized_coords );
    
    
    
let background_color vec3<f32>(0.780.570.4);
    
    
// mix some squares with brown
    
var color mixbackground_colorvec3(rr), clamp(pow(rr+0.75),0.01.0) );
    
    
// fade to black in the distance
    
color mixvec3(0.0), colorpow(length(abs_coords),2) );
    
    return 
vec4<f32>( color1.0);
}



Mixing in brown color on top of the dark grayscale squares.
Mixing in brown color on top of the dark grayscale squares.



We can use a color gradient from the random value instead of just blending to give greater control over each squares color.



Brown color gradient - with a  touch of green.
Brown color gradient - with a touch of green.


const numGradients:i32 8;
fn 
gradient_color(valuef32) -> vec3<f32> {
    
// Clamp the input value to the range [0.0, 1.0]
    
let t clamp(value0.01.0);

    
// Define gradient colors
    
let colors = array<vec3<f32>, numGradients>(
        
vec3<f32>(0.650.450.25), 
        
vec3<f32>(0.650.450.25), 
        
vec3<f32>(0.650.450.25)*0.7,
        
vec3<f32>(0.650.450.25)*0.5,
        
vec3<f32>(0.650.550.25)*0.7,  
        
vec3<f32>(0.650.450.25)*0.6,
        
vec3<f32>(0.450.270.07),
        
vec3<f32>(0.450.270.07)
    );

    
// Determine segment and local interpolation factor
    
let segment * ( f32(numGradients) - 1.0 ); // Map t to range [0, numGradients]
    
let index floor(segment); // Segment index
    
let local_t fract(segment); // Local interpolation factor

    // Interpolate between two consecutive colors
    
return mix(colors[i32(index)], colors[i32(index 1)], local_t);
}


Some other color values


Other gradient values for the color range.
Other gradient values for the color range.


const numGradients:i32 8;
fn 
gradient_color(valuef32) -> vec3<f32> {
    
// Clamp the input value to the range [0.0, 1.0]
    
let t clamp(value0.01.0);

    
let col0 vec3<f32>(111/255.085/255.060.0/255.0);
    
let col1 vec3<f32>(139/255.0118/255.099.0/255.0);
    
let col2 vec3<f32>(45/255.031/255.021.0/255.0);
    
    
// Define gradient colors
    
let colors = array<vec3<f32>, numGradients>(
        
col0,
        
col0,
        
col0,
        
col1,
        
col1,
        
col1,
        
col2 1.5,
        
col2
    
);

    
// Determine segment and local interpolation factor
    
let segment * ( f32(numGradients) - 1.0 ); // Map t to range [0, numGradients]
    
let index floor(segment); // Segment index
    
let local_t fract(segment); // Local interpolation factor

    // Interpolate between two consecutive colors
    
return mix(colors[i32(index)], colors[i32(index 1)], local_t);
}


Minecraft Tunnel


Let's go a bit further - the tunnel is fun and interesting - but it can do with a few more tweaks! Add some multisampling to smooth out some of those jaggedy edges, also a few more colors and a train rail at the bottom.


Minecraft tunnel with some extra bells and whistles.
Minecraft tunnel with some extra bells and whistles.


// Generates a pseudo-random value based on a 2D input vector
fn random2(pointvec2<f32>) -> f32 {
    
let seed dot(pointvec2<f32>(127.1311.7)); // Generate a hash seed using dot product
    
return fract(sin(seed) * 43758.5453123);        // Return a pseudo-random fractional value
}

// Custom smoothstep function for smooth interpolation
fn mysmoothstep(edge_startf32edge_endf32valuef32) -> f32 {
    
let t clamp((value edge_start) / (edge_end edge_start), 0.01.0); // Clamp and normalize value
    
return * (3.0 2.0 t); // Cubic Hermite interpolation for smooth transition
}

// Fragment shader main function
@fragment
fn main(@location(0input_coordsvec2<f32>) -> @location(0vec4<f32> {
    
// Scale the input coordinates
    
var pixel_coords input_coords;

    
// Initialize colors
    
var black vec3<f32>(0); // Base color (black)
    
var white vec3<f32>(1); // Secondary color (white)
    
var total_color black;   // Accumulator for final color



Multiple samples for each pixel to smooth out jaggedyness (anti-aliasing).
Multiple samples for each pixel to smooth out jaggedyness (anti-aliasing).


    // Perform anti-aliasing by sampling the pixel multiple times
    
for (var sample_xi32 0sample_x NUM_SAMPLESsample_x sample_x 1) {
        for (var 
sample_yi32 0sample_y NUM_SAMPLESsample_y sample_y 1) {
            
// Offset for sub-pixel sampling
            
var sample_offset = (vec2<f32>(f32(sample_x), f32(sample_y)) / f32(NUM_SAMPLES)) - 0.5;
            
sample_offset *= 1.0/resolution;
            
            
// Calculate normalized coordinates -1.0 to 1.0
            
var normalized_coords = (2.0 pixel_coords sample_offset 1.0);

            
// Compute additional parameters for distortion and patterns
            
let normalized_x normalize(normalized_coords).x;



Add a bit of normalization to the coordinates to
Add a bit of normalization to the coordinates to 'round' the edges - so it's less square.


            // warp the tunnel so it is 'rounded' a bit instead of flat edges
            
normalized_coords *= length(normalized_coords) * 0.9 0.1;
            
            
let abs_coords abs(normalized_coords); // 1-0-1 - mirror positive side



Scale the pixelated block size - smaller or larger blocks.
Scale the pixelated block size - smaller or larger blocks.


            // Scaling and timing factors for animation and patterns
            
let size 10.0;
            
let time mytimer 8.0;
            
let max_component max(abs_coords.xabs_coords.y);
            
let inverse_max size max_component;

            
// Intermediate calculations for texture and hash lookups
            
let texture_coords vec2<f32>(inverse_max time0.0);
            
let cell_coords floor(normalized_coords inverse_max texture_coords.yx);

            
// Generate noise for two color channels
            
let noise_x vec3<f32>(random2(floor(normalized_coords inverse_max texture_coords.xy)));
            var 
noise_y vec3<f32>(random2(cell_coords 0.0));



Extra lines of code for adding a train track to the bottom of the tunnel.
Extra lines of code for adding a train track to the bottom of the tunnel.


            // Add in the train tracks to the bottom
            
if (normalized_coords.0.0) {
                
// Additional noise values for variations
                
let noise_g random2(cell_coords 0.2);
                
let noise_h random2(cell_coords 0.3);
            
                
// Threshold-based patterns
                
let pattern_threshold step(abs(normalized_x), 0.68);
                
let abs_normalized_x abs(normalized_x);
                
let edge_check_0 step(abs(abs_normalized_x 0.5), 0.08);
                
let edge_check_1 step(abs(abs_normalized_x 0.5), 0.03);
                
let edge_check_2 step(abs(abs_normalized_x 0.55), 0.04);
                
                
// wooden blocks
                
noise_y mix(noise_yvec3<f32>(0.7, -0.05, -0.5) - noise_g 0.5step(sin(texture_coords.1.5), -0.9) * pattern_threshold);
                
noise_y mix(noise_yvec3<f32>(0.90.2, -0.5) + noise_h 0.5step(sin(texture_coords.0.45), -0.8) * pattern_threshold);
                
// metal track
                
noise_y -= edge_check_0 0.4;
                
noise_y mix(noise_yvec3<f32>(-0.2) * noise_h 2.0edge_check_1);
                
noise_y mix(noise_yvec3<f32>(3.0) - noise_h 1.1edge_check_2);
            }



Map the noise to the horizontal and vertical - use an if statement so it creates flat edges (square).
Map the noise to the horizontal and vertical - use an if statement so it creates flat edges (square).


            // Horizontal and vertical edges of the tunnel
            
var sample_colorvec3<f32>;
            if (
abs_coords.abs_coords.y) {
                
sample_color noise_x;
            } else {
                
sample_color noise_y;
            }



Soften the colors.
Soften the colors.


            // Adjust base color and mix with patterns
            
sample_color 0.5 sample_color 0.4;



Get the edge values to darken the corners of the tunnel.
Get the edge values to darken the corners of the tunnel.


            // Smooth transitions and edge blending
            
let diagonal_distance abs(-dot(abs_coordsvec2<f32>(-1.01.0)));
            
sample_color mix(blacksample_colorpow(diagonal_distance,0.3) );



Mixing in a bit of color to make the grayscale walls a bit more interesting.
Mixing in a bit of color to make the grayscale walls a bit more interesting.


            // Background gradient blending
            
let background_color vec3<f32>(0.780.570.4) * 5.5 mysmoothstep(5.0, -0.4length(normalized_coords));
            
sample_color *= mix(sample_colorbackground_colormysmoothstep(0.22.5length(normalized_coords) * 1.0));

            
// Accumulate color for anti-aliasing
            
total_color += sample_color;
        }
    }

    
// Average accumulated color across all samples
    
total_color /= f32(NUM_SAMPLES NUM_SAMPLES);

    
// return the final color
    
return vec4<f32>(total_color1.0);
}



Animation for the tunnel - see its effect as you move along inside.
Animation for the tunnel - see its effect as you move along inside.



Things to Try


• Shift the origin point so the train tunnel seems to move left and right as it moves forward
• Other color schemes
• Interaction - mouse cursor or keyboard can be used to speedup/slowdown the animation
• Try adding a 3d box or train (e.g., sdf model of a train or character on the tracks)


Resources & Links


Minimal Working Example (Code/Demo)

More Complex Version (Code/Demo)

















WebGPU Development Pixels - coding fragment shaders from post processing to ray tracing! WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Games WGSL 2d 3d interactive web-based fun learning WebGPU Compute WebGPU API - Owners WebGPU Development Cookbook - coding recipes for all your webgpu needs! WebGPU & WGSL Essentials: A Hands-On Approach to Interactive Graphics, Games, 2D Interfaces, 3D Meshes, Animation, Security and Production Kenwright graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu for dummies kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright WebGPU Shader Language Development: Vertex, Fragment, Compute Shaders for Programmers Kenwright wgsl webgpugems shading language cookbook kenwright WGSL Fundamentals book kenwright WebGPU Data Visualization Cookbook kenwright Special Effects Programming with WebGPU kenwright WebGPU Programming Guide: Interactive Graphics and Compute Programming with WebGPU & WGSL kenwright Ray-Tracing with WebGPU kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.