Retour

Moteur de rendu et d'animation 3D (OpenGL)

Moteur de rendu 3D et scène animée interactive en C++ et OpenGL moderne représentant l'île de Beurk (Dragons), intégrant du skinning de squelette glTF (Krokmou), des vagues de Gerstner, des flammes volumétriques FBM, des ombres portées et des reflets de Fresnel.

10 mai 2026
Moteur de rendu et d'animation 3D (OpenGL)
C++OpenGLGLSLglTF 2.0BlenderFramer Motion

Introduction

Ce projet a été réalisé pour le cours "Introduction à OpenGL" de la majeure IMAGE à l'EPITA, et a été nommé en référence à l'île de la franchise Dragons, "Ça, c’est Beurk".

L'objectif de ce projet est de modéliser, d'animer et de restituer de manière réaliste et optimisée l'île de Beurk. Pour essayer de ne pas se limiter à un simple rendu statique, nous avons également modélisé et animé le dragon Krokmou (Toothless) ainsi qu'un mouton low-poly (modélisé avec un budget géométrique serré !), et nous avons implémenté des effets visuels avancés tels que des vagues de Gerstner, des flammes volumétriques FBM, des reflets de Fresnel et du shadow mapping.


Vagues de Gerstner

Pour commencer, la première étape a été de voir comment faire pour implémenter un océan dynamique. Pour ce faire, nous avons utilisé les vagues de Gerstner, qui sont un modèle physique permettant de simuler des vagues réalistes.

L'idée est de superposer plusieurs fonctions sinusoïdales pour créer un mouvement d'eau réaliste. Nous avons choisi d'utiliser 8 fonctions sinusoïdales de Gerstner pour obtenir un rendu satisfaisant.

Vagues de base dans l'océan
Vagues de base dans l'océan

Soleil et skybox

Évidemment, un océan sans coucher de soleil serait décevant. Je me suis donc attelé à la tâche de créer un soleil dynamique et de l'intégrer dans la skybox.

Concrètement, pour générer le soleil, j'ai utilisé un shader procédural qui permet de créer un soleil réaliste et dynamique. Le soleil est ensuite intégré dans la skybox, qui est définie par un simple rectangle.

Coucher de soleil sur l'océan
Coucher de soleil sur l'océan

Brouillard sur l'eau

L'étape suivante a été d'ajouter du brouillard sur l'océan pour améliorer l'immersion, mais aussi pour adoucir la ligne d'horizon entre l'eau et le ciel. Ici, le brouillard est appliqué de manière à créer une transition douce entre l'océan et le ciel, et à estomper la transition abrupte à l'infini. Cela permet de lier visuellement la skybox et la surface de l'océan.

Coucher de soleil brumeux sur l'océan
Coucher de soleil brumeux sur l'océan

Nuages

Nous avons ensuite également testé d'implémenter des nuages volumétriques dans le ciel, mais nous avons constaté que cela alourdissait considérablement le temps de rendu (de 180 FPS à 30 FPS), et nous avons donc décidé de ne pas les intégrer dans la version finale du projet.

Nuages vus du dessous
Nuages vus du dessous
Nuages vus du dessus
Nuages vus du dessus


Feu - Version bruit et textures

Pour ajouter du réalisme, et pousser le projet un peu plus loin, nous avons également implémenté un shader de feu volumétrique basé à l'origine sur des textures animées. Ce shader aurait dû être utilisé pour les deux grands feux des piliers à l'entrée de Beurk, ainsi que pour les flambeaux et feux de camp disposés sur toute l'île...

Deux exemples de feux texturés
Deux exemples de feux texturés

Feu - Version Fractional Brownian Motion (FBM)

... Sauf que nous avons constaté que le rendu n'était pas très réaliste, et que les flammes étaient trop "plates" et statiques. Nous avons donc décidé de développer un shader de feu volumétrique basé sur le bruit de mouvement brownien fractionnaire (FBM), qui permet de générer des flammes plus réalistes et animées. Alors l'appellation de cette méthode est un peu barbare, mais le principe en réalité est assez simple. Cela consiste à superposer plusieurs couches de bruit de Perlin, chacune avec une fréquence et une amplitude différentes, pour créer un effet de turbulence réaliste.

