墨舟的AI笔记头像
关注
WebGL 帧缓冲区(FBO)与离屏渲染纹理传递封面图

WebGL 帧缓冲区(FBO)与离屏渲染纹理传递

WebGL 帧缓冲区(FBO)与离屏渲染纹理传递

封面信息图

在 WebGL 与图形学开发中,默认情况下所有的着色器绘制指令都是直接输出到屏幕的默认帧缓冲区(Default Framebuffer)上,也就是浏览器呈现给用户的 Canvas 像素。

但当我们要做后处理特效(Post-processing)、水面动态折射、阴影贴图(Shadow Map)、高斯模糊(Bloom 辉光)或多通行滤镜(Multi-pass Rendering)时,单次直接绘制就捉襟见肘了。我们必须先将三维场景渲染到一个不可见的离屏内存中,并将其转化为一张 2D 纹理(Texture),然后再把这张纹理送进下一个着色器进行采样加工。

这一技术的基石,就是 WebGL 的帧缓冲区对象(Frame Buffer Object, 简称 FBO)。

FBO 架构与附件装配机制

一个完整的 FBO 本身并不直接存储像素数据,它更像是一个装配架(Container)。要让 FBO 具备绘制能力,必须为其挂载至少一个颜色附件(Color Attachment),通常还可以附加深度附件(Depth Attachment)和模板附件(Stencil Attachment)。

flowchart LR
    subgraph Pass1 [第一通行:场景几何渲染]
        Scene[三维几何体/网格] --> FBO[自定义帧缓冲区 FBO]
        FBO --> ColorTex[(颜色纹理附件 Color Texture)]
        FBO --> DepthBuf[(深度缓冲区 Renderbuffer)]
    end
    subgraph Pass2 [第二通行:全屏四边形后处理]
        ColorTex --> PostShader[后处理片元着色器 (辉光/模糊)]
        PostShader --> Screen[默认屏幕帧缓冲区 Canvas]
    end

完整 FBO 创建与纹理绑定代码

export interface RenderTargetFBO {
  framebuffer: WebGLFramebuffer;
  texture: WebGLTexture;
  depthBuffer: WebGLRenderbuffer;
  width: number;
  height: number;
}

export function createFramebufferTexture(gl: WebGLRenderingContext, width: number, height: number): RenderTargetFBO {
  // 1. 创建 FBO 实例
  const framebuffer = gl.createFramebuffer();
  if (!framebuffer) throw new Error('无法创建 WebGLFramebuffer');

  // 2. 创建并配置颜色纹理附件
  const texture = gl.createTexture();
  if (!texture) throw new Error('无法创建 WebGLTexture');

  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.RGBA,
    width,
    height,
    0,
    gl.RGBA,
    gl.UNSIGNED_BYTE,
    null // 初始数据为空,由 GPU 绘制填充
  );

  // 设置纹理过滤模式:线性插值与边缘截取
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);

  // 3. 创建深度渲染缓冲区(用于三维场景的深度测试 Z-Buffer)
  const depthBuffer = gl.createRenderbuffer();
  if (!depthBuffer) throw new Error('无法创建 WebGLRenderbuffer');

  gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);

  // 4. 将纹理与深度缓冲区装配到 FBO
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

  // 5. 完整性检查
  const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  if (status !== gl.FRAMEBUFFER_COMPLETE) {
    throw new Error(`FBO 装配不完整: 状态码 0x${status.toString(16)}`);
  }

  // 解除绑定,恢复干净状态
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.bindTexture(gl.TEXTURE_2D, null);
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);

  return { framebuffer, texture, depthBuffer, width, height };
}

多通行渲染(Ping-Pong 双缓冲)与纹理传递

当实现高斯模糊或流体模拟时,我们需要水平模糊一次、垂直模糊一次,甚至迭代数十轮。如果频繁创建和销毁 FBO,会带来严重的显存碎片。

图形学标准做法是 Ping-Pong(乒乓双缓冲)架构:创建两个规格完全相同的 FBO(A 和 B)。

  • 通行 1:绑定 FBO A 为输出目标,将原始场景渲染进 A;
  • 通行 2:绑定 FBO B 为输出目标,将 A 的纹理作为输入采样,进行水平卷积;
  • 通行 3:绑定 FBO A 为输出目标,将 B 的纹理作为输入采样,进行垂直卷积;
  • 通行 4:绑定屏幕默认帧缓冲 null,将最终混合纹理绘制在全屏四边形(Screen-space Quad)上。
export class PingPongRenderer {
  private fboA: RenderTargetFBO;
  private fboB: RenderTargetFBO;
  private currentRead: 'A' | 'B' = 'A';

  constructor(gl: WebGLRenderingContext, width: number, height: number) {
    this.fboA = createFramebufferTexture(gl, width, height);
    this.fboB = createFramebufferTexture(gl, width, height);
  }

  getReadFBO(): RenderTargetFBO {
    return this.currentRead === 'A' ? this.fboA : this.fboB;
  }

  getWriteFBO(): RenderTargetFBO {
    return this.currentRead === 'A' ? this.fboB : this.fboA;
  }

  swap() {
    this.currentRead = this.currentRead === 'A' ? 'B' : 'A';
  }

  dispose(gl: WebGLRenderingContext) {
    gl.deleteFramebuffer(this.fboA.framebuffer);
    gl.deleteTexture(this.fboA.texture);
    gl.deleteRenderbuffer(this.fboA.depthBuffer);

    gl.deleteFramebuffer(this.fboB.framebuffer);
    gl.deleteTexture(this.fboB.texture);
    gl.deleteRenderbuffer(this.fboB.depthBuffer);
  }
}

踩坑要点与视口对齐

  1. 视口尺寸(Viewport)必须同步切换:
    从离屏 FBO 切换到主屏幕时,务必调用 gl.viewport(0, 0, screenWidth, screenHeight)。如果忘记更新,绘制的画面会出现非预期的拉伸或裁切。
  2. 清除标志位(Clear Flags):
    每次绑定新 FBO 时,记得显式调用 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT),避免前一帧的残影污染后续计算。

掌握 FBO 离屏渲染与纹理传递,意味着你正式跨过了 WebGL 初学者的门槛,拥有了自由驾驭高级着色与电影级后处理画面的核心能力。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_77785315/article/details/164426521

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--