Réalisation du lancer de rayons
>> Lancer un rayon
>> Antialiasing
>> Atténuation de la lumière
>> Ombres floues

>> Bruit de Perlin
>> Textures procédurales
>> Textures mapping
>> Bump mapping avec du bruit de Perlin
>> Résolution d'intersections pour les formes de bases
>> Résolution d'équations algébriques de degrés 4
>> Ce qui reste à faire ...


DETAILS DES CALCULS POUR LE LANCER DE RAYONS

Le rendu par lancer de rayons est un processus récursif et additif, c'est à dire qu'à chaque intersection d'un rayon avec un objet, un ou plusieurs nouveaux rayons sont lancés dans des nouvelles directions fonctions des propriétés matérielles de la surface de l'objet, et que la valeur finale de la lumière au point d'intersection est la somme de la lumière directe en ce point, et de la lumière apportée par les rayons ré-émis.



· Modélisation d'un rayon : Un rayon représente le trajet d'un photon, et donc n'a pas d'épaisseur. Il est modélisé dans l'algorithme de lancer de rayons par une demi-droite de l'espace, c'est à dire un point de départ D et une vecteur directeur V. Ces deux paramètres sont suffisants pour obtenir une équation paramétrée du rayon en fonction du temps. Les points P(x,y,z) par lesquels passe le rayon sont définis par P = D + tV, avec t>=0 . Une fois un rayon lancé, il suffit de calculer son intersection avec les objets de la scène.


· Calcul d'intersections : Le calcul de l'intersection d'un rayon avec les objets de la scène est la base de la technique de lancer de rayons. En effet, pour chaque objet composant la scène, il faut il faut obtenir les coordonnées de son intersection (si elle existe) avec le rayon, et ne retenir que l'objet le plus proche de la source du rayon. Ce calcul d'intersection ne peut se faire que si l'on connaît l'équation implicite de la surface représentant l'objet, de la forme S(x,y,z)=0. Pour connaître le point d'intersection avec le rayon lancé, il suffit de remplacer respectivement x, y et z de cette équation par Dx+t.Vx , Dy+t.Vy et Dz+t.Vz , et de trouver la valeur t solution. Une fois les coordonnées du point d'intersection connues, il est facile de calculer la distance de ce point au point de départ du rayon, et donc de déterminer l'objet le plus proche de la source du rayon.

· Normale au point d'intersection : Pour pouvoir connaître les directions des nouveaux rayons à émettre depuis le point d'intersection, il est indispensable de connaître la valeur de la normale à la surface en ce point. Ce calcul est évident pour les formes géométriques de base, mais peut nécessiter des calculs plus poussés pour les objets plus complexes.


L'ANTIALIASING


sans et avec anti-aliasing

La méthode de raytracing reposant sur l'utilisation de rayons de diamètre nul, il se crée sur les bords des objets un phénomène d'aliasing peu réaliste. Pour éviter cet effet de 'marches d'escalier', il existe plusieurs méthodes.

· Méthode naïve : Au lieu de lancer un seul rayon par pixel, qui prendra pour intensité la valeur de l'illumination du rayon passant par son centre, on lance plusieurs rayons (4, 9, 16…) par pixel, uniformément répartis sur la surface du pixel. La valeur finale de l'illumination est la moyenne des valeurs de chaque rayon. Cette méthode donne de bons résultats, mais multiplie les temps de calcul par le nombre de rayons que l'on décide de lancer. La plupart des pixels n'ayant pas besoin d'être antialiasés, cette méthode est trop lourde pour être acceptable.
· Méthode utilisée par notre programme: Puisqu'il est inutile d'effectuer de l'antialiasing pour tous les pixels, on ne lance qu'un seul rayon par pixel. Par contre, la valeur de l'intensité trouvée pour ce pixel est comparée à celle du pixel à gauche et du pixel au dessus. Si un écart trop important est trouvé entre ces trois intensités (supérieur au paramètre AA_THRESHOLD du fichier de description de scène), on lance un nouveau rayon au hasard dans la surface du pixel, et l'on moyenne avec la valeur précédente. Si la nouvelle valeur du pixel est encore trop éloignée de la valeur des deux voisins, on continue de lancer des rayons au hasard dans le pixel et de moyenner avec la valeur précédente, jusqu'à arriver soit à la profondeur maximale autorisée (paramètre ANTIALIASING du fichier de description de scène), soit jusqu'à obtenir une valeur conforme au paramètre du fichier de description de scène. Cette méthode n'est pas infaillible, mais permet dans une large majorité de cas d'obtenir un résultat acceptable, et possède l'avantage de ne pas engendrer de calculs inutiles ralentissant l'exécution du programme.


