WebGPU 图形API

用于浏览器中高性能3D图形和GPU计算的现代图形API

💻 基础 WebGPU 设置 javascript

🟡 intermediate ⭐⭐⭐

初始化WebGPU并创建简单渲染管线

⏱️ 20 min 🏷️ webgpu, graphics, rendering, 3d, shaders
Prerequisites: JavaScript, Graphics concepts, Shaders basics
// Basic WebGPU Setup
class WebGPURenderer {
  constructor(canvas) {
    this.canvas = canvas;
    this.device = null;
    this.context = null;
    this.format = null;
  }

  async init() {
    // Check WebGPU support
    if (!navigator.gpu) {
      throw new Error('WebGPU not supported');
    }

    // Get adapter and device
    const adapter = await navigator.gpu.requestAdapter();
    if (!adapter) {
      throw new Error('No appropriate GPU adapter found');
    }

    this.device = await adapter.requestDevice();

    // Configure canvas context
    this.context = this.canvas.getContext('webgpu');
    this.format = navigator.gpu.getPreferredCanvasFormat();

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

    console.log('WebGPU initialized successfully');
  }

  createBasicPipeline() {
    // Vertex shader
    const vertexShaderCode = `
      struct VertexOutput {
        @builtin(position) position: vec4<f32>,
        @location(0) color: vec3<f32>,
      }

      @vertex
      fn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
        var output: VertexOutput;

        // Triangle vertices
        let pos = array<vec2<f32>, 3>(
          vec2<f32>(-0.5, -0.5),
          vec2<f32>( 0.5, -0.5),
          vec2<f32>( 0.0,  0.5)
        );

        let colors = array<vec3<f32>, 3>(
          vec3<f32>(1.0, 0.0, 0.0), // Red
          vec3<f32>(0.0, 1.0, 0.0), // Green
          vec3<f32>(0.0, 0.0, 1.0)  // Blue
        );

        output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
        output.color = colors[vertexIndex];

        return output;
      }
    `;

    // Fragment shader
    const fragmentShaderCode = `
      @fragment
      fn main(@location(0) color: vec3<f32>) -> @location(0) vec4<f32> {
        return vec4<f32>(color, 1.0);
      }
    `;

    // Create shaders
    const vertexShader = this.device.createShaderModule({
      code: vertexShaderCode
    });

    const fragmentShader = this.device.createShaderModule({
      code: fragmentShaderCode
    });

    // Create pipeline
    const pipeline = this.device.createRenderPipeline({
      layout: 'auto',
      vertex: {
        module: vertexShader,
        entryPoint: 'main',
      },
      fragment: {
        module: fragmentShader,
        entryPoint: 'main',
        targets: [{
          format: this.format,
        }],
      },
      primitive: {
        topology: 'triangle-list',
      },
    });

    return pipeline;
  }

  render(pipeline) {
    // Create command encoder
    const commandEncoder = this.device.createCommandEncoder();

    // Create render pass
    const textureView = this.context.getCurrentTexture().createView();
    const renderPass = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: textureView,
        clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 },
        loadOp: 'clear',
        storeOp: 'store',
      }],
    });

    renderPass.setPipeline(pipeline);
    renderPass.draw(3); // Draw 3 vertices for a triangle
    renderPass.end();

    // Submit commands
    this.device.queue.submit([commandEncoder.finish()]);
  }

  async start() {
    await this.init();
    const pipeline = this.createBasicPipeline();

    // Animation loop
    const frame = () => {
      this.render(pipeline);
      requestAnimationFrame(frame);
    };
    frame();
  }
}

// Usage
async function main() {
  const canvas = document.getElementById('canvas');
  const renderer = new WebGPURenderer(canvas);

  try {
    await renderer.start();
  } catch (error) {
    console.error('WebGPU initialization failed:', error);
  }
}

// Check for WebGPU support before running
if (navigator.gpu) {
  main();
} else {
  console.warn('WebGPU is not supported in this browser');
}

💻 WebGPU 纹理映射 javascript

🟡 intermediate ⭐⭐⭐⭐

使用WebGPU为3D对象应用纹理

⏱️ 25 min 🏷️ webgpu, texture, mapping, graphics, rendering
Prerequisites: JavaScript, WebGPU basics, Texture mapping concepts
// WebGPU Texture Mapping
class TextureRenderer extends WebGPURenderer {
  async createTexturedPipeline(texture) {
    // Vertex shader with UV coordinates
    const vertexShaderCode = `
      struct VertexInput {
        @location(0) position: vec2<f32>,
        @location(1) uv: vec2<f32>,
      }

      struct VertexOutput {
        @builtin(position) position: vec4<f32>,
        @location(0) uv: vec2<f32>,
      }

      @vertex
      fn main(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;
        output.position = vec4<f32>(input.position, 0.0, 1.0);
        output.uv = input.uv;
        return output;
      }
    `;

    // Fragment shader with texture sampling
    const fragmentShaderCode = `
      @group(0) @binding(0) var textureSampler: sampler;
      @group(0) @binding(1) var textureData: texture_2d<f32>;

      @fragment
      fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
        return textureSample(textureData, textureSampler, uv);
      }
    `;

    // Create shaders
    const vertexShader = this.device.createShaderModule({
      code: vertexShaderCode
    });

    const fragmentShader = this.device.createShaderModule({
      code: fragmentShaderCode
    });

    // Create vertex buffer (position + UV)
    const vertices = new Float32Array([
      // Position (x, y)    UV (u, v)
      -0.8, -0.8,   0.0, 1.0,  // Bottom left
       0.8, -0.8,   1.0, 1.0,  // Bottom right
       0.8,  0.8,   1.0, 0.0,  // Top right
      -0.8,  0.8,   0.0, 0.0,  // Top left
    ]);

    const vertexBuffer = this.device.createBuffer({
      size: vertices.byteLength,
      usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
    });
    this.device.queue.writeBuffer(vertexBuffer, 0, vertices);

    // Create texture sampler
    const sampler = this.device.createSampler({
      magFilter: 'linear',
      minFilter: 'linear',
      addressModeU: 'repeat',
      addressModeV: 'repeat',
    });

    // Create bind group
    const bindGroup = this.device.createBindGroup({
      layout: this.device.createBindGroupLayout({
        entries: [
          {
            binding: 0,
            visibility: GPUShaderStage.FRAGMENT,
            sampler: { type: 'filtering' },
          },
          {
            binding: 1,
            visibility: GPUShaderStage.FRAGMENT,
            texture: { sampleType: 'float' },
          },
        ],
      }),
      entries: [
        { binding: 0, resource: sampler },
        { binding: 1, resource: texture.createView() },
      ],
    });

    // Create pipeline
    const pipeline = this.device.createRenderPipeline({
      layout: this.device.createPipelineLayout({
        bindGroupLayouts: [bindGroup.layout],
      }),
      vertex: {
        module: vertexShader,
        entryPoint: 'main',
        buffers: [{
          arrayStride: 4 * 4, // 4 floats per vertex (x, y, u, v)
          attributes: [
            {
              format: 'float32x2',
              offset: 0,
              shaderLocation: 0, // position
            },
            {
              format: 'float32x2',
              offset: 2 * 4,
              shaderLocation: 1, // uv
            },
          ],
        }],
      },
      fragment: {
        module: fragmentShader,
        entryPoint: 'main',
        targets: [{
          format: this.format,
        }],
      },
      primitive: {
        topology: 'triangle-strip',
      },
    });

    return { pipeline, vertexBuffer, bindGroup };
  }

  async loadTexture(imageUrl) {
    const response = await fetch(imageUrl);
    const imageBitmap = await createImageBitmap(await response.blob());

    const texture = this.device.createTexture({
      size: [imageBitmap.width, imageBitmap.height, 1],
      format: 'rgba8unorm',
      usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
    });

    this.device.queue.copyExternalImageToTexture(
      { source: imageBitmap },
      { texture: texture },
      [imageBitmap.width, imageBitmap.height]
    );

    return texture;
  }

  async renderTexturedQuad(textureUrl) {
    await this.init();

    const texture = await this.loadTexture(textureUrl);
    const { pipeline, vertexBuffer, bindGroup } = await this.createTexturedPipeline(texture);

    const frame = () => {
      const commandEncoder = this.device.createCommandEncoder();
      const textureView = this.context.getCurrentTexture().createView();

      const renderPass = commandEncoder.beginRenderPass({
        colorAttachments: [{
          view: textureView,
          clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 },
          loadOp: 'clear',
          storeOp: 'store',
        }],
      });

      renderPass.setPipeline(pipeline);
      renderPass.setVertexBuffer(0, vertexBuffer);
      renderPass.setBindGroup(0, bindGroup);
      renderPass.draw(4); // Draw 4 vertices for a quad
      renderPass.end();

      this.device.queue.submit([commandEncoder.finish()]);
      requestAnimationFrame(frame);
    };

    frame();
  }
}

// Usage
async function renderTexturedImage() {
  const canvas = document.getElementById('canvas');
  const renderer = new TextureRenderer(canvas);

  await renderer.renderTexturedQuad('texture.jpg');
}

renderTexturedImage();

💻 WebGPU 计算着色器 javascript

🔴 complex ⭐⭐⭐⭐⭐

使用计算着色器进行GPU并行处理

⏱️ 30 min 🏷️ webgpu, compute, shaders, gpu, parallel
Prerequisites: JavaScript, WebGPU, GPU computing, Parallel programming
// WebGPU Compute Shaders for Parallel Processing
class ComputeRenderer {
  async init() {
    if (!navigator.gpu) throw new Error('WebGPU not supported');

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

  async createComputePipeline() {
    // Compute shader for parallel matrix multiplication
    const computeShaderCode = `
      @group(0) @binding(0) var<storage, read> matrixA: array<f32>;
      @group(0) @binding(1) var<storage, read> matrixB: array<f32>;
      @group(0) @binding(2) var<storage, read_write> result: array<f32>;

      struct Params {
        size: u32,
      }

      @group(0) @binding(3) var<uniform> params: Params;

      @compute @workgroup_size(16, 16)
      fn main(@builtin(global_invocation_id) id: vec3<u32>) {
        let row = id.x;
        let col = id.y;

        if (row >= params.size || col >= params.size) {
          return;
        }

        var sum = 0.0;
        for (var i = 0u; i < params.size; i = i + 1u) {
          sum = sum + matrixA[row * params.size + i] * matrixB[i * params.size + col];
        }

        result[row * params.size + col] = sum;
      }
    `;

    const computeShader = this.device.createShaderModule({
      code: computeShaderCode
    });

    const pipeline = this.device.createComputePipeline({
      layout: 'auto',
      compute: {
        module: computeShader,
        entryPoint: 'main',
      },
    });

    return pipeline;
  }

  createMatrices(size) {
    const matrixA = new Float32Array(size * size);
    const matrixB = new Float32Array(size * size);
    const result = new Float32Array(size * size);

    // Initialize matrices with random values
    for (let i = 0; i < size * size; i++) {
      matrixA[i] = Math.random();
      matrixB[i] = Math.random();
    }

    return { matrixA, matrixB, result };
  }

  async matrixMultiplication(size = 512) {
    const { matrixA, matrixB, result } = this.createMatrices(size);

    // Create GPU buffers
    const bufferA = this.device.createBuffer({
      size: matrixA.byteLength,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    });

    const bufferB = this.device.createBuffer({
      size: matrixB.byteLength,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    });

    const bufferResult = this.device.createBuffer({
      size: result.byteLength,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
    });

    // CPU buffer for reading results
    const readBuffer = this.device.createBuffer({
      size: result.byteLength,
      usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
    });

    // Write data to GPU
    this.device.queue.writeBuffer(bufferA, 0, matrixA);
    this.device.queue.writeBuffer(bufferB, 0, matrixB);

    // Uniform buffer for matrix size
    const uniformBuffer = this.device.createBuffer({
      size: 4, // 1 uint32
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });

    const sizeArray = new Uint32Array([size]);
    this.device.queue.writeBuffer(uniformBuffer, 0, sizeArray);

    // Create bind group
    const pipeline = await this.createComputePipeline();
    const bindGroup = this.device.createBindGroup({
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        { binding: 0, resource: { buffer: bufferA } },
        { binding: 1, resource: { buffer: bufferB } },
        { binding: 2, resource: { buffer: bufferResult } },
        { binding: 3, resource: { buffer: uniformBuffer } },
      ],
    });

    // Execute compute shader
    const commandEncoder = this.device.createCommandEncoder();
    const computePass = commandEncoder.beginComputePass();

    computePass.setPipeline(pipeline);
    computePass.setBindGroup(0, bindGroup);
    computePass.dispatchWorkgroups(
      Math.ceil(size / 16),
      Math.ceil(size / 16)
    );
    computePass.end();

    // Copy result to readable buffer
    commandEncoder.copyBufferToBuffer(bufferResult, 0, readBuffer, 0, result.byteLength);

    // Submit commands
    this.device.queue.submit([commandEncoder.finish()]);

    // Read results
    await readBuffer.mapAsync(GPUMapMode.READ);
    const gpuResult = new Float32Array(readBuffer.getMappedRange());
    const cpuResult = new Float32Array(gpuResult.slice(0));
    readBuffer.unmap();

    return cpuResult;
  }

