Field Trips

Manning College of Nursing

10/9

Thursday

10/14

Tuesday

by Tuesday 9/30!

Assignment 3

Now Due 10/3!

CS460 Computer Graphics - University of Massachusetts Boston

Assignment 3

Now Due 10/3!

CS460 Computer Graphics - University of Massachusetts Boston

Quiz 5

Due Thursday 10/2 11:59pm

History of 3D Graphics

History of 3D Graphics (continued)

History of 3D Graphics (continued)

 WebGPU

 

  • Carry out high-performance computations
  • Draw complex images
  • Modern graphic features
  • Low-Level API

And many more ....

Browser Compatibility

Linux :

​     Firefox Nightly (recommended)

          Does not require any flags

 

 

Mac and Windows :

     WebGPU works out of the box
            Chrome

             Edge

Chrome Flags

Hello Rectangle !!

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

1. Initialize WebGPU

 

 

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

1. Initialize WebGPU

 

2. Shaders

 

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

1. Initialize WebGPU

 

2. Shaders

 

3. Create Geometry

 

 

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

1. Initialize WebGPU

 

2. Shaders

 

3. Create Geometry

 

4. Create Rendering Pipeline

 

 

WebGL

1. Initialize WebGL

 

2. Shaders

 

3. Create Geometry

 

4. Connect Shader with Geometry

 

5. Draw!

WebGPU

1. Initialize WebGPU

 

2. Shaders

 

3. Create Geometry

 

4. Create Rendering Pipeline

 

5. Draw!

1. Initialize WebGPU 

1. Initialize WebGPU 

setup canvas

request GPU adapter and Device

setup GPU context

1. Initialize WebGPU 

setup canvas

request GPU adapter and Device

setup GPU context

canvas = document.getElementById('c'); // setup canvas

1. Initialize WebGPU 

setup canvas

request GPU adapter and Device

setup GPU context

canvas = document.getElementById('c'); // setup canvas
adapter = await navigator.gpu.requestAdapter(); // request GPU
device = await adapter.requestDevice(); // request device

1. Initialize WebGPU 

setup canvas

request GPU adapter and Device

setup GPU context

canvas = document.getElementById('c'); // setup canvas
adapter = await navigator.gpu.requestAdapter(); // request GPU
device = await adapter.requestDevice(); // request device
context = canvas.getContext('webgpu'); // setup GPU context
format = navigator.gpu.getPreferredCanvasFormat();
context.configure({
  device: device,
  format: format,
  alphaMode: 'opaque'
});

2. Shaders

2. Shaders

define shaders
create shaders module

2. Shaders

define shaders
create shaders module

<script id="vertexshader" type="wgsl">  // vertex shader
    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return vec4(position, 1.0);
    }
</script>
<script id="fragmentshader" type="wgsl"> // fragment shader
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1.0, 1.0, 1.0, 1.0); // White color
    }
</script>

2. Shaders

define shaders
create shaders module

<script id="vertexshader" type="wgsl">  // vertex shader
    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return vec4(position, 1.0);
    }
</script>
<script id="fragmentshader" type="wgsl"> // fragment shader
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1.0, 1.0, 1.0, 1.0); // White color
    }
</script>
v_shader = device.createShaderModule({   // vertex module
	code: document.getElementById('vertexshader').innerText
});
f_shader = device.createShaderModule({  // fragment module
	code: document.getElementById('fragmentshader').innerText
});

3. Create Geometry

3. Create Geometry

create vertices

create buffer

map buffer

unmap buffer

3. Create Geometry

create vertices

create buffer

map buffer

unmap buffer

vertices = new Float32Array( [ // create vertices
  -0.5,  0.5, 0.0, // V0
  -0.5, -0.5, 0.0, // V1
   0.5,  0.5, 0.0, // V2
              
   0.5,  0.5, 0.0, // V3
  -0.5, -0.5, 0.0, // V4
   0.5, -0.5, 0.0  // V5
] );

