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

 

Per Vertex Lighting


Calculating the lighting in the vertex shader (simple directional light).


Per vertex lighting - example lighting simple spheres.
Per vertex lighting - example lighting simple spheres.


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

The function object for generating a 3d sphere and drawing the mesh on screen (vertex shader does the lighting calculation).

sphere = function()
{
// ------------------------------------------------

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

    
widthSegments Math.max3Math.floorwidthSegments ) );
    
heightSegments Math.max2Math.floorheightSegments ) );

    const 
thetaEnd Math.minthetaStart thetaLengthMath.PI );

    
let index 0;
    const 
grid = [];

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

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

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

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

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

      for ( 
let ix 0ix <= widthSegmentsix ++ ) {
        const 
ix widthSegments;
        
// vertex
        
vertex.= - radius Math.cosphiStart phiLength ) * Math.sinthetaStart thetaLength );
        
vertex.radius Math.costhetaStart thetaLength );
        
vertex.radius Math.sinphiStart phiLength ) * Math.sinthetaStart thetaLength );
        
mesh.v.push( [vertex.xvertex.yvertex.z] );

        
// normal
        
let vl vertex.x*vertex.vertex.y*vertex.vertex.z*vertex.z;
        
normal.vertex.x/vl;
        
normal.vertex.y/vl;
        
normal.vertex.z/vl;
        
mesh.n.push( [normal.xnormal.ynormal.z] );

        
// uv
        
mesh.t.push( [uOffsetv] );
        
verticesRow.pushindex++ );
      }
      
grid.pushverticesRow );
    }
  
      
// indices
    
for ( let iy 0iy heightSegmentsiy ++ ) {

      for ( 
let ix 0ix widthSegmentsix ++ ) {

        const 
gridiy ][ ix ];
        const 
gridiy ][ ix ];
        const 
gridiy ][ ix ];
        const 
gridiy ][ ix ];

        if ( 
iy !== || thetaStart mesh.i.pushab);
        if ( 
iy !== heightSegments || thetaEnd Math.PI mesh.i.pushbc);
      }
    }
    return 
mesh;
}
  
// -------------------------------
  
this.create async function(devicepresentationFormatpresentationSizetextureName="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==mesh.n.length mesh.v.length 3;
if ( 
mesh.t.length==mesh.t.length mesh.v.length 2;
  
mesh.mesh.i.flat();
mesh.mesh.v.flat();
mesh.mesh.n.flat();
mesh.mesh.t.flat();
  
this.numTris         mesh.i.length// number of indices
  
// ------------------

const 1.0;
this.positions = new Float32Arraymesh.);
this.indices   = new Uint32Arraymesh.);
this.normals   = new Float32Arraymesh.);
this.uvs       = new Float32Arraymesh.);
this.timer     = new Float32Array([0.0]);
  
this.positionBuffer device.createBuffer({
  
size:  this.positions.byteLength,
  
usageGPUBufferUsage.VERTEX GPUBufferUsage.COPY_DST
});

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

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

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

this.timerBuffer device.createBuffer({
  
size:  4// single float
  
usageGPUBufferUsage.UNIFORM GPUBufferUsage.COPY_DST
});