  // Particle simulation compute shader
  async createParticleSimulation() {
    const particleShaderCode = `
      struct Particle {
        position: vec2<f32>,
        velocity: vec2<f32>,
      }

      @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;

      struct Params {
        deltaTime: f32,
        count: u32,
      }

      @group(0) @binding(1) var<uniform> params: Params;

      @compute @workgroup_size(64)
      fn main(@builtin(global_invocation_id) id: vec3<u32>) {
        let index = id.x;
        if (index >= params.count) {
          return;
        }

        var particle = particles[index];

        // Apply gravity
        particle.velocity.y = particle.velocity.y - 9.81 * params.deltaTime;

        // Update position
        particle.position = particle.position + particle.velocity * params.deltaTime;

        // Bounce off bottom
        if (particle.position.y < -1.0) {
          particle.position.y = -1.0;
          particle.velocity.y = -particle.velocity.y * 0.8;
        }

        // Bounce off sides
        if (abs(particle.position.x) > 1.0) {
          particle.position.x = sign(particle.position.x) * 1.0;
          particle.velocity.x = -particle.velocity.x * 0.8;
        }

        particles[index] = particle;
      }
    `;

    const shader = this.device.createShaderModule({
      code: particleShaderCode
    });

    return this.device.createComputePipeline({
      layout: 'auto',
      compute: {
        module: shader,
        entryPoint: 'main',
      },
    });
  }
}

// Usage
async function runComputeExample() {
  const compute = new ComputeRenderer();
  await compute.init();

  // Matrix multiplication benchmark
  console.time('GPU Matrix Multiplication');
  const gpuResult = await compute.matrixMultiplication(512);
  console.timeEnd('GPU Matrix Multiplication');

  // For comparison, CPU implementation
  function cpuMatrixMultiply(A, B, size) {
    const result = new Float32Array(size * size);
    for (let i = 0; i < size; i++) {
      for (let j = 0; j < size; j++) {
        let sum = 0;
        for (let k = 0; k < size; k++) {
          sum += A[i * size + k] * B[k * size + j];
        }
        result[i * size + j] = sum;
      }
    }
    return result;
  }

  const { matrixA, matrixB } = compute.createMatrices(512);
  console.time('CPU Matrix Multiplication');
  const cpuResult = cpuMatrixMultiply(matrixA, matrixB, 512);
  console.timeEnd('CPU Matrix Multiplication');

  console.log('Performance improvement achieved!');
}

if (navigator.gpu) {
  runComputeExample();
}

💻 3D场景渲染 javascript

🔴 complex ⭐⭐⭐⭐⭐

创建带有光照和相机控制的完整3D场景

⏱️ 35 min 🏷️ webgpu, 3d, lighting, scene, graphics
Prerequisites: JavaScript, WebGPU, 3D graphics, Linear algebra
// WebGPU 3D Scene with Lighting
class Scene3D {
  constructor(canvas) {
    this.canvas = canvas;
    this.device = null;
    this.context = null;
    this.format = null;
    this.pipeline = null;
    this.uniformBuffer = null;
    this.camera = {
      position: [0, 0, 5],
      rotation: [0, 0, 0],
      fov: Math.PI / 4,
      aspect: canvas.width / canvas.height,
      near: 0.1,
      far: 100
    };
  }

