¿Cómo renderizar fuera de pantalla en OpenGL?

Mi objective es renderizar la escena OpenGL sin una ventana, directamente en un archivo. La escena puede ser más grande que la resolución de mi pantalla.

¿Cómo puedo hacer esto?

Quiero ser capaz de elegir el tamaño del área de renderizado a cualquier tamaño, por ejemplo 10000×10000, si es posible?

Todo comienza con glReadPixels , que usará para transferir los píxeles almacenados en un búfer específico en la GPU a la memoria principal (RAM). Como notará en la documentación, no hay argumento para elegir qué buffer. Como es habitual con OpenGL, el búfer actual para leer es un estado, que puede establecer con glReadBuffer .

Entonces, un método de representación fuera de pantalla muy básico sería algo como lo siguiente. Utilizo pseudocódigo c ++, por lo que es probable que contenga errores, pero debe hacer que el flujo general sea claro:

 //Before swapping std::vector data(width*height*4); glReadBuffer(GL_BACK); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]); 

Esto leerá el búfer de respaldo actual (generalmente el búfer al que está dibujando). Deberías llamar esto antes de intercambiar los buffers. Tenga en cuenta que también puede leer perfectamente el búfer posterior con el método anterior, borrarlo y dibujar algo totalmente diferente antes de intercambiarlo. Técnicamente, también puedes leer el buffer frontal, pero esto a menudo se desaconseja ya que teóricamente las implementaciones permitieron realizar algunas optimizaciones que podrían hacer que tu buffer frontal contenga basura.

Hay algunos inconvenientes con esto. En primer lugar, realmente no hacemos renderización fuera de pantalla, ¿verdad? Prestamos a los búferes de la pantalla y leemos de ellos. Podemos emular la representación fuera de la pantalla al no intercambiar nunca en el búfer posterior, pero no se siente bien. Junto a eso, los búferes frontales y posteriores están optimizados para mostrar los píxeles, no para leerlos. Ahí es donde los objetos Framebuffer entran en juego.

Esencialmente, un FBO le permite crear un framebuffer no predeterminado (como los búferes FRONTAL y ATRÁS) que le permiten dibujar en un búfer de memoria en lugar de los búferes de pantalla. En la práctica, puede dibujar una textura o un buffer de representación . El primero es óptimo cuando desea volver a utilizar los píxeles en OpenGL como una textura (por ejemplo, una “cámara de seguridad” ingenua en un juego), este último si solo desea renderizar / leer de nuevo. Con esto, el código anterior se convertiría en algo así como pseudocódigo, así que no me maten si me escriben mal o si olvidan algunas afirmaciones.

 //Somewhere at initialization GLuint fbo, render_buf; glGenFramebuffers(1,&fbo); glGenRenderbuffers(1,&render_buf); glBindRenderbuffer(render_buf); glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8, width, height); glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf); //At deinit: glDeleteFramebuffers(1,&fbo); glDeleteRenderbuffers(1,&render_buf); //Before drawing glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo); //after drawing std::vector data(width*height*4); glReadBuffer(GL_COLOR_ATTACHMENT0); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]); // Return to onscreen rendering: glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,0); 

Este es un ejemplo simple, en realidad es probable que también desee almacenar el búfer de profundidad (y stencil). También es posible que desee renderizar en textura, pero lo dejo como ejercicio. En cualquier caso, ahora realizará una representación fuera de pantalla real y podría funcionar más rápido que leer el búfer posterior.

Finalmente, puede usar objetos de memoria intermedia de píxeles para hacer que los píxeles de lectura sean asíncronos. El problema es que glReadPixels bloquea hasta que los datos de píxeles se transfieren por completo, lo que puede paralizar su CPU. Con los PBO, la implementación puede regresar de inmediato, ya que controla el búfer de todos modos. Solo cuando mapea el búfer que bloqueará la tubería. Sin embargo, las PBO pueden optimizarse para almacenar los datos únicamente en la memoria RAM, por lo que este locking podría tomar mucho menos tiempo. El código de píxeles de lectura se convertiría en algo como esto:

 //Init: GLuint pbo; glGenBuffers(1,&pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBufferData(GL_PIXEL_PACK_BUFFER, width*height*4, NULL, GL_DYNAMIC_READ); //Deinit: glDeleteBuffers(1,&pbo); //Reading: glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,0); // 0 instead of a pointer, it is now an offset in the buffer. //DO SOME OTHER STUFF (otherwise this is a waste of your time) glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); //Might not be necessary... pixel_data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); 

La parte en mayúsculas es esencial. Si acaba de emitir glReadPixels a un PBO, seguido de un glMapBuffer de ese PBO, no obtuvo más que un montón de código. Seguro que glReadPixels podría regresar inmediatamente, pero ahora el glMapBuffer se glMapBuffer porque tiene que mapear de manera segura los datos del buffer de lectura al PBO y a un bloque de memoria en la RAM principal.

Tenga en cuenta también que uso GL_BGRA en todas partes, esto se debe a que muchas tarjetas gráficas lo usan internamente como el formato de representación óptimo (o la versión GL_BGR sin alfa). Debería ser el formato más rápido para transferencias de píxeles como esta. Trataré de encontrar el artículo de nvidia que leí sobre esto algunos monts atrás.

Al usar OpenGL ES 2.0, GL_DRAW_FRAMEBUFFER puede no estar disponible, solo debe usar GL_FRAMEBUFFER en ese caso.

Asumiré que la creación de una ventana ficticia (no se procesa, solo está allí porque la API requiere que se haga una) en la que se crea el contexto principal es una estrategia de implementación aceptable.

Estas son sus opciones:

Buffers de pixel

Un búfer de píxeles, o pbuffer (que no es un objeto de búfer de píxeles ), es ante todo un contexto OpenGL . Básicamente, usted crea una ventana de forma normal, luego elige un formato de píxel de wglChoosePixelFormatARB (los formatos de pbuffer deben obtenerse de aquí). Luego, llama a wglCreatePbufferARB , dándole el HDC de la ventana y el formato de buffer de píxeles que quieres usar. Oh, y un ancho / alto; puede consultar el ancho / alto máximo de la implementación.

El framebuffer predeterminado para pbuffer no está visible en la pantalla, y el ancho / alto máximo es lo que el hardware quiere que le permita usar. Entonces puede renderizarlo y usar glReadPixels para leer desde allí.

Tendrá que compartir su contexto con el contexto dado si ha creado objetos en el contexto de la ventana. De lo contrario, puede usar el contexto de pbuffer completamente por separado. Simplemente no destruyas el contexto de la ventana.

La ventaja aquí es un mayor soporte de implementación (aunque la mayoría de los controladores que no admiten las alternativas también son controladores antiguos para hardware que ya no es compatible) o hardware Intel).

Los inconvenientes son estos. Los Pbuffers no funcionan con contextos OpenGL básicos . Pueden funcionar para compatibilidad, pero no hay forma de proporcionar información sobre wglCreatePbufferARB sobre las versiones y perfiles de OpenGL.

Objetos Framebuffer

Los objetos Framebuffer son más “apropiados” que los pbuffers. Los FBO se encuentran dentro de un contexto, mientras que los pbuffers tratan de crear nuevos contextos.

Los FBO son solo un contenedor de imágenes a las que le rindes. Se pueden consultar las dimensiones máximas que permite la implementación; puede suponer que es GL_MAX_VIEWPORT_DIMS (asegúrese de que un FBO esté vinculado antes de comprobar esto, ya que cambia en función de si un FBO está vinculado).

Como no está muestreando las texturas de estos (solo está leyendo valores atrás), debe usar renderbuffers en lugar de texturas. Su tamaño máximo puede ser mayor que el de las texturas.

Lo bueno es la facilidad de uso. En lugar de tener que lidiar con formatos de píxeles y glRenderbufferStorage así, simplemente elija un formato de imagen apropiado para su llamada glRenderbufferStorage .

El único inconveniente real es la banda más estrecha de hardware que los admite. En general, cualquier cosa que AMD o NVIDIA haga que sigan siendo compatibles (en este momento, GeForce 6xxx o mejor [tenga en cuenta el número de x], y cualquier tarjeta Radeon HD) tendrá acceso a ARB_framebuffer_object u OpenGL 3.0+ (donde es una función principal ) Los controladores más antiguos solo pueden tener soporte EXT_framebuffer_object (que tiene algunas diferencias). El hardware de Intel es potluck; incluso si reclaman compatibilidad 3.x o 4.x, aún puede fallar debido a errores del controlador.

Si necesita renderizar algo que exceda el tamaño máximo de FBO de su implementación de GL, libtr funciona bastante bien:

La biblioteca TR (Tile Rendering) es una biblioteca de utilidades OpenGL para hacer renderizado en mosaico. El procesamiento en mosaico es una técnica para generar imágenes grandes en piezas (mosaicos).

TR es eficiente con la memoria; se pueden generar archivos de imagen arbitrariamente grandes sin asignar un búfer de imagen de tamaño completo en la memoria principal.

La forma más fácil es usar algo llamado Frame Buffer Objects (FBO). Sin embargo, todavía tendrá que crear una ventana para crear un contexto OpenGL (pero esta ventana puede estar oculta).

La forma más fácil de cumplir su objective es usar FBO para hacer render fuera de la pantalla. Y no necesita renderizar para texturar, luego obtener el máximo. Simplemente preséntelo en el búfer y use la función glReadPixels . Este enlace será útil. Ver ejemplos de objetos Framebuffer