4

I have the following OpenGL/GLSL code. I'm trying to take two textures into a shader and get two different textures out.

At the moment I'm only doing pointless calculations. But for my actually application (HDR imaging) I need to get two+ textures in and out of a single shader.

My issue is in the int main() I don't know how to display one of the output textures on a quad.

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <stdio.h>

const GLchar* vertexSource =
    "#version 150 core\n"
    "in vec2 position;"
    "in vec3 color;"
    "in vec2 texcoord;"
    "out vec2 Texcoord;"
    "out vec3 Color;"
    "void main() {"
    "   Color = color;"
    "   Texcoord = texcoord;"
    "   gl_Position = vec4(position, 0.0, 1.0);"
    "}";

const GLchar* fragmentSource =
    "#version 150 core\n"
    "in vec3 Color;"
    "in vec2 Texcoord;"
    "out vec4 outColor1;"
    "out vec4 outColor2;"
    "uniform sampler2D texLena;"
    "uniform sampler2D texTex7;"
    "void main() {"
    "   vec4 colLena = texture(texLena, Texcoord);"
    "   vec4 colTex7 = texture(texTex7, Texcoord);"
    "   outColor1 = mix(colLena, colTex7, 0.75) * vec4(Color, 1.0);"
    "   outColor2 = mix(colLena, colTex7, 0.25);"
    "}";

void printShaderInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        free(infoLog);
    }
}

void printProgramInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        free(infoLog);
    }
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

int main()
{
    if (glfwInit() != GL_TRUE)
    {
        fprintf(stderr, "Failed to initialize GLFW\n");
        return -1;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", glfwGetPrimaryMonitor(), NULL);

    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);

    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK)
    {
        fprintf(stderr, "Failed to initialize GLEW\n");
        return -1;
    }

     GLuint vao;
     glGenVertexArrays(1, &vao);
     glBindVertexArray(vao);

     GLuint vbo;
     glGenBuffers(1, &vbo);

     GLfloat vertices[] = {
        -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
         0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
         0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
        -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f
     };

     glBindBuffer(GL_ARRAY_BUFFER, vbo);
     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

     GLuint ebo;
     glGenBuffers(1, &ebo);

     GLuint elements[] = {
        0, 1, 2,
        2, 3, 0
     };

     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);

     GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
     glShaderSource(vertexShader, 1, &vertexSource, NULL);
     glCompileShader(vertexShader);
     printShaderInfoLog(vertexShader);

     GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
     glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
     glCompileShader(fragmentShader);
     printShaderInfoLog(fragmentShader);

     GLuint shaderProgram = glCreateProgram();
     glAttachShader(shaderProgram, vertexShader);
     glAttachShader(shaderProgram, fragmentShader);
     glBindFragDataLocation(shaderProgram, 0, "outColor1");
     glBindFragDataLocation(shaderProgram, 1, "outColor2");
     glLinkProgram(shaderProgram);
     printProgramInfoLog(shaderProgram);
     glUseProgram(shaderProgram);

     GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
     glEnableVertexAttribArray(posAttrib);
     glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 7*sizeof(float), 0);

     GLint colAttrib = glGetAttribLocation(shaderProgram, "color");
     glEnableVertexAttribArray(colAttrib);
     glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE,
                            7*sizeof(float), (void*)(2*sizeof(float)));

     GLint texAttrib = glGetAttribLocation(shaderProgram, "texcoord");
     glEnableVertexAttribArray(texAttrib);
     glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE,
                            7*sizeof(float), (void*)(5*sizeof(float)));

     GLuint textures[2];
     glGenTextures(2, textures);

     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, textures[0]);
     cv::Mat image = cv::imread("lena.tiff");
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0,
                    GL_BGR, GL_UNSIGNED_BYTE, image.data);
     image.release();

     glUniform1i(glGetUniformLocation(shaderProgram, "texLena"), 0);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

     glActiveTexture(GL_TEXTURE1);
     glBindTexture(GL_TEXTURE_2D, textures[1]);
     image = cv::imread("tex7.jpg");
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0,
                    GL_BGR, GL_UNSIGNED_BYTE, image.data);

     glUniform1i(glGetUniformLocation(shaderProgram, "texTex7"), 1);

     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

     GLuint frameBuffer;
     glGenFramebuffers(1, &frameBuffer);
     glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

     GLuint texFront;
     glGenTextures(1, &texFront);
     glBindTexture(GL_TEXTURE_2D, texFront);

     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows,0, 
                    GL_RGB, GL_UNSIGNED_BYTE, NULL);

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

     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texFront, 0);

     GLuint texBack;
     glGenTextures(1, &texBack);
     glBindTexture(GL_TEXTURE_2D, texBack);

     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows,0, 
                    GL_RGB, GL_UNSIGNED_BYTE, NULL);

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

     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texBack, 0);

     image.release();

     GLenum bufs[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
     glDrawBuffers(2, bufs);

    while(!glfwWindowShouldClose(window))
    {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // What should I be doing here?
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texFront);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteTextures(1, textures);
    glDeleteTextures(1, &texFront);
    glDeleteTextures(1, &texBack);

    glDeleteFramebuffers(1, &frameBuffer);

    glDeleteProgram(shaderProgram);
    glDeleteShader(fragmentShader);
    glDeleteShader(vertexShader);

    glDeleteBuffers(1, &ebo);
    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    glfwTerminate();

    return 0;
}

How do I select texFront (which is my COLOR_ATTACHMENT0) to be displayed when I glDrawElements?

EDIT

enter image description here

I am getting a weird result where part of COLOR_ATTACHMENT0 appears when I try display COLOR_ATTACHMENT1

Maggick
  • 763
  • 2
  • 10
  • 26
  • What do you mean by "displayed when i glDrawElements"? If i am not mistaken you setup a shader program taking lena and textex7 as input and render to the texFront and texBack texture in your offscreen framebuffer by calling the `drawElements`. If you want to show the result on screen, you should revert to your default framebuffer by `glBindFramebuffer(GL_FRAMEBUFFER, 0);`, disable your shader by `glUseProgram(0)`, setup the texcoords and draw it. Or did i get you wrong? – Thomas Sep 15 '14 at 10:17
  • I only want to show one of the `COLOR_ATTACHMENTi` though. When I do glBindFramebuffer(GL_FRAMEBUFFER, 0); it seems to be only showing me the texFront, how do I switch between the `COLOR_ATTACHMENTS`? – Maggick Sep 15 '14 at 11:28
  • I also want to be able to turn each `COLOR_ATTACHMENT` back into it's own image? – Maggick Sep 15 '14 at 11:31
  • If you want to use the texBack instead of texFront, you have to bind that to a texture unit like `glBindTexture(GL_TEXTURE_2D, texBack);`. If you want both textures use different texture units by specifying `glActiveTexture(GL_TEXTURE0+i)` (i=1...maxtextures-1) as you already did with lena and textex7. Note that logically, this is not "switching COLOR_ATTACHMENTS", since these are framebuffer-terminology and that has not much to do with the texture objects itself. Also note, that you must not read and write to the same texture(s) from within a shader. – Thomas Sep 15 '14 at 12:29
  • So I added `glActiveTexture(GL_TEXTURE2)` for `texFront` and `glActiveTexture(GL_TEXTURE3)` for `texBack`. How would I go about displaying the contents of one of these textures after it has gone through my shader? – Maggick Sep 15 '14 at 13:11
  • If you just want to "see whats in there" go with @jozxyqk 's description and use BlitFramebuffer. If you want to texture a more complex model that a quad then use standard texturing mehtods like specify texture coordinates for vertices, enable texturing (or use a shader) and then draw the model. – Thomas Sep 15 '14 at 13:50

1 Answers1

6

