webgl2 方法解析: uniforms buffer object

Uniforms Buffer Object (UBO) 是 WebGL2 引入的一项重要功能,它允许开发者更高效地管理和传递着色器中的 uniform 变量。

基本概念

UBO 是一种特殊的缓冲区对象,用于存储和组织着色器中的 uniform 数据。与传统的逐个设置 uniform 变量不同,UBO 允许你将一组相关的 uniform 变量作为一个整体来处理。

主要优势

  1. 性能提升:减少 WebGL API 调用次数
  2. 数据共享:多个着色器程序可以共享同一个 UBO
  3. 大容量支持:支持比传统 uniform 更大的数据量
  4. 结构化数据:可以更好地组织复杂的 uniform 数据

核心组件

  1. GLSL 中的 Uniform Block

    glsl 复制代码
    layout(std140) uniform TransformBlock {
        mat4 modelViewMatrix;
        mat4 projectionMatrix;
    };
  2. JavaScript 中的缓冲区对象

    js 复制代码
    const ubo = gl.createBuffer();
  3. 绑定系统:通过绑定点连接 uniform block 和 buffer

基本工作流程

  1. 创建 Uniform Block(在着色器中)

  2. 创建并填充缓冲区

    js 复制代码
    gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
    gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW);
  3. 获取并绑定 Uniform Block

    js 复制代码
    const blockIndex = gl.getUniformBlockIndex(program, "TransformBlock");
    gl.uniformBlockBinding(program, blockIndex, 0);
  4. 绑定缓冲区到绑定点

    js 复制代码
    gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);

内存布局选项

UBO 支持不同的内存布局规范:

  1. std140:标准布局(webgl2 - glsl 300 es版本只可以使用这个)

    glsl 复制代码
    layout(std140) uniform MyBlock { ... };

使用场景

  • 矩阵和变换数据
  • 光照参数
  • 材质属性
  • 场景全局设置
  • 大量需要频繁更新的 uniform 数据

示例

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>uniforms buffer 学习示例</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
    canvas {
      display: block;
      width: 100vw;
      height: 100vh;
    }
  </style>
