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
数据量限制 较小 较大
更新效率 单个变量更新 批量更新
共享能力 不能共享 可以共享
代码复杂度 简单 较复杂
相关推荐
云中雾丽7 小时前
dart的继承和消息循环机制
前端
世界哪有真情7 小时前
Trae 蓝屏问题
前端·后端·trae
Moment7 小时前
NestJS 在 2025 年:对于后端开发者仍然值得吗 😕😕😕
前端·后端·github
热心市民小岳7 小时前
Konva.js 实现 腾讯文档 多维表格
前端·javascript
砺能7 小时前
uniapp生成的app添加操作日志
前端·uni-app
小Dno17 小时前
diff算法理解第一篇
前端
文心快码BaiduComate7 小时前
文心快码实测Markdown排版工具开发
前端·后端·程序员
九十一7 小时前
闭包那点事
javascript
杨超越luckly7 小时前
HTML应用指南:利用GET请求获取全国沃尔沃门店位置信息
前端·arcgis·html·数据可视化·门店数据
渣哥7 小时前
原文来自于:[https://zha-ge.cn/java/128](https://zha-ge.cn/java/128)
javascript·后端·面试