两个 FBO 之间的乒乓渲染在第一帧后失败。

Posted

技术标签:

【中文标题】两个 FBO 之间的乒乓渲染在第一帧后失败。【英文标题】:Ping-pong rendering between two FBOs fails after first frame. 【发布时间】:2011-11-28 16:49:48 【问题描述】:

我正在尝试创建两个 FBO 并实现乒乓渲染。但是,我只能让第一帧正常工作。我正在尝试模拟人生游戏,在第一帧之后,我只得到一个黑屏。你能帮我检查一下吗?我在这个问题上花了几个小时。

编辑

也许我没有描述清楚。其实我想用textureB作为texture渲染到textureA,然后用textureA渲染到screen,反之亦然。

编辑 我可以看到第一帧,即纹理B。在它通过片段着色器后,它变成黑色。起初,我怀疑片段着色器,我将其更改为仅将黑色恢复为白色,将白色恢复为黑色。它仍然全黑。

设置 fbo 和纹理

glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &textureA);
    glBindTexture(GL_TEXTURE_2D, textureA);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, NULL);

    glGenTextures(1, &textureB);
    glBindTexture(GL_TEXTURE_2D, textureB);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    data=(GLubyte*)malloc(256*256*4*sizeof(GLubyte));
    GLubyte val;
    for (int i = 0; i < 256 * 256 * 4; i+=4)    
        if (rand()%10 ==1) 
             val = 0;  
        else 
             val = 255; 
        data[i] = data[i+1] = data[i+2] = val;
        data[i+3] = 255;
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

    glGenFramebuffers(1, &fboA);
    glBindFramebuffer(GL_FRAMEBUFFER, fboA);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureA, 0);

    glGenFramebuffers(1, &fboB);
    glBindFramebuffer(GL_FRAMEBUFFER, fboB);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureB, 0);

渲染循环

if ([context API] == kEAGLRenderingAPIOpenGLES2) 


        if(counter%2==0)
        
            glUseProgram(automateProg);
            glBindFramebuffer(GL_FRAMEBUFFER, fboA);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, textureB);
            glUniform1i(AUTOMATE_TEXT, 0);
            glUniform1f(DU, 1.0/256);
            glUniform1f(DV, 1.0/256);
            // Update attribute values.
            glVertexAttribPointer(ATTRIB_VERTEX_2, 2, GL_FLOAT, 0, 0, squareVertices);
            glEnableVertexAttribArray(ATTRIB_VERTEX_2);

            glVertexAttribPointer(ATTRIB_TEXCOORD_2, 2, GL_FLOAT, GL_FALSE, 0, texCoord);    
            //glEnableVertexAttribArray(ATTRIB_TEXCOORD_2);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            if (![self validateProgram:automateProg]) 
                NSLog(@"Failed to validate program: %d", automateProg);
                return;
            

            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glUseProgram(0);
        
        else
        
            glUseProgram(automateProg);            
            glBindFramebuffer(GL_FRAMEBUFFER, fboB);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, textureA);
            glUniform1i(AUTOMATE_TEXT, 0);
            glUniform1f(DU, 1.0/256);
            glUniform1f(DV, 1.0/256);
            // Update attribute values.
            glVertexAttribPointer(ATTRIB_VERTEX_2, 2, GL_FLOAT, 0, 0, squareVertices);
            glEnableVertexAttribArray(ATTRIB_VERTEX_2);
            glVertexAttribPointer(ATTRIB_TEXCOORD_2, 2, GL_FLOAT, GL_FALSE, 0, texCoord); 
            //glEnableVertexAttribArray(ATTRIB_TEXCOORD_2);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            if (![self validateProgram:automateProg]) 
                NSLog(@"Failed to validate program: %d", automateProg);
                return;
            

            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glUseProgram(0);
        

        [(EAGLView *)self.view setFramebuffer];
        glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        if (counter % 2 == 0) 
            glUseProgram(normalProg);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, textureB);
            glUniform1i(NORMAL_TEXT, 0);
            glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
            glEnableVertexAttribArray(ATTRIB_VERTEX);
            glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, texCoord); 
            glEnableVertexAttribArray(ATTRIB_TEXCOORD);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            if (![self validateProgram:normalProg]) 
                NSLog(@"Failed to validate program: %d", normalProg);
                return;
            
            glUseProgram(0);

         else 
            glUseProgram(normalProg);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, textureA);
            glUniform1i(NORMAL_TEXT, 0);
            glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
            glEnableVertexAttribArray(ATTRIB_VERTEX);
            glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, texCoord); 
            glEnableVertexAttribArray(ATTRIB_TEXCOORD);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            if (![self validateProgram:normalProg]) 
                NSLog(@"Failed to validate program: %d", normalProg);
                return;
            
            glUseProgram(0);
        
        counter++;

[(EAGLView *)self.view presentFramebuffer];

片段着色器

precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D tex; //the input texture
uniform float du; //the width of the cells
uniform float dv; //the height of the cells
    void main() 
        int count = 0;

        vec4 C = texture2D( tex, v_texCoord );
        vec4 E = texture2D( tex, vec2(v_texCoord.x + du, v_texCoord.y) );
        vec4 N = texture2D( tex, vec2(v_texCoord.x, v_texCoord.y + dv) );
        vec4 W = texture2D( tex, vec2(v_texCoord.x - du, v_texCoord.y) );
        vec4 S = texture2D( tex, vec2(v_texCoord.x, v_texCoord.y - dv) );
        vec4 NE = texture2D( tex, vec2(v_texCoord.x + du, v_texCoord.y + dv) );
        vec4 NW = texture2D( tex, vec2(v_texCoord.x - du, v_texCoord.y + dv) );
        vec4 SE = texture2D( tex, vec2(v_texCoord.x + du, v_texCoord.y - dv) );
        vec4 SW = texture2D( tex, vec2(v_texCoord.x - du, v_texCoord.y - dv) );

        if (E.r == 1.0)  count++; 
        if (N.r == 1.0)  count++; 
        if (W.r == 1.0)  count++; 
        if (S.r == 1.0)  count++; 
        if (NE.r == 1.0)  count++; 
        if (NW.r == 1.0)  count++; 
        if (SE.r == 1.0)  count++; 
        if (SW.r == 1.0)  count++; 

        if ( (count == 2 || count == 3)) 
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); //cell lives...
         else 
            gl_FragColor = vec4(0.0,0.0,0.0, 1.0); //cell dies...
        
    

【问题讨论】:

【参考方案1】:

我是否理解您的代码,您想在第一个 if-else-block 中将结果渲染到纹理并在第二个 if-else-block 中将该结果渲染到屏幕? 如果是这样,那么看起来您在组织输入和输出的方式上存在错误。 这是您第一次通过时发生的事情(我减少了您的代码):

if(counter%2==0)

    glBindFramebuffer(GL_FRAMEBUFFER, fboA); // will render to textureA
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureB); // textureB is our input
 else 
    ...


if (counter % 2 == 0) 
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureB); // textureB still as input? not textureA?
 else 
    ...

...这就是第二遍中发生的情况:

if(counter%2==0)

    ...
 else 
    glBindFramebuffer(GL_FRAMEBUFFER, fboB); // will render to textureB
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureA); // textureA as input


if (counter % 2 == 0) 
    ...
 else 
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureA); // textureA as input again?

您在第一帧中看到某些内容的原因是,您实际上是在渲染输入数据,而不是第一遍的结果。第二遍出现黑屏的原因可能是您的片段着色器无法正常工作。从您的着色器代码来看,访问相邻纹素的错误似乎是最合理的原因。你能提供dudv的值吗?

另外,正如 Brad 之前指出的那样,我不认为只使用一个纹理单元会带来任何麻烦。不过我不确定。

附带说明:对于乒乓球,您应该考虑创建FBOs as an array 以使您的代码更具可读性。

编辑:

如果您将制服dudv 设置为glUniform1f(),请尝试glUniform1i()(然后您需要在着色器中使用float() 进行投射)或glUniform1fv()。我曾经在 PowerVR GLES2 驱动程序中遇到过同样的问题,该函数没有执行任何操作,导致制服为 0.0

【讨论】:

这可能会导致第一帧或第二帧显示为黑色,但我能看到的最糟糕的情况是之后的帧会比正在渲染的帧滞后一帧(通过绘制输入代替的输出)。这是要解决的问题,但我不确定这是否是他在这里停止渲染的原因。为什么我怀疑使用一个纹理单元是因为我尝试自己在 OpenGL ES 中执行此操作,并且需要将输入和输出纹理绑定到单独的纹理单元上以使渲染正常进行。 好的,这可能取决于设备/驱动程序,因为到目前为止我还没有经历过。不过,我仍然怀疑错误的片段着色器是真正的原因。但是,您当然是正确的,输入和输出排序错误只会影响屏幕外渲染。反正也不是很多。 @user1025555 du 和 dv 只是纹理的一步。纹理大小为256*256,所以它们的值为1/256,用于读取邻居位置。 @user1025555 我首先怀疑片段着色器。因此,我将其更改为仅将黑色恢复为白色,将白色恢复为黑色。它仍然变得全黑。 关于我之前的其他评论:当然,我的意思是on-屏幕渲染。 @Yongwei Ying:你检查glError()glCheckFramebufferStatus()(附加后的后者)?如果您只是将每个纹素设置为例如,会发生什么?片段着色器中的绿色。你得到一个绿色的窗口还是保持黑色?【参考方案2】:

您有两个要处理的纹理,但我看到这里只使用了一个纹理单元。也许如果您使用如下代码将 FBO 纹理绑定到纹理单元一:

glBindFramebuffer(GL_FRAMEBUFFER, fboA);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureA);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureB);

glBindFramebuffer(GL_FRAMEBUFFER, fboB);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureB);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureA);

在渲染每一帧之前,它会从绑定到单元 0 的一个纹理中正确读取,并通过 FBO 输出到单元 1 上的另一个纹理。

作为替代方案,您可以将一个纹理永久绑定到一个单元,将另一个纹理永久绑定到另一个单元,并为您的 AUTOMATE_TEXT 制服替换值以指示从哪个单元中提取。这会更高效一些,因为它可以避免在每次渲染时绑定纹理的开销。

【讨论】:

我认为他不想同时使用这两种纹理作为输入。他想使用一个作为输入并使用 FBO 渲染到另一个,所以我看不出绑定两个纹理如何解决他的问题。更重要的是,将纹理绑定为当前渲染的输入很可能是个坏主意。 @ChristianRau - 我读到它时,他想在纹理之间打乒乓球(将纹理 1 作为输入并渲染到纹理 2,然后将纹理 2 作为输入并渲染到纹理 1) .一次只能读取一个纹理作为输入。我在 OpenGL ES 设备上做过一次,并且必须在单独的纹理单元上具有输入纹理和输出 FBO 绑定纹理才能使其工作。是的,您不想从要渲染到的纹理中读取数据,但是如果将其拆分为单独的绘制事件,它应该可以工作。 @BradLarson -- 也许我没有描述清楚。其实我是想用textureB作为texture,渲染到textureA,然后用textureA渲染到screen,反之亦然。 @BradLarson 你的意思是我需要在渲染循环中绑定两个纹理吗?但是我怎么知道哪一个是输入纹理,哪一个是输出纹理。 @YongweiXing - 是的,您可以通过将一个纹理绑定到特定纹理单元并将另一个纹理绑定到另一个纹理来绑定两个纹理。您在 glUniform1i(AUTOMATE_TEXT, 0); 行中为着色器指定要从哪个纹理单元中提取纹理单元,该行当前告诉您的着色器使用单元 0 上的纹理作为该制服的输入。【参考方案3】:

如果必须访问帧的颜色平面,则必须将纹理附加到写入的帧缓冲区。如果必须在着色器中读取附加到帧缓冲区的纹理,则必须将纹理绑定到纹理单元,并且必须将纹理单元的索引设置为着色器的纹理采样器统一。

由于您不能一次从帧缓冲区读取并写入同一个帧缓冲区(这将导致未定义的行为),您必须绘制从一个帧缓冲区读取并写入第二个帧缓冲区。

在每一帧之后,帧缓冲区必须改变它们的位置。被读取的缓冲区将成为将被写入的缓冲区,而被写入的缓冲区将成为将被读取的缓冲区。

为帧缓冲区附件创建纹理:

GLuint colorTexture[2];
glGenTextures( 2, &colorTexture[0] );
glBindTexture( GL_TEXTURE_2D, colorTexture[0] );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
glBindTexture( GL_TEXTURE_2D, colorTexture[1] );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );

创建帧缓冲区:

GLuint frameBuffer[2];
glGenFramebuffers( 2, &frameBuffer[0] );
glBindFramebuffer( GL_FRAMEBUFFER, frameBuffer[0] );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture[0], 0 );
glBindFramebuffer( GL_FRAMEBUFFER, frameBuffer[1] );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture[1], 0 );

注意,如果需要深度缓冲区甚至模板缓冲区,则必须将 GL_DEPTH_ATTACHMENTGL_STENCIL_ATTACHMENTGL_DEPTH_STENCIL_ATTACHMENT 附加到帧缓冲区。

由于每一帧都被绘制到一个帧缓冲区,因此您必须实施一个后期处理,这会将颜色平面从帧缓冲区带到绘图缓冲区。 这可以通过glBlitFramebuffer 完成,它将像素值的矩形从读取帧缓冲区的一个区域传输到绘制帧缓冲区的另一个区域。

 glBindFramebuffer( GL_READ_FRAMEBUFFER, frameBuffer[ ... ]  );
 glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
 glBlitFramebuffer( 0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST ); 

你的渲染主循环应该是这样的:

int drawFB = 0;
while ( /* ... */ )

    int readFB = 1 - drawFB;

    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer[drawFB]);
    glActiveTexture(GL_TEXTURE0 + 1);
    glBindTexture(GL_TEXTURE_2D, colorTexture[readFB]);

    glProgramUse( /* shader program object */ );
    glUniform1i( /* texture sampler 2D location */, 1 ); 

    // do the drawing
    // ...

    // post processing
    glBindFramebuffer( GL_READ_FRAMEBUFFER, frameBuffer[drawFB]  );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
    glBlitFramebuffer( 0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST );

    drawFB = 1 - drawFB;

作为替代方案,您还可以使用 1 个帧缓冲区,并附加 2 个颜色平面和 2 个纹理。交替激活第一个或第二个颜色平面:

GLuint frameBuffer;
glGenFramebuffers( 1, &frameBuffer );
glBindFramebuffer( GL_FRAMEBUFFER, frameBuffer );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture[0], 0 );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, colorTexture[1], 0 );

int drawFB = 0;
while ( /* ... */ )

    int readFB = 1 - drawFB;

    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glDrawBuffer( drawFB == 0 ? GL_COLOR_ATTACHMENT0 : GL_COLOR_ATTACHMENT1 );
    glActiveTexture(GL_TEXTURE0 + 1);
    glBindTexture(GL_TEXTURE_2D, colorTexture[readFB]);

    glProgramUse( /* shader program object */ );
    glUniform1i( /* texture sampler 2D location */, 1 ); 

    // do the drawing
    // ...

    // post processing
    glBindFramebuffer( GL_READ_FRAMEBUFFER, frameBuffer[drawFB]  );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
    glBlitFramebuffer( 0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST );

    drawFB = 1 - drawFB;

请参阅以下简单的 WebGL 示例来演示该过程:

var ShaderProgram = ;
ShaderProgram.Create = function( shaderList, uniformNames ) 
    var shaderObjs = [];
    for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) 
        var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
        if ( shderObj == 0 )
            return 0;
        shaderObjs.push( shderObj );
    
    var progObj = this.LinkProgram( shaderObjs )
    if ( progObj != 0 ) 
        progObj.unifomLocation = ;
        for ( var i_n = 0; i_n < uniformNames.length; ++ i_n ) 
            var name = uniformNames[i_n];
            progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
        
    
    return progObj;