3. Create Geometry

create vertices

create buffer

map buffer

unmap buffer

vertices = new Float32Array( [ // create vertices
  -0.5,  0.5, 0.0, // V0
  -0.5, -0.5, 0.0, // V1
   0.5,  0.5, 0.0, // V2
              
   0.5,  0.5, 0.0, // V3
  -0.5, -0.5, 0.0, // V4
   0.5, -0.5, 0.0  // V5
] );
v_buffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  mappedAtCreation: true
});

3. Create Geometry

create vertices

create buffer

map buffer

unmap buffer

vertices = new Float32Array( [ // create vertices
  -0.5,  0.5, 0.0, // V0
  -0.5, -0.5, 0.0, // V1
   0.5,  0.5, 0.0, // V2
              
   0.5,  0.5, 0.0, // V3
  -0.5, -0.5, 0.0, // V4
   0.5, -0.5, 0.0  // V5
] );
v_buffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  mappedAtCreation: true
});
new Float32Array(v_buffer.getMappedRange()).set(vertices);

3. Create Geometry

create vertices

create buffer

map buffer

unmap buffer

vertices = new Float32Array( [ // create vertices
  -0.5,  0.5, 0.0, // V0
  -0.5, -0.5, 0.0, // V1
   0.5,  0.5, 0.0, // V2
              
   0.5,  0.5, 0.0, // V3
  -0.5, -0.5, 0.0, // V4
   0.5, -0.5, 0.0  // V5
] );
v_buffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  mappedAtCreation: true
});
new Float32Array(v_buffer.getMappedRange()).set(vertices);
v_buffer.unmap();

4. Create Rendering Pipeline

4. Create Rendering Pipeline

create pipeline

setup vertex and fragment modules

4. Create Rendering Pipeline

create pipeline

setup vertex and fragment modules

pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: v_shader,
    buffers: [{
      arrayStride: 3 * 4, // 3 components * 4 bytes per component (float32)
      attributes: [{
        shaderLocation: 0,
        offset: 0,
        format: 'float32x3'
      }]
    }]
  },
  fragment: {
    module: f_shader,
    targets: [{
      format: format
    }]
  },
  primitive: {
    topology: 'triangle-list'
  }
});

5. Draw

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

commandEncoder = device.createCommandEncoder();

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

commandEncoder = device.createCommandEncoder();
passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
.....

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

commandEncoder = device.createCommandEncoder();
passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
.....
renderPassDescriptor = {
  colorAttachments: [{
    view: textureView,
    clearValue: [0, 0, 0, 0],
    loadOp: 'clear',
    storeOp: 'store'
  }]
};

passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(pipeline);
passEncoder.setVertexBuffer(0, v_buffer);
passEncoder.draw(6); // Drawing 6 vertices (2 triangles)

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

commandEncoder = device.createCommandEncoder();
passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
.....
passEncoder.end();

5. Draw

create command encoder

begin render pass

end render pass

submit to GPU

commandEncoder = device.createCommandEncoder();
passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
.....
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>WebGPU!</title>
  <style>
    html, body {
      background:#000;
      margin:0; padding:0; height:100%;
      overflow:hidden !important;
    }
    #c { width:100%; height:100%; }
  </style>

  <!-- Vertex Shader -->
  <script id="vertexshader" type="wgsl">
    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return vec4(position, 1.0);
    }
  </script>

  <!-- Fragment Shader -->
  <script id="fragmentshader" type="wgsl">
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1.0, 1.0, 1.0, 1.0); // white
    }
  </script>
</head>

<body>
  <canvas id="c"></canvas>

  <!-- Step 2–5 will go inside this script -->
  <script>
    window.onload = async function () {
      /* paste Step 2 here */
    };
  </script>
</body>
</html>

HTML + canvas + shader tags

//************************************************************//
// 1) INITIALIZE WEBGPU
//************************************************************//
const canvas  = document.getElementById('c');
const adapter = await navigator.gpu.requestAdapter();
const device  = await adapter.requestDevice();
const context = canvas.getContext('webgpu');

const format  = navigator.gpu.getPreferredCanvasFormat();
context.configure({
  device,
  format,
  alphaMode: 'opaque'
});

/* paste Step 3 below this */

Step 1:Initialize WebGPU

//************************************************************//
// 2) SHADERS
//************************************************************//
const v_shader = device.createShaderModule({
  code: document.getElementById('vertexshader').textContent
});
const f_shader = device.createShaderModule({
  code: document.getElementById('fragmentshader').textContent
});

//************************************************************//
// 3) CREATE GEOMETRY  (three stacked rectangles)
//    each rect = 2 triangles = 6 vertices
//************************************************************//
const vertices = new Float32Array([
  // ----- RECT #1 (top) y: 0.45 .. 0.75 -----
  -0.3,  0.75, 0.0,
  -0.3,  0.45, 0.0,
   0.3,  0.75, 0.0,

   0.3,  0.75, 0.0,
  -0.3,  0.45, 0.0,
   0.3,  0.45, 0.0,

  // ----- RECT #2 (middle) y: -0.15 .. 0.15 -----
  -0.3,  0.15, 0.0,
  -0.3, -0.15, 0.0,
   0.3,  0.15, 0.0,

   0.3,  0.15, 0.0,
  -0.3, -0.15, 0.0,
   0.3, -0.15, 0.0,

  // ----- RECT #3 (bottom) y: -0.75 .. -0.45 -----
  -0.3, -0.45, 0.0,
  -0.3, -0.75, 0.0,
   0.3, -0.45, 0.0,

   0.3, -0.45, 0.0,
  -0.3, -0.75, 0.0,
   0.3, -0.75, 0.0,
]);

const v_buffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  mappedAtCreation: true
});
new Float32Array(v_buffer.getMappedRange()).set(vertices);
v_buffer.unmap();

/* paste Step 4 below this */

Step 2 : Shaders + Geometry + Vertex Buffer

//************************************************************//
// 4) CREATE RENDERING PIPELINE
//************************************************************//
const pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: v_shader,
    buffers: [{
      arrayStride: 3 * 4, // 3 floats (x,y,z) * 4 bytes
      attributes: [{
        shaderLocation: 0, // matches @location(0) in vertex shader
        offset: 0,
        format: 'float32x3'
      }]
    }]
  },
  fragment: {
    module: f_shader,
    targets: [{ format }]
  },
  primitive: { topology: 'triangle-list' }
});

/* paste Step 5 below this */

Step3: Create The Rendering Pipeline

Step4: Draw!

//************************************************************//
// 5) DRAW!
//************************************************************//
const commandEncoder = device.createCommandEncoder();
const textureView    = context.getCurrentTexture().createView();

const renderPassDescriptor = {
  colorAttachments: [{
    view: textureView,
    clearValue: [0, 0, 0, 1], // black background
    loadOp: 'clear',
    storeOp: 'store'
  }]
};

const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(pipeline);
passEncoder.setVertexBuffer(0, v_buffer);

// draw all 3 rectangles at once: 3 * 6 = 18 vertices
passEncoder.draw(18);

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

Step5: FunStuff!

//************************************************************//
// 6. ANIMATION LOOP (replaces Step 5 static draw)
//************************************************************//
let xOffset = 0;       // current movement offset
let direction = 1;     // 1 = right, -1 = left