  async init() {
    if (!navigator.gpu) throw new Error('WebGPU not supported');

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

    this.context = this.canvas.getContext('webgpu');
    this.format = navigator.gpu.getPreferredCanvasFormat();

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

    await this.createPipeline();
    await this.createGeometry();
    this.createUniforms();
  }

  async createPipeline() {
    // Vertex shader with 3D transformations
    const vertexShaderCode = `
      struct Uniforms {
        modelMatrix: mat4x4<f32>,
        viewMatrix: mat4x4<f32>,
        projectionMatrix: mat4x4<f32>,
        normalMatrix: mat4x4<f32>,
        cameraPosition: vec3<f32>,
        lightPosition: vec3<f32>,
        lightColor: vec3<f32>,
        lightIntensity: f32,
      }

      struct VertexInput {
        @location(0) position: vec3<f32>,
        @location(1) normal: vec3<f32>,
        @location(2) color: vec3<f32>,
      }

      struct VertexOutput {
        @builtin(position) position: vec4<f32>,
        @location(0) worldPosition: vec3<f32>,
        @location(1) worldNormal: vec3<f32>,
        @location(2) color: vec3<f32>,
      }

      @group(0) @binding(0) var<uniform> uniforms: Uniforms;

      @vertex
      fn main(input: VertexInput) -> VertexOutput {
        var output: VertexOutput;

        // Transform position to world space
        let worldPos = uniforms.modelMatrix * vec4<f32>(input.position, 1.0);
        output.worldPosition = worldPos.xyz;

        // Transform normal to world space
        output.worldNormal = normalize((uniforms.normalMatrix * vec4<f32>(input.normal, 0.0)).xyz);

        // Transform to clip space
        output.position = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos;

        // Pass through color
        output.color = input.color;

        return output;
      }
    `;

    // Fragment shader with Phong lighting
    const fragmentShaderCode = `
      struct Uniforms {
        modelMatrix: mat4x4<f32>,
        viewMatrix: mat4x4<f32>,
        projectionMatrix: mat4x4<f32>,
        normalMatrix: mat4x4<f32>,
        cameraPosition: vec3<f32>,
        lightPosition: vec3<f32>,
        lightColor: vec3<f32>,
        lightIntensity: f32,
      }

      @group(0) @binding(0) var<uniform> uniforms: Uniforms;

      @fragment
      fn main(
        @location(0) worldPosition: vec3<f32>,
        @location(1) worldNormal: vec3<f32>,
        @location(2) color: vec3<f32>
      ) -> @location(0) vec4<f32> {
        // Phong lighting model

        // Ambient
        let ambient = vec3<f32>(0.1, 0.1, 0.1);

        // Diffuse
        let lightDir = normalize(uniforms.lightPosition - worldPosition);
        let diffuse = max(dot(worldNormal, lightDir), 0.0) * uniforms.lightColor * uniforms.lightIntensity;

        // Specular
        let viewDir = normalize(uniforms.cameraPosition - worldPosition);
        let reflectDir = reflect(-lightDir, worldNormal);
        let specular = pow(max(dot(viewDir, reflectDir), 0.0), 32.0) * uniforms.lightColor;

        let finalColor = (ambient + diffuse + specular) * color;
        return vec4<f32>(finalColor, 1.0);
      }
    `;

    const vertexShader = this.device.createShaderModule({
      code: vertexShaderCode
    });

    const fragmentShader = this.device.createShaderModule({
      code: fragmentShaderCode
    });

    this.pipeline = this.device.createRenderPipeline({
      layout: 'auto',
      vertex: {
        module: vertexShader,
        entryPoint: 'main',
        buffers: [
          {
            arrayStride: 3 * 4, // position
            attributes: [{ format: 'float32x3', offset: 0, shaderLocation: 0 }]
          },
          {
            arrayStride: 3 * 4, // normal
            attributes: [{ format: 'float32x3', offset: 0, shaderLocation: 1 }]
          },
          {
            arrayStride: 3 * 4, // color
            attributes: [{ format: 'float32x3', offset: 0, shaderLocation: 2 }]
          }
        ]
      },
      fragment: {
        module: fragmentShader,
        entryPoint: 'main',
        targets: [{ format: this.format }]
      },
      primitive: {
        topology: 'triangle-list',
        cullMode: 'back'
      },
      depthStencil: {
        depthWriteEnabled: true,
        depthCompare: 'less',
        format: 'depth24plus'
      }
    });
  }

