Nous vous fournissons un moteur très basique pour démarrer directement dans le vif du sujet.
Il est important de bien comprendre l'architecture du code pour avancer de manière fluide par la suite.
Le code source est disponible ici : https://github.com/AlexandreLamure/OM3D
Les dossiers sont organisés de la manière suivante :
|- data/
––––– modèles 3D, textures, etc.
|- external/
––- bibliothèques externes
|- shaders/
––– shaders OpenGL
|- src/
–––––- code c++
Nous utilisons les bibliothèques externes suivantes :
Voici un diagramme non exhaustif du code. Les flèches ne représentent pas de l'héritage mais plutôt des liens logiques.
Vous pouvez aussi le visionner en plein écran ici.
Pour rappel, en OpenGL on ne manipule pas directement les données telles que les buffers, textures, etc. On appelle simplement des fonctions de créations, remplissage et bind. Pour référencer ces objets, OpenGL utilise généralement un ID, sous la forme d'un GLuint
(l'équivalent d'un uint32_t
).
Notre classe GLHandle
est une abstraction de ces ID.
Cependant on a parfois besoin d'accéder directement à des buffers au CPU, par exemple pour set les valeurs d'un buffer d'uniforms.
Notre classe BufferMapping
répond à ce besoin.
Nous utilisons le format glTF pour load des scènes et des objets.
Il peut contenir des vertices, des textures, des matériaux PBR, des lights, des caméra, des animations, etc.
L'extension .gltf
correspond à la version ASCII du format (donc lisible). Généralement nous utilisons plutôt l'extension .glb
, la version binaire (plus compact).
Pour tester avec cos propres scènes, vous pouvez facilement exporter au format glTF avec Blender.
Pour la coding style, il n'y a rien de surprenant. Nous respectons les règles classiques, notamment que les membres de classes ont le préfixe _
:
class MyClass {
float _myVar;
std::string _myString;
}
Vous êtes libres de coder comme bon vous semble, rappelez-vous simplement que nous devrons lire et noter votre code au rendu du projet.
Vous aurez besoin de supporter cmake 3.20 minimum, C++17, et OpenGL 4.5.
# At the project root
mkdir -p build/debug
cd build/debug
cmake ../..
make
Vous pouvez télécharger l'installeur ici https://renderdoc.org/ ou utiliser votre package manager préféré.
La manière classique de profiler un rendu est de capturer une (ou plusieurs) frame(s), et d'analyser les temps d'éxecution, les bottlenecks etc.
La manoeuvre à suivre est détaillée ici : https://renderdoc.org/docs/getting_started/quick_start.html#capturing-a-frame
Il faut donc lancer votre exécutable depuis RenderDoc, et capturer avec F12
.
Nous allons expliquer succintement comment utiliser RenderDoc.
Pour mieux comprendre l'interface, je vous renvoie vers la doc : https://renderdoc.org/docs/getting_started/quick_start.html#renderdoc-layout
Prenons pour exemple la scène par défaut du TP.
Une fois la frame capturée, vous pouvez voir chaque étape du rendu dans l'"Event Browser".
On y trouve les draw, dispatch, clear, etc.
(Les calls CPU tels que les state change sont quand à eux visibles dans la fenêtre "API Inspector".)
Ici, notre frame :
Pour obtenir les infos de durée de chaque action, cliquez sur l'icône d'horloge
En sélectionnant un event, on peut l'analyser plus en détail dans "Pipeline State" :
À chaque étape de la pipeline, on peut regarder les paramètres, le shader et la data.
Vous trouverez généralement votre bonheur ici.
Il y a beaucoup d'autres outils intéressants, comme le texture viewer et le mesh viewer.
Le texture viewer permet de visionner des textures et des render targets, ce qui est très pratique par exemple pour voir que les normales sont inversées, ou que l'on a bind la mauvaise texture.
Les options permettent d'inspecter les mip maps, le format, les dimensions, et même faire du color picking.
Le mesh viewer permet non seulement de voir le modèle 3D, mais aussi les valeurs de ses vertices.
Enfin, la "Timeline Bar" offre une alternative à la vue de l'Event Browser, très utile pour surveiller le budget temps de notre frame.
La notre est par défaut très vide :
Pour enrichir et clarifier l'analyse, il est possible d'ajouter des marqueurs via l'API graphique.
OpenGL propose plusieurs outils, à commencer par les sections, qui suivent le principe d'une simple pile :
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "My super section");
// contents of section here
glPopDebugGroup();
OpenGL permet aussi de nommer les ressources, plutôt que de laisser RenderDoc générer des noms :
// Creating an example resource - a 2D Texture.
GLuint tex2d = 0;
glGenTextures(1, &tex2d);
glBindTexture(GL_TEXTURE_2D, tex2d);
// apply the name, -1 means NULL terminated
glObjectLabel(GL_TEXTURE, tex2d, -1, "My awesome texture");
Moins souvent utile, mais toujours bon à connaître, vous pouvez aussi rajouter des events de message :
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 0,
GL_DEBUG_SEVERITY_NOTIFICATION, -1, "My amazing event");
Avec ces outils, on obtient une Timeline Bar et un Event Browser bien plus intéressants :
Nous avons glissé un bug dans le code du TP.
Si vous pensez avoir trouvé la solution, appelez l'enseignant pour vérifier avec lui.
Si au contraire vous êtes bloqués, appelez l'enseignant également.
Vous devez rajouter du backface culling dans le moteur.
Vous pouvez utiliser les fonctions OpenGL montrées en cours :
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
N'hésitez pas à consulter la documentation pour mieux comprendre en quoi elles consistent : https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glCullFace.xml
Attention : Certains objets ne devraient pas avoir de backface culling. Par exemple les matériaux transparents ou semi-transparents doivent généralement être visibles des deux côtés.
Comme expliqué en cours, l'idée est de ne pas dessiner les objets se trouvant en dehors du frustum.
Pour cela, nous vous demandons d'implémenter la méthode naïve :
StaticMesh
.La méthode pour calculer le frustum à partir de la caméra courante est déjà donnée dans Camera.h
:
Frustum build_frustum() const;
Attention, vous devez prendre en compte le transform de l'objet au moment du culling (scale et translation).
Une fois terminé, vous devez utiliser RenderDoc pour vous assurer que le discard est bien effectué, et vérifier le gain de performance.
Implémentez une Z-prepass afin de réduire l'overdraw.
Vous n'avez pas besoin d'utiliser des fonctions OpenGL, tout le nécessaire est fourni dans notre moteur.
Ne cherchez pas à faire compliqué, l'implémentation minimale de la Z-prepass est réalisable en moins de 10 lignes.
Utilisez RenderDoc pour vérifier l'ajout de la Z-prepass.
Vous devriez obtenir quelque chose comme ça :
Une fois terminé, vous devez tester l'impact de votre Z-prepass sur les performances des scènes bistro.glb
et bistro_lights.glb
fournies dans le drive des ressources.