</head>
<body>
  <canvas id="glCanvas"></canvas>

  <script>
    // 顶点着色器 - 使用 uniform block
    const vertexShaderSource = `#version 300 es
    in vec2 aPosition;
    
    layout(std140) uniform TransformBlock {
      mat4 uModelViewMatrix;
      mat4 uProjectionMatrix;
    };
    
    void main() {
      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 0.0, 1.0);
    }`;

    // 片段着色器 - 使用 uniform block
    const fragmentShaderSource = `#version 300 es
    precision highp float;
    
    layout(std140) uniform ColorBlock {
      vec4 uColor;
    };
    
    out vec4 fragColor;
    
    void main() {
      fragColor = uColor;
    }`;

    // 1. 获取渲染上下文
    const canvas = document.getElementById('glCanvas');
    const gl = canvas.getContext('webgl2');

    if (!gl) {
      alert('WebGL2 不支持!');
      throw new Error('WebGL2 不支持!');
    }

    // 检查 UBO 支持
    const uboSupported = gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS) > 0;
    if (!uboSupported) {
      alert('ubo 不支持!');
      throw new Error('UBO 不支持!');
    }

    // 调整画布大小
    function resizeCanvas() {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      gl.viewport(0, 0, canvas.width, canvas.height);
    }
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    // 2. 创建并编译着色器
    // 编译着色器
    function createShader(gl, source, type) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);

      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('Shader compilation error: ', gl.getShaderInfoLog(shader));
        gl.deleteShader(shadr);
        return null;
      }

      return shader;
    }

    // 3. 创建渲染程序
    function createProgram(vertexShader, fragmentShader) {
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);

      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error('Program linking error: ', gl.getProgramInfoLog(program));
        return null;
      }

      return program;
    }

    const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
    const fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
    const program = createProgram(vertexShader, fragmentShader);
    gl.useProgram(program);

    // 4. 传输 attrib
    const vertices = new Float32Array([
      0.0,  0.5,  // 顶部顶点
      -0.5, -0.5,  // 左下顶点
      0.5, -0.5   // 右下顶点
    ]);

    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    const positionAtributeLocation = gl.getAttribLocation(program, 'aPosition');
    gl.enableVertexAttribArray(positionAtributeLocation);
    gl.vertexAttribPointer(positionAtributeLocation, 2, gl.FLOAT, false, 0, 0);

    // 5. 传输 uniform block
    const transformBlockIndex = gl.getUniformBlockIndex(program, 'TransformBlock');
    const colorBlockIndex = gl.getUniformBlockIndex(program, 'ColorBlock');
    
    const transformBlockSize = gl.getActiveUniformBlockParameter(program, transformBlockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
    const colorBlockSize = gl.getActiveUniformBlockParameter(program, colorBlockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);

    // 创建 ubo
    const transformUniformBuffer = gl.createBuffer();
    gl.bindBuffer(gl.UNIFORM_BUFFER, transformUniformBuffer);
    gl.bufferData(gl.UNIFORM_BUFFER, transformBlockSize, gl.DYNAMIC_DRAW);

    // 创建 color uniform buffer
    const colorUniformBuffer = gl.createBuffer();
    gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
    gl.bufferData(gl.UNIFORM_BUFFER, colorBlockSize, gl.DYNAMIC_DRAW);

    // 绑定 uniform block 到 binding point
    gl.uniformBlockBinding(program, transformBlockIndex, 0);
    gl.uniformBlockBinding(program, colorBlockIndex, 1);

    // 绑定 uniform block 到 binding point
    gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, transformUniformBuffer);
    gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, colorUniformBuffer);

    // 设置初始颜色
    const initialColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
    gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
    gl.bufferSubData(gl.UNIFORM_BUFFER, 0, initialColor);


    // 6. 动画循环 与 渲染
    let angle = 0;
    function animate() {
      angle += 0.01;

      // 计算模型视图矩阵(旋转)
      const modelViewMatrix = new Float32Array(16);
      const cosA = Math.cos(angle);
      const sinA = Math.sin(angle);

      modelViewMatrix[0] = cosA;
      modelViewMatrix[1] = sinA;
      modelViewMatrix[4] = -sinA;
      modelViewMatrix[5] = cosA;
      modelViewMatrix[10] = 1.0;
      modelViewMatrix[15] = 1.0;

      // 计算投影矩阵(正交投影)
      const projectionMatrix = new Float32Array(16);
      const aspect = canvas.width / canvas.height;

      // 简单的正交投影
      projectionMatrix[0] = 1.0 / aspect;
      projectionMatrix[5] = 1.0;
      projectionMatrix[10] = 1.0;
      projectionMatrix[15] = 1.0;

      // 更新 transform uniform buffer
      gl.bindBuffer(gl.UNIFORM_BUFFER, transformUniformBuffer);
      gl.bufferSubData(gl.UNIFORM_BUFFER, 0, modelViewMatrix);
      gl.bufferSubData(gl.UNIFORM_BUFFER, 16 * 4, projectionMatrix);

      // 随时间改变颜色
      const time = Date.now() * 0.001;
      const color = new Float32Array([
          (Math.sin(time) + 1) / 2,
          (Math.sin(time * 0.7) + 1) / 2,
          (Math.sin(time * 0.3) + 1) / 2,
          1.0
      ]);
      gl.bindBuffer(gl.UNIFORM_BUFFER, colorUniformBuffer);
      gl.bufferSubData(gl.UNIFORM_BUFFER, 0, color);

      // 清除画布并绘制
      gl.clearColor(0.1, 0.1, 0.1, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      
      requestAnimationFrame(animate);
    }

    animate();
  </script>
</body>
</html>

与传统 uniform 的比较

特性 传统 uniform UBO
数据量限制 较小 较大
更新效率 单个变量更新 批量更新
共享能力 不能共享 可以共享
代码复杂度 简单 较复杂
相关推荐
前端付豪3 分钟前
实现 AI 回复支持 Markdown 渲染
前端·人工智能·markdown
阳火锅15 分钟前
鳌虾 AoCode:重新定义 AI 编程助手的下一代可视化工具
前端·人工智能·架构
拾贰_C18 分钟前
【node】node彻底卸载删除
前端
SuperEugene19 分钟前
Vue3 组合式函数(Hooks)封装规范实战:命名 / 输入输出 / 复用边界 + 避坑|Vue 组件与模板规范篇
开发语言·前端·javascript·vue.js·前端框架
芝士麻雀21 分钟前
掌握 .claude/ 目录:让 Claude Code 真正懂你的项目
前端·后端
cmd22 分钟前
JS深浅拷贝全解析|常用方法+手写实现+避坑指南(附完整代码)
前端·javascript
进击的尘埃23 分钟前
AbortController 实战:竞态取消、超时兜底与请求生命周期管理
前端·javascript
张一凡9324 分钟前
我用 Zustand 三年了,直到遇见 easy-model...
前端·javascript·react.js
张元清24 分钟前
React 拖拽:无需第三方库的完整方案
前端·javascript·面试
giszhc25 分钟前
geojson-to-kml (KML 格式转换工具)
前端