COLOR_ATTACHMENT's - How to render to multiple textures as color attachments inside a Framebuffer Object?
Asked Answered
F

1

6

I am trying to render to multiple textures as COLOR_ATTACHMENTs without success. All I get from displaying them is a black screen (with a red clear fill) meaning my texture is read but is 'empty'.

My pseudo code is : attach 3 textures to an FBO with texture indexes 1, 2 and 3 and color attachments 0, 1 and 2 respectively. As a test case, I tried to render my scene to the 3 color attachments so they are supposed to hold the same exact data. Then read either of those textures at shader pass 2 (with a 2Dsampler) and display them on a quad.

My original intent for those 2 extra color attachments is to use them as random data buffers using the GPU ping-pong technique. So far I just use them as texture clones for testing purpose.

When trying to read from GL_TEXTURE1 (COLOR_ATTACHMENT0) things go fine but not from the other 2 (black screen).

The code :

// Texture indices - inside a 'myGlut' struct
GLenum skyboxTextureIndex = GL_TEXTURE0;
GLenum colorTextureIndex = GL_TEXTURE1;
unsigned int colorTextureIndexInt = 1;
GLenum depthTexture1Index = GL_TEXTURE2;
unsigned int depthTexture1IndexInt = 2;
GLenum depthTexture2Index = GL_TEXTURE3;
unsigned int depthTexture2IndexInt = 3;

//** Below is inside 'main()' **//

// Create frame buffer
myGlut.frameBuffer = glutils::createFrameBuffer();

// Create texture to hold color buffer
glActiveTexture(myGlut.colorTextureIndex);
glBindTexture(GL_TEXTURE_2D, myGlut.colorTexture);
myGlut.colorTexture = glutils::createTextureAttachment(myGlut.camera -> getRenderResizedWidthPx(), myGlut.camera -> getRenderResizedHeightPx());
glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT0, myGlut.colorTexture);

// Create 1st texture to hold depth buffer wannabe :>
glActiveTexture(myGlut.depthTexture1Index);
glBindTexture(GL_TEXTURE_2D, myGlut.depthTexture1);
myGlut.depthTexture1 = glutils::createTextureAttachment(myGlut.camera -> getRenderResizedWidthPx(), myGlut.camera -> getRenderResizedHeightPx());
glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT1, myGlut.depthTexture1);

// Create 2nd texture to hold depth buffer wannabe :>
glActiveTexture(myGlut.depthTexture2Index);
glBindTexture(GL_TEXTURE_2D, myGlut.depthTexture2);
myGlut.depthTexture2 = glutils::createTextureAttachment(myGlut.camera -> getRenderResizedWidthPx(), myGlut.camera -> getRenderResizedHeightPx());
glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT2, myGlut.depthTexture2);

// Check FBO
if (!glutils::checkFBOStatus()) return 0;

With glutils:: functions

// Clear screen
void glutils::clearScreen (float r, float g, float b, float a) {
    glClearColor(r, g, b, a);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

// Bind select framebuffer
void glutils::bindFrameBuffer(int frameBuffer, int width, int height) {
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glViewport(0, 0, width, height);
}

// Create frame buffer
GLuint glutils::createFrameBuffer() {
    GLuint frameBuffer;
    glGenFramebuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    return frameBuffer;
}

// Create a texture attachment
GLuint glutils::createTextureAttachment(int width, int height) {
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    return texture;
}

// Bind a texture attachment to select framebuffer
void glutils::bindTextureAttachment (GLenum colorAttachment, GLuint texture) {
    glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachment, GL_TEXTURE_2D, texture, 0);
}

// Check current frame buffer status
bool glutils::checkFBOStatus () {
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "##### ERROR : Frambuffer not complete... #####" << std::endl;
        return false;
    }
    else return true;
}

Then the glut display func :

// Clear screen
glutils::clearScreen(1.f, 0.f, 0.f, 1.f);

// Bind to custom framebuffer
glutils::bindFrameBuffer(myGlut.frameBuffer, myGlut.camera -> getScreenWidthPx(), myGlut.camera -> getScreenHeightPx());

// Set draw context
GLuint drawBuffers[2];
if (myGlut.depthTextureSwitch) {    drawBuffers[0] = GL_COLOR_ATTACHMENT0;
                                    drawBuffers[1] = GL_COLOR_ATTACHMENT2;
} else {                            drawBuffers[0] = GL_COLOR_ATTACHMENT0;
                                    drawBuffers[1] = GL_COLOR_ATTACHMENT1;
} glDrawBuffers(2, drawBuffers);

// Use main program and bind uniforms
glUseProgram(myGlut.theProgram);
myGlut.refreshUniformsPass_1();

// Draw quad to sample
glutils::drawQuad();

// Unbind custom framebuffer -> use default (screen)
glutils::unbindCurrentFrameBuffer(myGlut.camera -> getScreenWidthPx(), myGlut.camera -> getScreenHeightPx());

// Use secondary program and bind uniforms
glUseProgram(myGlut.theProgram2);
myGlut.refreshUniformsPass_2();

// Draw quad to apply texture to
glutils::drawQuad();

// Switch
myGlut.depthTextureSwitch = !myGlut.depthTextureSwitch;

// Display & loop
glutSwapBuffers();
glutPostRedisplay();

Relevant uniform bindings -> pass 1

glUniform1i(glGetUniformLocation(myGlut.theProgram, "depthTexture"), !myGlut.depthTextureSwitch ? myGlut.depthTexture2IndexInt : myGlut.depthTexture1IndexInt);

Relevant shader code -> Pass 1

layout (location = 0) out vec4 outputColor;
layout (location = 1) out vec4 outputDepth1;
layout (location = 2) out vec4 outputDepth2;
uniform sampler2D depthTexture;

void main() {
    // ...
    outputColor = someColor;
    outputDepth1 = someColor;
    outputDepth2 = someColor;
}

Relevant uniform bindings -> pass 2

glUniform1i(glGetUniformLocation(myGlut.theProgram2, "texFramebuffer"), myGlut.depthTextureSwitch ? myGlut.depthTexture1IndexInt : myGlut.depthTexture2IndexInt);

With relevant shader code -> pass 2

uniform sampler2D texFramebuffer;
out vec4 outputColor;
// ...
void main() {
    outputColor = texture(texFramebuffer, vec2(gl_FragCoord.x / screenWidthPx * resRatio, gl_FragCoord.y / screenHeightPx * resRatio));
}

In a nutshell : my GL_TEXTURE0 holds the scene while GL_TEXTURE1 and GL_TEXTURE2 are black. Why ?

Farthest answered 27/5, 2015 at 13:54 Comment(13)
Based on the glDrawBuffers() call, you're only rendering to 2 textures. And it looks like for pass 2, you're using the texture you did not render to.Unpaid
It is true I am reading from the texture I didn't render to, but this is (expected) and because of the 'ping-pong' rendering technique. So it means I should get a black screen only @ first frame. Because then at frame 2 I am rendering to texture 2 (switch on) and reading from texture 1 - that I rendered to at frame 1 and so on... The real problem is my render is constantly black as if GL_TEXTURE1 and GL_TEXTURE2 are never rendered to. Why ? If I try to constantly read from GL_TEXTURE1 or GL_TEXTURE2 I get a constant black screen.Farthest
with only one texture sampled (texFramebuffer), you don(t need different ActiveTexture (tex slots), it's confusing...Scaly
Yes you are right. But I need to read from either depthTexture1 or depthTexture2 at shader pass 1. Hence the 3 used texture slots.Farthest
You need as much slot as #textureNames, in other words, #slot = #textures sampled in a single pass. But if you realy want to bind 3 textures to avoid calling bindTexture between passes, you can, but you have to declare 3 different uniformsScaly
Can't I just switch the uniform binding ? I'm using glUniform1i(glGetUniformLocation(myGlut.theProgram, "depthTexture"), !myGlut.depthTextureSwitch ? myGlut.depthTexture2IndexInt : myGlut.depthTexture1IndexInt); to get access to the right texture I need at this point. Then another uniform binding for the skybox.Farthest
You have (in your shader) only one uniform sample, so you only need texture0 slot, to switch, you just have to call bindTexture with correct texID generated with genTexture. With uniform call, you attach the name used in your shader (texFramebuffer) with the ActiveTexture slot (Texture0), you should normaly call it only once.Scaly
ok sorry, you have a second texture "depthTexture" for the second pass, so you're right to call uniform1i each cycle, but no need to use many texture slot, you may use slot 0 for both.Scaly
I still dont know why I fail at writing to color attachments 1 and 2. Any idea ???Farthest
Is depth test unabled? not sure it's allowed when no DepthAttachment texture is bound to fb. Also have you tried to output fixed colors or simply clear the fb with plain color. Note also that you output vec4 to texture that are defined as rgb without alpha.Scaly
I am clearing with glClearColor() and glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) each frame with a red color (255, 0, 0) - but my color attachments 1 & 2 are black when displaying corresponding textures (2 & 3). I am attaching a depth buffer too but didn't show in the code above. Also when displaying textures 2 & 3 they are black not red from the glClear() call.Farthest
So the last thing I would test is to use rgba (4 components) textures as output.Scaly
I found the culprit, see my answer below (in 2 minutes).Farthest
F
2

I finally found the culprit. Because I am binding the framebuffer inside the looped display() function, I needed to bind texture attachments as well after I bound the FBO. Changing to

// Bind to custom framebuffer
glutils::bindFrameBuffer(myGlut.frameBuffer, myGlut.camera -> getScreenWidthPx(), myGlut.camera -> getScreenHeightPx());

// Bind to select attachments
glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT0, myGlut.colorTexture);
if (!myGlut.depthTextureSwitch) glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT1, myGlut.depthTexture1);
else glutils::bindTextureAttachment(GL_COLOR_ATTACHMENT1, myGlut.depthTexture2);

allowed me to render to all needed color attachments.

Farthest answered 31/5, 2015 at 19:7 Comment(4)
Actually, the FBO attachments stay intact, no need to re-setup them. At least, if you don;t yourself detach them somewhere. Hard to tell what acutally is going on from the code fragments given so far.Arlana
I am implementing a GPU ping-pong (pixelnerve.com/v/2010/07/20/pingpong-technique) so I need to switch attachments every frame. For example : read from 0 & 1 + write to 2 @ frame 1, read from 0 & 2 + write to 1 @ frame to and so on... The problem being : I can't read AND write to a texture in a single pass hence the ping-pong workaround.Farthest
No need to reattach. All you need is switching the draw buffers around. BTW, Depending on what operations you actually do, it might even be possible to read from and write to the same texture in the same pass. The GL does allow this under certain specific conditions.Arlana
Switching draw buffers is exactly what I started doing but it didn't work for me for some reason... I.E. binding everything inside main() - textures and color attachments - and switching the draw buffers inside display(). Didn't work.Farthest

© 2022 - 2024 — McMap. All rights reserved.