tutorial tutor que programación español ejemplos documentacion curso acodigo opengl graphics visualization glut

opengl - tutor - que es opencv



¿Cómo usar GLUT/OpenGL para renderizar en un archivo? (5)

Tengo un programa que simula un sistema físico que cambia con el tiempo. Quiero, en intervalos predeterminados (digamos cada 10 segundos), mostrar una visualización del estado de la simulación en un archivo. Quiero hacerlo de tal manera que sea fácil "apagar la visualización" y no mostrar la visualización en absoluto.

Estoy buscando OpenGL y GLUT como herramientas gráficas para hacer la visualización. Sin embargo, el problema parece ser que, en primer lugar, parece que solo se envía a una ventana y no puede enviarse a un archivo. En segundo lugar, para generar la visualización, debe llamar a GLUTMainLoop y eso detiene la ejecución de la función principal: las únicas funciones que reciben llamadas a partir de ese momento son las llamadas de la GUI. Sin embargo, no quiero que sea una aplicación basada en la GUI. Quiero que sea solo una aplicación que ejecute desde la línea de comandos, y genera una serie de imágenes. ¿Hay alguna manera de hacer esto en GLUT / OpenGL? O es OpenGL la herramienta equivocada para esto por completo y debería usar algo más


No estoy seguro de que OpenGL sea la mejor solución.
Pero siempre puedes renderizar en un buffer fuera de la pantalla.

La forma típica de escribir la salida de OpenGL en un archivo es usar readPixels para copiar la escena pixel-pixel resultante en un archivo de imagen


Es casi seguro que no quieres GLUT, independientemente. Sus requisitos no se ajustan a lo que se pretende (incluso cuando sus requisitos se ajustan a su propósito previsto, generalmente no lo desea).

Puedes usar OpenGL. Para generar resultados en un archivo, básicamente configura OpenGL para renderizar una textura y luego lee la textura resultante en la memoria principal y la guarda en un archivo. Al menos en algunos sistemas (por ejemplo, Windows), estoy bastante seguro de que todavía tendrá que crear una ventana y asociar el contexto de representación con la ventana, aunque probablemente estará bien si la ventana está siempre oculta.


Puede usar SFML http://www.sfml-dev.org/ . Puede usar la clase de imagen para guardar su resultado renderizado.

http://www.sfml-dev.org/documentation/1.6/classsf_1_1Image.htm

Para obtener su resultado renderizado, puede renderizar una textura o copiar su pantalla.

Representación a una textura:

Copia de salida de pantalla:


No para quitar de las otras excelentes respuestas, pero si desea un ejemplo existente, hemos estado haciendo una representación GL sin pantalla durante algunos años en OpenSCAD, como parte de la representación de Test Framework en archivos .png desde la línea de comandos. Los archivos relevantes se encuentran en https://github.com/openscad/openscad/tree/master/src en Offscreen * .cc

Se ejecuta en OSX (CGL), Linux X11 (GLX), BSD (GLX) y Windows (WGL), con algunas peculiaridades debido a las diferencias de controladores. El truco básico es olvidarse de abrir una ventana (como, Douglas Adams dice que el truco para volar es olvidarse de tocar la tierra). Incluso se ejecuta en linux / bsd ''sin cabeza'' si tiene un servidor X11 virtual ejecutándose como Xvfb o Xvnc. También existe la posibilidad de utilizar Software Rendering en Linux / BSD estableciendo la variable de entorno LIBGL_ALWAYS_SOFTWARE = ​​1 antes de ejecutar su programa, lo que puede ayudar en algunas situaciones.

Este no es el único sistema para hacer esto, creo que el sistema de imágenes VTK hace algo similar.

Este código es un poco viejo en sus métodos (extraje el código GLX de los glxgears de Brian Paul), especialmente a medida que aparecen nuevos sistemas, OSMesa, Mir, Wayland, EGL, Android, Vulkan, etc., pero observe OffscreenXXX.cc nombres de archivos donde XXX es el subsistema de contexto GL, en teoría puede ser portado a diferentes generadores de contexto.


Ejemplo de PBO ejecutable

El siguiente ejemplo genera:

  • una ppm por cuadro a 200 FPS y sin dependencias adicionales,
  • una png por cuadro a 600 FPS con libpng
  • un mpg para todos los cuadros a 1200 FPS con FFmpeg

en un ramfs. Cuanto mejor sea la compresión, mayor será el FPS, por lo que debemos estar vinculados a IO de memoria.

FPS es más grande que 200 en mi pantalla de 60 FPS, y todas las imágenes son diferentes, así que estoy seguro de que no está limitado a los FPS de la pantalla.

glReadPixels es la función clave de OpenGL que lee los píxeles de la pantalla. También eche un vistazo a la configuración en init() .

glReadPixels lee glReadPixels la línea inferior de píxeles, a diferencia de la mayoría de los formatos de imagen, por lo que la conversión suele ser necesaria.

TODO: encuentre una manera de hacerlo en una máquina sin GUI (por ejemplo, X11). Parece que OpenGL simplemente no está hecho para representación fuera de pantalla, y que la lectura de píxeles de vuelta a la GPU se implementa en la interfaz con el sistema de ventanas (por ejemplo, GLX ). Ver: OpenGL sin X.org en Linux

TODO: use una ventana de 1x1, haga que no se pueda redimensionar y oculte para hacer las cosas más robustas. Si hago cualquiera de esos, la representación falla, vea los comentarios del código. Prevenir el cambio de tamaño parece imposible en Glut , pero GLFW lo admite . En cualquier caso, esos no importan mucho ya que mi FPS no está limitado por la frecuencia de actualización de la pantalla, incluso cuando está apagado.

/* Turn output methods on and off. */ #define PPM 1 #define LIBPNG 1 #define FFMPEG 1 #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define GL_GLEXT_PROTOTYPES 1 #include <GL/gl.h> #include <GL/glu.h> #include <GL/glut.h> #include <GL/glext.h> #if LIBPNG #include <png.h> #endif #if FFMPEG #include <libavcodec/avcodec.h> #include <libavutil/imgutils.h> #include <libavutil/opt.h> #include <libswscale/swscale.h> #endif enum Constants { SCREENSHOT_MAX_FILENAME = 256 }; static GLubyte *pixels = NULL; static GLuint fbo; static GLuint rbo_color; static GLuint rbo_depth; static const unsigned int HEIGHT = 100; static const unsigned int WIDTH = 100; static int offscreen = 1; static unsigned int max_nframes = 100; static unsigned int nframes = 0; static unsigned int time0; /* Model. */ static double angle; static double delta_angle; #if PPM /* Take screenshot with glReadPixels and save to a file in PPM format. - filename: file path to save to, without extension - width: screen width in pixels - height: screen height in pixels - pixels: intermediate buffer to avoid repeated mallocs across multiple calls. Contents of this buffer do not matter. May be NULL, in which case it is initialized. You must `free` it when you won''t be calling this function anymore. */ static void screenshot_ppm(const char *filename, unsigned int width, unsigned int height, GLubyte **pixels) { size_t i, j, k, cur; const size_t format_nchannels = 3; FILE *f = fopen(filename, "w"); fprintf(f, "P3/n%d %d/n%d/n", width, height, 255); *pixels = realloc(*pixels, format_nchannels * sizeof(GLubyte) * width * height); glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, *pixels); for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { cur = format_nchannels * ((height - i - 1) * width + j); fprintf(f, "%3d %3d %3d ", (*pixels)[cur], (*pixels)[cur + 1], (*pixels)[cur + 2]); } fprintf(f, "/n"); } fclose(f); } #endif #if LIBPNG /* Adapted from https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/png/open_manipulate_write.c */ static png_byte *png_bytes = NULL; static png_byte **png_rows = NULL; static void screenshot_png(const char *filename, unsigned int width, unsigned int height, GLubyte **pixels, png_byte **png_bytes, png_byte ***png_rows) { size_t i, nvals; const size_t format_nchannels = 4; FILE *f = fopen(filename, "wb"); nvals = format_nchannels * width * height; *pixels = realloc(*pixels, nvals * sizeof(GLubyte)); *png_bytes = realloc(*png_bytes, nvals * sizeof(png_byte)); *png_rows = realloc(*png_rows, height * sizeof(png_byte*)); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels); for (i = 0; i < nvals; i++) (*png_bytes)[i] = (*pixels)[i]; for (i = 0; i < height; i++) (*png_rows)[height - i - 1] = &(*png_bytes)[i * width * format_nchannels]; png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) abort(); png_infop info = png_create_info_struct(png); if (!info) abort(); if (setjmp(png_jmpbuf(png))) abort(); png_init_io(png, f); png_set_IHDR( png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); png_write_info(png, info); png_write_image(png, *png_rows); png_write_end(png, NULL); png_destroy_write_struct(&png, &info); fclose(f); } #endif #if FFMPEG /* Adapted from: https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/ffmpeg/encode.c */ static AVCodecContext *c = NULL; static AVFrame *frame; static AVPacket pkt; static FILE *file; static struct SwsContext *sws_context = NULL; static uint8_t *rgb = NULL; static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) { const int in_linesize[1] = { 4 * c->width }; sws_context = sws_getCachedContext(sws_context, c->width, c->height, AV_PIX_FMT_RGB32, c->width, c->height, AV_PIX_FMT_YUV420P, 0, NULL, NULL, NULL); sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0, c->height, frame->data, frame->linesize); } void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) { AVCodec *codec; int ret; avcodec_register_all(); codec = avcodec_find_encoder(codec_id); if (!codec) { fprintf(stderr, "Codec not found/n"); exit(1); } c = avcodec_alloc_context3(codec); if (!c) { fprintf(stderr, "Could not allocate video codec context/n"); exit(1); } c->bit_rate = 400000; c->width = width; c->height = height; c->time_base.num = 1; c->time_base.den = fps; c->gop_size = 10; c->max_b_frames = 1; c->pix_fmt = AV_PIX_FMT_YUV420P; if (codec_id == AV_CODEC_ID_H264) av_opt_set(c->priv_data, "preset", "slow", 0); if (avcodec_open2(c, codec, NULL) < 0) { fprintf(stderr, "Could not open codec/n"); exit(1); } file = fopen(filename, "wb"); if (!file) { fprintf(stderr, "Could not open %s/n", filename); exit(1); } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame/n"); exit(1); } frame->format = c->pix_fmt; frame->width = c->width; frame->height = c->height; ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); if (ret < 0) { fprintf(stderr, "Could not allocate raw picture buffer/n"); exit(1); } } void ffmpeg_encoder_finish(void) { uint8_t endcode[] = { 0, 0, 1, 0xb7 }; int got_output, ret; do { fflush(stdout); ret = avcodec_encode_video2(c, &pkt, NULL, &got_output); if (ret < 0) { fprintf(stderr, "Error encoding frame/n"); exit(1); } if (got_output) { fwrite(pkt.data, 1, pkt.size, file); av_packet_unref(&pkt); } } while (got_output); fwrite(endcode, 1, sizeof(endcode), file); fclose(file); avcodec_close(c); av_free(c); av_freep(&frame->data[0]); av_frame_free(&frame); } void ffmpeg_encoder_encode_frame(uint8_t *rgb) { int ret, got_output; ffmpeg_encoder_set_frame_yuv_from_rgb(rgb); av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; ret = avcodec_encode_video2(c, &pkt, frame, &got_output); if (ret < 0) { fprintf(stderr, "Error encoding frame/n"); exit(1); } if (got_output) { fwrite(pkt.data, 1, pkt.size, file); av_packet_unref(&pkt); } } void ffmpeg_encoder_glread_rgb(uint8_t **rgb, GLubyte **pixels, unsigned int width, unsigned int height) { size_t i, j, k, cur_gl, cur_rgb, nvals; const size_t format_nchannels = 4; nvals = format_nchannels * width * height; *pixels = realloc(*pixels, nvals * sizeof(GLubyte)); *rgb = realloc(*rgb, nvals * sizeof(uint8_t)); /* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */ glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels); for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { cur_gl = format_nchannels * (width * (height - i - 1) + j); cur_rgb = format_nchannels * (width * i + j); for (k = 0; k < format_nchannels; k++) (*rgb)[cur_rgb + k] = (*pixels)[cur_gl + k]; } } } #endif static int model_init(void) { angle = 0; delta_angle = 1; } static int model_update(void) { angle += delta_angle; return 0; } static int model_finished(void) { return nframes >= max_nframes; } static void init(void) { int glget; if (offscreen) { /* Framebuffer */ glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); /* Color renderbuffer. */ glGenRenderbuffers(1, &rbo_color); glBindRenderbuffer(GL_RENDERBUFFER, rbo_color); /* Storage must be one of: */ /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, WIDTH, HEIGHT); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo_color); /* Depth renderbuffer. */ glGenRenderbuffers(1, &rbo_depth); glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, WIDTH, HEIGHT); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth); glReadBuffer(GL_COLOR_ATTACHMENT0); /* Sanity check. */ assert(glCheckFramebufferStatus(GL_FRAMEBUFFER)); glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glget); assert(WIDTH * HEIGHT < (unsigned int)glget); } else { glReadBuffer(GL_BACK); } glClearColor(0.0, 0.0, 0.0, 0.0); glEnable(GL_DEPTH_TEST); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glViewport(0, 0, WIDTH, HEIGHT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); time0 = glutGet(GLUT_ELAPSED_TIME); model_init(); #if FFMPEG ffmpeg_encoder_start("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO, 25, WIDTH, HEIGHT); #endif } static void deinit(void) { printf("FPS = %f/n", 1000.0 * nframes / (double)(glutGet(GLUT_ELAPSED_TIME) - time0)); free(pixels); #if LIBPNG free(png_bytes); free(png_rows); #endif #if FFMPEG ffmpeg_encoder_finish(); free(rgb); #endif if (offscreen) { glDeleteFramebuffers(1, &fbo); glDeleteRenderbuffers(1, &rbo_color); glDeleteRenderbuffers(1, &rbo_depth); } } static void draw_scene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glRotatef(angle, 0.0f, 0.0f, -1.0f); glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f( 0.0f, 0.5f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-0.5f, -0.5f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.5f, -0.5f, 0.0f); glEnd(); } static void display(void) { char extension[SCREENSHOT_MAX_FILENAME]; char filename[SCREENSHOT_MAX_FILENAME]; draw_scene(); if (offscreen) { glFlush(); } else { glutSwapBuffers(); } #if PPM snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp%d.ppm", nframes); screenshot_ppm(filename, WIDTH, HEIGHT, &pixels); #endif #if LIBPNG snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp%d.png", nframes); screenshot_png(filename, WIDTH, HEIGHT, &pixels, &png_bytes, &png_rows); #endif # if FFMPEG frame->pts = nframes; ffmpeg_encoder_glread_rgb(&rgb, &pixels, WIDTH, HEIGHT); ffmpeg_encoder_encode_frame(rgb); #endif nframes++; if (model_finished()) exit(EXIT_SUCCESS); } static void idle(void) { while (model_update()); glutPostRedisplay(); } int main(int argc, char **argv) { GLint glut_display; glutInit(&argc, argv); if (argc > 1) offscreen = 0; if (offscreen) { /* TODO: if we use anything smaller than the window, it only renders a smaller version of things. */ /*glutInitWindowSize(50, 50);*/ glutInitWindowSize(WIDTH, HEIGHT); glut_display = GLUT_SINGLE; } else { glutInitWindowSize(WIDTH, HEIGHT); glutInitWindowPosition(100, 100); glut_display = GLUT_DOUBLE; } glutInitDisplayMode(glut_display | GLUT_RGBA | GLUT_DEPTH); glutCreateWindow(argv[0]); if (offscreen) { /* TODO: if we hide the window the program blocks. */ /*glutHideWindow();*/ } init(); glutDisplayFunc(display); glutIdleFunc(idle); atexit(deinit); glutMainLoop(); return EXIT_SUCCESS; }

