How can I use the texture in the framebuffer as the new texture?

Hello !

I’m trying to learn OpenGL language, and I would like to do a little code that do a horizontal blur (HR) on an image, and then a vertical blur (VB) on the previous result.

I used a Framebuffer to this purpose, but I’m not sure how to use the texture in the Framebuffer as the new texture.

This is my code :


// Include standard headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <ctime>

#include <iostream>

//#include <opencv.hpp>
#include <opencv/cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv/highgui.h>
using namespace cv;

// Include GLEW
#include <GL/glew.h> 

// Include GLFW
#include <GLFW/glfw3.h> 

#include <shader.hpp>
using namespace std;

int width = 512;// 1024;
int height = 512;// 768;

// perspective projection
bool perspective_ = false;

// shader variable pointers
GLint uniform_srcTex;
GLint uniform_srcTex1;
GLint uniform_offset_x;
GLint uniform_offset_y;

////////////////////////////////////////
// glfw callbacks for keystroke and error
void error_callback(int error, const char* description)
{
    fputs(description, stderr);
}

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);
}

void char_callback(GLFWwindow* window, unsigned int key)
{
    if (key == 'p' || key == 'P')
        perspective_ = true;
    if (key == 'o' || key == 'O')
        perspective_ = false;
}

/////////////////////////////////////////////
// texture loading
bool loadtexture(string fileName, GLuint & texIndex, GLuint texUnit, bool isRect)
{

// texture load through OpenCV
    Mat image = cv::imread(fileName, CV_LOAD_IMAGE_UNCHANGED);   // Read the file
    if (!image.data) // Check for invalid input
    {
        cout << "Could not open or find the image
";
        return false;
    }
    cout << "Loaded " << fileName.c_str() << " (" << image.channels() << " channels)
";

    int colorTransform = (image.channels() == 4) ? CV_BGRA2RGBA : (image.channels() == 3) ? CV_BGR2RGB : CV_GRAY2RGB;
    //if (image[index].channels() >= 3)
    //{
        cv::cvtColor(image, image, colorTransform);
    //}

    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &texIndex);
    glActiveTexture(texUnit);
    GLenum target = GL_TEXTURE_2D;
    if (isRect)
    {
        target = GL_TEXTURE_RECTANGLE;
    }
    glBindTexture(target, texIndex);
    if (image.channels() > 3)
    {
        glTexImage2D(target, 0, GL_RGBA8, image.cols, image.rows, 0, GL_RGBA, (image.depth()<2)?GL_UNSIGNED_BYTE: ((image.depth()<4) ? GL_UNSIGNED_SHORT : GL_FLOAT), image.ptr());
    }
    else
    {
        glTexImage2D(target, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, (image.depth()<2) ? GL_UNSIGNED_BYTE : ((image.depth()<4) ? GL_UNSIGNED_SHORT : GL_FLOAT), image.ptr());
    }
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // glGenerateMipmap(GL_TEXTURE_2D);
    return true;
    }

void consoleMessage()
{
    cout << "Renderer : " << string((char*)glGetString(GL_RENDERER)) << endl;
    cout << "OpenGL version: " << string((char*)glGetString(GL_VENDOR)) << " / " << string((char*)glGetString(GL_VERSION)) << endl;
    cout << "GLSL version: " << string((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)) << endl;
    cout << "GLEW version: " << string((char*)glewGetString(GLEW_VERSION)) << endl << endl;

    GLint MaxTextureUnits;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &MaxTextureUnits);
    cout << "Max supported textures : " << MaxTextureUnits << endl << endl;
}