Une fois ce shader développé, nous l'avons utilisé pour les deux grands feux des piliers à l'entrée de Beurk, ainsi que pour les flambeaux et feux de camp disposés sur toute l'île.

Grands feux des piliers à l'entrée
Grands feux des piliers à l'entrée
Feux placés sur l'île de Beurk
Feux placés sur l'île de Beurk

GL Transmission Format (glTF)

Une autre grosse étape dans ce projet a été de gérer le format de fichier glTF 2.0, qui est un format de fichier standard pour les modèles 3D. Ce format est très pratique car il permet de stocker les informations de géométrie, de matériaux, d'animations et de textures dans un seul fichier, ce qui facilite grandement l'importation et l'exportation des modèles 3D.

Comme j'étais en groupe, sans doute avec quelqu'un qui aime se compliquer la vie, nous avons décidé de développer un parseur pour ce format de fichier, afin de pouvoir charger des modèles 3D complexes dans notre moteur de rendu.

Sans trop rentrer dans les détails techniques, le moteur décode et structure les données selon la spécification Khronos (scene, node, camera, mesh, skin, animation, material, accessor, bufferView, buffer, texture, image, sampler), et les nœuds (nodes) et le squelette (skin / joints) sont reliés aux canaux d'animation pour propager les transformations.

Organisation et structure d'un fichier glTF
Organisation et structure d'un fichier glTF

Modélisation - Dragon Krokmou

Une fois que nous étions capables de charger des modèles 3D dans notre moteur, il nous était possible de charger tous types de modèles 3D. Nous avons donc décidé de modéliser le dragon Krokmou (Toothless) dans Blender, en utilisant des techniques de modélisation de surfaces de subdivision. Nous avons ensuite appliqué des matériaux spécifiques pour obtenir un rendu réaliste, avec des écailles noires et des yeux verts émissifs.

Modélisation de Krokmou dans Blender
Modélisation de Krokmou dans Blender
Krokmou finalisé avec les matériaux
Krokmou finalisé avec les matériaux

Modélisation - Île de Beurk

L'île de Beurk a, quant à elle, été reprise d'un forum sombre Russe, car nous n'avions pas le temps de la modéliser nous-mêmes. Nous avons donc récupéré un modèle 3D de l'île, que nous avons ensuite importé dans Blender pour y ajouter des textures et des matériaux, ainsi que pour l'optimiser pour le moteur de rendu.

Modélisation de l'île de Beurk dans Blender
Modélisation de l'île de Beurk dans Blender

Modélisation - Mouton

Dans le trailer original de la franchise Dragons, on peut voir un mouton qui est utilisé comme cible pour l'entraînement de Krokmou. Nous avons donc décidé de modéliser un mouton stylisé en low-poly dans Blender, afin de l'utiliser dans notre scène interactive (pourquoi low-poly ? Tout simplement parce que nous n'avions plus de budget, bien sûr !).

Modélisation low-poly d'un mouton
Modélisation low-poly d'un mouton

Animation - Blender

C'est dans Blender que nous avons créé les animations pour le dragon et le mouton. Nous avons configuré les squelettes (armatures) et les poids de sommets pour les deux modèles, puis nous avons créé des animations clés (keyframes) pour le vol du dragon et le déplacement du mouton.

Armature et timeline d'animation du dragon
Armature et timeline d'animation du dragon

Animation - OpenGL

Cette partie, bien que très complexe, n'est pas forcément la plus intéressante à expliquer dans ce blog.

Pour expliquer le fonctionnement du moteur reproduisant les mouvements conçus sous Blender en temps réel :

  • Skinning GPU : Déformation des sommets calculée sur la carte graphique à l'aide de matrices de transformation d'os.
  • Keyframes : Lecture de la position, de la rotation et de la mise à l'échelle de chaque joint à chaque image clé.
  • Interpolation : Interpolation linéaire pour les translations/échelles.
  • Quaternions : Utilisation de quaternions pour l'interpolation des rotations afin d'éviter le verrouillage de cardan (Gimbal Lock).
Schéma des transformations d'animation
Schéma des transformations d'animation

Représentation d'un maillage

Une fois que nous avons chargé un modèle 3D dans le moteur, il est nécessaire de le représenter sous forme de maillage pour pouvoir l'afficher à l'écran. Pour ce faire, nous avons utilisé une structure de données appelée Vertex, qui contient les informations nécessaires pour représenter un sommet d'un maillage.

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec3 color;
    glm::vec2 texUV;
    glm::ivec4 joints;
    glm::vec4 weights;
};