ATTENUATION DE LA LUMIERE

· Principe : L'atténuation de la lumière peu être spécifiée à l'aide de trois coefficients c1, c2 et c3 et ce uniquement pour les lumière ponctuelles.
Un facteur d'atténuation fd est alors applique à chacune de ces sources lumineuses, fd étant défini comme suit :

Fd = min( 1 / ( c1 + c2 * dist + c3 * dist*dist ) , 1 )
Dist étant la distance du point à illuminé à la source lumineuse considérée.


sans et avec atténuation

 

· Dans notre raytracer
Voici comment spécifier l'atténuation pour une source lumineuse dans notre raytracer :

Light_source {
Location <x,y,z>
Color <r,g,b>
Attenuation <c1,c2,c3>
}


OMBRES FLOUES

· Principe : Le principal problème dans une image rendue par lancer de rayon est que les zones d'ombres propres et portées des objets sont très franches. Il est cependant possible, mais pour un coût plus élevé en calculs, de rendre celles-ci plus ou moins floues en utilisant une petite astuce.
Celle-ci consiste à lancer pour chaque pixel de l'image, non un seul, mais plusieurs rayons d'ombres vers chaque source lumineuse considérée, en faisant bouger à chaque fois de façon aléatoire ces mêmes sources lumineuses. Chaque rayon d'ombre lancé va ou non apporter de la lumière sur le pixel en question, une simple moyenne suffit alors à déterminer la luminosité finale.
Les pixels situés à la limite d'une zone d'ombre vont donc se trouver parfois illuminés et parfois dans l'ombre, et vont donc constituer une limite en dégradé partant de l'ombre à la lumière. Si les ombres floues sont activées , il est alors préférable d'activer également l'antialiasing afin de lisser encore plus les limites ombres -lumières.


sans et avec ombres floues

· Dans notre raytracer

Voici comment spécifier les ombres floues dans notre raytracer :

Global_setting {
Soft_Shadow x
Shadow_Ray y
}

y est un entier indiquant le nombre de rayons d'ombres à générer pour chaque pixel
x est un nombre décimal indiquant l'intensité du déplacement des sources lumineuses. Par exemple, si x vaut 2, cela signifie que les sources lumineuses vont se déplacer dans un cube de taille 2, centré sur leur position initiale.

Bien entendu, plus x est grand plus les ombres sont floues, mais plus il faut augmenter y pour que les transitions soient uniformes.


LE BRUIT DE PERLIN

Afin de modéliser des motifs complexes (nuages, marbres …), il est nécessaire d'avoir à disposition des algorithmes capables de les générer de façon automatique. Pour introduire une grande diversité et par ailleurs un meilleur réalisme, ces algorithmes se doivent d'introduire de l'aléatoire dans ces motifs. C'est l qu'intervient le bruit de Perlin, car bien entendu, il ne s'agit pas de générer des motifs totalement aléatoire, car on obtiendrait bien sur, dans ce cas, un motif " moucheté " du plus mauvais effet. Le bruit de Perlin, qualifié de bruit cohérent, permet de palier ce problème.

Bruit totalement aléatoire

Bruit de Perlin


De plus, il présente l'avantage de pouvoir s'écrire sous le forme fNoise(x,y,z) et ainsi d'associer directement à un point dans l'espace, une valeur qui peu ensuite être convertie en couleur (pour faire de la texture procédurale) ou en hauteur (pour faire du bumpmapping), ce qui est très pratique pour l'insérer dans un moteur de raytracing. En effet la plupart des algorithmes de génération de bruit cohérent (pour générer des montagnes fractales par exemples), créent les motifs de proche en proches de façon récursive et ne peuvent donc associer de façon directe une valeur à un point.

Le principe pour générer du bruit de Perlin est d'additionner plusieurs fonctions de bruit d'amplitude et de fréquence différentes, ce qui permet d'avoir cet effet de cohérence dans le bruit généré. Voici un exemple ci-dessous :


Bruit de Perlin avec fonction à une dimension :

Bruit de Perlin avec fonction à deux dimensions :


TEXTURE PROCEDURALE

