I've been pulling hairs for quite some time now getting render to texture functionality getting to work using c & opengl. I'm using glfw3 and a subset of opengl es2 (so later i can compile this program using emscripten to webgl). I havent gotten to the emscripten part yet, because when i run this program "native" it just only shows the color of the main opengl buffer i clear (and not the texture i attached to the fbo).
I've gone through all tutorials and stackoverflow questions i could find on this subject (opengl es / webgl), some of the more comprehensive tutorials / questions i referred to where:
http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/
https://open.gl/framebuffers
http://in2gpu.com/2014/09/24/render-to-texture-in-opengl/
http://stackoverflow.com/questions/8439697/opengl-es-2-0-render-to-texture
http://stackoverflow.com/questions/9629763/opengl-render-to-texture-via-fbo-incorrect-display-vs-normal-texture/9630654#9630654
http://www.gamedev.net/topic/660287-fbo-render-to-texture-not-working/
I think i followed all the steps and suggestions they offer..
The relevant function for my fbo setup is:
// generate a FBO to draw in
glGenFramebuffers(1, &fbo);
// The actual texture to attach to the fbo which we're going to render to
glGenTextures(1, &texture);
// make our fbo active
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, texture);
// Create an empty 512x512 texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, 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);
// Set "texture" as our colour attachement #0
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 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) {
error_callback(-1, "Cannot initialize framebuffer");
}
// Render to the texture (should show up as a blue square)
glViewport(0, 0, 512, 512);
glClearColor(0, 0, 1, 0);
glClear(GL_COLOR_BUFFER_BIT);
// unbind textures and buffers
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// upload quad data
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
The relevant piece of code for drawing my fbo is:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, 1024, 768);
glClearColor(1.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, 0);
// Bind buffer with quad data for vertex shader
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GL_FLOAT), BUFFER_OFFSET(0));
glEnableVertexAttribArray(a_position);
glDrawArrays(GL_TRIANGLES, 0, 6);
glActiveTexture(0);
glBindTexture(GL_TEXTURE_2D, 0);
And here is the full code for a minimal self contained version that i'm using:
#include <GL/glew.h>
#include <GLFW/glfw3.h>
// include some standard libraries
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// include support libraries including their implementation
#define SHADER_IMPLEMENTATION
#include "shaders.h"
#define BUFFER_OFFSET(i) ((void*)(i))
char *VERTEX_SHADER_SRC =
"#version 100\n"
"attribute vec4 a_position;\n"
"varying vec2 v_uvcoord;\n"
"void main() {\n"
" gl_Position = a_position;\n"
" v_uvcoord = (a_position.xy + 0.5) * 2;\n"
"}\n";
char *FRAGMENT_SHADER_SRC =
"#version 100\n"
"precision mediump float;\n"
"varying vec2 v_uvcoord;\n"
"uniform sampler2D u_texture;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_texture, v_uvcoord);\n"
" //test: gl_FragColor = vec4(0,0,1,1);\n"
"}\n";
GLuint shader_program = NO_SHADER;
GLFWwindow* window;
// Shader attributes
GLuint a_position = -1;
GLuint u_texture = -1;
// FBO
GLuint fbo = 0;
// Target texture
GLuint texture;
// The fullscreen quad's VBO
GLuint vbo;
// The NDC quad vertices
static const GLfloat quad_data[] = {
-0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.0f, 0.0f,
};
// function for logging errors
void error_callback(int error, const char* description) {
// output to stderr
fprintf(stderr, "%i: %s\n", error, description);
};
void load_shaders() {
GLuint vertexShader = NO_SHADER, fragmentShader = NO_SHADER;
shaderSetErrorCallback(error_callback);
vertexShader = shaderCompile(GL_VERTEX_SHADER, VERTEX_SHADER_SRC);
fragmentShader = shaderCompile(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SRC);
shader_program = shaderLink(2, vertexShader, fragmentShader);
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader);
a_position = glGetAttribLocation(shader_program, "a_position");
u_texture = glGetUniformLocation(shader_program, "u_texture");
};
void load_objects() {
// generate a FBO to draw in
glGenFramebuffers(1, &fbo);
// The actual texture to attach to the fbo which we're going to render to
glGenTextures(1, &texture);
// make our fbo active
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, texture);
// Create an empty 512x512 texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, 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);
// Set "texture" as our colour attachement #0
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 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) {
error_callback(-1, "Cannot initialize framebuffer");
}
// Render to the texture (should show up as a blue square)
glViewport(0, 0, 512, 512);
glClearColor(0, 0, 1, 0);
glClear(GL_COLOR_BUFFER_BIT);
// unbind textures and buffers
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// upload quad data
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
};
void draw_objects() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, 1024, 768);
glClearColor(1.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, 0);
// Bind buffer with quad data for vertex shader
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GL_FLOAT), BUFFER_OFFSET(0));
glEnableVertexAttribArray(a_position);
glDrawArrays(GL_TRIANGLES, 0, 6);
glActiveTexture(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void do_render() {
glUseProgram(shader_program);
draw_objects();
glUseProgram(0);
// swap our buffers around so the user sees our new frame
glfwSwapBuffers(window);
glfwPollEvents();
}
void unload_objects() {
glActiveTexture(0);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &texture);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &vbo);
};
void unload_shaders() {
if (shader_program != NO_SHADER) {
glDeleteProgram(shader_program);
shader_program = NO_SHADER;
};
};
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(void) {
// tell GLFW how to inform us of issues
glfwSetErrorCallback(error_callback);
// see if we can initialize GLFW
if (!glfwInit()) {
exit(EXIT_FAILURE);
};
// create our window
window = glfwCreateWindow(1024, 768, "Hello GL", NULL, NULL);
if (window) {
GLenum err;
// make our context current
glfwMakeContextCurrent(window);
// init GLEW
glewExperimental=1;
err = glewInit();
if (err != GLEW_OK) {
error_callback(err, glewGetErrorString(err));
exit(EXIT_FAILURE);
};
// tell GLFW how to inform us of keyboard input
glfwSetKeyCallback(window, key_callback);
// load, compile and link our shader(s)
load_shaders();
// load our objects
load_objects();
//emscripten_set_main_loop(do_render, 0, 1);
while (!glfwWindowShouldClose(window)) {
do_render();
};
// close our window
glfwDestroyWindow(window);
};
// lets be nice and cleanup
unload_objects();
unload_shaders();
// the end....
glfwTerminate();
};
And for reference the shader lib used:
/********************************************************
* shaders.h - shader library by Bastiaan Olij 2015
*
* Public domain, use as you say fit, disect, change,
* or otherwise, all at your own risk
*
* This library is given as a single file implementation.
* Include this in any file that requires it but in one
* file, and one file only, proceed it with:
* #define SHADER_IMPLEMENTATION
*
* Note that OpenGL headers need to be included before
* this file is included as it uses several of its
* functions.
*
* This library does not contain any logic to load
* shaders from disk.
*
* Revision history:
* 0.1 09-03-2015 First version with basic functions
*
********************************************************/
#ifndef shadersh
#define shadersh
// standard libraries we need...
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
// and handy defines
#define NO_SHADER 0xFFFFFFFF
enum shaderErrors {
SHADER_ERR_UNKNOWN = -1,
SHADER_ERR_NOCOMPILE = -2,
SHADER_ERR_NOLINK = -3
};
#ifdef __cplusplus
extern "C" {
#endif
typedef void(* ShaderError)(int, const char*);
void shaderSetErrorCallback(ShaderError pCallback);
GLuint shaderCompile(GLenum pShaderType, const GLchar* pShaderText);
GLuint shaderLink(GLuint pNumShaders, ...);
#ifdef __cplusplus
};
#endif
#ifdef SHADER_IMPLEMENTATION
ShaderError shaderErrCallback = NULL;
// sets our error callback method which is modelled after
// GLFWs error handler so you can use the same one
void shaderSetErrorCallback(ShaderError pCallback) {
shaderErrCallback = pCallback;
};
// Compiles the text in pShaderText and returns a shader object
// pShaderType defines what type of shader we are compiling
// i.e. GL_VERTEX_SHADER
// On failure returns NO_SHADER
// On success returns a shader ID that can be used to link our program.
// Note that you must discard the shader ID with glDeleteShader
// You can do this after a program has been successfully compiled
GLuint shaderCompile(GLenum pShaderType, const GLchar * pShaderText) {
GLint compiled = 0;
GLuint shader;
const GLchar *stringptrs[1];
// create our shader
shader = glCreateShader(pShaderType);
// compile our shader
stringptrs[0] = pShaderText;
glShaderSource(shader, 1, stringptrs, NULL);
glCompileShader(shader);
// check our status
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint len = 0;
char type[50];
switch (pShaderType) {
case GL_VERTEX_SHADER: {
strcpy(type, "vertex");
} break;
case GL_TESS_CONTROL_SHADER: {
strcpy(type, "tessellation control");
} break;
case GL_TESS_EVALUATION_SHADER: {
strcpy(type, "tessellation evaluation");
} break;
case GL_GEOMETRY_SHADER: {
strcpy(type, "geometry");
} break;
case GL_FRAGMENT_SHADER: {
strcpy(type, "fragment");
} break;
default: {
strcpy(type, "unknown");
} break;
};
glGetShaderiv(shader, GL_INFO_LOG_LENGTH , &len);
if ((len > 1) && (shaderErrCallback != NULL)) {
GLchar* compiler_log;
// allocate enough space for our prefix and error
compiler_log = (GLchar*) malloc(len+50);
// write out our prefix first
sprintf(compiler_log, "Error compiling %s shader: ", type);
// append our error
glGetShaderInfoLog(shader, len, 0, &compiler_log[strlen(compiler_log)]);
// and inform our calling logic
shaderErrCallback(SHADER_ERR_NOCOMPILE, compiler_log);
free(compiler_log);
} else if (shaderErrCallback != NULL) {
char error[250];
sprintf(error,"Unknown error compiling %s shader", type);
shaderErrCallback(SHADER_ERR_UNKNOWN, error);
};
glDeleteShader(shader);
shader = NO_SHADER;
};
return shader;
};
// Links any number of programs into a shader program
// To compile and link a shader:
// ----
// GLuint vertexShader, fragmentShader, shaderProgram;
// vertexShader = shaderCompile(GL_VERTEX_SHADER, vsText);
// fragmentShader = shaderCompile(GL_FRAGMENT_SHADER, vsText);
// shaderProgram = shaderLink(2, vertexShader, fragmentShader);
// glDeleteShader(vertexShader);
// glDeleteShader(fragmentShader);
// ----
// Returns NO_SHADER on failure
// Returns program ID on success
// You must call glDeleteProgram to cleanup the program after you are done.
GLuint shaderLink(GLuint pNumShaders, ...) {
GLuint program;
va_list shaders;
int s;
// create our shader program...
program = glCreateProgram();
// now add our compiled code...
va_start(shaders, pNumShaders);
for (s = 0; s < pNumShaders && program != NO_SHADER; s++) {
GLuint shader = va_arg(shaders, GLuint);
if (shader == NO_SHADER) {
// assume we've set our error when the shader failed to compile...
glDeleteProgram(program);
program = NO_SHADER;
} else {
glAttachShader(program, shader);
};
};
va_end(shaders);
// and try and link our program
if (program != NO_SHADER) {
GLint linked = 0;
glLinkProgram(program);
// and check whether it all went OK..
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
GLint len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH , &len);
if ((len > 1) && (shaderErrCallback != NULL)) {
GLchar* compiler_log;
// allocate enough space for our prefix and error
compiler_log = (GLchar*) malloc(len+50);
// write out our prefix first
strcpy(compiler_log, "Error linking shader program: ");
// append our error
glGetProgramInfoLog(program, len, 0, &compiler_log[strlen(compiler_log)]);
// and inform our calling logic
shaderErrCallback(SHADER_ERR_NOLINK, compiler_log);
free(compiler_log);
} else if (shaderErrCallback != NULL) {
char error[250];
strcpy(error,"Unknown error linking shader program");
shaderErrCallback(SHADER_ERR_UNKNOWN, error);
};
glDeleteProgram(program);
program = NO_SHADER;
};
};
return program;
};
#endif
#endif
When i compile this using:
cc pkg-config --cflags glfw3
-o rtt rtt.c pkg-config --static --libs glfw3 glew
I just get an red screen (the main framebuffer i clear), but i expect a blue rectangle to appear in the middle of the screen (the texture which i cleared to blue before). And even if i uncomment the test line in the fragment shader no blue rectangle is shown!
Does anyone see what im missing here?
Thanks in advance!
Martijn
You never draw to the primary framebuffer. Omitting calls that are not part of the problem, you have this sequence in the draw_objects()
function:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
...
glClear(GL_COLOR_BUFFER_BIT);
...
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
...
glClear(GL_COLOR_BUFFER_BIT);
...
glDrawArrays(GL_TRIANGLES, 0, 6);
So at the time of the glDrawArrays()
call, your current framebuffer is fbo
, meaning that you overwrite the content of the FBO, instead of rendering to the default framebuffer. Well, what you actually have is a render feedback loop (using the same texture for sampling and as render target), with undefined behavior, but it's definitely not what you were after.
You should get much better results if you remove the second glBindFramebuffer()
call in the sequence above, so that the default framebuffer (0
) is bound during the draw call. You also have an extra glClear()
call.
In addition, the use of glActiveTexture()
is invalid:
glActiveTexture(GL_TEXTURE0+texture);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, GL_TEXTURE0+texture);
The argument to glActiveTexture()
is a texture unit, not a texture name (aka id). Also, the value passed to glUniform1i()
is just the index of the texture unit. So the proper calls are:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, 0);
If you need more background on texture units and names, I answered with a basic description here: Rendering multiple 2D images in OpenGL-ES 2.0, and wrote a fairly detailed explanation of these (and some more) concepts here: Multitexturing theory with texture objects and samplers.