Рендеринг в текстуру

Я довольно долго дергал за волосы, чтобы заставить рендерить функциональность текстуры работать с использованием c и opengl. Я использую glfw3 и подмножество opengl es2 (поэтому позже я могу скомпилировать эту программу, используя emscripten для webgl). Я еще не добрался до части emscripten, потому что, когда я запускаю эту программу «нативно», она показывает только цвет основного буфера opengl, который я очищаю (а не текстуру, которую я прикрепил к fbo).

Я просмотрел все учебники и вопросы о стеке, которые я мог найти по этой теме (opengl es/webgl), некоторые из более полных руководств/вопросов, на которые я ссылался, где:

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/

Я думаю, что выполнил все шаги и предложения, которые они предлагают.

Соответствующая функция для моей настройки 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

Я просто получаю красный экран (основной буфер кадра, который я очищаю), но я ожидаю, что в середине экрана появится синий прямоугольник (текстура, которую я очищал до синего раньше). И даже если я раскомментирую тестовую строку во фрагментном шейдере, синий прямоугольник не отображается!

Кто-нибудь видит, чего мне здесь не хватает?

Заранее спасибо!

Мартин


person Martijnh    schedule 18.01.2016    source источник
comment
Это довольно длинный фрагмент кода, на который стоит обратить внимание.   -  person Dietrich Epp    schedule 19.01.2016
comment
Извините, я хотел включить (минимальный) рабочий пример, я сначала выделю соответствующие функции (load_objects и draw_objects), прежде чем включать весь файл.   -  person Martijnh    schedule 19.01.2016


Ответы (1)


Вы никогда не рисуете в основной буфер кадра. Опустив вызовы, которые не являются частью проблемы, у вас есть эта последовательность в функции 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() является текстурная единица, а не имя текстуры (он же идентификатор). Кроме того, значение, переданное в glUniform1i(), является просто индексом текстурного блока. Итак, правильные вызовы:

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

Если вам нужно больше информации о текстурных блоках и именах, я ответил здесь с основным описанием: 2-0">Визуализация нескольких 2D-изображений в OpenGL-ES 2.0, и написал довольно подробное объяснение этих (и некоторых других) концепций здесь: Теория мультитекстурирования с текстурными объектами и сэмплерами.

person Reto Koradi    schedule 19.01.2016
comment
Спасибо, что нашли время, чтобы проверить мой код и предоставить эти ссылки, они полезны! Мое понимание fbo было действительно ошибочным. Теперь я понимаю, что вы не рисуете fbo, а используете его только как окно для рендеринга в текстуру (или другое вложение). Я обновил draw_objects, чтобы отразить это, но я все еще не вижу синий квадрат! :-/ - person Martijnh; 19.01.2016
comment
Хорошо, нашел .. я использовал fprintf для отладки вывода, который не сбрасывался напрямую! оказалось, у меня тоже была ошибка в шейдере! - person Martijnh; 21.01.2016