webgl2 方法解析: framebufferTexture2D()

120 阅读5分钟

framebufferTexture2D 是 WebGL 中的一个函数,用于将纹理对象(WebGLTexture)或渲染缓冲对象(WebGLRenderbuffer)附加到帧缓冲对象(WebGLFramebuffer)的某个附件点上。帧缓冲对象(Framebuffer)是 WebGL 中用于自定义渲染目标的高级功能,允许开发者将渲染结果输出到纹理或渲染缓冲中,而不是直接输出到默认的画布(Canvas)上。

函数签名

void framebufferTexture2D(
  GLenum target,
  GLenum attachment,
  GLenum textarget,
  WebGLTexture? texture,
  GLint level
);

参数说明

  1. target

    • 指定帧缓冲对象的类型。通常为:

      • gl.FRAMEBUFFER:表示当前绑定的帧缓冲对象。
      • gl.READ_FRAMEBUFFER:用于读取操作的帧缓冲对象(WebGL 2.0 中引入)。
      • gl.DRAW_FRAMEBUFFER:用于绘制操作的帧缓冲对象(WebGL 2.0 中引入)。
  2. attachment

    • 指定要附加的附件点。附件常见的点包括:

      • gl.COLOR_ATTACHMENT0:颜色附件点,用于存储颜色数据。
      • gl.DEPTH_ATTACHMENT:深度附件点,用于存储深度数据。
      • gl.STENCIL_ATTACHMENT:模板附件点,用于存储模板数据。
      • gl.DEPTH_STENCIL_ATTACHMENT:深度和模板组合附件点(WebGL 2.0 中引入)。
  3. textarget

    • 指定纹理的目标类型。常见的值包括:

      • gl.TEXTURE_2D:二维纹理。
      • gl.TEXTURE_CUBE_MAP_POSITIVE_Xgl.TEXTURE_CUBE_MAP_NEGATIVE_X 等:立方体贴图的各个面(用于立方体贴图纹理)。
      • gl.TEXTURE_3D(WebGL 2.0 中引入):三维纹理。
      • gl.TEXTURE_2D_ARRAY(WebGL 2.0 中引入):二维纹理数组。
  4. texture

    • 要附加的纹理对象(WebGLTexture)。如果为 null,则会从指定的附件点上分离纹理。
  5. level

    • 指定纹理的多级渐进(Mipmap)级别。通常为 0,表示基础纹理级别。

使用场景

framebufferTexture2D 的主要用途是将纹理对象附加到帧缓冲对象上,以便将渲染结果存储到纹理中。这在以下场景中非常有用:

  1. 离屏渲染(Off-screen Rendering) :将渲染结果存储到纹理中,而不是直接渲染到画布上。
  2. 后处理(Post-processing) :将渲染结果存储到纹理中,然后对该纹理进行进一步处理(如模糊、边缘检测等)。
  3. 阴影映射(Shadow Mapping) :将深度信息存储到纹理中,用于阴影计算。
  4. 立方体贴图渲染(Cube Map Rendering) :将渲染结果存储到立方体贴图的各个面上。

示例代码

以下是一个简单的示例,展示如何使用 framebufferTexture2D 将纹理附加到帧缓冲对象上, 可直接运行:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bindFramebuffer方法示例</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
    canvas {
      display: block;
    }
  </style>
</head>
<body>
  <canvas id="glCanvas"></canvas>
  <script>
    //////////////////////////1.获取渲染上下文//////////////////////////
    // 初始化WebGL
    const canvas = document.getElementById('glCanvas');
    const gl = canvas.getContext('webgl2');
​
    if (!gl) {
      alert('获取渲染上下文失败! ');
      throw new Error('获取渲染上下文失败! ');
    }
​
    // 设置画布大小
    canvas.width = 512;
    canvas.height = 512;
​
    //////////////////////////2.定义着色器//////////////////////////
    // 顶点着色器
    const vsSource = `#version 300 es
      in vec2 aPosition;
      
      void main() {
        gl_Position = vec4(aPosition, 0.0, 1.0);
      }
    `;
​
    // 片段着色器 - 绘制渐变三角形
    const fsSource = `#version 300 es
      precision mediump float;
      out vec4 fragColor;
      
      void main() {
        // 根据位置生成渐变颜色
        vec2 pos = gl_FragCoord.xy / vec2(512.0, 512.0);
        fragColor = vec4(pos.x, pos.y, 0.5, 1.0);  
      }
    `;
​
    // 编译着色器
    function compileShader(gl, source, type) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
​
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert('着色器编译错误! ');
        gl.deleteShader(shader);
        throw new Error('着色器编译错误! ');
      }
​
      return shader;
    }
​
    const vertexShader = compileShader(gl, vsSource, gl.VERTEX_SHADER);
    const fragmentShader = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);
​
    //////////////////////////3.定义program//////////////////////////
    // 创建着色器程序
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
​
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      alert('着色器程序链接失败! ');
      throw new Error('着色器程序链接失败! ');
    }
​
    //////////////////////////4.设置attrib//////////////////////////
    // 顶点数据: 一个三角形
    const positions = new Float32Array([
      0.0, 0.8,   // 顶部顶点
      -0.8, -0.8, // 左下顶点
      0.8, -0.8   // 右下顶点
    ]);
​
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
    const aPositionLoc = gl.getAttribLocation(program, 'aPosition');
    gl.enableVertexAttribArray(aPositionLoc);
    gl.vertexAttribPointer(aPositionLoc, 2, gl.FLOAT, false, 0, 0);
    
    //////////////////////////5.设置帧缓冲区//////////////////////////
    // 1. 创建帧缓冲对象(FBO)
    const framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
​
    // 2. 创建纹理作为颜色附件
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    // 分配纹理存储空间
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // 3. 将纹理附加到帧缓冲
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
    // 4. 检查帧缓冲是否完整
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
      alert('帧缓冲不完整: ', gl.checkFramebufferStatus(gl.FRAMEBUFFER));
      throw new Error('帧缓冲不完整: ', gl.checkFramebufferStatus(gl.FRAMEBUFFER));
    }
    //////////////////////////6.渲染到帧缓冲区//////////////////////////
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(0.2, 0.2, 0.2, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
​
    gl.useProgram(program);
    // 会将结果(本质就是一幅图像, 然后以数组形式 借帧缓冲区(帧缓冲区起到统一管理渲染结果的作用) 保存
    // 到上面创建并绑定到帧缓冲区的texture纹理中(这纹理本质也就是一幅图像,用数组保存像素数据))
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    
    //////////////////////////7. 读取纹理数据//////////////////////////
    // 将保存在上面texture纹理中的图像读取出来
    const pixels = new Uint8Array(canvas.width * canvas.height * 4); // RGBA格式
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
​
    // 输出帧缓冲渲染的结果
    console.log("帧缓冲渲染的结果: ", pixels);
​
    //////////////////////////将上方使用帧缓冲区离线渲染的结果可视化出来//////////////////////////
    //////////////////////////将上方使用帧缓冲区离线渲染的结果可视化出来//////////////////////////
    //////////////////////////将上方使用帧缓冲区离线渲染的结果可视化出来//////////////////////////
    //////////////////////////将上方使用帧缓冲区离线渲染的结果可视化出来//////////////////////////
    //////////////////////////将上方使用帧缓冲区离线渲染的结果可视化出来//////////////////////////
    
    // 切换到默认帧缓冲(屏幕上的画布 - canvas元素)
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
​
    //////////////////////////2.定义着色器//////////////////////////
    const displayVsSource = `#version 300 es
      in vec2 aPosition;
      out vec2 vTexCoord;
      
      void main() {
        vTexCoord = aPosition * 0.5 + 0.5;
        gl_Position = vec4(aPosition, 0.0, 1.0);
      }
    `;
​
    const displayFsSource = `#version 300 es
      precision mediump float;
      in vec2 vTexCoord;
      uniform sampler2D uTexture;
      out vec4 fragColor;
      
      void main() {
        fragColor = texture(uTexture, vTexCoord);
      }
    `;
​
    const displayVertexShader = compileShader(gl, displayVsSource, gl.VERTEX_SHADER);
    const displayFragmentShader = compileShader(gl, displayFsSource, gl.FRAGMENT_SHADER);
​
    //////////////////////////3.定义program//////////////////////////
    const displayProgram = gl.createProgram();
    gl.attachShader(displayProgram, displayVertexShader);
    gl.attachShader(displayProgram, displayFragmentShader);
    gl.linkProgram(displayProgram);
​
    if (!gl.getProgramParameter(displayProgram, gl.LINK_STATUS)) {
      alert('着色器程序链接失败! ');
      throw new Error('着色器程序链接失败! ');
    }
​
    //////////////////////////4.设置attrib//////////////////////////
    // 渲染全屏的四边形用以显示上面离线渲染得到的纹理
    const quadPositions = new Float32Array([
      -1.0, -1.0,
      1.0, -1.0,
      -1.0, 1.0,
      1.0, 1.0
    ]);
​
    const quadBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, quadPositions, gl.STATIC_DRAW);
    const aQuadPosition = gl.getAttribLocation(displayProgram, 'aPosition');
    gl.enableVertexAttribArray(aQuadPosition);
    gl.vertexAttribPointer(aQuadPosition, 2, gl.FLOAT, false, 0, 0);
​
    //////////////////////////5.设置uniform//////////////////////////
    gl.useProgram(displayProgram);
    const uTextureLoc = gl.getUniformLocation(displayProgram, 'uTexture');
    gl.uniform1i(uTextureLoc, 0);
​
    //////////////////////////6.渲染//////////////////////////
​
    // 9. 将纹理渲染到画布查看
    gl.viewport(0.0, 0.0, canvas.width, canvas.height);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
​
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
​
  </script>
</body>
</html>

注意事项

  1. 帧缓冲对象的完整性:在使用帧缓冲对象之前,必须确保其完整。可以通过 gl.checkFramebufferStatus 检查帧缓冲对象的状态。
  2. 纹理格式和附件点的匹配:纹理的格式必须与附件点的要求匹配。例如,颜色附件点通常需要 RGBA 格式的纹理,而深度附件点需要深度格式的纹理。
  3. 解绑帧缓冲对象:在完成渲染后,记得解绑帧缓冲对象,以避免后续渲染操作受到影响。

framebufferTexture2D 是 WebGL 中非常重要的功能之一,它为开发者提供了强大的自定义渲染目标的能力,是实现高级图形效果的关键工具。