0

The Problem

I work on the open source game torcs (http://torcs.sourceforge.net/). The game's graphic pipeline is still using the fixed function pipeline (FFP) of OpenGL 1.3.

I try to render the game scenes to textures in a FBO (Framebuffer Object) in order to do some post-processing on the rendered textures. I use OpenGL 3.3. on my machine.

Currently I have set up the FBO with attached textures at GL_COLOR_ATTACHMENT0&1 (2 in order to have two consecutive frames readable in the shader) and an attached renderbuffer at GL_DEPTH_ATTACHMENT.

After binding the FBO the game rendering function is executed. When I afterwards unbind the FBO and for validation write back the texture via a shader program to the window buffer the scene is incomplete. To be more specific, only the outline of the car is rendered, so are skidmarks of the tires and some smoke. This shows that something is rendered to the texture of the FBO, but not everything. Among others no textures (for trees, houses, grass etc.) are rendered to the texture in the FBO. This suggests that my texture set up is incorrect, but unfortunately my knowledge of OpenGL is limited, which is why I hope for your help.

One other thing worth noting is, that if I leave out the line glActiveTexture(GL_TEXTURE0); before the drawing happens then one texture will be displayed (i.e. will be written to the FBO and written back to the window system framebuffer instead of the car outline.

The Code

The following code shows the initialization of the FBO (from https://en.wikibooks.org/wiki/OpenGL_Programming/Post-Processing):

int screen_width = 640; 
int screen_height = 480;
/* Texture A*/
glGenTextures(1, &fbo_texture);
glBindTexture(GL_TEXTURE_2D, fbo_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);

/* Texture B*/
//glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &fbo_texture_a);
glBindTexture(GL_TEXTURE_2D, fbo_texture_a);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);

/* Depth buffer */
glGenRenderbuffers(1, &rbo_depth);
glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, screen_width, screen_height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

/* Framebuffer to link everything together */
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, fbo_texture_a, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth);
GLenum status;
if ((status = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) {
    fprintf(stderr, "glCheckFramebufferStatus: error 0x%x", status);
    return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

/* Compile and link shaders */
...

The following code shows where the drawing happens: Edit: if use_fbo=false then everything will be rendered to screen directly as before. The only changes I made are within the brackets.

if (use_fbo) 
{
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glPushAttrib(GL_VIEWPORT_BIT);
    glViewport(0,0,grWinw, grWinh);

    if (fbo_a) // drawing to fbo_texture_a
    {               
        glDrawBuffer(GL_COLOR_ATTACHMENT1);
        glActiveTexture(GL_TEXTURE0+11);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, fbo_texture_a);
    }
    else
    {
        glDrawBuffer(GL_COLOR_ATTACHMENT0);
        glActiveTexture(GL_TEXTURE0+12);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, fbo_texture);
    }
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
}

glClearColor(0.7f, 0.1f, 0.1f, 1.0f); //clear with red to see what is drawn to the fbo
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

grScreens[0]->update(s, grFps);//THIS IS WHERE THE DRAWING HAPPENS unchanged from original drawing in TORCS

if (use_fbo) 
{
    glPopAttrib();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glEnable(GL_TEXTURE_2D);
    glDrawBuffer(GL_BACK);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glUseProgram(program_postproc);

    if (fbo_a) // drawn to fbo_texture_a
    {
        glUniform1i(uniform_fbo_texture, 11);        
        glUniform1i(uniform_fbo_texture_a, 12);        
        fbo_a=!fbo_a;
    }
    else
    {
        glUniform1i(uniform_fbo_texture, 12);        
        glUniform1i(uniform_fbo_texture_a, 11);        
        fbo_a=!fbo_a;
    }

    glEnableVertexAttribArray(attribute_v_coord_postproc);

    glBindBuffer(GL_ARRAY_BUFFER, vbo_fbo_vertices);
    glVertexAttribPointer(
            attribute_v_coord_postproc, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDisableVertexAttribArray(attribute_v_coord_postproc);
    glUseProgram(0); 
}

I hope I provided enough information for you to help me with this. Any advice is appreciated.

EDIT: I checked my shader code and FBO implementation again (simplified it to just one color attachment etc. with a simplified drawing) and it all worked. I think the trouble is the mix of fixed function pipeline for drawing and my implementation of the FBO...

EDIT: here are two images of what happens with use_fbo=true vs. false: (Note: the red color is the clear color after the FBO is bound, to see what gets rendered to the fbo: nothing apart from the shadow and skid marks)

use_fbo=true

use_fbo=false

I also tried to visualize the depth buffer (changed the implementation to a texture attachment for depth) and even though I linearized, there was no information. I suppose the depth is not correctly written to the FBO either.

Konstantin
  • 109
  • 1
  • 6
  • Edit in a [mcve]. – genpfault May 10 '17 at 18:15
  • 3
    It seems to me that you are trying to read from `fbo_texture_a` while rendering to `fbo_texture`. You cannot read from a texture that is attached to the currently active framebuffer. I didn't write a answer because it is unclear if you really try to do this (code hidden in `->update`) but the fact that you bind the texture like you do seems to be a good indication. – BDL May 10 '17 at 18:16
  • What gfx card you got? older **Intel HD** cards have problems with rendering to texture ... the workaround is to render to screen instead without swapping buffers then read pixels to **CPU** side and then copy back to **GPU** as texture. After that render screen normally. That is much slower and limited by screen resolution but works. see [OpenGL Scale Single Pixel Line](http://stackoverflow.com/a/43654398/2521214) – Spektre May 11 '17 at 07:49
  • @BDL I clarified the code above. Neither fbo_texture_a nor fbo_texture are accessed in -> update. ->update() is the original drawing function that I left untouched. It is rather complex and includes multiple classes and the library PLIB: http://plib.sourceforge.net/ – Konstantin May 11 '17 at 09:43
  • @Spektre: I am using a GeForce 9500 GT. I already have an implementation with glReadPixels in place, which is not satisfying from its performance. – Konstantin May 11 '17 at 09:45
  • If you don't use the textures, then you should remove the texture binding and enabling code in the `if (use_fbo)` block (or at least move them after the update call. – BDL May 11 '17 at 09:47
  • well if use_fbo=true then the scene will be rendered to said textures. Only after unbinding the fbo I do use them in the shader program again. – Konstantin May 11 '17 at 10:01
  • @Konstantin On nVidia rendering to texture works without problems with **FBO**. Have you tried `glGetError()` to check for some bad token use? – Spektre May 11 '17 at 11:34
  • yes, I've used glGetError() to check for bugs, nothing :( I still suspect some trouble with texture, view or other settings – Konstantin May 11 '17 at 12:22

1 Answers1

1

When I compared your code with mine working engine I see these differences so try them one by one:

  1. texture format

    you are using:

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    

    so merging all your to:

    GL_COLOR_ATTACHMENT0: GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE
    GL_COLOR_ATTACHMENT1: GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE
    

    I am using:

    GL_COLOR_ATTACHMENT0 : GL_RGBA           , GL_RGBA8            , GL_UNSIGNED_BYTE
    GL_DEPTH_ATTACHMENT  : GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_UNSIGNED_BYTE
    GL_STENCIL_ATTACHMENT: GL_STENCIL_INDEX  , GL_STENCIL_INDEX8   , GL_UNSIGNED_BYTE
    

    You need to change internal pixel format of textures to specify bit width. If my memory serves well when I code this (some years back) it did not work with just GL_RGBA,GL_RGBA.

  2. depth target

    I am using depth and stencil textures the same way as color attachment I do not use any RenderBuffer calls. That does not mean your code is wrong but mine is tested and works.

  3. texture size

    This is most likely not valid anymore as most gfx cards support rectangle texture extension but OpenGL textures should be power of 2 resolution. So for starters try 512x512 instead of your 640x480 And change back when your code is working (just to be sure ...).

In case it helps here is my C++ FBO class taken from mine engine so you got something to compare to (will not work alone as it uses textures and stuff from the engine):

//------------------------------------------------------------------------------
//--- Open GL FBO object ver 2.31 ----------------------------------------------
//------------------------------------------------------------------------------
#ifndef _OpenGL_FBO_h
#define _OpenGL_FBO_h
//------------------------------------------------------------------------------
class OpenGL_FBO
    {
public:
    GLuint fbo;
    int xs,ys;
    struct _dst
        {
        GLint txr;      // texture id
        GLenum dst;     // GL_DEPTH_COMPONENT, GL_COLOR_ATTACHMENT0, ...
        _dst()          { txr=-1; dst=GL_COLOR_ATTACHMENT0; }
        _dst(_dst& a)   { *this=a; }
        ~_dst()         {}
        _dst* operator = (const _dst *a) { *this=*a; return this; }
        //_dst* operator = (const _dst &a) { ...copy... return this; }
        };
    List<_dst> dst;

    OpenGL_FBO() { fbo=0xFFFFFFFF; xs=1; ys=1; dst.reset(); }
    OpenGL_FBO(OpenGL_FBO& a)   { fbo=0xFFFFFFFF; dst.reset(); *this=a; }
    ~OpenGL_FBO() { if (fbo!=0xFFFFFFFF) glDeleteFramebuffers(1,&fbo); }
    OpenGL_FBO* operator = (const OpenGL_FBO *a) { *this=*a; return this; }
    //OpenGL_FBO* operator = (const OpenGL_FBO &a) { ...copy... return this; }

    void resize(OpenGLscreen &scr,int _xs=-1,int _ys=-1)
        {
        int i;
        _dst *d;
        if (_xs<=0) _xs=scr.xs;
        if (_ys<=0) _ys=scr.ys;
//      for (xs=1;xs<_xs;xs<<=1);
//      for (ys=1;ys<_ys;ys<<=1);
        xs=_xs; ys=_ys; // ****

        if (fbo==0xFFFFFFFF) glGenFramebuffers(1,&fbo);
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        for (d=dst.dat,i=0;i<dst.num;i++,d++)
            {
            scr.txrs.bind(d->txr);
            scr.txrs.resize(d->txr,xs,ys,1);
//          glFramebufferTexture2D(GL_FRAMEBUFFER,t->dst,GL_TEXTURE_2D,scr.txrs.names[d->txr],0);
            glFramebufferTexture(GL_FRAMEBUFFER,d->dst,scr.txrs.names[d->txr],0);
//          glCheckFramebufferStatus(GL_FRAMEBUFFER);
            }
        scr.txrs.unbind();
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        }
    int add(OpenGLscreen &scr,int _dest=GL_COLOR_ATTACHMENT0)   // add txr to fbo
        {
        _dst d;
        OpenGL_TXR tmp;
        // colro atachments
        tmp.pixelformat =GL_RGBA;
        tmp.pixeliformat=GL_RGBA8;
        tmp.pixeltype=GL_UNSIGNED_BYTE;
        tmp.mag=GL_NEAREST;
        tmp.min=GL_NEAREST;
        if (_dest==GL_DEPTH_ATTACHMENT)
            {
            tmp.pixelformat =GL_DEPTH_COMPONENT;
            tmp.pixeliformat=GL_DEPTH_COMPONENT16;
//          tmp.pixeltype=GL_FLOAT;
            tmp.pixeltype=GL_UNSIGNED_BYTE;
            }
        if (_dest==GL_STENCIL_ATTACHMENT)
            {
            tmp.pixelformat =GL_STENCIL_INDEX;
            tmp.pixeliformat=GL_STENCIL_INDEX8;
            tmp.pixeltype=GL_UNSIGNED_BYTE;
            }
        tmp.xs=xs;
        tmp.ys=ys;
        tmp.zs=1;
        tmp._mipmap=0;
        tmp.txrtype=GL_TEXTURE_2D;
        d.txr=scr.txrs.add(tmp);
        d.dst=_dest;
        dst.add(d);
        return d.txr;
        }
    void bind(OpenGLscreen &scr)    // init fbo >> txr
        {
        // init and resize
        if (fbo==0xFFFFFFFF) glGenFramebuffers(1,&fbo);
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        glViewport(0,0,xs,ys);
        scr.cls();
        }
    void unbind(OpenGLscreen &scr)
        {
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        glViewport(scr.x0,scr.y0,scr.xs,scr.ys);
        }
    };
//------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------
//--- end. ---------------------------------------------------------------------
//------------------------------------------------------------------------------

where:


OpenGLscreen scr is my rendering engine
scr.cls() is just glClear and stuff to init frame
scr.x0,y0,xs,ys is viewport of target window
scr.txrs is texture system class (handles all the textures) like add new texture loading/saving from/to file, conversion between CPU/GPU and much much more.

I also use mine dynamic list template so:


List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items

typical usage is:

// [globals and init]
OpenGLScreen scr; // can ignore this
OpenGL_FBO fbo;
scr.init(window_handle); // init OpenGL stuff can ignore this
fbo.add(scr,GL_COLOR_ATTACHMENT0);
fbo.add(scr,GL_DEPTH_ATTACHMENT);
fbo.resize(scr,512,512);

// [render loop]
fbo.bind(scr);
// here render
fbo.unbind(scr);
// here you can use the textures fbo.dst[].txr

Take a look here for specific example:

Of coarse for those of you that are stuck with older Intel HD graphics do not expect that this will work due to bug in drivers. See this slow workaround:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thanks for your suggestions! I tried 1., 2. and 3. unfortunately none of your suggestions helped. It all still worked ( with a shifted partial rendering with 3.). Next I will try to render depth to the screen using the shader with depth attachment as texture, to see if any depth information is written to the fbo. Thanks again, unfortunately not my solution. – Konstantin May 12 '17 at 10:14
  • @Konstantin there is also a possibility that the problem lies inside your shaders instead. The texture unit `10/11` swap looks suspicious – Spektre May 12 '17 at 10:45
  • the swap is probably not ideal, but I just worked on a simplification that only uses one texture unit to render to and then displays its contents back to the window system. Unfortunately that did not change anything. – Konstantin May 15 '17 at 12:24
  • @Konstantin try to add images of good and wrong rendering ... it may provide some hints on what is going on... Also do you use Blending? Or Multiple pass rendering? – Spektre May 15 '17 at 12:31
  • thanks for your enduring help efforts! I uploaded two pictures, guess which is which ;) I do not use Blending nor Multipass rendering (not sure if anything like that happens in the drawing function, not even sure if the fixed function pipeline allowed that) – Konstantin May 15 '17 at 15:54
  • @Konstantin just a guess the rendering itself is not your code? I do not know what happens if the rendering uses stencil or alpha and you do not have it attached to FBO it should work but never tried it. Try to find out what techniques are used in rendering and add all the buffers to match it if it makes a difference. Also make sure the bit-widths and internal format match Window in case it uses some dirty bit hack – Spektre May 15 '17 at 17:22
  • yeah you guessed right, to render I just use whatever the TORCS game is doing already. Unfortunately the rendering relies on the plib library (http://plib.sourceforge.net/) and I am not sure if that might be the problem, since it seems abandoned for quite a while already :( I am giving up here, tried my luck, thanks for your help though. I am not sure how to match bit-widths with the Window. – Konstantin May 16 '17 at 13:32
  • @Konstantin obtain pixel format and look into it ... see [OpenGL - vertex normals in OBJ](http://stackoverflow.com/a/31913542/2521214) example and inside look for `SetPixelFormat` function. The same stuff should be in your OpenGL init code find it and look what buffers are set and how many bits allocated to each. If my memory serves well there should be also `GetPixelFormat` which loads your actual state in case you do not have access to init code. Also if the render uses FBO on its own it could mess with yours. – Spektre May 16 '17 at 15:26