PeterVE PeterVE - 3 months ago 12
C++ Question

GL_TEXTURE_2D as render input

I have an image in a

GL_TEXTURE_2D
. From here, I need to move this texture onto the framebuffer object. Can someone please point me to an example that uses a 2d texture that is filled with data and places it onto the FBO? I am not trying to render to a texture, but from a texture.

Answer

Here is a simple FBO demonstration.

You can find the entire code here on github (the code is based on this sample program from the OpenGL SuperBible).

The program does the following

  1. Draws a red square onto a green background into a FBO. The backing store for the FBO is a 2D texture.

  2. Draw a square onto a blue screen but map the texture from step 1 to the square. This maps the entire scene from the previous step onto the square.

Screen shot

So the theory when using a framebuffer,

  1. (Initialisation) Attach textures to an off-screen framebuffer.

and then there are 2 programs (or 2 passes)

  1. In program 1, render into the framebuffer (which renders into the textures)

  2. In program 2, now render to the window (screen) and use the texture(s) from the first program as input.

So the output of the first program is the input for the second program.

That means the vertex shaders for both stages will be the same. As there is no mathes (rotation) and no lighting effects, all the vertex shader does is pass the input to the output (i.e. the coordinates).

But each stage will have slightly different fragment shader.

The 1st fragment shader outputs a constant color for each pixel. In the source code the output of the 1st fragment shader is configured to the FBO and the FBO uses a texture to store the color data.

The texture generated in the 1st fragment shader is used as input in the 2nd fragment shader. In the 2nd fragment shader, the color is determined by the texture (which is the output of the 1st fragment shader).

So here is the vertex shader. Note the input to the vertex shader is the coordinates (vp) and the texture coordinates. The texcoords are needed by the 2 program's fragment shader to know where to map the texture. In the vertex shader the texcoord's are simply passed from the input to the output.

#version 130
in vec3 vp;
in vec2 texcoord;

out vec2 outtexcoord;

void main () {
    gl_Position = vec4 (vp, 1.0);
    outtexcoord = texcoord; 
}

This is the 1st program's fragment shader. Every pixel is drawn red.

#version 130

in vec2 outtexcoord;

out vec4 frag_colour;
void main () {
  frag_colour = vec4 (1.0, 0.0, 0.0, 0.0); //everything red
}

And the 2nd programs fragment shader. Note the sampler2D which is the input texture (generated by the 1st program). Also the outtexcoord are the texture coordinates from the vertex shader. The output color (frag_color) is determined by the texture.

#version 130
uniform sampler2D tex;

in vec2 outtexcoord;

out vec4 frag_color;

void main () {
    frag_color = texture(tex, outtexcoord);
}

And here is the C program (I compiled with g++, see the makefile in the github link).

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "shader_utils.h" //see github link for details

void Initialize();
void InitGL();
void InitProgramFBO();
void InitProgramScreen();
void InitBuffer();
void InitFBO();
void Loop();
void RenderToFBO();
void RenderToScreen();
void Shutdown();
void OnWindowResize(GLFWwindow* window, int width, int height);

GLFWwindow* window;
int screenWidth = 640;
int screenHeight = 480;
GLuint render2FBOProgram;
GLuint render2ScreenProgram;
GLuint vao;
GLuint vbo;
GLuint fbo;
GLuint color_texture;

int main() {
   Initialize();
   Loop();
   Shutdown();
   return 0;
}

void Initialize() {
    InitGL();
    InitProgramFBO();
    InitProgramScreen();
    InitProgramFBO();
    InitBuffer();
    InitFBO();
}

void InitGL() {
    glfwInit();
    window = glfwCreateWindow(screenWidth, screenHeight, "FBO Demo", NULL, NULL);

    glfwMakeContextCurrent(window);
    glewInit();

    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
}

void InitProgramFBO() {
  GLuint vs;
  GLuint fs;
  render2FBOProgram = create_program("vs.glsl", "fbo.fs.glsl", vs, fs);
  glDeleteShader(vs);
  glDeleteShader(fs);
}

void InitProgramScreen() {
  GLuint vs;
  GLuint fs;
  render2ScreenProgram = create_program("vs.glsl", "screen.fs.glsl", vs, fs);
  glDeleteShader(vs);
  glDeleteShader(fs);
}

void InitBuffer() {
  //define the square made up of 2 triangles
  static const GLfloat points[] = {
  //x     y      z       texcoord u and v
  -0.5f,  0.5f,  0.0f,   0.0f, 0.0f,
   0.5f,  0.5f,  0.0f,   1.0f, 0.0f,
   0.5f, -0.5f,  0.0f,   1.0f, 1.0f,

   0.5f, -0.5f,  0.0f,   1.0f, 1.0f,
  -0.5f, -0.5f,  0.0f,   0.0f, 1.0f,
  -0.5f,  0.5f,  0.0f,   0.0f, 0.0f
  };

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

  //create buffer for points
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

  //tell opengl how to find the coordinate data
  glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLubyte*)NULL);
  glEnableVertexAttribArray(0);

  //tell opengl how to find the texcoord data
  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid *)(3 * sizeof(GLfloat)));
  glEnableVertexAttribArray(1);
}

void InitFBO() {

  //create a framebuffer
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);

  //create a texture as the backing store for the framebuffer
  glGenTextures(1, &color_texture);
  glBindTexture(GL_TEXTURE_2D, color_texture);
  glTexStorage2D(GL_TEXTURE_2D, 9, GL_RGBA8, 512, 512); //1 = mipmap     levels

  //mip map filtering
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  //attach the texture as the color attachment of the framebuffer
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,     GL_TEXTURE_2D, color_texture, 0);

  //tell opengl to draw into the color attachment
  static const GLenum draw_buffers[] = { GL_COLOR_ATTACHMENT0 };
  glDrawBuffers(1, draw_buffers);
}

void Loop() {

  //glBindVertexArray(vao);
  //glBindBuffer(GL_ARRAY_BUFFER, vbo);

  while (!glfwWindowShouldClose(window)) {

    RenderToFBO();

    RenderToScreen();

    glfwSwapBuffers(window);

    glfwPollEvents();
    if (GLFW_PRESS == glfwGetKey(window, GLFW_KEY_ESCAPE)) {
      glfwSetWindowShouldClose(window, 1);
    }

  }
}

void RenderToFBO() {
  static const GLfloat green[] = { 0.0f, 1.0f, 0.0f, 1.0f }; //texture background is green

  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  glViewport(0, 0, 512, 512); //set view port to texture size
  glClearBufferfv(GL_COLOR, 0, green);
  glUseProgram(render2FBOProgram);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void RenderToScreen() {
  static const GLfloat blue[] = { 0.0f, 0.0f, 1.0f, 1.0f }; //screen     background is blue

  glViewport(0, 0, screenWidth, screenHeight);
  glClearBufferfv(GL_COLOR, 0, blue);
  glBindTexture(GL_TEXTURE_2D, color_texture);
  glUseProgram(render2ScreenProgram);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glBindTexture(GL_TEXTURE_2D, 0);
}

void Shutdown() {
  glUseProgram(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glDeleteProgram(render2FBOProgram);
  glDeleteProgram(render2ScreenProgram);
  glfwTerminate();
 }

// a call-back function
void OnWindowResize(GLFWwindow* window, int width, int height) {
  screenWidth = width;
  screenHeight = height;
  glViewport(0, 0, screenWidth, screenHeight);
}