www.xbdev.net
xbdev - software development
Thursday June 4, 2026
Home | Contact | Support | WebGPU Graphics and Compute ... | WebGPU.. Games, Tutorials, Demos, Projects, and Code.....
     
 

WebGPU..

Games, Tutorials, Demos, Projects, and Code.....

 


Fog


Fog is an easy effect that helps manage the complexity of your scene - as things that are very far away can be made to `fade` into the distance - instead of either 'popping' due to the far plane clipping. You can add it in by using the 'depth' information you've stored in the depth buffer.


The calculated z-distances can be used as a
The calculated z-distances can be used as a 'fog' factor - as objects get further away they're made transparent or blending with the background color (so they disapear).


Functions Used: setVertexBuffer(), setIndexBuffer(), drawIndexed(), createBuffer(), getMappedRange(), getContext(), requestAdapter(), getPreferredCanvasFormat(), createCommandEncoder(), beginRenderPass(), setPipeline(), draw(), end(), submit(), getCurrentTexture(), createView(), createShaderModule()

For example purposes - we use the calculated depth (z-distance) and use it as the lighting color for the shape. As geometry gets further away into the distance its z value increase and it becomes more white.

<?php
sphere = function()
{
// ------------------------------------------------

function geo()
{
      let widthSegments  = 10, 
        heightSegments = 10, 
        radius = 1.0,
        phiStart = 0, phiLength = Math.PI * 2, 
        thetaStart = 0, thetaLength = Math.PI;

    widthSegments = Math.max( 3, Math.floor( widthSegments ) );
    heightSegments = Math.max( 2, Math.floor( heightSegments ) );

    const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );

    let index = 0;
    const grid = [];

    const vertex = {x:0, y:0, z:0};
    const normal = {x:0, y:0, z:0};

    // buffers
    const mesh = { v:[], n:[], t:[], i:[] }

    // generate vertices, normals and uvs
    for ( let iy = 0; iy <= heightSegments; iy ++ ) {

      const verticesRow = [];
      const v = iy / heightSegments;
      // special case for the poles

      let uOffset = 0;
      if ( iy === 0 && thetaStart === 0 ) {
        uOffset = 0.5 / widthSegments;
      } else if ( iy === heightSegments && thetaEnd === Math.PI ) {
        uOffset = - 0.5 / widthSegments;
      }

      for ( let ix = 0; ix <= widthSegments; ix ++ ) {
        const u = ix / widthSegments;
        // vertex
        vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
        vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
        vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
        mesh.v.push( [vertex.x, vertex.y, vertex.z] );

        // normal
        let vl = vertex.x*vertex.x + vertex.y*vertex.y + vertex.z*vertex.z;
        normal.x = vertex.x/vl;
        normal.y = vertex.y/vl;
        normal.z = vertex.z/vl;
        mesh.n.push( [normal.x, normal.y, normal.z] );

        // uv
        mesh.t.push( [u + uOffset, v] );
        verticesRow.push( index++ );
      }
      grid.push( verticesRow );
    }
  
      // indices
    for ( let iy = 0; iy < heightSegments; iy ++ ) {

      for ( let ix = 0; ix < widthSegments; ix ++ ) {

        const a = grid[ iy ][ ix + 1 ];
        const b = grid[ iy ][ ix ];
        const c = grid[ iy + 1 ][ ix ];
        const d = grid[ iy + 1 ][ ix + 1 ];

        if ( iy !== 0 || thetaStart > 0 ) mesh.i.push( a, b, d );
        if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) mesh.i.push( b, c, d );
      }
    }
    return mesh;
}

// -------------------------------
  
this.create = async function(device, presentationFormat, presentationSize, textureName="https://webgpulab.xbdev.net/var/images/earth.jpg")
{
let mesh = geo();

console.log('num Vertices:', mesh.v.length);
console.log('num Coords:' ,  mesh.t.length);
console.log('num Normals:',  mesh.n.length);
console.log('num Indices:',  mesh.i.length);

// -------------------------------------------------

// Lets biuld the buffers to hold the data

if ( mesh.n.length==0 ) mesh.n.length = mesh.v.length * 3;
if ( mesh.t.length==0 ) mesh.t.length = mesh.v.length * 2;
  
mesh.i = mesh.i.flat();
mesh.v = mesh.v.flat();
mesh.n = mesh.n.flat();
mesh.t = mesh.t.flat();
  
this.numTris         = mesh.i.length; // number of indices
  
// ------------------

const s = 1.0;
this.positions = new Float32Array( mesh.v );
this.indices   = new Uint32Array( mesh.i );
this.normals   = new Float32Array( mesh.n );
this.uvs       = new Float32Array( mesh.t );
this.timer     = new Float32Array([0.0]);
  
this.positionBuffer = device.createBuffer({
  size:  this.positions.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

this.normalBuffer = device.createBuffer({
  size:  this.normals.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

this.uvBuffer = device.createBuffer({
  size:  this.uvs.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

this.indicesBuffer = device.createBuffer({
  size:  this.indices.byteLength,
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
});

this.timerBuffer = device.createBuffer({
  size:  4, // single float
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});

device.queue.writeBuffer(this.positionBuffer, 0, this.positions);
device.queue.writeBuffer(this.normalBuffer  , 0, this.normals  );
device.queue.writeBuffer(this.indicesBuffer , 0, this.indices  );
device.queue.writeBuffer(this.uvBuffer      , 0, this.uvs      );
device.queue.writeBuffer(this.timerBuffer   , 0, this.timer    );

// var vertWGSL = document.getElementById('vertex.wgsl').innerHTML;
// var fragWGSL = document.getElementById('fragment.wgsl').innerHTML;

var vertWGSL = `
struct Uniforms {
    modelMatrix : mat4x4<f32>,
    viewMatrix  : mat4x4<f32>,
    projMatrix  : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;

struct VSOut {
    @builtin(position) Position : vec4<f32>,
    @location(0)       normal   : vec3<f32>,
    @location(1)       uvs      : vec2<f32>,
    @location(2)       pos      : vec4<f32>,
};

@vertex 
fn main(@location(0) inPos  : vec3<f32>,
        @location(1) normal : vec3<f32>,
        @location(2) uvs    : vec2<f32>) -> VSOut  
{ 

  let mvMatrix = uniforms.viewMatrix * uniforms.modelMatrix;
  let mvp      = uniforms.projMatrix * mvMatrix;
  
  var vsOut: VSOut;
  vsOut.Position   = mvp * vec4<f32>( inPos, 1.0);
  vsOut.uvs        = uvs;
  vsOut.normal     = ( uniforms.modelMatrix * vec4<f32>(normal, 0.0) ).xyz;
  vsOut.pos        = ( mvMatrix    * vec4<f32>(inPos,1.0) );
  return vsOut;
}
`;
  
var fragWGSL = `
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;
@group(0) @binding(3) var <uniform> myTimer:   f32;

@fragment
fn main(@location(0) normal   : vec3<f32>,
        @location(1) uvs      : vec2<f32>,
        @location(2) position : vec4<f32> ) -> @location(0) vec4<f32> 
{
    // calculate the 'fog' factor - use the scaled position
    // z goes from 0 to 1.0
    
    let pos = position / position.w;
    
    let dist = abs( pos.z * 0.1 );
    
    return vec4<f32>(dist, dist, dist, 1.0);
    
    /*
    let scrolluv = uvs + vec2<f32>( myTimer*0.2, 0.0  );
    let texCol = textureSample(myTexture, mySampler, scrolluv );
       
    let lightdirection = normalize( vec3<f32>(1, 0, 1) );
    let nnormal        = normalize( -normal );

    // scale brightness based on the normal vs ref drection
    let illum = clamp( dot( lightdirection, nnormal ), 0.0, 1.0 ) * 2.0;

    return vec4<f32>( texCol.xyz*illum , 1.0 ); 
    */

}
`;

// ----------------------------------------------------------------

let textureSampler = device.createSampler({
     minFilter:    "linear",
     magFilter:    "linear",
  
     addressModeU: "repeat",
     addressModeV: "repeat",
     addressModeW: "repeat", 
});

const img = document.createElement("img");
img.src = textureName; // 'https://webgpulab.xbdev.net/var/images/earth.jpg';
await img.decode();

const basicTexture = device.createTexture({
    size: [img.width, img.height, 1],
    format: "rgba8unorm" , // "bgra8unorm",
    usage:  GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
});  

const imageCanvas = document.createElement('canvas');
imageCanvas.width =  img.width;
imageCanvas.height = img.height;
const imageCanvasContext = imageCanvas.getContext('2d');
imageCanvasContext.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
const imageData = imageCanvasContext.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
const textureData = imageData.data;
  
device.queue.writeTexture( { texture: basicTexture },
            textureData,
            {   offset     :  0,
                bytesPerRow:  img.width * 4,
                rowsPerImage: img.height
             },
            [ img.width  ,  img.height,  1  ]   );


// ----------------------------------------------------------------

this.mvpUniformBuffer = device.createBuffer({
  size: 64*3,
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});

// ----------------------------------------------------------------

this.sceneUniformBindGroupLayout = device.createBindGroupLayout({
  entries: [
    { binding: 0, visibility: GPUShaderStage.VERTEX,   buffer:  { type: "uniform"  }   }, 
    { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering"}   },
    { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float",
                                                                  viewDimension: "2d"} },
    { binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer:  { type: "uniform"  }   }, 
  ]
});

this.uniformBindGroup = device.createBindGroup({
  layout:   this.sceneUniformBindGroupLayout,
  entries: [
    { binding : 0, resource: { buffer: this.mvpUniformBuffer } },
    { binding : 1, resource: textureSampler },
    { binding : 2, resource: basicTexture.createView()  },
    { binding : 3, resource: { buffer: this.timerBuffer        } }
   ],
});

// ----------------------------------------------------------------


this.pipeline = device.createRenderPipeline({
    layout: device.createPipelineLayout({bindGroupLayouts: [this.sceneUniformBindGroupLayout]}),
    vertex:   {  module    : device.createShaderModule({ 
                             code : vertWGSL }),
                 entryPoint: 'main',
                 buffers    : [ { arrayStride: 12, attributes: [{ shaderLocation: 0,
                                                                  format: "float32x3",
                                                                  offset: 0  }]        },
                                { arrayStride: 12, attributes: [{ shaderLocation: 1,
                                                                  format: "float32x3",
                                                                  offset: 0  }]        },
                                { arrayStride: 8,  attributes: [{ shaderLocation: 2,
                                                                  format: "float32x2",
                                                                  offset: 0  }]        }
                              ]},
    fragment: {  module    : device.createShaderModule({ 
                             code : fragWGSL,     }),
                 entryPoint: 'main',
                 targets: [{  format : presentationFormat  }] },
    primitive: { topology  : 'triangle-list',
                 frontFace : "ccw",
                 cullMode  : 'back',
                 stripIndexFormat: undefined },
    depthStencil: {
                 depthWriteEnabled: true,
                 depthCompare     : 'less',
                 format           : 'depth24plus' }
});

  
this.meshData = { active:false, p:{x:0,y:0,z:0}, r:{x:0,y:0,z:0}, s:{x:1,y:1,z:1} };
  
}// end create

// -------------------------------

this.getMeshData = function ()
{
    return this.meshData;
}
  
// ------------------------------

function buildMatrix( p, r, s )
{
    // Create the matrix in Javascript (using matrix library)
    const modelMatrix          = mat4.create();

    // create the model transform with a rotation and translation
    let translateMat = mat4.create();   mat4.fromTranslation( translateMat, Object.values(p) );
    let rotateXMat   = mat4.create();   mat4.fromXRotation(rotateXMat, r.x);
    let rotateYMat   = mat4.create();   mat4.fromYRotation(rotateYMat, r.y);
    let rotateZMat   = mat4.create();   mat4.fromZRotation(rotateZMat, r.z);
    let scaleMat     = mat4.create();   mat4.fromScaling(scaleMat, Object.values(s) );

    mat4.multiply(modelMatrix, modelMatrix,   translateMat);
    mat4.multiply(modelMatrix, modelMatrix,   rotateXMat);
    mat4.multiply(modelMatrix, modelMatrix,   rotateYMat);
    mat4.multiply(modelMatrix, modelMatrix,   rotateZMat);
    mat4.multiply(modelMatrix, modelMatrix,   scaleMat);
    return modelMatrix;
}

this.draw = async function( device, context, depthTexture, viewMatrix, projectionMatrix ) 
{

  // --------------------------------------------------
  // Update uniform buffer 
  
  const modelMatrix = buildMatrix( this.meshData.p, this.meshData.r, this.meshData.s );
  device.queue.writeBuffer(this.mvpUniformBuffer, 0,    modelMatrix);
  device.queue.writeBuffer(this.mvpUniformBuffer, 64,  viewMatrix );
  device.queue.writeBuffer(this.mvpUniformBuffer, 128, projectionMatrix);

  
  this.timer[0] = this.timer[0] + 0.01;
  device.queue.writeBuffer(this.timerBuffer   , 0, this.timer    );


  // --------------------------------------------------

  // GPURenderPassDescriptor 
  this.renderPassDescriptor = { 
        colorAttachments:  [{    
             view     : undefined, // asign later in frame
             loadOp:   "load", clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
             storeOp  : 'store' }],
        depthStencilAttachment: {
             view:  depthTexture.createView(),
             depthLoadOp: "load", 
             depthClearValue: 1.0,
             depthStoreOp: 'store',
          } 
  };
  
  // -------------------------------------------------
  
  this.renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView();

  const commandEncoder = device.createCommandEncoder();

  const renderPass = commandEncoder.beginRenderPass(this.renderPassDescriptor);
  renderPass.setPipeline(this.pipeline);
  renderPass.setBindGroup(0, this.uniformBindGroup);
  renderPass.setVertexBuffer(0, this.positionBuffer);
  renderPass.setVertexBuffer(1, this.normalBuffer);
  renderPass.setVertexBuffer(2, this.uvBuffer);
  renderPass.setIndexBuffer(this.indicesBuffer, 'uint32');
  renderPass.drawIndexed( this.numTris, 1, 0, 0);
  renderPass.end();
  await device.queue.submit([commandEncoder.finish()]);
  
}// end render(..)
  

  
}// end sphere





Resources and Links


• WebGPU Lab Example [LINK]













WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Compute 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 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 wgsl webgpugems shading language cookbook kenwright



 
Advert (Support Website)

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