Lancer de rayons : Introduction

1. Structure du projet

Téléchargez et décompressez l’archive : mds3d_td1.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, différentes classes seront à compléter et certaines à créer de toutes pièces.

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
  • 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.
  • 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 : l'interface utilisateur.
  • 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.
    • 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).

Remarque : si la commande cmake n'est pas disponible, ajoutez le chemin "/opt/local/bin" à votre PATH (avec la commande export, dans le terminal ou dans votre fichier d’environnement).

Créez un répertoire de compilation à l'extérieur du dossier contenant les sources, configurez et compilez :

$ mkdir build
$ cd build
$ cmake ../mds3d_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 ../mds3d_td1 -DCMAKE_BUILD_TYPE=Debug

À l'issue de la compilation, vous obtiendrez un exécutable m3d_raytracer.

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>

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" (dans le répertoire data :

$ ./m3d_raytracer ../m3d_td1/data/troisSpheres.scn
En pressant la touche 'r', vous devriez obtenir une image noire que vous pouvez sauvegarder au format EXR ou PNG.