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
数据量限制 较小 较大
更新效率 单个变量更新 批量更新
共享能力 不能共享 可以共享
代码复杂度 简单 较复杂
相关推荐
LLLLYYYRRRRRTT6 分钟前
MariaDB 数据库管理与web服务器
前端·数据库·mariadb
胡gh7 分钟前
什么是瀑布流?用大白话给你讲明白!
前端·javascript·面试
universe_0113 分钟前
day22|学习前端ts语言
前端·笔记
teeeeeeemo17 分钟前
一些js数组去重的实现算法
开发语言·前端·javascript·笔记·算法
Zz_waiting.18 分钟前
Javaweb - 14.1 - 前端工程化
前端·es6
掘金安东尼20 分钟前
前端周刊第426期(2025年8月4日–8月10日)
前端·javascript·面试
Abadbeginning20 分钟前
FastSoyAdmin导出excel报错‘latin-1‘ codec can‘t encode characters in position 41-54
前端·javascript·后端
ZXT22 分钟前
WebAssembly
前端
卢叁22 分钟前
Flutter开发环境安装指南
前端·flutter
curdcv_po40 分钟前
Three.js,闲谈3D——智慧XX
前端