Search code examples
iosobjective-copengl-esblending

Trails effect, clearing a frame buffer with a transparent quad


I want to get a trails effect. I am drawing particles to a frame buffer. which is never cleared (accumulates draw calls). Fading out is done by drawing a black quad with small alpha, for example 0.0, 0.0, 0.0, 0.1. A two step process, repeated per frame: - drawing a black quad - drawing particles at new positions

All works nice, the moving particles produce long trails EXCEPT the black quad does not clear the FBO down to perfect zero. Faint trails remain forever (e.g. buffer's RGBA = 4,4,4,255).

I assume the problem starts when a blending function multiplies small values of FBO's 8bit RGBA (destination color) by, for example (1.0-0.1)=0.9 and rounding prevents further reduction. For example 4 * 0.9 = 3.6 -> rounded back to 4, for ever.

Is my method (drawing a black quad) inherently useless for trails? I cannot find a blend function that could help, since all of them multiply the DST color by some value, which must be very small to produce long trails.

The trails are drawn using a code:

    GLuint drawableFBO;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &drawableFBO);


    glBindFramebuffer(GL_FRAMEBUFFER, FBO);   /// has an attached texture glFramebufferTexture2D -> FBOTextureId

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

    glUseProgram(fboClearShader);
    glUniform4f(fboClearShader.uniforms.color, 0.0, 0.0, 0.0, 0.1);
    glUniformMatrix4fv(fboClearShader.uniforms.modelViewProjectionMatrix, 1, 0, mtx.m);
    glBindVertexArray(fboClearShaderBuffer);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);


    glUseProgram(particlesShader);
    glUniformMatrix4fv(shader.uniforms.modelViewProjectionMatrix, 1, 0, mtx.m);
    glUniform1f(shader.uniforms.globalAlpha, 0.9);
    glBlendFunc(GL_ONE, GL_ONE);
    glBindTexture(particleTextureId);
    glBindVertexArray(particlesBuffer);
    glDrawArrays(GL_TRIANGLES, 0, 1000*6);


    /// back to drawable buffer
    glBindFramebuffer(GL_FRAMEBUFFER, drawableFBO);

    glUseProgram(fullScreenShader);
    glBindVertexArray(screenQuad);
    glBlendFuncGL_ONE dFactor:GL_ONE];
    glBindTexture(FBOTextureId);
    glDrawArrays(GL_TRIANGLES, 0, 6);

Solution

  • Blending is not only defined by the by the blend function glBlendFunc, it is also defined by the blend equation glBlendEquation.
    By default the source value and the destination values are summed up, after they are processed by the blend function.

    Use a blend function which subtracts a tiny value from the destination buffer, so the destination color will slightly decreased in each frame and finally becomes 0.0.
    The the results of the blend equations is clamped to the range [0, 1].

    e.g.

    dest_color = dest_color - RGB(0.01)
    

    The blend equation which subtracts the source color form the destination color is GL_FUNC_REVERSE_SUBTRACT:

    float dec = 0.01f; // should be at least 1.0/256.0
    
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
    glBlendFunc(GL_ONE, GL_ONE);
    
    glUseProgram(fboClearShader);
    glUniform4f(fboClearShader.uniforms.color, dec, dec, dec, 0.0);