Monde à l’envers

Pour développer un nouveau projet, Cube Art Project a adopté la méthodologie Test Driven Development. Dans cette approche, un test pour une fonctionnalité spécifique de l’application est d’abord implémenté, puis la fonctionnalité spécifique est implémentée. Je considère que le gros avantage de cette approche réside dans la mise en œuvre des interfaces finales, qui sont aussi peu impliquées que possible dans les détails de mise en œuvre, avant le début du développement des fonctionnalités. Avec cette approche, le test dicte la poursuite de l’implémentation, ajoutant tous les avantages de la programmation contractuelle, lorsque les interfaces sont des contrats pour une implémentation spécifique.
Projet d’art cubique – Un éditeur 3D dans lequel l’utilisateur construit des figures à partir de cubes ; il n’y a pas si longtemps, ce genre était très populaire. Puisqu’il s’agit d’une application graphique, j’ai décidé d’ajouter des tests avec validation de capture d’écran.
Pour valider les captures d’écran, vous devez les récupérer depuis le contexte OpenGL, cela se fait à l’aide de la fonction glReadPixels. La description des arguments de la fonction est simple : position de départ, largeur, hauteur, format (RGB/RGBA/etc.), pointeur vers le tampon de sortie ; toute personne ayant travaillé avec SDL ou ayant de l’expérience avec les tampons de données en C remplacera simplement les arguments nécessaires. Cependant, je pense qu’il est nécessaire de décrire une fonctionnalité intéressante du tampon de sortie glReadPixels : les pixels y sont stockés de bas en haut, tandis que dans SDL_Surface, toutes les opérations de base se déroulent de haut en bas.
Autrement dit, après avoir chargé une capture d’écran de référence à partir d’un fichier png, je n’ai pas pu comparer directement les deux tampons, car l’un d’eux était à l’envers.
Pour retourner le tampon de sortie d’OpenGL, vous devez le remplir en soustrayant la hauteur de la capture d’écran pour la coordonnée Y. Cependant, il convient de considérer qu’il y a une chance d’aller au-delà des limites du tampon si vous n’en soustrayez pas une lors du remplissage, ce qui le fera. conduire à une corruption de la mémoire.
Comme j’essaie toujours d’utiliser le paradigme POO de « programmation par interfaces », au lieu d’un accès direct à la mémoire de type C par pointeur, lorsque j’ai essayé d’écrire des données en dehors du tampon, l’objet m’en a informé grâce à la validation des limites dans la méthode. .
Le code final pour la méthode permettant d’obtenir une capture d’écran de haut en bas :

    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

Sources

https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

Code source

https://gitlab.com/demensdeum/cube- art-project-bootstrap

WebGL + SDL + Emscript

J’ai fini par porter Mika sur WebGL en utilisant SDL 1 et Emscripten.

Ensuite, je décrirai ce qui devait être modifié dans le code pour que la construction en JavaScript se termine avec succès.

  1. Utilisez SDL 1 au lieu de SDL 2. Il existe actuellement un portage SDL 2 pour emscripten, mais j’ai trouvé plus approprié d’utiliser le SDL 1 intégré à emscripten. Le contexte n’est pas initialisé dans la fenêtre, mais à l’aide de SDL_SetVideoMode et du flag SDL_OPENGL. Le tampon est dessiné à l’aide de la commande SDL_GL_SwapBuffers()
  2. En raison de la manière dont JavaScript boucle – le rendu est placé dans une fonction distincte et son appel périodique est effectué à l’aide de la fonction emscripten_set_main_loop
  3. L’assemblage doit également être effectué avec la clé “-s FULL_ES2=1
  4. J’ai dû abandonner la bibliothèque assimp, charger le modèle depuis le système de fichiers et charger la texture depuis le disque. Tous les tampons nécessaires ont été chargés sur la version de bureau et insérés dans le fichier c-header pour l’assemblage à l’aide d’emscripten.

Code :
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/9-sdl-gles-obj-textured-assimp-miku-webgl/mikuWebGL

Articles :
http://blog.scottlogic.com/2014/03/12/native-code-emscripten-webgl-simmer-gently.html
https://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html

Modèle :
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

Il n’y a que Miku

Le résultat du travail sur la bibliothèque FSGL avec OpenGL ES et le code :

Ensuite, je décrirai comment tout a été programmé, divers problèmes intéressants ont été résolus.

Tout d’abord, nous allons initialiser le contexte OpenGL ES, comme je l’ai écrit dans le post précédent. De plus, nous ne considérerons que le rendu et une brève description du code.

La Matrix vous surveille

Cette figure de Miku dans la vidéo est constituée de triangles. Pour dessiner un triangle dans OpenGL, vous devez spécifier trois points avec les coordonnées x, y, z. en coordonnées 2D du contexte OpenGL.
Puisque nous devons dessiner une figure contenant des coordonnées 3D, nous devons utiliser une matrice de projection. Nous devons également faire pivoter, zoomer ou faire tout ce que nous voulons faire avec le modèle. À cette fin, la matrice de modèle est utilisée. Il n’y a pas de concept de caméra dans OpenGL ; en fait, les objets tournent autour d’une caméra statique. Pour cela, une matrice de vues est utilisée.

Pour simplifier la mise en œuvre d’OpenGL ES – il ne contient pas de données matricielles. Vous pouvez utiliser des bibliothèques qui ajoutent des fonctionnalités manquantes, par exemple GLM.

Shaders

Afin de permettre au développeur de dessiner n’importe quoi, et de quelque manière que ce soit, OpenGL ES doit implémenter des vertex et fragment shaders. Le vertex shader doit recevoir les coordonnées de rendu en entrée, effectuer des transformations à l’aide de matrices et transmettre les coordonnées à gl_Position. Fragment ou pixel shader – dessine déjà la couleur/texture, applique une superposition, etc.

J’ai écrit des shaders en GLSL. Dans mon implémentation actuelle, les shaders sont intégrés directement dans le code de l’application principale sous forme de chaînes C.

Tampons

Le vertex buffer contient les coordonnées des sommets (vertices) ; ce tampon contient également les coordonnées pour la texturation et d’autres données nécessaires aux shaders. Après avoir généré le vertex buffer, vous devez lier le pointeur aux données du vertex shader. Cela se fait avec la commande glVertexAttribPointer, où vous devez spécifier le nombre d’éléments, un pointeur vers le début des données et la taille du pas qui sera utilisé pour parcourir le tampon. Dans mon implémentation, la liaison des coordonnées de sommet et des coordonnées de texture pour le pixel shader est effectuée. Cependant, il convient de dire que le transfert des données (coordonnées de texture) vers le fragment shader s’effectue via le vertex shader. Pour y parvenir, les coordonnées sont déclarées en utilisant variant.

Pour qu’OpenGL sache dans quel ordre dessiner les points des triangles – vous aurez besoin d’un tampon d’index (index). Le tampon d’index contient le numéro de sommet dans le tableau ; en utilisant trois de ces indices, un triangle est obtenu.

Textures

Vous devez d’abord charger/générer une texture pour OpenGL. Pour cela j’ai utilisé SDL_LoadBMP, la texture est chargée depuis un fichier bmp. Cependant, il convient de noter que seuls les BMP 24 bits conviennent et que les couleurs qu’ils contiennent ne sont pas stockées dans l’ordre RVB habituel, mais en BGR. Autrement dit, après le chargement, vous devez remplacer le canal rouge par un bleu.
Les coordonnées de texture sont spécifiées au format UV< /a>, c’est-à-dire qu’il vous suffit de transférer deux coordonnées. La sortie de texture est effectuée dans le fragment shader. Pour ce faire, vous devez lier la texture dans un fragment shader.

Rien de plus

Puisque, selon nos instructions, OpenGL dessine de la 3D à la 2D – puis pour implémenter la profondeur et sélectionner des triangles invisibles – vous devez utiliser l’élimination et un tampon de profondeur (Z-Buffer). Dans mon implémentation, j’ai réussi à éviter la génération manuelle du tampon de profondeur à l’aide de deux commandes : glEnable(GL_DEPTH_TEST); et sélections glEnable(GL_CULL_FACE);
Assurez-vous également de vérifier que le plan proche de la matrice de projection est supérieur à zéro, car vérifier la profondeur avec un plan proche nul ne fonctionnera pas.

Rendu

Pour remplir le tampon de vertex, le tampon d’index avec quelque chose de conscient, par exemple le modèle Miku, vous devez charger ce modèle. Pour cela, j’ai utilisé la bibliothèque assimp. Miku a été placé dans un fichier au format Wavefront OBJ, chargé à l’aide de assimp, et la conversion des données d’assimp en tampons de sommets et d’index a été implémentée.

Le rendu s’effectue en plusieurs étapes :

  1. Faire pivoter Miku à l’aide de la rotation de la matrice du modèle
  2. Effacer l’écran et le tampon de profondeur
  3. Dessiner des triangles à l’aide de la commande glDrawElements.

Prochaine étape – Implémentation du rendu en WebGL à l’aide d’Emscripten.

Code source :
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/8-sdl-gles-obj-textured-assimp-miku
Modèle :
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

 

Projetez-le

Après avoir dessiné une théière rouge en 3D, je considère qu’il est de mon devoir de décrire brièvement comment cela se fait.

L’OpenGL moderne ne dessine pas en 3D, il dessine uniquement des triangles, des points, etc. en coordonnées d’écran 2D.
Pour générer au moins quelque chose en utilisant OpenGL, vous devez fournir un vertex buffer, écrire un vertex shader, ajouter toutes les matrices nécessaires (projection, modèle, vue) au vertex shader,associer toutes les données d’entrée à le shader, appelez la méthode de rendu en OpenGL. Est-ce que ça a l’air simple ?


Ok, qu’est-ce qu’un vertex buffer ? Liste des coordonnées à tracer (x, y, z)
Le vertex shader indique au GPU quelles coordonnées dessiner.
Le pixel shader indique quoi dessiner (couleur, texture, mélange, etc.)
Les matrices traduisent les coordonnées 3D en coordonnées OpenGL 2D qu’elles peuvent restituer

Dans les articles suivants, je fournirai des exemples de code et des résultats.

SDL2 – OpenGL ES

I love Panda3D game engine. But right now this engine is very hard to compile and debug on Microsoft Windows operation system. So as I said some time ago, I begin to develop my own graphics library. Right now it’s based on OpenGL ES and SDL2.
In this article I am going to tell how to initialize OpenGL ES context and how SDL2 helps in this task. We are going to show nothing.

King Nothing

First of all you need to install OpenGL ES3 – GLES 3 libraries. This operation is platform dependant, for Ubuntu Linux you can just type sudo apt-get install libgles2-mesa-dev. To work with OpenGL you need to initialize OpenGL context. There is many ways to do that, by using one of libraries – SDL2, GLFW, GLFM etc. Actually there is no one right way to initialize OpenGL context, but I chose SDL2 because it’s cross-platform solution, code will look same for Windows/*nix/HTML5/iOS/Android/etc.

To install sdl2 on Ubuntu use this command sudo apt-get install libsdl2-dev

So here is OpenGL context initialization code with SDL2:

    SDL_Window *window = SDL_CreateWindow(
            "SDL2 - OGLES",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            SDL_WINDOW_OPENGL
            );
	    

    SDL_GLContext glContext = SDL_GL_CreateContext(window);

After that, you can use any OpenGL calls in that context.

Here is example code for this article:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/3sdl-gles
https://github.com/demensdeum/OpenGLES3-Experiments/blob/master/3sdl-gles/sdlgles.cpp

You can build and test it with command cmake . && make && ./SDLGles