function drawFrame() {
    // Move geometry back and forth in X
    xOffset += 0.01 * direction;
    if (xOffset > 0.5 || xOffset < -0.5) {
        direction *= -1;  // bounce when hitting edge
    }

    // Apply offset to geometry
    const movedVertices = new Float32Array(vertices.length);
    for (let i = 0; i < vertices.length; i += 3) {
        movedVertices[i]   = vertices[i] + xOffset; // shift X
        movedVertices[i+1] = vertices[i+1];         // keep Y
        movedVertices[i+2] = vertices[i+2];         // keep Z
    }
    device.queue.writeBuffer(v_buffer, 0, movedVertices);

    // Normal draw commands
    const commandEncoder = device.createCommandEncoder();
    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor = {
        colorAttachments: [{
            view: textureView,
            clearValue: [0, 0, 0, 0],
            loadOp: 'clear',
            storeOp: 'store'
        }]
    };

    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(pipeline);
    passEncoder.setVertexBuffer(0, v_buffer);
    passEncoder.draw(18); // 18 vertices = 3 rectangles
    passEncoder.end();

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

    requestAnimationFrame(drawFrame); // 🔁 repeat each frame
}

// 🔹 Start animation
drawFrame();

<!doctype html>
<html>

<head>
    <meta charset="utf-8" />
    <title>WebGPU!</title>
    <style>
        html,
        body {
            background: #000;
            margin: 0;
            padding: 0;
            height: 100%;
            overflow: hidden !important;
        }

        #c {
            width: 100%;
            height: 100%;
        }
    </style>

    <!-- Vertex Shader -->
    <script id="vertexshader" type="wgsl">
    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return vec4(position, 1.0);
    }
  </script>

    <!-- Fragment Shader -->
    <script id="fragmentshader" type="wgsl">
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1.0, 1.0, 1.0, 1.0); // white
    }
  </script>
</head>

