代码里两处 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);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)- ①: 把新建的 fbo 绑定到帧缓冲绑定点,让后面的
framebufferTexture2D作用到这个 fbo 对象上,把 pickTex 纹理挂载为它的颜色附件。挂载关系会永久保存在 fbo 对象内部。 gl.bindFramebuffer(gl.FRAMEBUFFER, null)- ②: 初始化挂载完成,切回浏览器默认屏幕帧缓冲。
如果这里不切回 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);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)③: 鼠标点击触发拾取,再次激活 fbo 。 此时所有drawArrays绘制,不再画到屏幕,而是绘制输出到pickTex纹理;把每条线段的 ID 颜色渲染到离屏纹理。readPixels也必须在 fbo 绑定状态下,才能读取离屏纹理的像素。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 是点击的时候临时切到离屏做拾取,读完像素马上切回屏幕渲染画面。