Cette structure contient la position, la normale, la couleur, les coordonnées de texture, les indices des joints et les poids d'influence pour chaque sommet. Cela permet de représenter un maillage de manière efficace et de l'envoyer à la carte graphique pour le rendu.

Notre code a aussi été conçu pour être très modulaire, car nous avons encapsulé les objets OpenGL (VAO, VBO, EBO) et les shaders pour faciliter l'intégration et la réutilisation des différents modèles 3D.


BRDF (Bidirectional Reflectance Distribution Function)

Pour rendre les matériaux de manière réaliste, nous avons utilisé une fonction de distribution bidirectionnelle de la réflectance (BRDF) pour simuler l'interaction de la lumière avec la peau du dragon. Cela permet de créer des effets de réflexion et de diffusion réalistes, en fonction de l'angle d'incidence de la lumière et de la surface du matériau.

À noter que c'est aussi comme cela que Blender calcule les matériaux PBR (Physically Based Rendering), et que c'est aussi comme cela que les moteurs de jeux vidéo modernes calculent les matériaux.

Sur les photos juste en dessous, on voit que la tête du dragon prend une coloration orangée lors du coucher du soleil et devient blanche/spéculaire sous la lumière directe de l'après-midi.

Rendu de la tête du dragon sous différentes lumières
Rendu de la tête du dragon sous différentes lumières

Reflets sur l'eau

Pour rendre l'océan plus réaliste, nous avons intégré des reflets dynamiques. Pour ce faire, nous avons utilisé un Framebuffer (FBO) pour calculer les reflets des objets 3D (île, dragon) hors-écran, puis les retourner à 180° et les appliquer sur la surface de l'eau.

Pour rendre les reflets encore plus réalistes, nous avons également utilisé la loi de Fresnel, qui permet de pondérer l'intensité du reflet en fonction de l'angle de vue de la caméra. Cela crée une transition douce entre la transparence de l'eau et sa réflectivité, ce qui rend l'océan plus immersif.

Reflets des maillages sur l'océan
Reflets des maillages sur l'océan

Shadow Mapping

Une partie assez importante du projet a été de gérer les ombres portées, afin de rendre la scène plus réaliste. Pour ce faire, nous avons utilisé une technique appelée Shadow Mapping, qui consiste à calculer les ombres d'une scène en utilisant une carte de profondeur (Depth Buffer) depuis le point de vue de la lumière.

Dans notre implémentation, nous avons appliqué un filtre PCF (Percentage-Closer Filtering) pour obtenir des ombres douces et estomper l'effet d'escalier sur les bords.

Rendu des ombres portées avec Shadow Mapping
Rendu des ombres portées avec Shadow Mapping

Frustum Culling

Si vous avez lu jusqu'ici, vous avez sans doute remarqué que le moteur de rendu est assez complexe et qu'il y a beaucoup d'objets 3D à afficher à l'écran. Pour optimiser les performances d'affichage, nous avons implémenté une technique appelée Frustum Culling, qui consiste à ne pas afficher les objets qui sont en dehors du champ de vision de la caméra.

Le frustum culling permet de réduire considérablement le nombre d'objets à afficher à l'écran, ce qui permet d'améliorer les performances du moteur de rendu.:

  • Définition : La caméra est définie géométriquement par 6 plans formant sa pyramide de vue (frustum).
  • Test d'intersection : Utilisation d'une sphère englobante autour de chaque maillage glTF.
  • Optimisation : Si la sphère est entièrement en dehors du frustum, le rendu de l'objet est ignoré, ce qui permet des gains de performance majeurs en évitant d'envoyer des géométries invisibles au GPU.
Schéma explicatif du Frustum Culling
Schéma explicatif du Frustum Culling

Démonstration et Exécution

Il n'est malheureusement pas possible de vous montrer une démonstration en direct du projet, mais nous avons réalisé une vidéo de démonstration dans laquelle nous avons essayé de refaire le début du film Dragons que vous pouvez visionner sur YouTube : Démonstration du projet OpenGL