  async createGeometry() {
    // Create a cube
    const positions = new Float32Array([
      // Front face
      -1, -1,  1,  1, -1,  1,  1,  1,  1, -1,  1,  1,
      // Back face
      -1, -1, -1, -1,  1, -1,  1,  1, -1,  1, -1, -1,
      // Top face
      -1,  1, -1, -1,  1,  1,  1,  1,  1,  1,  1, -1,
      // Bottom face
      -1, -1, -1,  1, -1, -1,  1, -1,  1, -1, -1,  1,
      // Right face
       1, -1, -1,  1,  1, -1,  1,  1,  1,  1, -1,  1,
      // Left face
      -1, -1, -1, -1, -1,  1, -1,  1,  1, -1,  1, -1
    ]);

    const normals = new Float32Array([
      // Front, Back, Top, Bottom, Right, Left
      0, 0, 1,  0, 0, 1,  0, 0, 1,  0, 0, 1,
      0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
      0, 1, 0,  0, 1, 0,  0, 1, 0,  0, 1, 0,
      0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
      1, 0, 0,  1, 0, 0,  1, 0, 0,  1, 0, 0,
      -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0
    ]);

    const colors = new Float32Array([
      1, 0, 0,  1, 0, 0,  1, 0, 0,  1, 0, 0, // Red
      0, 1, 0,  0, 1, 0,  0, 1, 0,  0, 1, 0, // Green
      0, 0, 1,  0, 0, 1,  0, 0, 1,  0, 0, 1, // Blue
      1, 1, 0,  1, 1, 0,  1, 1, 0,  1, 1, 0, // Yellow
      1, 0, 1,  1, 0, 1,  1, 0, 1,  1, 0, 1, // Magenta
      0, 1, 1,  0, 1, 1,  0, 1, 1,  0, 1, 1  // Cyan
    ]);

    const indices = new Uint16Array([
      0,  1,  2,   0,  2,  3,   // Front
      4,  5,  6,   4,  6,  7,   // Back
      8,  9,  10,  8,  10, 11,  // Top
      12, 13, 14,  12, 14, 15,  // Bottom
      16, 17, 18,  16, 18, 19,  // Right
      20, 21, 22,  20, 22, 23   // Left
    ]);

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

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

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

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

    this.device.queue.writeBuffer(this.positionBuffer, 0, positions);
    this.device.queue.writeBuffer(this.normalBuffer, 0, normals);
    this.device.queue.writeBuffer(this.colorBuffer, 0, colors);
    this.device.queue.writeBuffer(this.indexBuffer, 0, indices);

    this.indexCount = indices.length;
  }