////////////////////////////////////////
// main file
int main()
{
    // start GL context and O/S window using the GLFW helper library
    if (!glfwInit()) // Initialise GLFW
    {
        fprintf(stderr, "ERROR: could not start GLFW3
");
        return 1;
    }

    GLFWwindow* window = glfwCreateWindow(width, height, "test1", NULL, NULL);
    if (!window)
    {
        fprintf(stderr, "ERROR: could not open window with GLFW3
");
        glfwTerminate();
        return 1;
    }
    glfwMakeContextCurrent(window); // Initialise GLEW

    // Set key callback function
    glfwSetErrorCallback(error_callback); 
    glfwSetKeyCallback(window, key_callback); 
    glfwSetCharCallback(window, char_callback); 

    // start GLEW extension handler
    glewExperimental = GL_TRUE; 
    glewInit(); // Initialise GLEW

    // get version info
    consoleMessage();

    GLuint srcTexIndex;

    if (!loadtexture("blablabla.png", srcTexIndex, GL_TEXTURE0, true)) 
    { 
        return -1;
    }

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    ////////////////////////////////////////
    // Load shaders
    GLuint shader_image_programme_HB = LoadShaders("shaders/SimpleVertexShader_VS_HB.glsl", "shaders/AddGrain_FS_HB.glsl");
    GLuint shader_image_programme_VB = LoadShaders("shaders/SimpleVertexShader_VS_VB.glsl", "shaders/AddGrain_FS_VB.glsl");

    ////////////////////////////////////////
    // shader parameter bindings

    uniform_srcTex = glGetUniformLocation(shader_image_programme_HB, "srcTex"); 
    uniform_offset_x = glGetUniformLocation(shader_image_programme_HB, "offset_x");
    uniform_offset_y = glGetUniformLocation(shader_image_programme_HB, "offset_y");

    const int nb_frame = 4096;

    vector<float> offset_x(nb_frame, 0.0);
    vector<float> offset_y(nb_frame, 0.0);

    int frame_index = 0;

    glUseProgram(shader_image_programme_HB); 

    glUniform1i(uniform_srcTex, 0); //Texture unit 0

    // input texture (loaded texture)
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, srcTexIndex);

    // setup the projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, (double)width, 0, (double)height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
    GLuint FramebufferName;
    glGenFramebuffers(1, &FramebufferName);
    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

    // The texture we're going to render to
    GLuint renderedTexture;
    glGenTextures(1, &renderedTexture);

    glActiveTexture(GL_TEXTURE1);
    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, renderedTexture);

    // Give an empty image to OpenGL ( the last "0" )
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_FLOAT, 0);

    // Poor filtering. Needed !
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // Set "renderedTexture" as our colour attachement #0
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

    // Set the list of draw buffers.
    GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
    glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers

    // Always check that our framebuffer is ok
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        printf("Error with Frame Buffer !!!
");
        return 1;
    }

    // Render to our framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    cout << "ok HB" << endl;


    GLuint srcTexIndex1;

    uniform_srcTex1 = glGetUniformLocation(shader_image_programme_VB, "srcTex"); 
    uniform_offset_x = glGetUniformLocation(shader_image_programme_VB, "offset_x");
    uniform_offset_y = glGetUniformLocation(shader_image_programme_VB, "offset_y");

    frame_index = 0;

    glUseProgram(shader_image_programme_VB); // On dit à OpenGL qu'on veut utiliser les shaders

    glUniform1i(uniform_srcTex1, 1); //Texture unit 1

    // input texture (loaded texture)
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_RECTANGLE, srcTexIndex1);

    // setup the projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, (double)width, 0, (double)height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
    GLuint FramebufferName1;
    glGenFramebuffers(1, &FramebufferName1);
    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName1);

    // The texture we're going to render to
    GLuint renderedTexture1;
    glGenTextures(1, &renderedTexture1);

    glActiveTexture(GL_TEXTURE2);
    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, renderedTexture1);

    // Give an empty image to OpenGL ( the last "0" )
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_FLOAT, 0);

    // Poor filtering. Needed !
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // Set "renderedTexture" as our colour attachement #1
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, renderedTexture1, 0);

    // Set the list of draw buffers.
    GLenum DrawBuffers1[1] = { GL_COLOR_ATTACHMENT1 };
    glDrawBuffers(1, DrawBuffers1); // "1" is the size of DrawBuffers

    // Always check that our framebuffer is ok
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        printf("Error with Frame Buffer !!!
");
        return 1;
    }

    // Render to our framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    cout << "ok VB" << endl;

    glViewport(0, 0, width, height); // Render on the whole framebuffer, complete from the lower left corner to the upper right

    // Returned data
    Mat myData = Mat::zeros(height, width, CV_32FC3);

    ////////////////////////////////////////
    // endless rendering loop
    int nbFrames = 0;

    clock_t start = clock();

    while (!glfwWindowShouldClose(window))
    {


        //////////////////////////////////////////////////
        // PASS #1
        // output buffer cleanup
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

        glUniform1f(uniform_offset_x, offset_x[frame_index]);
        glUniform1f(uniform_offset_y, offset_y[frame_index]);

        // Define the drawing area by setting the corresponding vertices
        glBegin(GL_QUADS);
        glVertex2f(0., 0.);
        glVertex2f(0., (float)height);
        glVertex2f((float)width, (float)height);
        glVertex2f((float)width, 0.);
        glEnd();


        if (nbFrames == 0)
        {
            glReadPixels(0, 0, width, height, GL_RGB, GL_FLOAT, myData.ptr(0));

            myData.convertTo(myData, CV_8U, 255.0, 0.0);

            imwrite("C:/Temp/images/testOpenGL.png", myData);
        }


        ///////////////////////////////////////
        // EVENTS + FB swap 
        // Permet de quitter la fenêtre avec la touche esc

        // update other events like input handling 
        glfwPollEvents();

        // put the stuff we've been drawing onto the display
        glfwSwapBuffers(window);

        nbFrames++;
        frame_index = (frame_index + 1) % 4096;
    }

    clock_t duration = clock() - start;

    //printf("%d processed frames
", nbFrames);

    cout << nbFrames << " in " << duration << " ms : " << 1000.0*(float)nbFrames / (float)duration << " frame/s" << endl;

    // close GL context and any other GLFW resources
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

The vertex shader for HB :

void main()
{
    gl_Position = ftransform();
}

The vertex shader for VB :

void main()
{
    gl_Position = ftransform();
}

The fragment shader for HB :

in vec2 texCoordOut;
layout (location = 0) out vec4 outColor0;
uniform sampler2DRect srcTex;

uniform float offset_x;
uniform float offset_y;

const vec2 texOffset = vec2(1.0, 1.0);
const int BLUR_AMOUNT = 100;

void main()
{
    vec2 CoordRef = gl_FragCoord.xy + vec2(offset_x,offset_y);
    vec3 luma = vec3(0);
    luma = texture2DRect( srcTex, CoordRef ).rgb;

    for (int i = 1; i < BLUR_AMOUNT+1; ++i) {
        luma.r += texture2DRect( srcTex, CoordRef + vec2(0, i) ).r * 0.5/BLUR_AMOUNT;
        luma.g += texture2DRect( srcTex, CoordRef + vec2(0, i) ).g * 0.5/BLUR_AMOUNT;
        luma.b += texture2DRect( srcTex, CoordRef + vec2(0, i) ).b * 0.5/BLUR_AMOUNT;
    }
    vec4 out_bw = vec4(luma, 1.0);
    gl_FragColor = out_bw;  
}

The fragment shader for VB :

in vec2 texCoordOut;
layout (location = 0) out vec4 outColor0;
uniform sampler2DRect srcTex; 

uniform float offset_x;
uniform float offset_y;

const vec2 texOffset = vec2(1.0, 1.0);
const int BLUR_AMOUNT = 100;

void main()
{
    vec2 CoordRef = gl_FragCoord.xy + vec2(offset_x,offset_y);
    vec3 luma = vec3(0);
    luma = texture2DRect( srcTex, CoordRef ).rgb;

    for (int i = 1; i < BLUR_AMOUNT+1; ++i) {
        luma.r += texture2DRect( srcTex, CoordRef + vec2(i, 0) ).r * 0.5/BLUR_AMOUNT;
        luma.g += texture2DRect( srcTex, CoordRef + vec2(i, 0) ).g * 0.5/BLUR_AMOUNT;
        luma.b += texture2DRect( srcTex, CoordRef + vec2(i, 0) ).b * 0.5/BLUR_AMOUNT;
    }

    vec4 out_bw = vec4(luma, 1.0);
    gl_FragColor = out_bw;
}

At the end, I have a full black screen, which is not the attended result (I checked). I had something that works, but with one shader at a time. I also had a combination of both that gave me only the result of the second shader, as if the first had never happened (when I write glUniform1i(uniform_srcTex1, 0); instead of glUniform1i(uniform_srcTex1, 1);, which is normal because I use my input image as the input of the second shader). The black screen comes with glUniform1i(uniform_srcTex1, 1); so I suppose I didn’t bind well TEXTURE1 to the FrameBuffer. I think the answer is pretty obvious but I searched and I can’t find what I did wrong in my code. Could someone help me with this ?

Thank you for your help !

You never render to a texture; the framebuffer is unbound before the rendering starts, so you end up rendering to the window (using shader_image_programme_VB; nothing is rendered with shader_image_programme_HB).

To apply a two-pass process to a texture, the procedure is basically:

[ul]
[li] Bind framebuffer.
[/li][li] Bind source texture to a texture unit.
[/li][li] Bind intermediate texture to framebuffer.
[/li][li] Use first-pass program, set sampler uniform to texture unit.
[/li][li] Render a quad.
[/li][li] Bind intermediate texture to a texture unit.
[/li][li] Bind destination texture to framebuffer.
[/li][li] Use second-pass program, set sampler uniform to texture unit.
[/li][li] Render a quad.
[/li][/ul]

If you want to display the final texture in the window, you then need:

[ul]
[li] Unbind framebuffer.
[/li][li] Bind destination texture to a texture unit.
[/li][li] Use display program, set sampler uniform to texture unit.
[/li][li] Render a quad.
[/li][/ul]

Thank you for the explanation, I did all these things in the wrong order… I’ll try this. Just a question before : why do we have to render a quad if we don’t display the image on the screen ? I thought it was only use for this purpose.

I have an other question as well : what do you mean by “Set sampler uniform to texture unit” ? Did you mean the function glUniform1i ?

You need to render in order to modify the pixels in the framebuffer, whether that’s the default framebuffer (i.e. the window) or a framebuffer object used to render to a texture or renderbuffer.

Yes.

With recent versions of GLSL that isn’t strictly necessary, as you can use a [var]binding=[/var] qualifier on a sampler uniform to set the initial value.

Ok, so here is my new code. I followed the steps, but maybe I forgot something because I have only the result of the second shader.

// Include standard headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <ctime>

#include <iostream>

//#include <opencv.hpp>
#include <opencv/cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv/highgui.h>
using namespace cv;

// Include GLEW
#include <GL/glew.h> 

// Include GLFW
#include <GLFW/glfw3.h> // Gère le clavier et la fenêtre

#include <shader.hpp>
using namespace std;

int width = 512;
int height = 512;

// perspective projection
bool perspective_ = false;

// shader variable pointers
GLint uniform_srcTex;
GLint uniform_srcTex1;
GLint uniform_offset_x;
GLint uniform_offset_y;

////////////////////////////////////////
// glfw callbacks for keystroke and error
void error_callback(int error, const char* description)
{
	fputs(description, stderr);
}

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);
}

void char_callback(GLFWwindow* window, unsigned int key)
{
	if (key == 'p' || key == 'P')
		perspective_ = true;
	if (key == 'o' || key == 'O')
		perspective_ = false;
}

/////////////////////////////////////////////
// texture loading
bool loadtexture(string fileName, GLuint & texIndex, GLuint texUnit, bool isRect)
{

	// texture load through OpenCV
	Mat image = cv::imread(fileName, CV_LOAD_IMAGE_UNCHANGED);   // Read the file
	if (!image.data) // Check for invalid input
	{
		cout << "Could not open or find the image
";
		return false;
	}
	cout << "Loaded " << fileName.c_str() << " (" << image.channels() << " channels)
";

	int colorTransform = (image.channels() == 4) ? CV_BGRA2RGBA : (image.channels() == 3) ? CV_BGR2RGB : CV_GRAY2RGB;
	//if (image[index].channels() >= 3)
	//{
		cv::cvtColor(image, image, colorTransform);
	//}

	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &texIndex);
	glActiveTexture(texUnit);
	GLenum target = GL_TEXTURE_2D;
	if (isRect)
	{
		target = GL_TEXTURE_RECTANGLE;
	}
	glBindTexture(target, texIndex);
	if (image.channels() > 3)
	{
		glTexImage2D(target,     // Type of texture
			0,                 // Pyramid level (for mip-mapping) - 0 is the top level
			GL_RGBA8,            // Internal colour format to convert to
			image.cols,          // Image width  i.e. 640 for Kinect in standard mode
			image.rows,          // Image height i.e. 480 for Kinect in standard mode
			0,                 // Border width in pixels (can either be 1 or 0)
			GL_RGBA, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.)
			(image.depth()<2)?GL_UNSIGNED_BYTE: ((image.depth()<4) ? GL_UNSIGNED_SHORT : GL_FLOAT),  // Image data type
			image.ptr());        // The actual image data itself
	}
	else
	{
		glTexImage2D(target,     // Type of texture
			0,                 // Pyramid level (for mip-mapping) - 0 is the top level
			GL_RGB,            // Internal colour format to convert to
			image.cols,          // Image width  i.e. 640 for Kinect in standard mode
			image.rows,          // Image height i.e. 480 for Kinect in standard mode
			0,                 // Border width in pixels (can either be 1 or 0)
			GL_RGB, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.)
			//GL_UNSIGNED_BYTE,  // Image data type
			(image.depth()<2) ? GL_UNSIGNED_BYTE : ((image.depth()<4) ? GL_UNSIGNED_SHORT : GL_FLOAT),  // Image data type
			image.ptr());        // The actual image data itself
	}
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	// glGenerateMipmap(GL_TEXTURE_2D);
	return true;
}

void consoleMessage()
{
	cout << "Renderer : " << string((char*)glGetString(GL_RENDERER)) << endl;
	cout << "OpenGL version: " << string((char*)glGetString(GL_VENDOR)) << " / " << string((char*)glGetString(GL_VERSION)) << endl;
	cout << "GLSL version: " << string((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)) << endl;
	cout << "GLEW version: " << string((char*)glewGetString(GLEW_VERSION)) << endl << endl;

	GLint MaxTextureUnits;
	glGetIntegerv(GL_MAX_TEXTURE_UNITS, &MaxTextureUnits);
	cout << "Max supported textures : " << MaxTextureUnits << endl << endl;
}


////////////////////////////////////////
// main file
int main()
{
	// start GL context and O/S window using the GLFW helper library
	if (!glfwInit()) // Initialise GLFW
	{
		fprintf(stderr, "ERROR: could not start GLFW3
");
		return 1;
	}

	GLFWwindow* window = glfwCreateWindow(width, height, "test1", NULL, NULL); // Ouvre une fenêtre et créé son contexte OpenGL
	if (!window)
	{
		fprintf(stderr, "ERROR: could not open window with GLFW3
");
		glfwTerminate();
		return 1;
	}
	glfwMakeContextCurrent(window); // Initialise GLEW

	// Set key callback function
	glfwSetErrorCallback(error_callback); 
	glfwSetKeyCallback(window, key_callback); 
	glfwSetCharCallback(window, char_callback); 

	// start GLEW extension handler
	glewExperimental = GL_TRUE; 
	glewInit(); 

	// get version info
	consoleMessage();

	GLuint srcTexIndex;

	if (!loadtexture("blablabla.png", srcTexIndex, GL_TEXTURE0, true)) 
	{
		return -1;
	}
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	////////////////////////////////////////
	// Load shaders
	GLuint shader_image_programme_HB = LoadShaders("shaders/SimpleVertexShader_VS_HB.glsl", "shaders/AddGrain_FS_HB.glsl");
	GLuint shader_image_programme_VB = LoadShaders("shaders/SimpleVertexShader_VS_VB.glsl", "shaders/AddGrain_FS_VB.glsl");

	////////////////////////////////////////
	// shader parameter bindings

	uniform_srcTex = glGetUniformLocation(shader_image_programme_HB, "srcTex"); // Permet de lier la variable uniforme "srcTex" du programme "shader_image_program" à la variable uniform_srcTex
	uniform_offset_x = glGetUniformLocation(shader_image_programme_HB, "offset_x");
	uniform_offset_y = glGetUniformLocation(shader_image_programme_HB, "offset_y");

	const int nb_frame = 4096;

	vector<float> offset_x(nb_frame, 0.0);
	vector<float> offset_y(nb_frame, 0.0);

	int frame_index = 0;


	////////////// BIND FRAMEBUFFER //////////////
	// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
	GLuint FramebufferName;
	glGenFramebuffers(1, &FramebufferName);
	glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);


	////////////// BIND SOURCE TEXTURE TO A TEXTURE UNIT //////////////
	glUniform1i(uniform_srcTex, 0); //Texture unit 0

	// input texture (loaded texture)
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_RECTANGLE, srcTexIndex);


	////////////// BIND INTERMEDIATE TEXTURE TO FRAMEBUFFER //////////////
	// The texture we're going to render to
	GLuint intermediateTexture;
	glGenTextures(1, &intermediateTexture);

	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, intermediateTexture, 0);

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

	////////////// USE HB PROGRAMM //////////////
	glUseProgram(shader_image_programme_HB); // On dit à OpenGL qu'on veut utiliser les shaders

	////////////// SET SAMPLER UNIFORM TO TEXTURE UNIT //////////////
	glUniform1i(intermediateTexture, 1); //Texture unit 1

	///////////// RENDER A QUAD /////////////
	glBegin(GL_QUADS);
	glVertex2f(0., 0.);
	glVertex2f(0., (float)height);
	glVertex2f((float)width, (float)height);
	glVertex2f((float)width, 0.);
	glEnd();

	///////////// BIND INTERMEDIATE TEXTURE TO A TEXTURE UNIT /////////////
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_RECTANGLE, intermediateTexture);
	
	///////////// BIND DESTINATION TEXTURE TO FRAMEBUFFER /////////////
	GLuint destinationTexture;
	glGenTextures(1, &destinationTexture);

	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, destinationTexture, 0);

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

	////////////// USE HB PROGRAMM //////////////
	glUseProgram(shader_image_programme_VB); // On dit à OpenGL qu'on veut utiliser les shaders

	////////////// SET SAMPLER UNIFORM TO TEXTURE UNIT //////////////
	glUniform1i(destinationTexture, 2); //Texture unit 2

	///////////// RENDER A QUAD /////////////
	glBegin(GL_QUADS);
	glVertex2f(0., 0.);
	glVertex2f(0., (float)height);
	glVertex2f((float)width, (float)height);
	glVertex2f((float)width, 0.);
	glEnd();

	// setup the projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, (double)width, 0, (double)height, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();


	///////////// UNBIND FRAMEBUFFER /////////////
	glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind frame buffer

	///////////// BIND DESTINATION TEXTURE TO A TEXTURE UNIT /////////////
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_RECTANGLE, destinationTexture);
	

	glViewport(0, 0, width, height); // Render on the whole framebuffer, complete from the lower left corner to the upper right

	// Returned data
	Mat myData = Mat::zeros(height, width, CV_32FC3);

	////////////////////////////////////////
	// endless rendering loop
	int nbFrames = 0;

	clock_t start = clock();

	while (!glfwWindowShouldClose(window))
	{


		//////////////////////////////////////////////////
		// PASS #1
		// output buffer cleanup
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // On nettoie l'écran

		glUniform1f(uniform_offset_x, offset_x[frame_index]);
		glUniform1f(uniform_offset_y, offset_y[frame_index]);

		// Define the drawing area by setting the corresponding vertices
		glBegin(GL_QUADS);
		glVertex2f(0., 0.);
		glVertex2f(0., (float)height);
		glVertex2f((float)width, (float)height);
		glVertex2f((float)width, 0.);
		glEnd();


		if (nbFrames == 0)
		{
			glReadPixels(0, 0, width, height, GL_RGB, GL_FLOAT, myData.ptr(0));

			myData.convertTo(myData, CV_8U, 255.0, 0.0);

			imwrite("C:/Temp/images/testOpenGL.png", myData);
		}


		///////////////////////////////////////
		// EVENTS + FB swap 

		// update other events like input handling 
		glfwPollEvents();

		// put the stuff we've been drawing onto the display
		glfwSwapBuffers(window);

		nbFrames++;
		frame_index = (frame_index + 1) % 4096;
	}

	clock_t duration = clock() - start;

	//printf("%d processed frames
", nbFrames);

	cout << nbFrames << " in " << duration << " ms : " << 1000.0*(float)nbFrames / (float)duration << " frame/s" << endl;

	// close GL context and any other GLFW resources
	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}



I don’t understand how we are supposed to bind our texture with the framebuffer. In the function glFramebufferTexture2DEXT, we don’t specify the name of the framebuffer. That means that if we have multiple framebuffers, we can’t chose which one we want to bind with our texture ?

You need to initialise the texture (glBindTexture, glTexImage2D with a null [var]data[/var] pointer) before binding it to the framebuffer.

Also, these are nonsense:

[var]destinationTexture[/var] is a texture name, not a uniform index.

More generally, textures which are bound to framebuffers shouldn’t be bound to texture units. Or if they are bound to texture units, those texture units shouldn’t be accessible from the current shader program via sampler uniforms, as this creates a feedback loop; this is true if a texture level bound to the framebuffer is accessible from the shader program, regardless of whether the texture is actually accessed.

[QUOTE=PrevostP;1291783]
I don’t understand how we are supposed to bind our texture with the framebuffer. In the function glFramebufferTexture2DEXT, we don’t specify the name of the framebuffer. That means that if we have multiple framebuffers, we can’t chose which one we want to bind with our texture ?[/QUOTE]
Those functions affect the framebuffer object which has been bound to the specified target (GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER or GL_READ_FRAMEBUFFER) by glBindFramebuffer(). The use of targets is a common idiom in OpenGL; OpenGL 4.5 incorporated the Direct State Access (DSA) extension which allows objects to be identified by name (handle) rather than by the target to which they’re bound. In 4.5 and later, you can use glNamedFramebufferTexture() to bind a texture to a specific framebuffer without the framebuffer having to be bound.