En GitHub .

Compilar con:

gcc main.c -lGL -lGLU -lglut #-lpng -lavcodec -lswscale -lavutil

Ejecutar "fuera de pantalla" (principalmente TODO, funciona pero no tiene ninguna ventaja):

./a.out

Ejecutar en la pantalla (no limita mi FPS tampoco):

./a.out 1

Probado en Ubuntu 15.10, OpenGL 4.4.0 NVIDIA 352.63, Lenovo Thinkpad T430.

Otras opciones además de PBO

  • renderizar a backbuffer (lugar de renderizado predeterminado)
  • renderizar a una textura
  • renderizar a un objeto Pixelbuffer (PBO)

Framebuffer y Pixelbuffer son mejores que el backbuffer y la textura, ya que están hechos para que los datos se lean de vuelta a la CPU, mientras que el backbuffer y las texturas están hechos para permanecer en la GPU y mostrarse en la pantalla.

PBO es para transferencias asíncronas, por lo que creo que no lo necesitamos, consulte: ¿Cuáles son las diferencias entre un Objeto Buffer de Cuadro y un Objeto Buffer de Pixel en OpenGL? ,

Quizás vale la pena investigar en Mesa fuera de la pantalla: http://www.mesa3d.org/osmesa.html

apiretrace

https://github.com/apitrace/apitrace

Solo funciona, y no requiere que modifique su código en absoluto:

git clone https://github.com/apitrace/apitrace cd apitrace git checkout 7.0 mkdir build cd build cmake .. make # Creates opengl_executable.out.trace ./apitrace /path/to/opengl_executable.out ./apitrace dump-images opengl_executable.out.trace

Ahora tiene un montón de capturas de pantalla llamadas así:

animation.out.<n>.png

TODO: principio de funcionamiento.

Los documentos también sugieren esto para video:

apitrace dump-images -o - application.trace / | ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4

Vulkan

Parece que Vulkan está diseñado para admitir el procesamiento fuera de pantalla mejor que OpenGL.

Esto se menciona en esta visión general de NVIDIA: https://developer.nvidia.com/transitioning-opengl-vulkan

Hay un ejemplo ejecutable en: https://github.com/SaschaWillems/Vulkan/blob/0616eeff4e697e4cd23cb9c97f5dd83afb79d908/offscreen/offscreen.cpp pero aún no he logrado ejecutar Vulkan. 1 kloc :-)

Relacionado: ¿Es posible hacer una representación fuera de pantalla sin Surface en Vulkan?

Bibliografía

FBO más grande que la ventana:

Sin ventana / X11: