Saltar al contenido

¿Cómo renderizar fuera de la pantalla en OpenGL?

Solución:

Todo comienza con glReadPixels, que utilizará 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 ningún argumento para elegir qué búfer. Como es habitual con OpenGL, el búfer actual para leer es un estado, que puede configurar con glReadBuffer.

Entonces, un método de renderizado fuera de la pantalla muy básico sería algo como el siguiente. Utilizo pseudocódigo c ++, por lo que probablemente contenga errores, pero debería aclarar el flujo general:

//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 retroceso actual (generalmente el búfer al que está dibujando). Debería llamar a esto antes de intercambiar los búferes. 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 cambiarlo. Técnicamente, también puede leer el búfer frontal, pero esto a menudo se desaconseja ya que teóricamente se permitieron implementaciones para realizar algunas optimizaciones que podrían hacer que su búfer frontal contenga basura.

Esto tiene algunos inconvenientes. En primer lugar, realmente no hacemos renderizado fuera de la pantalla, ¿verdad? Procesamos en los búferes de pantalla y leemos de ellos. Podemos emular el renderizado fuera de la pantalla sin cambiar nunca en el búfer trasero, pero no se siente bien. Además, los búferes frontal y posterior están optimizados para mostrar píxeles, no para leerlos. Ahí es donde entran en juego los objetos Framebuffer.

Básicamente, un FBO le permite crear un búfer de fotogramas no predeterminado (como los búferes FRONT y BACK) que le permiten dibujar en un búfer de memoria en lugar de los búferes de pantalla. En la práctica, puede dibujar en una textura o en un búfer de procesamiento. El primero es óptimo cuando quieres reutilizar los píxeles en OpenGL como textura (por ejemplo, una ingenua “cámara de seguridad” en un juego), el segundo si solo quieres renderizar / leer. Con esto, el código anterior se convertiría en algo como esto, nuevamente pseudocódigo, así que no me mates si lo escribí mal o si olvidé algunas declaraciones.

//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 almacenamiento para el búfer de profundidad (y plantilla). También es posible que desee renderizar a textura, pero lo dejo como ejercicio. En cualquier caso, ahora realizará un renderizado real fuera de la pantalla y podría funcionar más rápido que leer el búfer trasero.

Por último, puede utilizar objetos de búfer de píxeles para hacer que los píxeles de lectura sean asincrónicos. El problema es ese glReadPixels bloques hasta que los datos de píxeles se transfieran por completo, lo que puede detener su CPU. Con PBO, la implementación puede regresar inmediatamente, ya que controla el búfer de todos modos. Solo cuando mapees el búfer se bloqueará la canalización. Sin embargo, los PBO pueden optimizarse para almacenar los datos únicamente en la RAM, por lo que este bloque podría llevar 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 fundamental. Si solo emite un glReadPixels a un PBO, seguido de un glMapBuffer de ese PBO, no obtuviste nada más que mucho código. Seguro el glReadPixels podría regresar de inmediato, pero ahora el glMapBuffer se detendrá porque tiene que asignar de forma segura los datos del búfer de lectura al PBO y a un bloque de memoria en la RAM principal.

Tenga en cuenta también que utilizo GL_BGRA en todas partes, esto se debe a que muchas tarjetas gráficas lo usan internamente como el formato de renderizado ó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. Intentaré encontrar el artículo de nvidia que leí sobre esto hace unos meses.

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

Asumiré que la creación de una ventana ficticia (no se representa en ella; solo está ahí porque la API requiere que cree una) en la que crea su contexto principal es una estrategia de implementación aceptable.

Aquí tienes tus opciones:

Búferes de píxeles

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, crea una ventana como de costumbre, luego elige un formato de píxel de wglChoosePixelFormatARB (los formatos pbuffer deben obtenerse desde aquí). Entonces, llamas wglCreatePbufferARB, dándole el HDC de su ventana y el formato de búfer de píxeles que desea utilizar. Ah, 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 quiera permitirle usar. Entonces puedes renderizarlo y usar glReadPixels para volver a leerlo.

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

La ventaja aquí es una mayor compatibilidad con la 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 es hardware Intel).

Las desventajas son estas. Los pbuffers no funcionan con contextos centrales de OpenGL. Pueden funcionar por compatibilidad, pero no hay forma de dar wglCreatePbufferARB información sobre versiones y perfiles de OpenGL.

Objetos Framebuffer

Los objetos Framebuffer son destinos de renderizado fuera de pantalla más “adecuados” que los pbuffers. Los FBO están dentro de un contexto, mientras que los pbuffers tratan de crear nuevos contextos.

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

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

La ventaja es la facilidad de uso. En lugar de tener que lidiar con formatos de píxeles y demás, simplemente elija un formato de imagen apropiado para su glRenderbufferStorage llama.

El único inconveniente real es la banda más estrecha de hardware que los admite. En general, cualquier cosa que AMD o NVIDIA produzca y que aún admitan (en este momento, GeForce 6xxx o mejor [note the number of x’s], y cualquier tarjeta Radeon HD) tendrá acceso a ARB_framebuffer_object u OpenGL 3.0+ (donde es una característica principal). Es posible que los controladores más antiguos solo tengan compatibilidad con EXT_framebuffer_object (que tiene algunas diferencias). El hardware de Intel es una comida compartida; incluso si afirman que es compatible con 3.xo 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 realizar renderizado en mosaico. El renderizado en mosaico es una técnica para generar imágenes grandes en piezas (mosaicos).

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

Sección de Reseñas y Valoraciones

Puedes añadir valor a nuestra información añadiendo tu veteranía en las reseñas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *