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

WebGPU..

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

 


Rotating Cube (Voxel)



The famous cube! You might know it from voxel worlds like Minecraft or Roboblox. Cubes are like small 3d pixels (but instead of pixels they can represent small geometric elements in the 3d world). If they get small enough - and you have enough of them - you can create highly realistic world.


The famous 3D cube - think of a cube as
The famous 3D cube - think of a cube as 'virtual' lego blocks.


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


WARNING
Cubes (or voxels) are great - but most rasterization engines are built around triangles. Voxels are popular due to their interlocking ability - like lego!


The example essentially builds a cube from positions and colors (no texturing, external libraries) - just a unit cube. The cube is rotated using some simple linear maths in the WGSL shader.


const canvas document.createElement('canvas');
document.body.appendChildcanvas );
canvas.width  canvas.height 512;

const 
gpu navigator.gpu;
console.log'navigator.gpu:'gpu );

const 
adapter await gpu.requestAdapter();
const 
device  await adapter.requestDevice();
const 
context canvas.getContext('webgpu');

const 
presentationFormat navigator.gpu.getPreferredCanvasFormat(); // context.getPreferredFormat(adapter); - no longer supported
context.configure({
  
device,
  
formatpresentationFormat
});

////////////////////////////////////////
// Create vertex buffers and load data
////////////////////////////////////////

const cubeVertexSize     8// Byte size of one cube vertex.
const cubePositionOffset 0;
const 
cubeColorOffset    4// Each float 4 bytes
const cubeVertexCount    36;

// unit cube
let cubeVertexArray = new Float32Array([
  
// float4 position, float4 color
   
1, -1,  11,   1001,  
  -
1, -1,  11,   1001
  -
1, -1, -11,   1001,  
   
1, -1, -11,   0101
   
1, -1,  11,   0101,  
  -
1, -1, -11,   0101,  

   
1,  1,  11,   0011,
   
1, -1,  11,   0011,  
   
1, -1, -11,   0011,  
   
1,  1, -11,   1011,  
   
1,  1,  11,   1011
   
1, -1, -11,   1011,

  -
1,  1,  11,   0111,  
   
1,  1,  11,   0111,
   
1,  1, -11,   0111
  -
1,  1, -11,   1011,  
  -
1,  1,  11,   1011,  
   
1,  1, -11,   1011,  

  -
1, -1,  11,   1111
  -
1,  1,  11,   1111
  -
1,  1, -11,   1111
  -
1, -1, -11,   0001
  -
1, -1,  11,   0001
  -
1,  1, -11,   0001

   
1,  1,  11,   1001
  -
1,  1,  11,   1001
  -
1, -1,  11,   1001
  -
1, -1,  11,   0101
   
1, -1,  11,   0101
   
1,  1,  11,   0101

   
1, -1, -11,   1011
  -
1, -1, -11,   1011
  -
1,  1, -11,   1011
   
1,  1, -11,   1101
   
1, -1, -11,   1101
  -
1,  1, -11,   1101
]);

// unit cube - so scale to +/- 0.5
for (let i=0i<cubeVertexArray.length/8i++)
{       
  for (
let k=0k<8k++)
  {
    const 
indx i*k;
      if ( 
k<cubeVertexArray[indx] = cubeVertexArray[indx] * 0.4;
  }
}

const 
numVertices cubeVertexArray.length 8;
console.log('numVertices:'numVertices );
console.assertcubeVertexCount == numVertices );

const 
gpuBuffer device.createBuffer({
  
size:  cubeVertexArray.byteLength,
  
usageGPUBufferUsage.VERTEX GPUBufferUsage.COPY_DST
});

device.queue.writeBuffer(gpuBuffer0cubeVertexArray);


/////////////////////////////////////////////
// Create uniform buffers and binding layout
/////////////////////////////////////////////
  
const vertexUniformBuffer device.createBuffer({
  
size:  4// single float for the timer
  
usageGPUBufferUsage.UNIFORM GPUBufferUsage.COPY_DST
});

let timeData = new Float32Array);
timeData[0] = 0.0;
device.queue.writeBuffer(vertexUniformBuffer,   0,    timeData );

const 
sceneUniformBindGroupLayout device.createBindGroupLayout({
  
entries: [ { binding0visibilityGPUShaderStage.VERTEXbuffer: { type"uniform" } }
           ]
});

const 
sceneUniformBindGroup device.createBindGroup({
  
layoutsceneUniformBindGroupLayout,
  
entries: [ {  binding0resource: { buffervertexUniformBuffer } }
           ]
});

///////////////////////////
// Create render pipeline
///////////////////////////


let basicVertWGSL = `
@binding(0) @group(0) var<uniform> timer : f32;

struct VertexOutput {
  @builtin(position) Position : vec4<f32>,
  @location(0) fragColor      : vec4<f32>,
  @location(1) fragPosition   : vec4<f32>
};

@vertex
fn main(@location(0) position : vec4<f32>,
        @location(1) color    : vec4<f32>) -> VertexOutput {
        
  // bit of trig math to rotate around 'y' axis
  var p    = position.xyz;
  var newP = vec4<f32>( p.x*cos(timer) - p.z*sin(timer),
                        p.y,
                        p.z*cos(timer) + p.x*sin(timer),
                        1.0);
    
  // add a bit of 'perspective' - gets smaller further away
  newP = vec4<f32>(newP.xyz*0.5, 1.0/(newP.z + 2.0) );
        
  var output : VertexOutput;
  output.Position     = newP;
  output.fragColor    = color;
  output.fragPosition = newP;
  return output;
}
`;

let basicPixelWGSL = `
@fragment
fn main(@location(0) fragColor:   vec4<f32>,
        @location(1) fraPosition: vec4<f32>) -> @location(0) vec4<f32> {
 
  return fragColor;
  // if you want a 'constant' color for the shape
  // return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
`;


const 
pipeline device.createRenderPipeline({
  
layoutdevice.createPipelineLayout({bindGroupLayouts: [sceneUniformBindGroupLayout]}),
  
vertex: {
    
moduledevice.createShaderModule({
      
codebasicVertWGSL
    
}),
    
entryPoint"main",
    
buffers: [ {arrayStridecubeVertexSize,
                
attributes: [ {shaderLocation0offsetcubePositionOffsetformat'float32x4' }, // position
                              
{shaderLocation1offsetcubeColorOffset,    format'float32x4'  // color
             
] } ]
  },
  
fragment: {
    
moduledevice.createShaderModule({ codebasicPixelWGSL }),
    
entryPoint"main",
    
targets: [{ formatpresentationFormat }]
  },
  
primitive: {
    
topology"triangle-list",
    
cullMode'back'
  
},
  
depthStencil: {
    
format"depth24plus",
    
depthWriteEnabledtrue,
    
depthCompare"less"
  
}
});


///////////////////////////
// Render pass description
///////////////////////////
    
const depthTexture device.createTexture({
  
size: [canvas.widthcanvas.height1],
  
format"depth24plus",
  
usage:  GPUTextureUsage.RENDER_ATTACHMENT
})

const 
renderPassDescription = {
  
colorAttachments: [{
    
viewcontext.getCurrentTexture().createView(),
    
loadOp:"clear"clearValue: [0.90.90.91], // clear screen color
    
storeOp'store'
  
}],
  
depthStencilAttachment: {
    
viewdepthTexture.createView(),
    
depthLoadOp:"clear"depthClearValue1,
    
depthStoreOp"store",
    
// stencilLoadValue: 0,
    // stencilStoreOp: "store"
  
}
};

let timer        0;

function 
draw() {
  
// update uniform buffer
  
timeData[0] += 0.005;
  
device.queue.writeBuffer(vertexUniformBuffer0timeData);

  
// Swap framebuffer
  
renderPassDescription.colorAttachments[0].view context.getCurrentTexture().createView();

  const 
commandEncoder device.createCommandEncoder();
  const 
renderPass commandEncoder.beginRenderPass(renderPassDescription);

  
renderPass.setPipeline(pipeline);
  
renderPass.setVertexBuffer(0gpuBuffer);
  
renderPass.setBindGroup(0sceneUniformBindGroup);
  
renderPass.draw(numVertices100);
  
renderPass.end();

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

  
requestAnimationFrame(draw);
};

draw();

console.log('ready..');


You might notice this if you run the example - or if you look at the image very closly - but the back of the cube is slightly getting clipped when it rotates. This is because there isn't any camera or projection matrix set - it's drawing the unit cube in the unit space (i.e., anything outside the +/- 1 is clipped by the renderer - usually the camera and projection matrix would transform your geometry into this area).


Things to Try


The cube example is minimilstic but gets you started, some cool things to try - to help you develop your skills:

• Add transforms (e.g., gl-matrix) library and insert a model-view-projection matrix
• Try adding more cubes - draw multiple cubes around the screen (set a differnet local model matrix for each and you can draw it many times) - later we'll get to instancing (then you can draw thousands of cubes very easily)
• Build some shapes out of cubes and move the camera around - cube floor, stack of cubes, a cube cloud... just think of minecraft! Try and color the cubes differently (so they stand for different things - if you make a cube-tree - green for the cube leaves)




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-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.