<body>
    <canvas id="c"></canvas>

    <!-- Step 2–5 will go inside this script -->
    <script>
        window.onload = async function () {
            /* paste Step 2 here */
            //************************************************************//
            // 1) INITIALIZE WEBGPU
            //************************************************************//
            const canvas = document.getElementById('c');
            const adapter = await navigator.gpu.requestAdapter();
            const device = await adapter.requestDevice();
            const context = canvas.getContext('webgpu');

            const format = navigator.gpu.getPreferredCanvasFormat();
            context.configure({
                device,
                format,
                alphaMode: 'opaque'
            });
            //************************************************************//
            // 2) SHADERS
            //************************************************************//
            const v_shader = device.createShaderModule({
                code: document.getElementById('vertexshader').textContent
            });
            const f_shader = device.createShaderModule({
                code: document.getElementById('fragmentshader').textContent
            });

            //************************************************************//
            // 3) CREATE GEOMETRY  (three stacked rectangles)
            //    each rect = 2 triangles = 6 vertices
            //************************************************************//
            const vertices = new Float32Array([
                // ----- RECT #1 (top) y: 0.45 .. 0.75 -----
                -0.3, 0.75, 0.0,
                -0.3, 0.45, 0.0,
                0.3, 0.75, 0.0,

                0.3, 0.75, 0.0,
                -0.3, 0.45, 0.0,
                0.3, 0.45, 0.0,

                // ----- RECT #2 (middle) y: -0.15 .. 0.15 -----
                -0.3, 0.15, 0.0,
                -0.3, -0.15, 0.0,
                0.3, 0.15, 0.0,

                0.3, 0.15, 0.0,
                -0.3, -0.15, 0.0,
                0.3, -0.15, 0.0,

                // ----- RECT #3 (bottom) y: -0.75 .. -0.45 -----
                -0.3, -0.45, 0.0,
                -0.3, -0.75, 0.0,
                0.3, -0.45, 0.0,

                0.3, -0.45, 0.0,
                -0.3, -0.75, 0.0,
                0.3, -0.75, 0.0,
            ]);

            const v_buffer = device.createBuffer({
                size: vertices.byteLength,
                usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
                mappedAtCreation: true
            });
            new Float32Array(v_buffer.getMappedRange()).set(vertices);
            v_buffer.unmap();

            //************************************************************//
            // 4) CREATE RENDERING PIPELINE
            //************************************************************//
            const pipeline = device.createRenderPipeline({
                layout: 'auto',
                vertex: {
                    module: v_shader,
                    buffers: [{
                        arrayStride: 3 * 4, // 3 floats (x,y,z) * 4 bytes
                        attributes: [{
                            shaderLocation: 0, // matches @location(0) in vertex shader
                            offset: 0,
                            format: 'float32x3'
                        }]
                    }]
                },
                fragment: {
                    module: f_shader,
                    targets: [{ format }]
                },
                primitive: { topology: 'triangle-list' }
            });
            //************************************************************//
            // 5) DRAW!
            //************************************************************//
            //************************************************************//
            // 6. ANIMATION LOOP (replaces Step 5 static draw)
            //************************************************************//
            let xOffset = 0;       // current movement offset
            let direction = 1;     // 1 = right, -1 = left

            function drawFrame() {
                // Move geometry back and forth in X
                xOffset += 0.01 * direction;
                if (xOffset > 0.5 || xOffset < -0.5) {
                    direction *= -1;  // bounce when hitting edge
                }

                // Apply offset to geometry
                const movedVertices = new Float32Array(vertices.length);
                for (let i = 0; i < vertices.length; i += 3) {
                    movedVertices[i] = vertices[i] + xOffset; // shift X
                    movedVertices[i + 1] = vertices[i + 1];         // keep Y
                    movedVertices[i + 2] = vertices[i + 2];         // keep Z
                }
                device.queue.writeBuffer(v_buffer, 0, movedVertices);

                // Normal draw commands
                const commandEncoder = device.createCommandEncoder();
                const textureView = context.getCurrentTexture().createView();
                const renderPassDescriptor = {
                    colorAttachments: [{
                        view: textureView,
                        clearValue: [0, 0, 0, 0],
                        loadOp: 'clear',
                        storeOp: 'store'
                    }]
                };

                const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
                passEncoder.setPipeline(pipeline);
                passEncoder.setVertexBuffer(0, v_buffer);
                passEncoder.draw(18); // 18 vertices = 3 rectangles
                passEncoder.end();

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

                requestAnimationFrame(drawFrame); // 🔁 repeat each frame
            }

            // 🔹 Start animation
            drawFrame();




            /* paste Step 5 below this */


            /* paste Step 4 below this */




            /* paste Step 3 below this */

        };

    </script>
</body>

</html>

Messing With Geometry

const vertices = new Float32Array([
  -0.6, -0.4, 0.0,
   0.6, -0.4, 0.0,
   0.0,  0.6, 0.0,
]);

 Single triangle

const vertices = new Float32Array([
  -0.5,  0.5, 0.0,
  -0.5, -0.5, 0.0,
   0.5,  0.5, 0.0,

   0.5,  0.5, 0.0,
  -0.5, -0.5, 0.0,
   0.5, -0.5, 0.0,
]);

Centered rectangle

const vertices = new Float32Array([
   0.0,  0.7, 0.0,  // top
  -0.7,  0.0, 0.0,  // left
   0.7,  0.0, 0.0,  // right

   0.0, -0.7, 0.0,  // bottom
  -0.7,  0.0, 0.0,  // left
   0.7,  0.0, 0.0,  // right
]);

Diamond

House

const vertices = new Float32Array([
  // base (quad)
  -0.6, -0.4, 0.0,
   0.6, -0.4, 0.0,
  -0.6,  0.4, 0.0,

  -0.6,  0.4, 0.0,
   0.6, -0.4, 0.0,
   0.6,  0.4, 0.0,

  // roof
  -0.7,  0.4, 0.0,
   0.7,  0.4, 0.0,
   0.0,  0.8, 0.0,
]);

Messing With Geometry