To bind multiple textures for use in a shader (as you already have):

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex2);
  • Then glUniform1i to point the uniform sampler2Ds to the texture bind points (0/1).

  • Read as normal (i.e. texture()/texelFetch() (texture2D() < GL3).

To write to multiple textures (as you already have):

  • Attach multiple textures as colour attachments with glFramebufferTexture2D and then set glDrawBuffers.

  • Bind the framebuffer.

  • Draw your geometry, writing data from the fragment shader.

    With < GL3:

    • Write to gl_FragData[*]

    Or >= GL3:

    • glBindFragDataLocation and out colour1, colour2

ARB_image_load_store is an alternative, but not necessary here.

Side note: reading from a texture bound to the current FBO is possible and works if there are no RMW hazards.

To display the output, if desired

Either:

  • Draw a full screen polygon, and use a fragment shader to draw whatever you want (i.e. a passthrough for the texture you just wrote to using the FBO). This can be done with a single really big triangle clipped using glViewport (it doesn't matter if bits go outside the screen).

    Note that to draw anything to the default framebuffer, unbind any currently bound FBO with glBindFramebuffer(GL_FRAMEBUFFER, 0);

  • Much simpler, blit the contents of your FBO to the default framebuffer.

    https://www.opengl.org/sdk/docs/man3/xhtml/glBlitFramebuffer.xml

    glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); //destination
    glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    

    The colour attachments that are copied can be set with glReadBuffer/glDrawBuffers`. See here. Another here.

Community
  • 1
  • 1
jozxyqk
  • 16,424
  • 12
  • 91
  • 180
  • If I have two `COLOR_ATTACHMENTS` in my `sourceFBO` how do I display just one of them say `COLOR_ATTACHMENT1`? Do I then call `glReadBuffer(GL_COLOR_ATTACHMENT1);`? Then how do I get the texture onto the viewport? – Maggick Sep 15 '14 at 13:09
  • 1
    Yes, first bind `GL_READ_FRAMEBUFFER`, then `glReadBuffer(GL_COLOR_ATTACHMENT1)`, bind `GL_DRAW_FRAMEBUFFER` as above (you could set the draw buffer but AFAIK leaving as initial (front/back for single/double buffered context) is fine), then blit. – jozxyqk Sep 15 '14 at 13:39
  • I am not able to get `COLOR_ATTACHMENT1` to display. It keeps displaying `COLOR_ATTACHMENT0`. I've changed so my code looks like: `glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); glReadBuffer(GL_COLOR_ATTACHMENT1); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer(0, 0, image.cols, image.rows, 0, 0, image.cols, image.rows, GL_COLOR_BUFFER_BIT, GL_LINEAR);` and then in the while loop I call `glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);` – Maggick Sep 15 '14 at 17:00
  • You must not call glDrawElements after blitting, glBlitFramebuffer itself will copy the pixels to the output. If you call drawelements after blitting it will likely overwrite your result again (and show `COLOR_ATTACHMENT0` again). – Thomas Sep 15 '14 at 17:09
  • See my edit. I am getting a weird result. I moved the binding to after I call `glDrawElements` – Maggick Sep 15 '14 at 17:22
  • What binding? Just to be clear, your while loop should probably be `glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glClearColor(...); glClear(...); glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); glReadBuffer(GL_COLOR_ATTACHMENT1); glBlitFramebuffer(0, 0, image.cols, image.rows, 0, 0, image.cols, image.rows, GL_COLOR_BUFFER_BIT, GL_LINEAR); glfwSwapBuffers(window), glfwPollEvents();` - in that order and no `glDrawElements` at all. – Thomas Sep 15 '14 at 17:35
  • If I don't draw elements then what in the `COLOR_ATTACHMENTi` going to be drawn onto? – Maggick Sep 15 '14 at 17:39
  • @Maggick You can't draw to both the default framebuffer and a texture at the same time. Instead, you draw to your two textures and then blit (copy) one of your textures to the default framebuffer to see it. – jozxyqk Sep 15 '14 at 18:12