跳到主要内容
3D Playground
WebGL 与着色器第 10 / 12 节

帧缓冲与后期处理

高级WebGL 2

渲染到纹理

默认情况下,绘制结果直接写到画布上。帧缓冲对象(FBO)让你改为画进一张纹理,随后这张纹理就可以像普通贴图一样被采样。几乎所有后期效果(泛光、景深、色调映射、描边)都建立在这一步之上。

glsl
// JS:
// gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);

创建 FBO 的步骤:

  1. 创建一张空纹理:texImage2D(..., null) 只分配存储,不上传数据。
  2. 把它作为 COLOR_ATTACHMENT0 挂到帧缓冲上。
  3. 用 gl.checkFramebufferStatus 确认状态是 FRAMEBUFFER_COMPLETE。
  4. 画布尺寸变化时,重新分配纹理大小。

如果场景需要深度测试,还要再挂一个深度渲染缓冲(DEPTH_ATTACHMENT)。本例是纯 2D 加法混合,所以省略了。

两遍渲染

  • 第 1 遍:绑定 FBO,先画网格背景,再用 drawArraysInstanced 一次画出几百个发光圆点。gl_InstanceID 决定每个圆点的轨道和颜色,blendFunc(ONE, ONE) 让重叠处变得更亮。
  • 第 2 遍:bindFramebuffer(null) 切回画布,画一个全屏三角形,在片元着色器里读取第 1 遍的纹理。

后期效果

  • 模糊:对周围 5×5 个样本做高斯加权平均。真实项目会拆成水平、垂直两遍(可分离卷积),把开销从 n² 降到 2n。
  • 色差:对 R、G、B 三个通道分别沿径向偏移不同的距离采样,模拟镜头的色散,越靠近边缘越明显。
  • 暗角:按到中心的距离逐渐压暗。
glsl
vec2 off = (vUV - 0.5) * uAberration * 0.02;
vec3 c = vec3(blurred(vUV + off).r, blurred(vUV).g, blurred(vUV - off).b);

拖动“before | after”,左侧显示原始的 FBO 纹理,右侧是处理后的效果。

注意采样时使用的 uTexel = 1 / 尺寸:后期着色器里所有“偏移几个像素”的操作,都要换算成 UV 单位。

核心 API

gl.createFramebuffergl.framebufferTexture2Dgl.checkFramebufferStatusgl.bindFramebuffergl.drawArraysInstancedgl_InstanceIDgl.blendFunctexture()

动手试试

  1. 1把 blur 拉到 12,观察 5×5 核在大半径下出现的分层伪影。
  2. 2在后期着色器里加一个灰度开关:dot(c, vec3(0.299, 0.587, 0.114))。
  3. 3把模糊拆成水平和垂直两遍,需要两个 FBO 来回切换(ping-pong)。
运行中