Il s'agit ici de déterminer la couleur de tout point de coordonnées (x1, x2, x3) dans le repère de la scène à l'aide d'une procédure.
On peut ainsi définir toute sorte de procédures. En voici 2 exemples :

· Checker
Le checker est défini par 2 couleurs c1, c2 et la longueur L des carreaux.
Ici on calcule la parité pi de la partie entière de xi / L.
Si xi < 0 alors on prends la parité inverse.


Voici l'algorithme déterminant la couleur en fonction des pi :


· Perlin
La couleur d'un point peut être donnée à partir d'un bruit de Perlin. Ceci nous permet d'obtenir des textures très réalistes comme le bois ou le marbre.

· Bruit de perlin seul

Pour appliquer le bruit de perlin comme texture procédurale, il suffit de demander pour chaque point (x,y,z) dont on veut connaître la couleur, la valeur n de fNoise(x,y,z). Selon la valeur de n, on interpole alors une couleur à partir d'une table de couleur.

Par exemple :

Soit la table de couleur suivant :

Si n = v1 alors couleur = (r1,g1,b1)
Si n = v2 alors couleur = (r2,g2,b2)
Si n = v3 alors couleur = (r3,g3,b3)
Si n = v4 alors couleur = (r4,g4,b4)

Avec v1 < v2 < v3 < v4

Si v2 < n < v3, alors on prendra comme la couleur (r,g,b) avec :
r = r2 * (n-v2) / (v3-v2) + r3 * (v3-n) / (v3-v2)
g = g2 * (n-v2) / (v3-v2) + g3 * (v3-n) / (v3-v2)
b = b2 * (n-v2) / (v3-v2) + b3 * (v3-n) / (v3-v2)

· Effet marbré

Pour obtenir un effet marbre, il suffit de remplacer fNoise(x,y,z) par :

fMarbre(x,y,z) = cos( x + fNoise(x,y,z) )

· Effet bois

Pour obtenir un effet bois, il suffit de remplacer fNoise(x,y,z) par :

v = 20 * fNoise(x,y,z)
fBois(x,y,z) = v - partieEntiere(v)

· Dans notre raytracer

Voici comment spécifier ces textures procédurales dans notre ray-tracer :

Objet {
PARAMETRES_OBJET
Pigment {
Perlin {
amplitude,frequence,octave,intensite
}
Scale <sx,sy,sz>
Rotate <rx,ry,rz>
Translate <tx,ty,tz>
}
}

intensite est un coefficient multiplicateur appliqué au bruit
octave est le nombres de fonctions de bruit additionnée
frequence est la fréquence de la fonction de bruit
amplitude est l'amplitude de la fonction de bruit

rotate, translate et scale sont des transformation apportées sur le motif de la texture procédurale.

Pour l'effet marbre il suffit de remplacer le mot clé perlin par marble et pour l'effet bois de remplacer perlin par wood.


TEXTURE MAPPING

Le principe est d'appliquer une image sur une surface, de façon répétitive ou non.
Il faut donc faire correspondre un point de l'image à chaque point de la surface considérée.

Pour permettre cette correspondance il nous faut tout d'abord déterminer un paramétrage de la surface :
x = fx(u, v)
y = fy(u, v)
z = fz(u, v)

Puis nous faisons correspondre à (u, v) un pixel (i, j) de l'image.


· Mapping sur le cercle

Les équations paramétriques du cercle sont les suivantes :