const vertices = new Float32Array([
   0.0,  0.8, 0.0,
  -0.2,  0.2, 0.0,
   0.2,  0.2, 0.0,

  -0.8,  0.0, 0.0,
  -0.2,  0.2, 0.0,
   0.0, -0.2, 0.0,

   0.8,  0.0, 0.0,
   0.2,  0.2, 0.0,
   0.0, -0.2, 0.0
]);

Star

const vertices = new Float32Array([
  -0.5,  0.5, 0.0,
   0.0, -0.5, 0.0,
   0.5,  0.5, 0.0,
]);
passEncoder.draw(3);

V

const vertices = new Float32Array([
  // Vertical bar
  -0.1,  0.6, 0.0,
   0.1,  0.6, 0.0,
  -0.1, -0.6, 0.0,
   0.1,  0.6, 0.0,
   0.1, -0.6, 0.0,
  -0.1, -0.6, 0.0,

  // Horizontal bar
  -0.6,  0.1, 0.0,
   0.6,  0.1, 0.0,
  -0.6, -0.1, 0.0,
   0.6,  0.1, 0.0,
   0.6, -0.1, 0.0,
  -0.6, -0.1, 0.0,
]);
passEncoder.draw(12);

Cross

Trapezoid

const vertices = new Float32Array([
  -0.6,  0.5, 0.0,
   0.6,  0.5, 0.0,
  -0.3, -0.5, 0.0,

   0.6,  0.5, 0.0,
   0.3, -0.5, 0.0,
  -0.3, -0.5, 0.0,
]);
passEncoder.draw(6);
<html>
<head>
  <title>WebGPU!</title>
  <style>
    html, body {
      background-color: #000;
      margin: 0;
      padding: 0;
      height: 100%;
      overflow: hidden !important;
    }
    #c {
      width: 100%;
      height: 100%;
    }
  </style>
  <script id="vertexshader" type="wgsl">
    struct VertexInput {
        @location(0) position: vec3<f32>,
        @location(1) offset: vec3<f32>,
        @location(2) color: vec4<f32>, // Color for the instance
    };

    struct VertexOutput {
        @builtin(position) position: vec4<f32>,
        @location(0) color: vec4<f32>, // Pass color to fragment shader
    };

    @vertex
    fn main(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4(input.position + input.offset, 1.0); // Add offset
        output.color = input.color; // Pass color to fragment shader
        return output;
    }
  </script>
  <script id="fragmentshader" type="wgsl">
    @fragment
    fn main(@location(0) color: vec4<f32>) -> @location(0) vec4<f32> {
        return color; // Use the color passed from the vertex shader
    }
  </script>
  <script>
    window.onload = async function() {
      //************************************************************//
      //
      // INITIALIZE WEBGPU
      //
      const canvas = document.getElementById('c');
      const adapter = await navigator.gpu.requestAdapter();
      const device = await adapter.requestDevice();
      const context = canvas.getContext('webgpu');

      const format = navigator.gpu.getPreferredCanvasFormat();
      context.configure({
        device: device,
        format: format,
        alphaMode: 'opaque'
      });

      //************************************************************//
      //
      // SHADERS
      //
      const v_shader = device.createShaderModule({
        code: document.getElementById('vertexshader').innerText
      });
      const f_shader = device.createShaderModule({
        code: document.getElementById('fragmentshader').innerText
      });

      //************************************************************//
      //
      // CREATE GEOMETRY
      //
      // Define a single rectangle using triangles
      const vertices = new Float32Array([
        -0.5,  0.5, 0.0,  // V0
        -0.5, -0.5, 0.0,  // V1
        0.5,  0.5, 0.0,   // V2
        0.5,  0.5, 0.0,   // V3
        -0.5, -0.5, 0.0,  // V4
        0.5, -0.5, 0.0    // V5
      ]);

      const v_buffer = device.createBuffer({
        size: vertices.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
        mappedAtCreation: true
      });
      new Float32Array(v_buffer.getMappedRange()).set(vertices);
      v_buffer.unmap();

      //************************************************************//
      //
      // INSTANCE DATA
      //
      // Define the initial offsets for 3 rectangles
      const instanceOffsets = new Float32Array([
        -0.4,  -0.4, 0.0,  // Rectangle 1
        0.0,   0.0, 0.0,  // Rectangle 2
        0.4,   0.4, 0.0,  // Rectangle 3
      ]);

      const i_buffer = device.createBuffer({
        size: instanceOffsets.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
        mappedAtCreation: true
      });
      new Float32Array(i_buffer.getMappedRange()).set(instanceOffsets);
      i_buffer.unmap();


      // Define colors for 3 rectangles
      const instanceColors = new Float32Array([
        1.0, 0.0, 0.0, 1.0, // Red for Rectangle 1
        0.0, 1.0, 0.0, 1.0, // Green for Rectangle 2
        0.0, 0.0, 1.0, 1.0, // Blue for Rectangle 3
      ]);

      const c_buffer = device.createBuffer({
        size: instanceColors.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
        mappedAtCreation: true
      });
      new Float32Array(c_buffer.getMappedRange()).set(instanceColors);
      c_buffer.unmap();

      //************************************************************//
      //
      // SETUP RENDERING PIPELINE
      //
      const pipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex: {
          module: v_shader,
          buffers: [
            {
              arrayStride: 3 * 4, // Vertex size
              attributes: [{
                shaderLocation: 0,
                offset: 0,
                format: 'float32x3'
              }]
            },
            {
              arrayStride: 3 * 4, // Instance size
              attributes: [{
                shaderLocation: 1,
                offset: 0,
                format: 'float32x3'
              }],
              stepMode: 'instance' // Instance data
            },
            {
              arrayStride: 4 * 4, // Color size
              attributes: [{
                shaderLocation: 2,
                offset: 0,
                format: 'float32x4'
              }],
              stepMode: 'instance' // Color data for each instance
            }
          ]
        },
        fragment: {
          module: f_shader,
          targets: [{
            format: format
          }]
        },
        primitive: {
          topology: 'triangle-list'
        }
      });

      //************************************************************//
      //
      // ANIMATION VARIABLES
      //
      let offsetSpeed = 0.005; // Speed of movement
      let offsetDirection = 1; // 1 for right, -1 for left

      //************************************************************//
      //
      // ANIMATION LOOP
      //

      console.log(instanceOffsets.length);
      function animate() {
        // Update instance offsets
        for (let i = 0; i < instanceOffsets.length / 3; i++) {
          instanceOffsets[i * 3] += offsetSpeed * offsetDirection;// Move in x direction
          // Reverse direction when reaching bounds
          if (instanceOffsets[i * 3]  > 1.0 || instanceOffsets[i * 3] < -1.0) {
            offsetDirection *= -1;
          }
        }

        // Update the instance buffer with new offsets
        device.queue.writeBuffer(i_buffer, 0, instanceOffsets);

        // Draw!
        const commandEncoder = device.createCommandEncoder();
        const textureView = context.getCurrentTexture().createView();

        const renderPassDescriptor = {
          colorAttachments: [{
            view: textureView,
            clearValue: [0, 0, 0, 0],
            loadOp: 'clear',
            storeOp: 'store'
          }]
        };

        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        passEncoder.setVertexBuffer(0, v_buffer); // Set vertex buffer
        passEncoder.setVertexBuffer(1, i_buffer); // Set instance buffer for offsets
        passEncoder.setVertexBuffer(2, c_buffer); // Set instance buffer for colors
        passEncoder.draw(6, 3); // Draw 6 vertices (2 triangles) for 3 instances
        passEncoder.end();

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

        requestAnimationFrame(animate); // Continue the animation
      }

      animate(); // Start the animation loop
    }
  </script>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>

CODE !!