Comment utiliser GLUT/OpenGL pour rendre vers un fichier?
j'ai un programme qui simule un système physique qui change avec le temps. Je veux, à intervalles prédéterminés (dire toutes les 10 secondes) sortie d'une visualisation de l'état de la simulation dans un fichier. Je veux le faire de telle manière qu'il est facile de "tourner la visualisation off" et pas la sortie de la visualisation.
je regarde OpenGL et GLUT comme des outils graphiques pour faire la visualisation. Cependant, le problème semble être que, tout d'abord, il semble comme il ne sort que vers une fenêtre et ne peut pas sortir vers un fichier. Deuxièmement, pour générer la visualisation vous devez appeler GLUTMainLoop et cela arrête l'exécution de la fonction principale - les seules fonctions qui sont appelées à partir de là sont les appels de L'interface graphique. Cependant je ne veux pas que cela soit une application graphique - je veux juste être une application que vous exécutez à partir de la ligne de commande, et il génère une série d'images. Y a-t-il un moyen de le faire dans GLUT/OpenGL? Ou est OpenGL le mauvais outil pour cela complètement et je devrais utiliser quelque chose d'autre
5 réponses
vous ne voulez presque certainement pas de surabondance, peu importe. Vos exigences ne correspondent pas à ce qu'il est prévu de faire (et même si vos exigences do correspondent à son but, vous ne le voulez généralement pas de toute façon).
vous pouvez utiliser OpenGL. Pour générer une sortie dans un fichier, vous configurez essentiellement OpenGL pour rendre à une texture, puis lisez la texture résultante dans la mémoire principale et enregistrez-la dans un fichier. Au moins sur certains systèmes (par exemple, Windows), je suis jolie bien sûr, vous devrez quand même créer une fenêtre et associer le contexte de rendu à la fenêtre, bien qu'il soit probablement très bien si la fenêtre est toujours cachée.
exemple
l'exemple ci-dessous génère soit:
- un ppm par image à 200 FPS et pas de supplément de dépendances,
- un png, image par image à 600 FPS avec libpng
- un mpg pour tous les cadres à 1200 FPS avec FFmpeg
sur un ramfs. Plus la compression est bonne, plus le FPS est grand,donc nous devons être liés à la mémoire.
FPS est plus grand que 200 sur mon écran de 60 FPS, et toutes les images sont différentes, donc je suis sûr que ce n'est pas limité aux FPS de l'écran.
glReadPixels
est la fonction OpenGL clé qui lit les pixels de l'écran. Jetez également un oeil à la configuration sous init()
.
glReadPixels
lit le ligne inférieure des pixels d'abord, contrairement à la plupart des formats d'image, de sorte que la conversion qui est généralement nécessaire.
TODO: trouver un moyen de le faire sur une machine sans GUI (par exemple X11). Il semble Qu'OpenGL n'est tout simplement pas fait pour le rendu hors écran, et que la lecture des pixels de retour au GPU est implémentée sur l'interface avec le système de fenêtrage (par exemple GLX ). Voir: OpenGL sans X.org sous linux
TODO: utiliser un 1x1 fenêtre, assurez-elle de l'onu-redimensionnable, et de le cacher à rendre les choses plus robuste. Si je fais l'un ou l'autre, le rendu échoue, voir les commentaires de code. La prévention de redimensionner semble impossible dans la surabondance , mais GLFW soutient . Dans tous les cas, ceux-ci n'ont pas beaucoup d'importance car mon FPS n'est pas limité par la fréquence de rafraîchissement de l'écran, même lorsque offscreen
est éteint.
/* 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;
}
Compiler avec:
gcc main.c -lGL -lGLU -lglut #-lpng -lavcodec -lswscale -lavutil
Exécuter "à l'écran" (surtout TODO, fonctionne mais n'a pas d'avantage):
./a.out
Exécuter à l'écran (ne limite pas mes FPS):
./a.out 1
testé sur Ubuntu 15.10, OpenGL 4.4.0 NVIDIA 352.63, Lenovo Thinkpad T430.
autres options que PBO
- il faut rendre à backbuffer (rendu par défaut place)
- rendre à une texture
- il faut rendre à un
Pixelbuffer
objet (PBO)
Framebuffer
et Pixelbuffer
sont mieux que le backbuffer et de la texture, car ils sont faits pour la lecture des données du retour d'UC, tandis que le backbuffer et les textures sont faites pour rester sur le GPU et l'afficher sur l'écran.
PBO est asynchrone pour les transferts, je pense donc que nous n'avons pas besoin, voir: Quelles sont les différences entre un objet tampon de Frame et un objet tampon de Pixel dans OpenGL? ,
peut-être Qu'il vaut la peine de regarder Mesa hors-écran: http://www.mesa3d.org/osmesa.html
apiretrace
https://github.com/apitrace/apitrace
fonctionne simplement, et ne vous oblige pas à modifier votre code du tout:
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
vous avez maintenant un tas de screenshots nommés comme:
animation.out.<n>.png
TODO: principe de fonctionnement.
les docs suggèrent aussi ceci pour la vidéo:
apitrace dump-images -o - application.trace \
| ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4
Vulkan
il semble que Vulkan soit conçu pour supporter un rendu offscreen meilleur qu'OpenGL.
ceci est mentionné sur cette vue d'ensemble NVIDIA: https://developer.nvidia.com/transitioning-opengl-vulkan
il y a un exemple runnable à: https://github.com/SaschaWillems/Vulkan/blob/0616eeff4e697e4cd23cb9c97f5dd83afb79d908/offscreen/offscreen.cpp mais je n'ai pas encore réussi à faire courir Vulkan. 1 kloc: -)
Related: Est-il possible de le faire à l'écran le rendu sans Surface de Vulkan?
Bibliographie
- comment utiliser GLUT/OpenGL pour rendre vers un fichier?
- Comment prendre une capture d'écran en OpenGL
- Comment rendre l'écran sur OpenGL?
- glReadPixels() "base de données" argument d'utilisation?
- Render Ouvrir les 2.0 à l'image
- http://www.songho.ca/opengl/gl_fbo.html
- http://www.mesa3d.org/brianp/sig97/offscrn.htm
- rendu hors écran (avec FBO et RenderBuffer) et transfert de pixel de couleur, profondeur, stencil
- https://gamedev.stackexchange.com/questions/59204/opengl-fbo-render-off-screen-and-texture
- quelles sont les différences entre un objet tampon de trame et un objet tampon de Pixel dans OpenGL?
- glReadPixels() "base de données" argument d'utilisation?
FBO plus grande que la fenêtre:
- OpenGL comment créer, et rendre à, un framebuffer plus grande que la fenêtre?
- FBO lwjgl plus grand que la taille de L'écran-ce que je fais mal?
- Renderbuffers plus grande que la taille de la fenêtre OpenGL
- problème d'économie d'openGL FBO plus grande que la fenêtre
Pas De Fenêtre / X11:
ne pas prendre à part les autres excellentes réponses, mais si vous voulez un exemple existant, nous avons fait le rendu GL Offscreen depuis quelques années maintenant dans OpenSCAD, dans le cadre du rendu de cadre de Test à .fichiers png de la ligne de commande. Les fichiers pertinents sont à https://github.com/openscad/openscad/tree/master/src sous L'écran*.cc
il fonctionne sur OSX (CGL), Linux X11 (GLX), BSD (GLX), et Windows (WGL), avec quelques bizarreries aux différences de conduite. L'astuce de base est d'oublier d'ouvrir une fenêtre (comme Douglas Adams dit que l'astuce pour voler est d'oublier de toucher le sol). Il fonctionne même sous linux/bsd "sans tête" si vous avez un serveur X11 virtuel tournant comme Xvfb ou Xvnc. Il est également possible d'utiliser le rendu du Logiciel sur Linux/BSD en définissant la variable D'environnement LIBGL_ALWAYS_SOFTWARE=1 avant d'exécuter votre programme, ce qui peut aider dans certaines situations.
Ce n'est pas le seul système de faites ceci, je crois que le système d'imagerie VTK fait quelque chose de similaire.
ce code est un peu vieux dans ses méthodes, (j'ai arraché le code GLX des glxgears de Brian Paul), surtout que de nouveaux systèmes viennent le long, OSMesa, Mir, Wayland, EGL, Android, Vulkan, etc, Mais remarquez le OffscreenXXX.cc noms de fichiers où XXX est le sous-système de contexte GL, il peut en théorie être porté à différents générateurs de contexte.
pas sûr Qu'OpenGL soit la meilleure solution.
Mais vous pouvez toujours vous rendre à un écran tampon.
la façon typique d'écrire la sortie openGL dans un fichier est d'utiliser readPixels pour copier la scène pixel-pixel résultante dans un fichier image
vous pouvez utiliser SFML http://www.sfml-dev.org / . Vous pouvez utiliser la classe image pour enregistrer votre Rendu.
http://www.sfml-dev.org/documentation/1.6/classsf_1_1Image.htm
pour obtenir votre rendu de sortie, vous pouvez rendre à une texture ou copier votre écran.
rendu à une texture:
- http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=36
- http://zavie.free.fr/opengl/index.html.en#rendertotexture
Copie de la sortie de l'écran: