渲染到纹理

Render to texture

我已经纠结了很长一段时间,现在开始使用 c 和 opengl 渲染到纹理功能。我正在使用 glfw3 和 opengl es2 的一个子集(所以稍后我可以使用 emscripten 将这个程序编译为 webgl)。我还没有进入 emscripten 部分,因为当我 运行 这个程序 "native" 它只显示我清除的主要 opengl 缓冲区的颜色(而不是我附加到 fbo 的纹理)。

我已经浏览了我能找到的关于这个主题 (opengl es / webgl) 的所有教程和 Whosebug 问题,一些更全面的教程/问题我提到了哪里:

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://www.gamedev.net/topic/660287-fbo-render-to-texture-not-working/

我想我遵循了他们提供的所有步骤和建议..

我的 fbo 设置的相关函数是:

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

绘制我的fbo的相关代码是:

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

这是我正在使用的最小独立版本的完整代码:

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

并参考使用的着色器库:

/********************************************************
 * 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

当我编译它时使用: cc pkg-config --cflags glfw3 -o rtt rtt.c pkg-config --static --libs glfw3 glew

我刚看到一个红色屏幕(我清除了主帧缓冲区),但我希望屏幕中间出现一个蓝色矩形(我之前清除为蓝色的纹理)。即使我取消注释片段着色器中的测试线,也不会显示任何蓝色矩形!

有人看到我在这里遗漏了什么吗?

提前致谢!

马丁

您永远不会绘制到主帧缓冲区。省略不属于问题的调用,你在 draw_objects() 函数中有这个序列:

glBindFramebuffer(GL_FRAMEBUFFER, 0);
...
glClear(GL_COLOR_BUFFER_BIT);
...
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
...
glClear(GL_COLOR_BUFFER_BIT);
...
glDrawArrays(GL_TRIANGLES, 0, 6);

因此在 glDrawArrays() 调用时,您当前的帧缓冲区是 fbo,这意味着您覆盖了 FBO 的内容,而不是渲染到默认帧缓冲区。好吧,你实际上拥有的是一个渲染反馈循环(使用相同的纹理进行采样和渲染目标),具有未定义的行为,但这绝对不是你想要的。

如果删除上面序列中的第二个 glBindFramebuffer() 调用,您应该会得到更好的结果,以便在绘制调用期间绑定默认的帧缓冲区 (0)。您还有一个额外的 glClear() 电话。

另外,glActiveTexture()的使用无效:

glActiveTexture(GL_TEXTURE0+texture);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, GL_TEXTURE0+texture);

glActiveTexture() 的参数是 纹理单元 ,而不是纹理名称(又名 ID)。此外,传递给 glUniform1i() 的值只是纹理单元的索引。所以正确的调用是:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(u_texture, 0);

如果您需要更多关于纹理单元和名称的背景知识,我在这里回答了基本描述:, and wrote a fairly detailed explanation of these (and some more) concepts here: