Rendu hors-ligne - Introduction

Le but de ce projet est de réaliser une application permettant de générer des images photo-réalistes en utilisant les extensions du lancer de rayon, qui permettent de prendre en compte des effets de lumière et de matériaux complexes.

Un affichage en 3D comme celui proposé par OpenGL n'est pas nécessaire pour créer un tel programme. L'un des buts de ce projet est néanmoins de vous persuader de l'utilité d'une telle visualisation pour la mise au point des algorithmes, et de vous faire réviser certaines notions acquises dans l'UE Mondes 3D.

1. Structure du projet

Téléchargez et décompressez l’archive sia_td4.zip

Une base de code conséquente est fournie afin que vous n'ayez à vous concentrer que sur les parties graphiques. Tout au long des TD, de nombreuses classes seront à compléter, certaines à créer de toutes pièces, tandis que d'autres ne seront utilisées complètement qu'à la fin du projet.

1.1. Arborescence

Le projet est organisé selon cette arborescence :

  • include : contient les fichiers d'entête
  • src : continent le code source C++ principal
  • ext : contient les dépendances externes
  • shaders : contient les shaders GLSL pour l'affichage OpenGL
  • data : les fichiers de scène et leurs données associées (maillages, textures, etc.)
  • CMakeLists.txt : fichier CMake spécifiant comment compiler le projet

Plusieurs dépendances externes sont utilisées, en voici une présentation succincte :

  • Eigen est une bibliothèque C++ d'algèbre linéaire. Elle sera utilisée pour la représentation et manipulation de matrices, vecteurs, transformations géométriques et résolution de systèmes d'équations (documentation).
  • pugixml est un analyseur syntaxique XML utilisé pour charger les fichiers de scène.
  • tinyexr est un bibliothèque minimaliste pour charger le format de fichier standardisé OpenEXR utilisé pour stocker des images à grande gamme dynamique (HDR).
  • lodepng est une bibliothèque pour charger des images au format PNG.
  • NanoGUI est une bibliothèque C++ qui permet de créer une fenêtre OpenGL (via GLFW) et des contrôles utilisateurs.
  • tinyformat est l'implémentation en C++11 de printf et sprintf avec sûreté de typage.
  • filesystem est une classe pour manipuler les chemins de façon transparent sous Linux, Windows et Mac OS X.
  • lib3ds est une bibliothèque pour charger des maillages au format 3D-Studio ".3DS".
  • tinyobjloader est une bibliothèque pour charger des maillages au format Wavefront OBJ.

1.2. Architecture logicielle

Parcourez les fichiers d'entête pour comprendre la structure générale du projet. En voici les classes principales :

  • Viewer : le visualiseur/débogueur OpenGL.
  • Scene : décrit une scène complète.
    • Shape : classe abstraite dont dérivent les entités géométriques de la scène.
      • Sphere : dérive de Shape, représente une sphère.
      • Plane : dérive de Shape, représente un plan infini.
      • Mesh : dérive de Shape, représente un maillage triangulaire.
    • BVH : structure d'accélération pour le calcul des intersections rayon-maillage.
    • Material : la définition complète du matériau d'un objet.
    • Light : classe abstraite dont dérivent les sources lumineuses.
    • Integrator : classe abstraite dont dérivent les méthodes d'intégration de l'équation du rendu.
    • Camera : la caméra de la scène d'où on va créer l'image.
    • Transform : contient une transformation de l'espace 3D en coordonnée homogènes et son inverse, ainsi que des opérateurs pour transformer correctement vecteurs, positions et normales.
  • Ray : représente un rayon en 3D (point d'origine et direction).
  • Hit : regroupe toutes les informations sur l'intersection d'un rayon avec un objet.

Étant donné le nombre important de fichiers sources, je vous recommande vivement d'utiliser un IDE comme, par exemple, QtCreator. Le projet n'utilise certes pas la bibliothèque logicielle Qt, mais QtCreator conserve les avantages de supporter nativement CMake et de proposer une interface graphique intuitive au dessus de GDB.

1.3. Compilation

CMake est utilisé pour gérer la compilation du projet (tutoriel, documentation). Créez un répertoire de build à l'extérieur du dossier contenant les sources, configurez, et compilez :

$ mkdir build
$ cd build
$ cmake ../sia_td1
$ make -j6
La première compilation sera longue étant donné le nombre important de dépendances externes. Par défaut le projet est compilé en mode Release. Pour le compiler en mode Debug (pour pouvoir utiliser GDB), configurez le projet avec la ligne de commande :
$ cmake ../sia_raytracer -DCMAKE_BUILD_TYPE=Debug

À l'issue de la compilation, vous obtiendrez un exécutable sia_raytracer qui assurera deux fonctions :

  • la synthèse d'images par lancer de rayon (que vous allez implémenter en grande partie),
  • un afficheur de fichier OpenEXR effectuant un tone-mapping minimaliste (conversion dans l'espace de couleurs sRGB et contrôle de l'exposition).

2. Fichiers de scène

Pour faciliter les tests et les comparaisons, les scènes sont décrites par des fichiers au format XML (extension ".scn"). Ce type de fichiers permet de représenter une grande variété de données avec un formalisme rigoureux. Il permet également de facilement extraire et transformer cette information. Le langage de description de la scène peut être vu comme un plan de construction : au fur et à mesure que l'analyseur syntaxique parcoure le fichier XML, il instancie les objets composant la scène.

2.1. Syntaxe

Chaque tag XML représente soit un objet, soit une propriété. Les objets correspondent directement à des instances de classes C++ qui seront allouées sur la pile. Les propriétés sont des paramètres passés au constructeur de l'objet lors de l'instanciation. Par exemple, ce bloc XML permet de créer un matériau diffus rouge :

<material type="diffuse">
    <color name="diffuseColor" value="0.8, 0.0, 0.0" />
</material>
Le tag <material> va entraîner la création d'un objet Material, et l'attribut diffuse indique quelle classe fille de Material doit être utilisé. Le tag <color> va créer une propriété nommée diffuseColor qui sera passée à son constructeur. Si vous ouvrez le fichier "src/material.cpp", vous verrez que le constructeur recherche cette propriété :
Diffuse::Diffuse(const PropertyList &propList)
{
    m_diffuseColor = propList.getColor("diffuseColor",Color3f(0.2));
}
La macro suivante permet de faire le lien automatiquement entre l'attribut diffuse du XML et le code C++ :
REGISTER_CLASS(Diffuse, "diffuse")

Les propriétés suivantes sont actuellement reconnues par l'analyseur syntaxique :

1
2
3
4
5
6
7
<string name="property name" value="arbitrary string"/>
<boolean name="property name" value="true/false"/>
<float name="property name" value="float value"/>
<integer name="property name" value="integer value"/>
<vector name="property name" value="x, y, z"/>
<point name="property name" value="x, y, z"/>
<color name="property name" value="r, g, b"/>
Les transformations de l'espace possèdent cependant une syntaxe spécifique :
1
2
3
4
5
6
7
<transform name="property name">
    <translate value="x, y, z"/>
    <scale value="x, y, z"/>
    <rotate axis="x, y, z" angle="deg."/>
    <rotate value="q1, q2, q3, q4"/>
    <lookat origin="x,y,z" target="x,y,z" up="x,y,z"/>
</transform>
La transformation résultante est l'application de cette séquence d'opérations à la matrice identité.

Voici un extrait XML définissant une caméra :

1
2
3
4
5
6
7
8
9
<camera type="perspective">
   <float name="fieldOfView" value="45.0" /> 
   <integer name="width" value="400"/>
   <integer name="height" value="400"/>
   <integer name="samples" value="1"/>
   <transform name="toWorld">
      <lookat target="-11, 5, 0" origin="15, 1, 0" up="0, 1, 0"/>
   </transform>
 </camera>
Attention, la Camera instanciée dans la classe Scene est distinct de la caméra OpenGL qui affiche la scène dans le Viewer. Néanmoins, au chargement de la scène, les deux caméras partagent les même attributs et la fenêtre possède la même résolution que l'image finale. À l'exécution, vous pouvez appuyez sur la touche 'c' pour copier la caméra OpenGL vers celle de la scène, ce qui vous permet d'avoir une représentation OpenGL de la scène qui va être rendue. (Attention, la résolution de la caméra correspondra alors à celle de la fenêtre.)

2.2. Utilisation

Lorsque la touche 'l' est pressée, le Viewer ouvre une fenêtre pour choisir une fichier se terminant par l'extension ".scn" et charge le fichier de scène sélectionné (méthode loadScene). Vous pouvez également déposer directement le fichier sur la fenêtre pour obtenir le même résultat. Enfin, vous pouvez passer le fichier en ligne de commande à l'exécutable pour le charger au démarrage. Testez par exemple avec le fichier "troisSpheres.scn" :

$ ./sia_raytracer troisSpheres.scn
Vous devriez obtenir le rendu OpenGL suivant :
OpenGL

Exécutez le rendu par lancer de rayons avec la touche 'r', vous obtenez l'image suivante :
Trois spheres

Remerciements

Ce projet est basé sur ceux de Gilles Debunne, Gaël Guennebaud et de Wenzel Jakob.