web-dev-qa-db-fra.com

Comment rendre hors écran sur OpenGL?

Mon objectif est de rendre une scène OpenGL sans fenêtre, directement dans un fichier. La scène peut être plus grande que ma résolution d'écran.

Comment puis-je faire ceci?

Je veux pouvoir choisir la taille de la zone de rendu à n'importe quelle taille, par exemple 10000x10000, si possible?

35
Rookie

Tout commence par glReadPixels , que vous utiliserez pour transférer les pixels stockés dans un tampon spécifique du GPU vers la mémoire principale (RAM). Comme vous le remarquerez dans la documentation, il n'y a aucun argument pour choisir le tampon. Comme d'habitude avec OpenGL, le tampon courant à lire est un état que vous pouvez définir avec glReadBuffer.

Ainsi, une méthode de rendu hors écran très basique ressemblerait à ceci. J'utilise un pseudo-code c ++ donc il contiendra probablement des erreurs, mais devrait clarifier le flux général:

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

Cela lira le tampon arrière actuel (généralement le tampon sur lequel vous dessinez). Vous devez l'appeler avant d'échanger les tampons. Notez que vous pouvez également parfaitement lire le tampon arrière avec la méthode ci-dessus, l'effacer et dessiner quelque chose de totalement différent avant de l'échanger. Techniquement, vous pouvez également lire le tampon avant, mais cela est souvent déconseillé car, théoriquement, les implémentations étaient autorisées à effectuer certaines optimisations susceptibles de faire en sorte que votre tampon avant contienne des déchets.

Il y a quelques inconvénients à cela. Tout d'abord, nous ne faisons pas vraiment de rendu hors écran. Nous rendons sur les tampons d'écran et lisons à partir de ceux-ci. Nous pouvons émuler le rendu hors écran en ne remplaçant jamais le tampon arrière, mais cela ne semble pas correct. À côté de cela, les tampons avant et arrière sont optimisés pour afficher les pixels, pas pour les lire. C'est là que Framebuffer Objects entrent en jeu.

Essentiellement, un FBO vous permet de créer un framebuffer non par défaut (comme les tampons FRONT et BACK) qui vous permet de dessiner vers un tampon de mémoire au lieu des tampons d'écran. En pratique, vous pouvez soit dessiner sur une texture, soit sur un renduerbuffer . La première est optimale lorsque vous souhaitez réutiliser les pixels dans OpenGL lui-même comme texture (par exemple une "caméra de sécurité" naïve dans un jeu), la seconde si vous souhaitez simplement effectuer un rendu/relecture. Avec cela, le code ci-dessus deviendrait quelque chose comme ça, encore une fois un pseudo-code, alors ne me tuez pas si vous avez mal tapé ou oublié certaines déclarations.

//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<std::uint8_t> 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);

Ceci est un exemple simple, en réalité, vous souhaitez probablement également un stockage pour le tampon de profondeur (et de gabarit). Vous pouvez également vouloir effectuer un rendu sur la texture, mais je laisse cela comme un exercice. Dans tous les cas, vous allez maintenant effectuer un rendu hors écran réel et cela pourrait fonctionner plus rapidement que la lecture du tampon arrière.

Enfin, vous pouvez utiliser objets tampon de pixels pour rendre les pixels lus asynchrones. Le problème est que glReadPixels se bloque jusqu'à ce que les données de pixel soient complètement transférées, ce qui peut bloquer votre CPU. Avec PBO, l'implémentation peut revenir immédiatement car elle contrôle le tampon de toute façon. Ce n'est que lorsque vous mappez le tampon que le pipeline se bloquera. Cependant, les PBO peuvent être optimisés pour tamponner les données uniquement sur la RAM, ce bloc peut donc prendre beaucoup moins de temps. Le code des pixels lus deviendrait quelque chose comme ceci:

//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 partie en casquettes est indispensable. Si vous émettez simplement un glReadPixels à un PBO, suivi d'un glMapBuffer de ce PBO, vous ne gagnez que beaucoup de code. Bien sûr, le glReadPixels peut revenir immédiatement, mais maintenant le glMapBuffer va caler car il doit mapper en toute sécurité les données du tampon de lecture au PBO et à un bloc de mémoire dans la RAM principale.

Veuillez également noter que j'utilise GL_BGRA partout, car de nombreuses cartes graphiques l'utilisent en interne comme format de rendu optimal (ou la version GL_BGR sans alpha). Ce devrait être le format le plus rapide pour les transferts de pixels comme celui-ci. Je vais essayer de trouver l'article nvidia que j'ai lu à ce sujet il y a quelques mois.

Lorsque vous utilisez OpenGL ES 2.0, GL_DRAW_FRAMEBUFFER peut ne pas être disponible, vous devez simplement utiliser GL_FRAMEBUFFER dans ce cas.

87
KillianDS

Je suppose que la création d'une fenêtre factice (vous ne la restituez pas; elle est juste là parce que l'API vous oblige à en créer une) dans laquelle vous créez votre contexte principal est une stratégie d'implémentation acceptable.

Voici vos options:

Tampons de pixels

Un tampon de pixels, ou pbuffer (qui n'est pas un objet de tampon de pixels ), est avant tout un contexte OpenGL . Fondamentalement, vous créez une fenêtre comme d'habitude, puis choisissez un format de pixel dans wglChoosePixelFormatARB (les formats pbuffer doivent être obtenus à partir d'ici). Ensuite, vous appelez wglCreatePbufferARB, en lui donnant le HDC de votre fenêtre et le format de tampon de pixels que vous souhaitez utiliser. Oh, et une largeur/hauteur; vous pouvez interroger la largeur/hauteur maximale de l'implémentation.

Le framebuffer par défaut pour pbuffer n'est pas visible à l'écran, et la largeur/hauteur maximale correspond à ce que le matériel veut vous permettre d'utiliser. Vous pouvez donc y effectuer un rendu et utiliser glReadPixels pour le relire.

Vous devrez partager votre contexte avec le contexte donné si vous avez créé des objets dans le contexte de la fenêtre. Sinon, vous pouvez utiliser le contexte pbuffer entièrement séparément. Ne détruisez simplement pas le contexte de la fenêtre.

L'avantage ici est une meilleure prise en charge de l'implémentation (bien que la plupart des pilotes qui ne prennent pas en charge les alternatives soient également d'anciens pilotes pour du matériel qui n'est plus pris en charge. Ou est du matériel Intel).

Les inconvénients sont les suivants. Les tampons ne fonctionnent pas avec contextes OpenGL principaux . Ils peuvent fonctionner pour la compatibilité, mais il n'y a aucun moyen de donner des informations sur les versions et profils OpenGL wglCreatePbufferARB.

Objets Framebuffer

Framebuffer Objects sont plus "propres" les rendus d'arts à l'écran que les pbuffers. Les FBO sont dans un contexte, tandis que les pbuffers visent à créer de nouveaux contextes.

Les FBO ne sont qu'un conteneur pour les images que vous rendez. Les dimensions maximales autorisées par l'implémentation peuvent être interrogées; vous pouvez supposer que c'est GL_MAX_VIEWPORT_DIMS (assurez-vous qu'un FBO est lié avant de le vérifier, car il change selon que le FBO est lié).

Comme vous n'échantillonnez pas de textures à partir de celles-ci (vous ne faites que lire des valeurs), vous devez utiliser des rendus de tampons au lieu de textures. Leur taille maximale peut être supérieure à celle des textures.

L'avantage est la facilité d'utilisation. Plutôt que d'avoir à gérer des formats de pixels et autres, vous choisissez simplement un format d'image approprié pour votre appel glRenderbufferStorage.

Le seul véritable inconvénient est la bande de matériel plus étroite qui les prend en charge. En général, tout ce que AMD ou NVIDIA fait qu'ils prennent encore en charge (en ce moment, GeForce 6xxx ou mieux [notez le nombre de x], et toute carte Radeon HD) aura accès à ARB_framebuffer_object ou OpenGL 3.0+ (où il s'agit d'une fonctionnalité de base ). Les pilotes plus anciens peuvent uniquement prendre en charge EXT_framebuffer_object (ce qui présente quelques différences). Le matériel Intel est un repas-partage; même s'ils revendiquent la prise en charge 3.x ou 4.x, il peut toujours échouer en raison de bogues de pilote.

18
Nicol Bolas

Si vous devez rendre quelque chose qui dépasse la taille FBO maximale de votre GL implémentation libtr fonctionne plutôt bien:

La bibliothèque TR (Tile Rendering) est une bibliothèque utilitaire OpenGL pour effectuer un rendu en mosaïque. Le rendu en mosaïque est une technique pour générer de grandes images en morceaux (tuiles).

TR est efficace en mémoire; des fichiers d'image arbitrairement grands peuvent être générés sans allouer un tampon d'image de taille normale dans la mémoire principale.

3
genpfault

Le moyen le plus simple consiste à utiliser quelque chose appelé Frame Buffer Objects (FBO). Vous devrez quand même créer une fenêtre pour créer un contexte opengl (mais cette fenêtre peut être masquée).

2
Andreas Brinck

La façon la plus simple d'atteindre votre objectif est d'utiliser FBO pour effectuer un rendu hors écran. Et vous n'avez pas besoin de rendre la texture, puis d'obtenir l'image tex. Rendez simplement dans le tampon et utilisez la fonction glReadPixels . Ce lien vous sera utile. Voir Exemples d'objets Framebuffer

1
rtrobin