帧缓冲区和制服的非常奇怪的情况(意外反馈)

Very Weird Situation With Framebuffers And Uniforms (Unexpected Feedback)

我对帧缓冲区有这种情况,我根本不明白。看看以下内容:

class Shader {
  constructor(cvs, dim) {
    cvs.width = dim[0];
    cvs.height = dim[1];
    this.gl = twgl.getContext(cvs);
    this.bfi = twgl.createBufferInfoFromArrays(this.gl, {
      a_position: {
        numComponents: 2,
        data: [-1, -1, -1, 3, 3, -1]
      }
    });
    this.pgi = {
      write: twgl.createProgramInfo(this.gl, ["vs", "fs_write"]),
      read: twgl.createProgramInfo(this.gl, ["vs", "fs_read"])
    };
    this.fbs = Array(2)
      .fill()
      .map(() => twgl.createFramebufferInfo(this.gl));
    this.gl.useProgram(this.pgi.write.program);
    twgl.setUniforms(this.pgi.write, {
      u_resolution: dim
    });
    this.gl.useProgram(this.pgi.read.program);
    twgl.setUniforms(this.pgi.read, {
      u_framebuffer: this.fbs[0].attachments[0],
      u_resolution: dim
    });
  }
  ldImg(src, handler) {
    this.gl.useProgram(this.pgi.write.program);
    twgl.setUniforms(this.pgi.write, {
      u_texture: twgl.createTexture(
        this.gl,
        { src },
        handler
      )
    });
  }
  frame() {
    twgl.bindFramebufferInfo(this.gl, this.fbs[0]);
    this.gl.useProgram(this.pgi.write.program);
    twgl.setBuffersAndAttributes(this.gl, this.pgi.write, this.bfi);
    twgl.drawBufferInfo(this.gl, this.bfi);
    twgl.bindFramebufferInfo(this.gl, null);
    this.gl.useProgram(this.pgi.read.program);
    // twgl.setUniforms(this.pgi.read, {
    //   u_framebuffer: this.fbs[0].attachments[0]
    // });
    twgl.setBuffersAndAttributes(this.gl, this.pgi.read, this.bfi);
    twgl.drawBufferInfo(this.gl, this.bfi);
    // this.fbs = [this.fbs[1], this.fbs[0]];
  }
}
(function main() {
  const dim = [512, 512];
  const shr = new Shader(document.querySelector("canvas"), dim);
  shr.ldImg("https://assets.codepen.io/854924/ad.png", frame);
  function frame(ms) {
    shr.frame();
    window.requestAnimationFrame(frame);
  }
})();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
  attribute vec4 a_position;

  void main() {
    gl_Position = a_position;
  }
</script>
<script id="fs_write" type="x-shader/x-fragment">
  precision highp float;
  
  uniform vec2 u_resolution;
  uniform sampler2D u_texture;

  void main() {
    gl_FragColor = texture2D(u_texture, (gl_FragCoord.xy + 1.0) / u_resolution);
  }
</script>
<script id="fs_read" type="x-shader/x-fragment">
  precision highp float;

  uniform sampler2D u_framebuffer;
  uniform vec2 u_resolution;
  
  void main() {
    gl_FragColor = vec4(
      1.0 - texture2D(u_framebuffer, gl_FragCoord.xy / u_resolution).rgb,
      1.0
    );
  }
</script>
<canvas></canvas>

我尽量让事情变得简单。有两个程序,writeread,其中 write 从纹理读取,转换一个像素并写入输出。然后 read 从帧缓冲区读取,反转图像并写入输出。在当前情况下,我让 write 渲染到帧缓冲区,让 read 渲染到屏幕。一切都按预期工作。

现在,如果我取消注释 JS 中的第一个注释行,我会得到错误 Feedback loop formed between Framebuffer and active Texture,这很奇怪,因为 read 中的统一 u_framebuffer 始终设置为相同的帧缓冲区,我只是重新绑定一个已经绑定的制服。此外,它应该不会创建反馈循环,因为 write 没有绑定帧缓冲区,而 read 不会尝试渲染到帧缓冲区。

如果我取消注释 JS 中的两条注释行,以便每帧都切换帧缓冲区,我可以在屏幕上看到纹理 'walk'。所以这显然是一个反馈回路(一个像素的偏移量被累积),但我看不出它是从哪里来的。 write 只是从纹理中读取,它应该是绝对静态的,但不知何故它似乎消耗了自己的输出。

我在这里很迷茫,制服和帧缓冲区中是否存在我根本不了解的逻辑,或者我以前从未遇到过的错误?

纹理不像制服,纹理绑定到全局可用的纹理单元之一,这是一个有状态的操作,也就是一旦你将纹理绑定到一个单元,它就会保持绑定状态,直到你解除绑定,就像帧缓冲区一样。 因此,当您使用纹理调用 setUniforms 时,它将纹理绑定到一个单元并告诉采样器要从哪个全局单元读取,在您的情况下,忽略这会导致以下着色器从之前绑定的任何内容中读取帧缓冲区纹理。所以你想要做的是在你用你的读取程序渲染之前重新绑定读取的纹理:

    this.gl.useProgram(this.pgi.write.program);
    twgl.setUniforms(this.pgi.write, {
       u_texture: this.readTexture //@TODO: store read texture in ldImg
    });
    twgl.setBuffersAndAttributes(this.gl, this.pgi.write, this.bfi);
    twgl.drawBufferInfo(this.gl, this.bfi);
    twgl.bindFramebufferInfo(this.gl, null);
    this.gl.useProgram(this.pgi.read.program);
    twgl.setUniforms(this.pgi.read, {
       u_framebuffer: this.fbs[0].attachments[0]
    });