11
votes

So I have been trying to create a trailing particle effect (seen here) with OpenGL ES 2.0. Unfortunately it appears that the OpenGL command (accumulation buffer) that makes this possible is not available in OpenGL es. This means that it will be necessary to go the LONG way.

This topic described a possible method to do such a thing. However I am quite confused about how to store things inside a buffer and combine buffers. So my thought was to do the following.

  1. Draw the current frame into a texture using a buffer that writes to a texture
  2. Draw the previous frames (but faded) into another buffer.
  3. Put step 1 ontop of step 2. And display that.
  4. Save whatever is displayed for use next frame.

My understanding so far is that buffers store pixel data in the same way textures do, just that buffers can more easily be drawn to using shaders.

So the idea would probably be to render to a buffer THEN move it into a texture.

One theory for doing this that I found is this

In retrospect, you should create two FBOs (each with its own texture); using the default framebuffer isn't reliable (the contents aren't guaranteed to be preserved between frames).

After binding the first FBO, clear it then render the scene normally. Once the scene has been rendered, use the texture as a source and render it to the second FBO with blending (the second FBO is never cleared). This will result in the second FBO containing a mix of the new scene and what was there before. Finally, the second FBO should be rendered directly to the window (this can be done by rendering a textured quad, similarly to the previous operation, or by using glBlitFramebuffer).

Essentially, the first FBO takes the place of the default framebuffer while the second FBO takes the place of the accumulation buffer.

In summary:

Initialisation:

For each FBO: - glGenTextures - glBindTexture - glTexImage2D - glBindFrameBuffer - glFramebufferTexture2D

Each frame:

glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, fbo1) glClear glDraw* // scene

glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, fbo2) glBindTexture(tex1) glEnable(GL_BLEND) glBlendFunc glDraw* // full-screen quad

glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, 0) glBindFrameBuffer(GL_READ_FRAMEBUFFER, fbo2) glBlitFramebuffer

unfortunately it didnt have quite enough code (especially for initialization to get me started).

But I have tried, and so far all I have gotten is a disappointing blank screen. I dont really know what I am doing, so probably this code is quite wrong.

var fbo1:GLuint = 0
var fbo2:GLuint = 0
var tex1:GLuint = 0

Init()
{
    //...Loading shaders  OpenGL etc.   
    //FBO 1
        glGenFramebuffers(1, &fbo1)
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), fbo1)

        //Create texture for shader output
        glGenTextures(1, &tex1)
        glBindTexture(GLenum(GL_TEXTURE_2D), tex1)
        glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGB, width, height, 0, GLenum(GL_RGB), GLenum(GL_UNSIGNED_BYTE), nil)

        glFramebufferTexture2D(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_TEXTURE_2D), tex1, 0)
        //FBO 2
        glGenFramebuffers(1, &fbo2)
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), fbo2)

        //Create texture for shader output
        glGenTextures(1, &tex1)
        glBindTexture(GLenum(GL_TEXTURE_2D), tex1)
        glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGB, width, height, 0, GLenum(GL_RGB), GLenum(GL_UNSIGNED_BYTE), nil)

        glFramebufferTexture2D(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_TEXTURE_2D), tex1, 0)
}

func drawFullScreenTex()
{
        glUseProgram(texShader)
        let rect:[GLint] = [0, 0, GLint(width), GLint(height)]
        glBindTexture(GLenum(GL_TEXTURE_2D), tex1)
        //Texture is allready
        glTexParameteriv(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_CROP_RECT_OES), rect)
        glDrawTexiOES(0, 0, 0, width, height)
 }

fun draw()
{
    //Prep
        glBindFramebuffer(GLenum(GL_DRAW_FRAMEBUFFER), fbo1)
        glClearColor(0, 0.1, 0, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))

        //1
        glUseProgram(pointShader);
        passTheStuff() //Just passes in uniforms
        drawParticles(glGetUniformLocation(pointShader, "color"), size_loc: glGetUniformLocation(pointShader, "pointSize")) //Draws particles


        //2
        glBindFramebuffer(GLenum(GL_DRAW_FRAMEBUFFER), fbo2)
        drawFullScreenTex()


        //3
        glBindFramebuffer(GLenum(GL_DRAW_FRAMEBUFFER), 0)
        glBindFramebuffer(GLenum(GL_READ_FRAMEBUFFER), fbo2)
        glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GLbitfield(GL_COLOR_BUFFER_BIT), GLenum(GL_NEAREST))

}

BTW here are some sources I found useful.

  1. Site 1
  2. Site 2
  3. Site 3
  4. Site 4

My main question is: Could someone please write out the code for this. I think I understand the theory involved, but I have spent so much time trying in vain to apply it.

If you want a place to start I have the Xcode project that draws dots, and has a blue one that moves across the screen periodically here, also the code that isn't working is in their as well.

Note: If you are going to write code you can use any language c++, java, swift, objective-c it will be perfectly fine. As long as it is for OpenGL-ES

1
Why do you want to use an accumulation buffer of all things to fade out particles?Nicol Bolas
I need a way to have them have trails. Look at the video on the first link. I need particle trails. However I don't want to use tons of entities for particle trails.J.Doe
I don't see why particle trails would require full-fledged entities. Merely a position, orientation (possibly), and time. My point being that the accumulation buffer method requires lots of work, including switching render targets (usually a big performance no-no on mobile hardware). While particles that fade out on their own merely requires a decent particle system.Nicol Bolas
That wouldn't work. These particles will do curves and various speeds and so I would need angle over time.J.Doe

1 Answers

2
votes

You call glGenTextures(1, &tex1) twice with the same variable tex1. This overwrites the variable. When you later call glBindTexture(GLenum(GL_TEXTURE_2D), tex1), it does not bind the texture corresponding to fbo1, but rather that of fbo2. You need a different texture for every fbo.

As for a reference, below is a sample from a working program of mine which uses multiple FBOs and renders to texture.

GLuint fbo[n];
GLuint tex[n];

init() {
    glGenFramebuffers(n, fbo);
    glGenTextures(n, tex);

    for (int i = 0; i < n; ++i) {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]);

        glBindTexture(GL_TEXTURE_2D, tex[i]);
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[i], 0);
    }
}

render() {

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw scene into buffer 0

    glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 
    glClear(GL_COLOR_BUFFER_BIT);

    glBindTexture(cbo[0]);

    //Draw full screen tex


    ...


    glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, 0);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 
    glClear(GL_COLOR_BUFFER_BIT);

    glBindTexture(cbo[n - 1]);
    // Draw to screen

    return;
}

A few notes. In order to get it to work I had to add the texture parameters.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

This is because on my system they defaulted to GL_NEAREST_MIPMAP_LINEAR. This did not work for the FBO texture, as no mipmap was generated. Set these to anything you like.

Also, make sure you have textures enabled with

glEnable(GL_TEXTURE_2D)

I hope this will help.