目录

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 帧缓冲区(Framebuffers)

本文旨在帮助你建立对 WebGL 中 framebuffer 的一个直观理解。
framebuffer 之所以重要,是因为它们允许你渲染到纹理

一个 Framebuffer 其实就是一个附件的集合(attachments)。就是这样!
它的作用是让你可以将内容渲染到纹理渲染缓冲区中。

你可以将一个 Framebuffer 对象想象成这样:

class Framebuffer {
  constructor() {
    this.attachments = new Map();  // attachments by attachment point
    this.drawBuffers = [gl.BACK, gl.NONE, gl.NONE, gl.NONE, ...];
    this.readBuffer = gl.BACK,
  }
}

WebGL2RenderingContext(也就是 gl 对象)可以理解为如下结构:

// pseudo code
gl = {
  drawFramebufferBinding: defaultFramebufferForCanvas,
  readFramebufferBinding: defaultFramebufferForCanvas,
}

这里有两个绑定点(binding point),设置方式如下:

gl.bindFramebuffer(target, framebuffer) {
  framebuffer = framebuffer || defaultFramebufferForCanvas; // if null use canvas
  switch (target) {
    case: gl.DRAW_FRAMEBUFFER:
      this.drawFramebufferBinding = framebuffer;
      break;
    case: gl.READ_FRAMEBUFFER:
      this.readFramebufferBinding = framebuffer;
      break;
    case: gl.FRAMEBUFFER:
      this.drawFramebufferBinding = framebuffer;
      this.readFramebufferBinding = framebuffer;
      break;
    default:
      ... error ...
  }
}

DRAW_FRAMEBUFFER:用于向 framebuffer 绘制内容,如通过 gl.cleargl.draw???gl.blitFramebufferREAD_FRAMEBUFFER:用于从 framebuffer 中读取内容,如通过 gl.readPixelsgl.blitFramebuffer

你可以通过三个函数向 framebuffer 添加附件,framebufferTexture2DframebufferRenderbufferframebufferTextureLayer

它们的内部逻辑可以想象成如下实现:

// pseudo code
gl._getFramebufferByTarget(target) {
  switch (target) {
    case gl.FRAMEBUFFER:
    case gl.DRAW_FRAMEBUFFER:
      return this.drawFramebufferBinding;
    case gl.READ_FRAMEBUFFER:
      return this.readFramebufferBinding;
  }
}
gl.framebufferTexture2D(target, attachmentPoint, texTarget, texture, mipLevel) {
  const framebuffer = this._getFramebufferByTarget(target);
  framebuffer.attachments.set(attachmentPoint, {
    texture, texTarget, mipLevel,
  });
}
gl.framebufferTextureLayer(target, attachmentPoint, texture, mipLevel, layer) {
  const framebuffer = this._getFramebufferByTarget(target);
  framebuffer.attachments.set(attachmentPoint, {
    texture, texTarget, mipLevel, layer
  });
}
gl.framebufferRenderbuffer(target, attachmentPoint, renderbufferTarget, renderbuffer) {
  const framebuffer = this._getFramebufferByTarget(target);
  framebuffer.attachments.set(attachmentPoint, {
    renderbufferTarget, renderbuffer
  });
}

你可以使用 gl.drawBuffers 设置 framebuffer 的绘制目标数组,其内部实现如下所示:

// pseudo code
gl.drawBuffers(drawBuffers) {
  const framebuffer = this._getFramebufferByTarget(gl.DRAW_FRAMEBUFFER);
  for (let i = 0; i < maxDrawBuffers; ++i) {
    framebuffer.drawBuffers[i] = i < drawBuffers.length
        ? drawBuffers[i]
        : gl.NONE
  }
}

drawBuffers 数组决定了哪些附件会被渲染。

合法值包括:

  • gl.NONE:不渲染到这个附件
  • gl.COLOR_ATTACHMENTx:其中 x 和附件索引一样
  • gl.BACK:仅在当前 framebuffernull 时有效,表示渲染到默认 canvas 的 backbuffer

你还可以使用 gl.readBuffer 设置读缓冲:

// pseudo code
gl.readBuffer(readBuffer) {
  const framebuffer = this._getFramebufferByTarget(gl.READ_FRAMEBUFFER);
  framebuffer.readBuffer = readBuffer;
}

readBuffer 决定在调用 gl.readPixels 时会从哪个附件读取。

重点总结:framebuffer 本质上只是一个附件的简单集合。 真正复杂的是这些附件之间的限制与兼容性。 例如:浮点纹理附件默认不能被渲染,除非通过扩展如 EXT_color_buffer_float 开启支持。 此外,如果 framebuffer 包含多个附件,它们必须具有相同的尺寸。

有意见或建议? 在GitHub上提issue.
comments powered by Disqus