ShaderProgram.Use = function( progObj )  gl.useProgram( progObj );  
ShaderProgram.SetUniformInt = function( progObj, name, val )  gl.uniform1i( progObj.unifomLocation[name], val ); 
ShaderProgram.SetUniform2i = function( progObj, name, arr )  gl.uniform2iv( progObj.unifomLocation[name], arr ); 
ShaderProgram.SetUniformFloat = function( progObj, name, val )  gl.uniform1f( progObj.unifomLocation[name], val ); 
ShaderProgram.SetUniform2f = function( progObj, name, arr )  gl.uniform2fv( progObj.unifomLocation[name], arr ); 
ShaderProgram.SetUniform3f = function( progObj, name, arr )  gl.uniform3fv( progObj.unifomLocation[name], arr ); 
ShaderProgram.SetUniformMat44 = function( progObj, name, mat )  gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); 
ShaderProgram.CompileShader = function( source, shaderStage ) 
    var shaderScript = document.getElementById(source);
    if (shaderScript) 
      source = "";
      var node = shaderScript.firstChild;
      while (node) 
        if (node.nodeType == 3) source += node.textContent;
        node = node.nextSibling;
      
    
    var shaderObj = gl.createShader( shaderStage );
    gl.shaderSource( shaderObj, source );
    gl.compileShader( shaderObj );
    var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
    if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
    return status ? shaderObj : 0;
 
ShaderProgram.LinkProgram = function( shaderObjs ) 
    var prog = gl.createProgram();
    for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
        gl.attachShader( prog, shaderObjs[i_sh] );
    gl.linkProgram( prog );
    status = gl.getProgramParameter( prog, gl.LINK_STATUS );
    if ( !status ) alert("Could not initialise shaders");
    gl.useProgram( null );
    return status ? prog : 0;


var FrameBuffer = ;
FrameBuffer.Create = function( vp, texturePlan ) 
    var texPlan = texturePlan ? new Uint8Array( texturePlan ) : null;
    var fb = gl.createFramebuffer();
    fb.width = vp[0];
    fb.height = vp[1];
    gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
    fb.color0_texture = gl.createTexture();
    gl.bindTexture( gl.TEXTURE_2D, fb.color0_texture );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
    gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, texPlan );
    fb.renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer( gl.RENDERBUFFER, fb.renderbuffer );
    gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height );
    gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.color0_texture, 0 );
    gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fb.renderbuffer );
    gl.bindTexture( gl.TEXTURE_2D, null );
    gl.bindRenderbuffer( gl.RENDERBUFFER, null );
    gl.bindFramebuffer( gl.FRAMEBUFFER, null );

    fb.Bind = function( clear ) 
        gl.bindFramebuffer( gl.FRAMEBUFFER, this );
        if ( clear ) 
            gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
            //gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
            gl.clear( gl.DEPTH_BUFFER_BIT );
        
    ;

    fb.Release = function( clear ) 
        gl.bindFramebuffer( gl.FRAMEBUFFER, null );
        if ( clear ) 
            gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
            //gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
            gl.clear( gl.DEPTH_BUFFER_BIT );
        
    ;

    fb.BindTexture = function( textureUnit ) 
        gl.activeTexture( gl.TEXTURE0 + textureUnit );
        gl.bindTexture( gl.TEXTURE_2D, this.color0_texture );
    ;

    return fb;


