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.....

 


Sky Box


Skybox is a trick to sit within a textured cube - so when the camera looks around - you think you're in an open world. Later on when we get to lighting and reflections - the shiny surfaces need to reflect something - and the skybox is the solution.


A cube has  sides. Make a texture to each side (6 textures). These textures are seamlessly mapped - so they wrap around without...
A cube has sides. Make a texture to each side (6 textures). These textures are seamlessly mapped - so they wrap around without showing the edges. Then sitting inside that cube you think you're in the open world.


Functions Used: performance.now(), getContext(), requestAdapter(), getPreferredCanvasFormat(), createCommandEncoder(), beginRenderPass(), setPipeline(), draw(), end(), submit(), getCurrentTexture(), createView(), createShaderModule()


var script = document.createElement('script');
script.type  = 'text/javascript';
script.async = false;
script.src   = 'https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.6.0/gl-matrix-min.js';
document.head.appendChild(script); 


const canvas = document.createElement('canvas');
document.body.appendChild( canvas );
canvas.width  = canvas.height = 512;
console.log( canvas.width, canvas.height );
const context = canvas.getContext('webgpu');

const gpu = navigator.gpu;
console.log( 'gpu:', gpu );

const adapter = await gpu.requestAdapter();
const device  = await adapter.requestDevice();

const presentationSize = [ canvas.clientWidth,
                           canvas.clientHeight  ];
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); // context.getPreferredFormat(adapter); - no longer supported
console.log( presentationFormat  );

context.configure({ device: device,
                    format: presentationFormat,
                    alphaMode: 'opaque' });

var positions =  new Float32Array([
    -1, 1, 1,  -1,-1, 1,   1,-1, 1,   1, 1, 1, // front
    -1, 1,-1,  -1,-1,-1,   1,-1,-1,   1, 1,-1, // back
     1,-1, 1,   1,-1,-1,   1, 1,-1,   1, 1, 1, // left
    -1,-1, 1,  -1,-1,-1,  -1, 1,-1,  -1, 1, 1, // right
    
    -1,-1,-1,  -1,-1, 1,   1,-1, 1,   1,-1,-1,
    -1, 1,-1,  -1, 1, 1,   1, 1, 1,   1, 1,-1 ]);

var indices = new Uint32Array([
    0, 1, 2,    0, 2, 3,   
      4, 5, 6,    4, 6, 7,
    8, 9, 10,   8, 10,11,  
      12,13,14,     12,14,15,
    16,17,18,   16,18,19,  
      20,21,22,   20,22,23 ]);

var texCoords  = new Float32Array([
    0,0, 0,1, 1,1,  1,0, 
    1,0, 1,1, 0,1,  0,0, 
  
    0,1, 1,1, 1,0, 0,0,
    1,1, 0,1, 0,0, 1,0,
  
    0,1 , 0,0, 1,0, 1,1, 
    0,0 , 0,1, 1,1, 1,0 ]);

var texIds = new Int32Array([
    0, 0, 0, 0, // front
    1, 1, 1, 1, // back
    2, 2, 2, 2, // right
    3, 3, 3, 3, // left
    4, 4, 4, 4, // up
    5, 5, 5, 5  // down
]);


const positionBuffer = device.createBuffer({
  size:  positions.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

const texCoordsBuffer = device.createBuffer({
  size:  texCoords.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

const texIdsBuffer = device.createBuffer({
  size:  texIds.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

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

device.queue.writeBuffer(positionBuffer,  0, positions);
device.queue.writeBuffer(texCoordsBuffer, 0, texCoords);
device.queue.writeBuffer(texIdsBuffer,    0, texIds   );
device.queue.writeBuffer(indicesBuffer ,  0, indices  );

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

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

@vertex
fn main(@location(0) inPos     : vec3<f32>,
        @location(1) texCoords : vec2<f32>,
        @location(2) texId     : i32        ) -> VSOut  
{ 
  var vm : mat4x4<f32> = uniforms.viewMatrix;
  vm[3][0]  = 0.0;
  vm[3][1]  = 0.0;
  vm[3][2]  = 0.0;
  
  var vsOut: VSOut;
  vsOut.Position = uniforms.projMatrix * vm * vec4<f32>( inPos*2.0, 1.0);
  vsOut.uvs      = texCoords;
  vsOut.texId    = f32(texId);
  return vsOut;

}`;

var fragWGSL = `
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d_array<f32>;

@fragment
fn main( @location(0) uvs    : vec2<f32>,
         @location(1) texId  : f32       ) -> @location(0) vec4<f32> 
{
    let tid = i32(texId);
    //if ( tid < 0 )
    //{
    //    return vec4<f32>(1.0, 0.0, 0.0, 1.0);
    //}
    return textureSample(myTexture, mySampler, uvs, tid );
}`;

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

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

// -------------------------------------------------
// texture image
// -------------------------------------------------
/*
let imgs = [ 'https://webgpulab.xbdev.net/var/images/sky01/rt.png',  // 0-1-2-3
             'https://webgpulab.xbdev.net/var/images/sky01/lf.png',  // 2-3-0-1
             'https://webgpulab.xbdev.net/var/images/sky01/ft.png',  // 1-2-3-0
             'https://webgpulab.xbdev.net/var/images/sky01/bk.png',  // 3-0-1-2
             'https://webgpulab.xbdev.net/var/images/sky01/dn.png',
               'https://webgpulab.xbdev.net/var/images/sky01/up.png'
              ]; 
*/

/*
let imgs = [ 'https://webgpulab.xbdev.net/var/images/sky03/rt.jpg',  
             'https://webgpulab.xbdev.net/var/images/sky03/lf.jpg', 
             'https://webgpulab.xbdev.net/var/images/sky03/ft.jpg',  
             'https://webgpulab.xbdev.net/var/images/sky03/bk.jpg', 
             'https://webgpulab.xbdev.net/var/images/sky03/dn.jpg',
               'https://webgpulab.xbdev.net/var/images/sky03/up.jpg'
              ]; 
*/

let imgs = [ 'https://webgpulab.xbdev.net/var/images/sky05/posz.jpg', 
             'https://webgpulab.xbdev.net/var/images/sky05/negz.jpg',
             'https://webgpulab.xbdev.net/var/images/sky05/posx.jpg',
             'https://webgpulab.xbdev.net/var/images/sky05/negx.jpg',
             'https://webgpulab.xbdev.net/var/images/sky05/negy.jpg',
               'https://webgpulab.xbdev.net/var/images/sky05/posy.jpg'
              ]; 


/*
let imgs = [ 'https://webgpulab.xbdev.net/var/images/sky02/posz.jpg', 
             'https://webgpulab.xbdev.net/var/images/sky02/negz.jpg',
             'https://webgpulab.xbdev.net/var/images/sky02/posx.jpg',
             'https://webgpulab.xbdev.net/var/images/sky02/negx.jpg',
             'https://webgpulab.xbdev.net/var/images/sky02/negy.jpg',
               'https://webgpulab.xbdev.net/var/images/sky02/posy.jpg'
              ];
*/
const basicTexture = device.createTexture({
    size: [1024, 1024, 6],
    format: presentationFormat , // "bgra8unorm", // "rgba8unorm",
    usage: 0x4 | 0x2 // GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
    //usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT 
});


let textureData= new Uint8Array( 6 * 1024 * 1024 * 4); // 6 images

// scale/stretch all images 
for (let i=0; i<imgs.length; i++)
{
  const img = document.createElement("img");
  img.src = imgs[i];
  await img.decode();

  const imageCanvas = document.createElement('canvas');
  imageCanvas.width =  1024; // img width;
  imageCanvas.height = 1024; // 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);

  for (let x=0; x<1024*1024*4; x++)
  {
     textureData[1024*1024*4*i + x] = imageData.data[ x ];
  }
}



device.queue.writeTexture(
            { texture: basicTexture },
            textureData,
            {   offset     :  0,
                bytesPerRow:  1024* 4, // width * 4 (4 bytes per float)
                rowsPerImage: 1024     // height
             },
            [ 1024  ,  1024,  6  ]   );


// GPUTextureView 
let texView = basicTexture.createView( { format: presentationFormat , 
                                         dimension: '2d-array',
                                         aspect: 'all',
                                         arrayLayerCount: 6 }  );

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

const projectionMatrix     = mat4.create();
const viewMatrix           = mat4.create();

mat4.perspective(projectionMatrix, Math.PI / 2, canvas.width / canvas.height, 0.1, 10.0)
mat4.lookAt(viewMatrix, [0, 0, -3], [0, 0, 0], [0, 1, 0]);
//mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix);

const vertexUniformBuffer = device.createBuffer({
  size: 128,
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});

device.queue.writeBuffer(vertexUniformBuffer,   0,  viewMatrix       );
device.queue.writeBuffer(vertexUniformBuffer,   64, projectionMatrix );

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

const 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-array"} } 
  ]
});

const uniformBindGroup = device.createBindGroup({
  layout:   sceneUniformBindGroupLayout,
  entries: [
    { binding : 0, resource: { buffer: vertexUniformBuffer } },
    { binding : 1, resource: textureSampler },
    { binding : 2, resource: texView        },
   ],
});

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


const pipeline = device.createRenderPipeline({
    layout: device.createPipelineLayout({bindGroupLayouts: [sceneUniformBindGroupLayout]}),
    vertex:   {  module    : device.createShaderModule({ 
                             code : vertWGSL }),
                 entryPoint: 'main',
                 buffers    : [ { arrayStride: 12, attributes: [{ shaderLocation: 0,
                                                                  format: "float32x3",
                                                                  offset: 0  }]        },
                                { arrayStride: 8,  attributes: [{ shaderLocation: 1,
                                                                  format: "float32x2",
                                                                  offset: 0  }]        },
                                { arrayStride: 4,  attributes: [{ shaderLocation: 2,
                                                                  format: "sint32", // negative means no tex
                                                                  offset: 0  }]        }
                              ]},
    fragment: {  module    : device.createShaderModule({ 
                             code : fragWGSL,     }),
                 entryPoint: 'main',
                 targets: [{  format : presentationFormat  }] },
    primitive: { topology  : 'triangle-list',
                 frontFace : "ccw",
                 cullMode  : 'none',
                 stripIndexFormat: undefined }
                 
});

// GPURenderPassDescriptor 
const renderPassDescriptor = { 
           colorAttachments:  [{    
           view     : undefined, // asign later in frame
           loadOp:"clear", clearValue: { r: 0.0, g: 0.5, b: 0.5, a: 1.0 },
           storeOp  : 'store' }]
};



let mi = new mouseinput( canvas, 0,0.0, 0.4  );

function frame() 
{
    let rot = mi.getPos();
    let dis = mi.getPos().d;
    let yaw   = rot.x;
    let pitch = rot.y;

    const ta = vec3.fromValues( 0.0, 0.0, 0.0 );

    let cos = Math.cos;
    let sin = Math.sin;

    let dir = vec3.create();
    dir[2] = -cos( yaw   ) * cos( pitch );
    dir[1] = sin( pitch );
    dir[0] = sin( yaw   ) * cos( pitch );

    let va = vec3.create(); vec3.add( va, ta, dir );
    vec3.scale( va, va, dis );
  
    mat4.perspective(projectionMatrix, Math.PI / 2, canvas.width / canvas.height, 0.01, 5000.0)
    mat4.lookAt(viewMatrix, va,  ta, [0, 1, 0]);
  

    device.queue.writeBuffer(vertexUniformBuffer,   0,  viewMatrix       );
    device.queue.writeBuffer(vertexUniformBuffer,   64, projectionMatrix );


    //  ---------------------
  
    renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView();

    const commandEncoder = device.createCommandEncoder();

    const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(pipeline);

    renderPass.setBindGroup(0, uniformBindGroup);
    renderPass.setVertexBuffer(0, positionBuffer);
    renderPass.setVertexBuffer(1, texCoordsBuffer);
    renderPass.setVertexBuffer(2, texIdsBuffer);
    renderPass.setIndexBuffer(indicesBuffer, 'uint32');
    renderPass.drawIndexed(indices.length, 1, 0, 0);

    renderPass.end();
    device.queue.submit([commandEncoder.finish()]);

    // if you want constant updates (animated) - keep refreshing
    requestAnimationFrame(frame);
};
frame();




function mouseinput(elin, xa, ya, dd)
{
   var base = {x:xa, y:ya, d:dd};
   var tmp  = {x:xa, y:ya, d:dd};
   var el = elin;
   el.addEventListener('mousedown', downListener);
   el.addEventListener('mouseup',   upListener  );
   el.addEventListener('mouseout',  upListener  );
   el.addEventListener("wheel",     wheel       );
   var start = {x:0, y:0};
  
   function downListener(e){
     if (e.button !== 0 ) return; // left
       
     start = {x:e.pageX, y:e.pageY};
     tmp   = {x:base.x,  y:base.y,  d:base.d };
     el.addEventListener('mousemove', moveListener)
   }
   function upListener(e){
     el.removeEventListener('mousemove', moveListener)
   }
   function moveListener(e){
     //console.log('moving..');
     const diffX = (e.pageX - start.x);
     const diffY = (e.pageY - start.y);
     const delta = 0.01;
     base.x = tmp.x + diffX*delta;
     base.y = tmp.y + diffY*delta;
   }
   function wheel(e){
     base.d -= e.deltaY * 0.0001;
   }
  
   this.getPos = function()
   {
     return base;
   }
}// end mouseinput


console.log('ready... click mouse and drag to look around..');






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.