bindFramebuffer

代码里两处 bindFramebuffer 分别干什么

先把代码里两处关键位置摘出来:

第一处:初始化阶段,构建 FBO

TypeScript 复制代码
const fbo = gl.createFramebuffer();
const pickTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, pickTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

//👉①
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, pickTex, 0);
//👉②
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  1. gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)
  2. ①: 把新建的 fbo 绑定到帧缓冲绑定点,让后面的framebufferTexture2D作用到这个 fbo 对象上,把 pickTex 纹理挂载为它的颜色附件。挂载关系会永久保存在 fbo 对象内部。
  3. gl.bindFramebuffer(gl.FRAMEBUFFER, null)
  4. ②: 初始化挂载完成,切回浏览器默认屏幕帧缓冲

如果这里不切回 null,后面页面初始render()执行 drawArrays,渲染会跑到 pickTex 离屏纹理上,屏幕画布就是黑的,什么都看不见。
⚠️注意:解绑null不会销毁 fbo,也不会取消纹理挂载关系,只是把 "当前操作对象" 切回屏幕。后续点击拾取还可以再次 bind 这个 fbo。


第二处:点击鼠标,pickLine()拾取函数内部

复制代码
function pickLine(mouseX, mouseY) {
    //👉③
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.clearColor(0,0,0,0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(pickProg);
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.vertexAttribPointer(aPosPick, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aPosPick);

    for(let id=0; id<LINE_COUNT; id++){
        const color = idToColor(id);
        gl.uniform3fv(uIdColorPick, color);
        gl.drawArrays(gl.LINES, id*2, 2);
    }

    const pixels = new Uint8Array(4);
    const glY = canvas.height - mouseY;
    gl.readPixels(mouseX, glY, 1,1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

    //👉④
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    const [r,g,b,a] = pixels;
    if(a === 0) return -1;
    return colorToId(r,g,b);
}
  1. gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) ③: 鼠标点击触发拾取,再次激活 fbo 。 此时所有drawArrays绘制,不再画到屏幕,而是绘制输出到pickTex纹理;把每条线段的 ID 颜色渲染到离屏纹理。readPixels也必须在 fbo 绑定状态下,才能读取离屏纹理的像素。
  2. gl.bindFramebuffer(gl.FRAMEBUFFER, null) ④: 拾取逻辑全部完成,再次切回屏幕帧缓冲。 退出 pickLine 之后回到render(),正常绘制场景,输出到页面 canvas。

四处 bindFramebuffer 总结

表格

序号 位置 作用
初始化 绑定 fbo,执行framebufferTexture2D挂载纹理
初始化 解绑,切回屏幕,防止初始化渲染跑到离屏纹理
pickLine 拾取函数开头 激活 fbo,draw 绘制 ID 颜色、readPixels 读取离屏纹理像素
pickLine 拾取函数末尾 解绑,切回屏幕,后续正常渲染画到页面

核心逻辑:

  • bindFramebuffer (fbo) = "切换输出目标到离屏 FBO"
  • bindFramebuffer (null) = "切换输出目标回到浏览器屏幕画布"

这个代码有一个严重性能 bug(百万线段)

复制代码
// pickLine里面
for(let id=0; id<LINE_COUNT; id++){
    const color = idToColor(id);
    gl.uniform3fv(uIdColorPick, color);
    gl.drawArrays(gl.LINES, id*2, 2);
}

一百万次循环调用 drawArrays,百万次 drawcall,浏览器会巨卡甚至直接崩溃。

正确做法:顶点缓冲区里直接预存每一条线的 id 颜色属性,一次 drawArrays 全部提交,而不是循环一百万次逐个 draw。


简单一句话: 初始化阶段两次 bindFramebuffer 是为了给 FBO 挂上纹理,做完立刻切回屏幕; 拾取函数内部两次 bindFramebuffer 是点击的时候临时切到离屏做拾取,读完像素马上切回屏幕渲染画面。

相关推荐
林恒smileZAZ1 小时前
前端如何实现在线预览office常见文件功能?
前端
码云之上1 小时前
从Popup类组件看定位参考系陷阱与设计取舍
前端·react.js·scss
BigTopOne1 小时前
ArLiveLite -视频 PTS/DTS 时间戳详解
前端
数字化小张1 小时前
时序数据库选型——InfluxDB与TDengine对比
前端
BigTopOne1 小时前
ArLiveLite 用到的 OpenGL 技术点
前端
函数小陈1 小时前
我给自己写了个「AI 代言人」
前端
FungLeo1 小时前
React 管理后台实战 · 列表里突然出现一堆空白行?PIPL 擦除后前端怎么处理才不露馅
前端·react.js·状态模式·pipl
BigTopOne1 小时前
ArLiveLite — System Architecture
前端
JL152 小时前
如何用 MySQL 持久化、历史快照与游标实现多步 Redo/Undo
前端·数据库·mysql