device.queue.writeBuffer(this.positionBuffer0this.positions);
device.queue.writeBuffer(this.normalBuffer  0this.normals  );
device.queue.writeBuffer(this.indicesBuffer 0this.indices  );
device.queue.writeBuffer(this.uvBuffer      0this.uvs      );
device.queue.writeBuffer(this.timerBuffer   0this.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)       uvs      : vec2<f32>,
    @location(1)       illum    : 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;
  
  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;
  
  var vsOut: VSOut;
  vsOut.Position   = mvp * vec4<f32>( inPos, 1.0);
  vsOut.uvs        = uvs;
  vsOut.illum      = illum;
  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) uvs      : vec2<f32>,
        @location(1) illum    : f32        ) -> @location(0) vec4<f32> 
{
    return vec4<f32>( illum, illum, 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.widthimg.height1],
    
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(img00imageCanvas.widthimageCanvas.height);
const 
imageData imageCanvasContext.getImageData(00imageCanvas.widthimageCanvas.height);
const 
textureData imageData.data;
  
device.queue.writeTexture( { texturebasicTexture },
            
textureData,
            {   
offset     :  0,
                
bytesPerRow:  img.width 4,
                
rowsPerImageimg.height
             
},
            [ 
img.width  ,  img.height,  1  ]   );


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

this.mvpUniformBuffer device.createBuffer({
  
size64*3,
  
usageGPUBufferUsage.UNIFORM GPUBufferUsage.COPY_DST
});

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

this.sceneUniformBindGroupLayout device.createBindGroupLayout({
  
entries: [
    { 
binding0visibilityGPUShaderStage.VERTEX,   buffer:  { type"uniform"  }   }, 
    { 
binding1visibilityGPUShaderStage.FRAGMENTsampler: { type"filtering"}   },
    { 
binding2visibilityGPUShaderStage.FRAGMENTtexture: { sampleType"float",
                                                                  
viewDimension"2d"} },
    { 
binding3visibilityGPUShaderStage.FRAGMENTbuffer:  { type"uniform"  }   }, 
  ]
});

this.uniformBindGroup device.createBindGroup({
  
layout:   this.sceneUniformBindGroupLayout,
  
entries: [
    { 
binding 0resource: { bufferthis.mvpUniformBuffer } },
    { 
binding 1resourcetextureSampler },
    { 
binding 2resourcebasicTexture.createView()  },
    { 
binding 3resource: { bufferthis.timerBuffer        } }
   ],
});

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


this.pipeline device.createRenderPipeline({
    
layoutdevice.createPipelineLayout({bindGroupLayouts: [this.sceneUniformBindGroupLayout]}),
    
vertex:   {  module    device.createShaderModule({ 
                             
code vertWGSL }),
                 
entryPoint'main',
                 
buffers    : [ { arrayStride12attributes: [{ shaderLocation0,
                                                                  
format"float32x3",
                                                                  
offset0  }]        },
                                { 
arrayStride12attributes: [{ shaderLocation1,
                                                                  
format"float32x3",
                                                                  
offset0  }]        },
                                { 
arrayStride8,  attributes: [{ shaderLocation2,
                                                                  
format"float32x2",
                                                                  
offset0  }]        }
                              ]},
    
fragment: {  module    device.createShaderModule({ 
                             
code fragWGSL,     }),
                 
entryPoint'main',
                 
targets: [{  format presentationFormat  }] },
    
primitive: { topology  'triangle-list',
                 
frontFace "ccw",
                 
cullMode  'back',
                 
stripIndexFormatundefined },
    
depthStencil: {
                 
depthWriteEnabledtrue,
                 
depthCompare     'less',
                 
format           'depth24plus' }
});

  
this.meshData = { active:falsep:{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 buildMatrixpr)
{
    
// 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.fromTranslationtranslateMatObject.values(p) );
    
let rotateXMat   mat4.create();   mat4.fromXRotation(rotateXMatr.x);
    
let rotateYMat   mat4.create();   mat4.fromYRotation(rotateYMatr.y);
    
let rotateZMat   mat4.create();   mat4.fromZRotation(rotateZMatr.z);
    
let scaleMat     mat4.create();   mat4.fromScaling(scaleMatObject.values(s) );

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

this.draw async function( devicecontextdepthTextureviewMatrixprojectionMatrix 
{

  
// --------------------------------------------------
  // Update uniform buffer 
  
  
const modelMatrix buildMatrixthis.meshData.pthis.meshData.rthis.meshData.);
  
device.queue.writeBuffer(this.mvpUniformBuffer0,    modelMatrix);
  
device.queue.writeBuffer(this.mvpUniformBuffer64,  viewMatrix );
  
device.queue.writeBuffer(this.mvpUniformBuffer128projectionMatrix);

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


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

  // GPURenderPassDescriptor 
  
this.renderPassDescriptor = { 
        
colorAttachments:  [{    
             
view     undefined// asign later in frame
             
loadOp:   "load"clearValue: { r0.0g0.0b0.0a1.0 },
             
storeOp  'store' }],
        
depthStencilAttachment: {
             
view:  depthTexture.createView(),
             
depthLoadOp"load"
             
depthClearValue1.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(0this.uniformBindGroup);
  
renderPass.setVertexBuffer(0this.positionBuffer);
  
renderPass.setVertexBuffer(1this.normalBuffer);
  
renderPass.setVertexBuffer(2this.uvBuffer);
  
renderPass.setIndexBuffer(this.indicesBuffer'uint32');
  
renderPass.drawIndexedthis.numTris100);
  
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-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.