  createUniforms() {
    const uniformSize =
      64 + // modelMatrix (4x4)
      64 + // viewMatrix (4x4)
      64 + // projectionMatrix (4x4)
      64 + // normalMatrix (4x4)
      12 + // cameraPosition (vec3)
      12 + // lightPosition (vec3)
      12 + // lightColor (vec3)
      4;   // lightIntensity (float)

    this.uniformBuffer = this.device.createBuffer({
      size: uniformSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });

    this.bindGroup = this.device.createBindGroup({
      layout: this.pipeline.getBindGroupLayout(0),
      entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }]
    });

    // Create depth texture
    this.depthTexture = this.device.createTexture({
      size: [this.canvas.width, this.canvas.height],
      format: 'depth24plus',
      usage: GPUTextureUsage.RENDER_ATTACHMENT,
    });
  }

  // Matrix helper functions
  createIdentityMatrix() {
    return new Float32Array([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ]);
  }

  createTranslationMatrix(x, y, z) {
    return new Float32Array([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      x, y, z, 1
    ]);
  }

  createRotationMatrix(angleX, angleY, angleZ) {
    const cosX = Math.cos(angleX), sinX = Math.sin(angleX);
    const cosY = Math.cos(angleY), sinY = Math.sin(angleY);
    const cosZ = Math.cos(angleZ), sinZ = Math.sin(angleZ);

    const rotX = new Float32Array([
      1, 0, 0, 0,
      0, cosX, sinX, 0,
      0, -sinX, cosX, 0,
      0, 0, 0, 1
    ]);

    const rotY = new Float32Array([
      cosY, 0, -sinY, 0,
      0, 1, 0, 0,
      sinY, 0, cosY, 0,
      0, 0, 0, 1
    ]);

    const rotZ = new Float32Array([
      cosZ, sinZ, 0, 0,
      -sinZ, cosZ, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ]);

    return this.multiplyMatrices(this.multiplyMatrices(rotX, rotY), rotZ);
  }

  createPerspectiveMatrix(fov, aspect, near, far) {
    const f = 1.0 / Math.tan(fov / 2);
    const rangeInv = 1 / (near - far);

    return new Float32Array([
      f / aspect, 0, 0, 0,
      0, f, 0, 0,
      0, 0, (near + far) * rangeInv, -1,
      0, 0, near * far * rangeInv * 2, 0
    ]);
  }

  multiplyMatrices(a, b) {
    const result = new Float32Array(16);
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < 4; j++) {
        result[i * 4 + j] = 0;
        for (let k = 0; k < 4; k++) {
          result[i * 4 + j] += a[i * 4 + k] * b[k * 4 + j];
        }
      }
    }
    return result;
  }

  updateUniforms(time) {
    const modelMatrix = this.multiplyMatrices(
      this.createTranslationMatrix(0, 0, 0),
      this.createRotationMatrix(time * 0.5, time * 0.3, time * 0.2)
    );

    const viewMatrix = this.createIdentityMatrix();
    viewMatrix[14] = -5; // Move camera back

    const projectionMatrix = this.createPerspectiveMatrix(
      this.camera.fov,
      this.camera.aspect,
      this.camera.near,
      this.camera.far
    );

    const uniformData = new Float32Array([
      ...modelMatrix,
      ...viewMatrix,
      ...projectionMatrix,
      ...modelMatrix, // Normal matrix (simplified)
      ...this.camera.position,
      2, 2, 2, // Light position
      1, 1, 1, // Light color
      1.0       // Light intensity
    ]);

    this.device.queue.writeBuffer(this.uniformBuffer, 0, uniformData);
  }

  render(time) {
    this.updateUniforms(time);

    const commandEncoder = this.device.createCommandEncoder();
    const textureView = this.context.getCurrentTexture().createView();

    const renderPass = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: textureView,
        clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 },
        loadOp: 'clear',
        storeOp: 'store',
      }],
      depthStencilAttachment: {
        view: this.depthTexture.createView(),
        depthClearValue: 1.0,
        depthLoadOp: 'clear',
        depthStoreOp: 'store',
      },
    });

    renderPass.setPipeline(this.pipeline);
    renderPass.setBindGroup(0, this.bindGroup);
    renderPass.setVertexBuffer(0, this.positionBuffer);
    renderPass.setVertexBuffer(1, this.normalBuffer);
    renderPass.setVertexBuffer(2, this.colorBuffer);
    renderPass.setIndexBuffer(this.indexBuffer, 'uint16');
    renderPass.drawIndexed(this.indexCount);
    renderPass.end();

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

  async start() {
    await this.init();

    const frame = (time) => {
      this.render(time * 0.001); // Convert to seconds
      requestAnimationFrame(frame);
    };

    frame(0);
  }
}

// Usage
async function render3DScene() {
  const canvas = document.getElementById('canvas');
  const scene = new Scene3D(canvas);

  try {
    await scene.start();
  } catch (error) {
    console.error('3D scene initialization failed:', error);
  }
}

if (navigator.gpu) {
  render3DScene();
}