x = r sin u
y = r cos u sin v
z = r cosu cos v
u varie dans [-pi /2, pi/2] et v varie dans [0, 2pi[.

Soit n la fréquence de répétition de l'image sur la sphère, H la hauteur de l'image, L sa largeur.

Calcul du pixel i correspondant au paramètre u :
t = n ( u / P + 0.5)
i = PartieEntière ( H (t-PartieEntière(t))

Calcul du pixel j correspondant au paramètre v :
t = n ( u / (2P))
j = PartieEntière ( L (t-PartieEntière(t))

Pour n = 1 on obtient le résultat suivant :

· Mapping sur le plan

Les équations paramétriques du plan d'équation x = 0 sont les suivantes :
x = 0
y = v + n/2
z = u - m/2
Soit n la largeur désirée de l'image sur le plan, H la hauteur de l'image, L sa largeur initiale et m la hauteur sur le plan égale à n.H / L.

Calcul du pixel i correspondant au paramètre u :
t = m.PartieEntière(u / m)
i = PartieEntière ( H (u - t) / m)
si i < 0 alors i = -i.

Calcul du pixel j correspondant au paramètre v :
t = n.PartieEntière(v / n)
j = PartieEntière ( L (v - t) / n)
si j < 0 alors j = -j.


Ainsi, dans le cas suivant :
- camera "orthographique" définie avec une largeur de projection de n,
- un plan parallèle à la camera à qui on applique l'image avec une largeur de n,
- une image de sortie de la même taille (H.L pixels) que l'image appliquée sur le plan,
on obtient une image de sortie identique à l'image appliquée, hormis les effets de lumières, d'ombres, de réflexions ou de transparences apportés par le lancé de rayon.


BUMP MAPPING avec du bruit de Perlin

· Principe
Le bump-mapping est une technique permettant de donner un aspect non lisse à un objet géométriquement lisse. Il consiste en l'altération des normales d'un objet, ce qui modifie par la même l'illumination des points de l'objet situés à la base de la normale.

La technique utilisée ici est d'utiliser le même bruit de perlin que pour les textures procédurales, mais de considérer les valeurs fournis par la fonction de bruit, non pas comme une couleur, mais comme une hauteur.

· Dans notre raytracer

Voici comment spécifier du bump-mapping dans notre raytracer :
Objet {
PARAMETRES_OBJET
Pigment { … }
Normal {
Perlin {
amplitude,frequence,octave,intensite
Scale <sx,sy,sz>
Rotate <rx,ry,rz>
Translate <tx,ty,tz>
}
}
}

Pour modifier la normale au point (x,y,z) d'une surface, on crée le vecteur v dont les directions x,y et z suivent les variations du bruit de perlin autour d'un point considéré :

V[0] = fNoise (x-e,y,z) - fNoise (x+e,y,z)
V[1] = fNoise (x,y-e,z) - fNoise (x,y+e,z)
V[2] = fNoise (x,y,z-e) - fNoise (x,y,z+e)

On altère ensuite la normale au point (x,y,z) en lui additionnant v. Reste seulement a choisir les bon paramètres pour générer le bon bruit de perlin !


RESOLUTION D'INTERSECTIONS POUR LES FORMES DE BASES
L'algorithme de raytracing engendre un nombre très important de calculs sur des nombres réels, dont la majeure partie consiste à déterminer des intersections objet-rayon. La résolution des problèmes d'intersections revient essentiellement à trouver les racines de polynômes, jusqu'au 4eme degré pour le tore et le cube troué.

Les calculs d'intersections et de normales
Chaque objet de la scène est défini dans son repère propre. Lors de la description de la scène, les paramètres de translation, rotation et mise à l'échelle permettent de définir une matrice de transformation propre à chaque objet, qui permet de passer des coordonnées de points ou de vecteurs dans le repère absolu à celles dans le repère de l'objet, et inversement. Cette transformation se fait grâce à des produits matrice-vecteur, et n'est possible que parce que l'on utilise des coordonnées homogènes pour représenter les points de l'espace.
Pour chaque objet, il suffit de définir les méthodes calculant l'intersection avec un rayon et la normale en un point ; il est donc aisé d'ajouter de nouveau objets au programme.
Dans la fonction intersection, on calcule le point d'intersection entre le rayon et l'objet. Pour cela, il est tout d'abord nécessaire d'appliquer la matrice inverse de transformation sur le rayon pour que ce dernier soit dans le même repère que l'objet. Par la suite, il faut effectuer le calcul d'intersection propre à chaque objet, que l'on détaille en annexe. Pour finir, on applique la matrice de transformation sur le point d'intersection pour le ramener dans le repère de la scène, et on retourne la valeur de la distance entre ce point d'intersection et le point origine du rayon.
Pour le calcul de la normale au point d'intersection, il est également nécessaire d'utiliser la matrice inverse de transformation pour placer le point d'intersection dans le repère de l'objet. Le rayon est transformé dans le repère de l'objet. Puis on prend en compte le côté d'où provient le rayon par rapport à l'objet (intérieur, extérieur, dessus, dessous) à l'aide du produit scalaire du rayon et de la normale. Pour finir, on normalise cette normale, et on lui applique la matrice de transformation pour la replacer dans le repère de la scène.
Les équations mathématiques à résoudre pour chaque objet sont détaillées ci-dessous.

>> cone
>> cube
>> cubeTroue
>> cylindre
>> parallélogrammme
>> plan
>> prisme
>> pyramide
>> sphere
>> tore
>> triangle


RESOLUTION D'EQUATIONS POLYNOMIALES DE DEGRES 4
Pour trouver les racines de polynômes jusqu'au degré 4, nous utilisons la méthode de François Vieta, qui date de 1735.

Soit à résoudre

Le cas où a3 et a4 sont nuls, ce qui revient à résoudre une équation du 2nd degrés, n'est pas traité ici. En effet ce cas est considéré comme trivial.
Ici la résolution proposée est purement algébrique.

Cas où a4 est nul et a3 non nul : résolution d'une équation du 3ième degrés


Cas où a4 est non nul : résolution d'une équation du 4ième degrés
Methode de Francois Vieta (1735)




CE QUI RESTE A FAIRE...
Notre programme permet d'obtenir des images photoréalistes de qualité, meilleures que l'algorithme du z-buffer combiné avec ceux de Gouraud ou Phong qui ne prennent pas en compte la réflexion et la réfraction. D'autre part, le lancer de rayon ne se limite pas comme les algorithmes précédents à la seule visualisation des modèles facétisés, c'est à dire composé exclusivement de polygones. Il permet la visualisation de surfaces implicites. Cependant, les temps de calculs sont généralement considérablement longs et la visualisation ne peut pas se faire en temps réel comme avec le z-buffer. De plus il est de moins bonne qualité que l'algorithme de radiosité qui modélise mieux les phénomènes de transport d'énergie.

L'une des améliorations à apporter à notre programme est d'implémenter le lancer de rayons arrière. Cette méthode consiste à lancer des rayons depuis les sources de lumière vers la scène, permettant de déterminer les sources de lumière secondaires (la réflexion de la source de lumière sur un miroir par exemple), afin d'obtenir des résultats plus réalistes.

Notre " raytracer " traite des scènes comportant des objets de base relativement simples, mais néanmoins suffisants pour construire des scènes intéressantes. Il serait possible de rajouter quelques objets de bases supplémentaires, tels que :
· les ellipsoïdale, hyperboloïdale (formes super-quadratiques) d 'équations implicites de la forme : (x/a)2/p + (y/b)2/p + (z/c)2/q = 1 ou (x/a)2/p + (y/b)2/p - (z/c)2/q = 1
· les meta-balls d 'équations implicites de la forme : a1 * exp ( - b1 * f1 (x,y,z)) + ... + an * exp ( - bn * fn (x,y,z)) = 0.
· des objets plus complexes tels que les Constructive Solid Geometry (C.S.G.), arbres dont les feuilles sont des volumes simples.

Le calcul d'intersections constituant la majeure quantité de calculs à effectuer, il y aurait plusieurs façons pour pouvoir accélérer cette partie. En effet, nous avons ici traité la méthode naïve consistant à calculer l'intersection d'un rayon avec toutes les surfaces de la scène puis à sélectionner la plus proche. La première optimisation est de diminuer le nombre de rayons primaires en projetant les objets de la scène sur l'écran. La deuxième est l'utilisation des volumes simples englobants un ensemble d'objets, à savoir que si un rayon n'intersecte pas ce volume, il est inutile de faire le calcul d'intersection avec tous les objets qu'il contient. Mais la génération de ces volumes englobants s'est avérée problématique. Une autre solution est de décomposer l'espace en voxels, puis à l'aide de méthodes incrémentales (de type Bresenham) on détermine la succession de voxels rencontrés par le rayon et donc les objets susceptibles d'intersecter le rayon, ce qui réduit les temps de calcul. Pour obtenir une bonne efficacité, il faut un grand nombre de voxels et le coût mémoire sera donc énorme. La technique de l'arbre octal tente de palier à cet inconvénient : la scène est divisée en 8 voxels jointifs, et eux-même divisés en sous voxels, récursivement tant qu'un voxel contient un nombre trop important d'objets. Cette méthode permet de créer un arbre non-symétrique où 8 branches partent de chaque nœud, et où chaque feuille est une liste d'objets contenus dans le voxel correspondant. L'arbre octal réduit considérablement le coût mémoire de par la moindre occupation des zones vides.


Auteurs : Thomas Bonfort, Delphine Chaigneau, Olivier Galizzi, Laure Heigeas