var curBufInx = 0;  
var tick = 0;     
var signal = 0; 
function drawScene()

    var canvas = document.getElementById( "glow-canvas" );
    var vp = [canvas.width, canvas.height];

    var currentTime = Date.now();   
    var deltaMS = currentTime - startTime
    testTick = Tick( currentTime, 0.05 )
    signal = testTick > tick ? 1 : 0;
    tick = testTick
    
    var srcBufInx = curBufInx == 0 ? 1 : 0;
        
    gl.viewport( 0, 0, drawFB[curBufInx].width, drawFB[curBufInx].height );
    gl.enable( gl.DEPTH_TEST );
    drawFB[curBufInx].Bind( true );
    
    // set up draw shader
    ShaderProgram.Use( progDraw );
    var texUnitSource = 2;
    drawFB[srcBufInx].BindTexture( texUnitSource );
    ShaderProgram.SetUniformInt( progDraw, "u_colorAttachment0", texUnitSource );
    ShaderProgram.SetUniform2i( progDraw, "u_textureSize", [drawFB[curBufInx].width, drawFB[curBufInx].height] );
    ShaderProgram.SetUniformInt( progDraw, "u_signal", signal );
    
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
    gl.drawElements( gl.TRIANGLES, bufQuad.inxLen, gl.UNSIGNED_SHORT, 0 );
    gl.disableVertexAttribArray( progDraw.inPos );

    drawFB[curBufInx].Release( true );
    gl.viewport( 0, 0, canvas.width, canvas.height );
    var texUnitDraw = 2;
    drawFB[curBufInx].BindTexture( texUnitDraw );
    ShaderProgram.Use( progScreenSpace );
    ShaderProgram.SetUniformInt( progScreenSpace, "u_colorAttachment0", texUnitDraw );

    gl.enableVertexAttribArray( progScreenSpace.inPos );
    gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
    gl.vertexAttribPointer( progScreenSpace.inPos, 2, gl.FLOAT, false, 0, 0 );
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
    gl.drawElements( gl.TRIANGLES, bufQuad.inxLen, gl.UNSIGNED_SHORT, 0 );
    gl.disableVertexAttribArray( progScreenSpace.inPos );

    curBufInx = curBufInx == 0 ? 1 : 0;


function Tick( currentTime, intervall ) 
    return Math.trunc( (currentTime - startTime) / intervall );


var plot_download_request = false;
var drawFB;
var sliderScale = 100.0
var gl;
var progDraw;
var progScreenSpace;
var bufCube = ;
var bufQuad = ;
function sceneStart() 

    var canvas = document.getElementById( "glow-canvas");
    var vp = [canvas.width, canvas.height];
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = ShaderProgram.Create( 
      [  source : "draw-shader-vs", stage : gl.VERTEX_SHADER ,
         source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER 
      ],
      [ "u_colorAttachment0", "u_textureSize", "u_signal" ] );
    progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
    if ( progDraw == 0 )
        return;

    progScreenSpace = ShaderProgram.Create( 
      [  source : "screen-shader-vs", stage : gl.VERTEX_SHADER ,
         source : "screen-shader-fs", stage : gl.FRAGMENT_SHADER 
      ],
      [ "u_colorAttachment0" ] );
    progScreenSpace.inPos = gl.getAttribLocation( progDraw, "inPos" );
    if ( progDraw == 0 )
        return;

    // create frame buffers
    var texCX = Math.floor(vp[0] / 4);
    var texCY = Math.floor(vp[1] / 4);
    var texPlan = [];
    for (ix = 0; ix < texCX; ++ix) 
        for (iy = 0; iy < texCY; ++iy) 
            texPlan.push( 0, 0, 0, 0 );
        
    
    for (ip = 0; ip < texCX * texCY / 20; ++ip) 
        var inx_tex = Math.floor( Math.random() * texCY ) * texCX + Math.floor( Math.random() * texCX );
        texPlan[inx_tex * 4 + 0] = 255 * Math.random();
        texPlan[inx_tex * 4 + 1] = 255 * Math.random();
        texPlan[inx_tex * 4 + 2] = 127;
        texPlan[inx_tex * 4 + 3] = 255;
    
    drawFB = [ FrameBuffer.Create( [texCX, texCY], texPlan ), FrameBuffer.Create( [texCX, texCY], texPlan ) ];

    bufQuad.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ] ), gl.STATIC_DRAW );
    bufQuad.inx = gl.createBuffer();
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );
    bufQuad.inxLen = 6;

    startTime = Date.now();
    setInterval(drawScene, 50);
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vertPos;
void main()

    vertPos.xy  = inPos.xy;
    gl_Position = vec4( inPos, 0.0, 1.0 );

</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertPos;
uniform sampler2D u_colorAttachment0;
uniform ivec2 u_textureSize;
uniform int u_signal;

vec3 Merge( in vec2 texC, in vec2 dir )

  vec2 testC = texC + dir;
  vec2 rangeTest = step( vec2(0.0), testC ) * step( testC, vec2(1.0) );  
  vec3 texCol = texture2D( u_colorAttachment0, testC ).rgb;
  vec2 tempDir = texCol.xy * 2.0 - 1.0;
  vec2 pDir = tempDir;
  pDir.x *= step( abs(tempDir.y * 0.7), abs( tempDir.x ) );
  pDir.y *= step( abs(tempDir.x * 0.7), abs( tempDir.y ) );
  pDir = sign( pDir );
  vec2 tDir = sign( dir );
  //vec2 dirTestTemp = step( vec2(0.5), -tDir * pDir );
  //float dirTest = dirTestTemp.x * dirTestTemp.y;
  vec2 dirTestTemp = tDir + pDir;
  float dirTest = 1.0 - step( 0.5, abs( dirTestTemp.x ) + abs( dirTestTemp.y ) );
  return rangeTest.x * rangeTest.y * dirTest * texCol;


void main()

    ivec2 texSize = u_textureSize;
    vec2  texStep = vec2( 1.0 / float( texSize.x ), 1.0 / float( texSize.y ) );
    vec2  texC    = vertPos.st * 0.5 + 0.5;
    
    vec3 texCol = vec3(0.0);
    if ( u_signal == 0 )
    
        texCol = texture2D( u_colorAttachment0, texC ).rgb;
    
    else
    
        texCol += Merge( texC, -texStep );
        texCol += Merge( texC, vec2( -texStep.x, 0.0 ) );
        texCol += Merge( texC, vec2( -texStep.x, texStep.y ) );
        texCol += Merge( texC, vec2( 0.0, -texStep.y ) );
        texCol += Merge( texC, vec2( 0.0, texStep.y ) );
        texCol += Merge( texC, vec2( texStep.x, -texStep.y ) );
        texCol += Merge( texC, vec2( texStep.x, 0.0 ) );
        texCol += Merge( texC, texStep );
    

    if ( texCol.b > 0.0 )
    
        vec2 colDir = texCol.rg * 2.0 - 1.0;
        vec2 pDir = sign( colDir );
        vec2 nextTexC = texC + pDir * texStep;
        if ( nextTexC.x <= texStep.x/2.0 || nextTexC.x >= 1.0-texStep.x/2.0 )
            colDir.x = -colDir.x;
        if ( nextTexC.y <= texStep.y/2.0 || nextTexC.y >= 1.0-texStep.y/2.0 )
            colDir.y *= -1.0;
        texCol.rg = colDir * 0.5 + 0.5;
    

    vec3 col = texCol.rgb;
    gl_FragColor = vec4( col, 1.0 );

</script>

<script id="screen-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vertPos;
void main()

    vertPos.xy  = inPos.xy;
    gl_Position = vec4( inPos, 0.0, 1.0 );

</script>

<script id="screen-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertPos;
uniform sampler2D u_colorAttachment0;
void main()

    vec4 texCol = texture2D( u_colorAttachment0, vertPos.st * 0.5 + 0.5 );
    gl_FragColor = vec4( texCol.rgb, 1.0 );

</script>

<body onload="sceneStart();">
    <canvas id="glow-canvas" style="border: none;"  ></canvas>
</body>

【讨论】:

以上是关于两个 FBO 之间的乒乓渲染在第一帧后失败。的主要内容,如果未能解决你的问题,请参考以下文章

带有 FBO 的 OpenGL 阴影映射 - 多个拖尾绘制

轮播总结

使用 FBO 的问题:仅第一次渲染。设置渲染目标时可能出现问题?

ue渲染就第一帧有问题

tyflow birth节点

视频卡在第一帧