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
数据量限制 较小 较大
更新效率 单个变量更新 批量更新
共享能力 不能共享 可以共享
代码复杂度 简单 较复杂
相关推荐
我只会写Bug啊1 分钟前
复制可用!纯前端基于 Geolocation API 实现经纬度获取与地图可视化
前端·高德地图·地图·百度地图·经纬度
刘一说9 分钟前
Vue3 模块语法革命:移除过滤器(Filters)的深度解析与迁移指南
前端·vue.js·js
lkbhua莱克瓦2442 分钟前
JavaScript核心语法
开发语言·前端·javascript·笔记·html·ecmascript·javaweb
Trae1ounG42 分钟前
这是什么dom
前端·javascript·vue.js
比老马还六1 小时前
Bipes项目二次开发/扩展积木功能(八)
前端·javascript
易营宝1 小时前
全球建站SaaS平台能提升SEO评分吗?是否值得切换?
大数据·前端·人工智能
C_心欲无痕1 小时前
Next.js 的服务端路由:对应api文件夹
开发语言·javascript·ecmascript
513495921 小时前
在Vue.js项目中使用docx和file-saver实现Word文档导出
前端·vue.js·word
Shirley~~2 小时前
leetcode移除元素
javascript·数据结构·算法
AC赳赳老秦2 小时前
Prometheus + DeepSeek:自动生成巡检脚本与告警规则配置实战
前端·javascript·爬虫·搜索引擎·prometheus·easyui·deepseek