Also: don’t mix the framebuffer functions (and enumerants) from EXT_framebuffer_object (with an EXT suffix) with those from ARB_framebuffer_object and 3.0 (with no suffix). If you can’t assume 3.0 or ARB_framebuffer_object, you have to use the EXT suffix everywhere. If you can assume 3.0 or ARB_framebuffer_object, you don’t need the EXT suffix.

Thank you for your help !

don’t mix the framebuffer functions (and enumerants) from EXT_framebuffer_object (with an EXT suffix) with those from ARB_framebuffer_object and 3.0 (with no suffix). If you can’t assume 3.0 or ARB_framebuffer_object, you have to use the EXT suffix everywhere. If you can assume 3.0 or ARB_framebuffer_object, you don’t need the EXT suffix.

I change all the framebuffer functions to use the EXT suffix, thanks.

Can we take the code point by point ? I’m so confused right now ! I want to be sure of the use of each step to reuse this code in other applications.

  1. I load my image and put it in the texture named “srcTexIndex”, which is TEXTURE0 :
GLuint srcTexIndex;

if (!loadtexture("blablabla.png", srcTexIndex, GL_TEXTURE0, true)) 
{ 
	return -1;
}
  1. I load the shaders and bind the parameters :
GLuint shader_image_programme_HB = LoadShaders("/shaders/SimpleVertexShader_VS_HB.glsl", "/shaders/AddGrain_FS_HB.glsl");
GLuint shader_image_programme_VB = LoadShaders("/shaders/SimpleVertexShader_VS_VB.glsl", "/shaders/AddGrain_FS_VB.glsl");

uniform_srcTex = glGetUniformLocation(shader_image_programme_HB, "srcTex");
uniform_offset_x = glGetUniformLocation(shader_image_programme_HB, "offset_x");
uniform_offset_y = glGetUniformLocation(shader_image_programme_HB, "offset_y");

intermediateTexture = glGetUniformLocation(shader_image_programme_VB, "srcTex1"); 
uniform_offset_x1 = glGetUniformLocation(shader_image_programme_VB, "offset_x1");
uniform_offset_y1 = glGetUniformLocation(shader_image_programme_VB, "offset_y1");

const int nb_frame = 4096;

vector<float> offset_x(nb_frame, 0.0);
vector<float> offset_y(nb_frame, 0.0);

vector<float> offset_x1(nb_frame, 0.0);
vector<float> offset_y1(nb_frame, 0.0);

int frame_index = 0;
  1. I bind the Framebuffer :
GLuint FramebufferName;
glGenFramebuffersEXT(1, &FramebufferName);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FramebufferName);
  1. I bind the source texture to a texture unit, which is TEXTURE0 (already did that in 1, right ?) :
glUniform1i(uniform_srcTex, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, srcTexIndex);
  1. I bind intermediate texture to framebuffer :
GLuint intermediateTextureIndex;
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &intermediateTextureIndex);

glBindTexture(GL_TEXTURE_RECTANGLE, intermediateTextureIndex);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, 0, width, height, 0, GL_RGB, GL_FLOAT, NULL);

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, intermediateTexture, 0);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  1. I use first-pass programm, and set sampler uniform to texture unit, which is TEXTURE1 :
glUseProgram(shader_image_programme_HB);
glUniform1i(intermediateTexture, 1);
  1. Render a quad :
glBegin(GL_QUADS);
glVertex2f(0., 0.);
glVertex2f(0., (float)height);
glVertex2f((float)width, (float)height);
glVertex2f((float)width, 0.);
glEnd();
  1. I bind intermediate texture to a texture unit :
glBindTexture(GL_TEXTURE_RECTANGLE, intermediateTextureIndex);
  1. I bind destination texture to framebuffer (the same framebuffer ?) :
GLint destinationTexture;
GLuint destinationTextureIndex;
glGenTextures(1, &destinationTextureIndex);

glBindTexture(GL_TEXTURE_RECTANGLE, destinationTextureIndex);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, 0, width, height, 0, GL_RGB, GL_FLOAT, NULL);

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, destinationTexture, 0);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  1. I use second-pass programm, and set sampler uniform to texture unit, which is TEXTURE2 :
glUseProgram(shader_image_programme_VB);
glUniform1i(destinationTexture, 2);
  1. I render a quad :
glBegin(GL_QUADS);
glVertex2f(0., 0.);
glVertex2f(0., (float)height);
glVertex2f((float)width, (float)height);
glVertex2f((float)width, 0.);
glEnd();

And then I unbind the framebuffer and do the display.

The parts a little tricky for me are:

  • Activation of textures. I do not know when I have to activate them. For example, my input image is TEXTURE0, my output image of the first program is TEXTURE1 (in the framebuffer therefore). Should I activate TEXTURE1 when I bind intermediateTexture to the framebuffer, or before using the program, or just after, before setting the sampler uniform to texture unit?
  • initialize destinationTexture. srcTex is the source image, so we can use a getLocation. The same is true for intermediateTexture, which is the source texture of the VB program. However, destinationTexture is only there to contain the final rendering, and then be displayed on the screen.

Once again, thank you for your help and your patience !

That part is wrong. The intermediate texture should be bound to the framebuffer. It shouldn’t be bound to a texture unit, and it shouldn’t be accessible to the shader via a sampler uniform. Only the source texture should be accessible in the shader.

Same mistake.

In both cases, the shader should read from the input texture via a sampler uniform; binding the output texture to the framebuffer causes the shader’s output (gl_FragColor) to be written to the output texture.

I see what you mean, but I don’t know how to do it.

If I write

glUseProgram(shader_image_programme_HB);

the shader’s output must be written in the texture bound to the framebuffer right ? I did that in 5, so it must be written in “intermediateTexture”.

After that, I write

intermediateTexture = glGetUniformLocation(shader_image_programme_VB, "srcTex");
glUniform1i(intermediateTexture, 1);

But what about destinationTexture ? I can’t do a sampler uniform for this texture

That part is correct.

Part 6 was:


glUseProgram(shader_image_programme_HB);
glUniform1i(intermediateTexture, 1);

Which is affecting the HB shader, not the VB shader. The glUseProgram() is correct, but you don’t need another glUniform() call. You’ve already set srcTex to the source texture in step 4, and the intermediate texture is attached to the framebuffer; it isn’t accessed from the shader via a sampler uniform.

[QUOTE=PrevostP;1291806]
But what about destinationTexture ? I can’t do a sampler uniform for this texture[/QUOTE]

The second pass works the same as the first pass, but with the textures “shifted” one place: the intermediate texture is bound to a texture unit and passed to the shader via a sampler uniform, the destination texture is attached to the framebuffer.

The third pass is similar: the destination texture is bound to a texture unit and passed to the shader via a sampler uniform, the framebuffer is the default framebuffer (the window), not a framebuffer object. If you don’t need the blurred image in a texture, the third pass could be omitted, i.e. you can perform the vertical blur from the intermediate texture directly to the window.