Initiation aux shaders : RayMarching (Partie 2)
Lecture 5 min. âą
Table des matiĂšres

On a vu dans la partie prĂ©cĂ©dente comment manipuler les pixels dâune image grĂące au langage de shader.
Il est temps de sâattaquer Ă du bien plus intĂ©ressant: la 3D ! đ
Lâalgorithme de RayMarching
LittĂ©ralement âmarchĂ© de rayonâ.
LâidĂ©e est trĂšs Ă©lĂ©gante. Elle se base entiĂšrement sur le principe des SDF (Signed Distance Field).
On a comme situation de dĂ©part un oeil situĂ© au centre de lâĂ©cran mais reculĂ© dâune certaine distance.
La situation est représentée vu de haut.
On veut afficher notre objet en bleu.

On choisi un pixel sur notre écran.

On détermine un rayon directeur entre notre oeil et le pixel.

GrĂące Ă la SDF de notre solide on dĂ©termine la distance de lâobjet par rapport Ă notre oeil et on trace un cercle du rayon de la distance nous sĂ©parant de lâobjet. Autrement dit le point du solide le plus proche de lâoeil.

Puis on trace un rayon infini partant de lâoeil et dans la direction du vecteur. Le point dâintersection M entre le cercle que lâon vient de tracer devient notre nouveau point de dĂ©part.

On rĂ©itĂšre lâopĂ©ration de ce point M comme oeil.

On récupÚre un point O et rebelote.

Lorsque le diamĂštre du cercle obtenu est suffisamment petit, on considĂšre que lâon a touchĂ© lâobjet.
On somme lâensemble des distances trouvĂ©es prĂ©cĂ©demment et lâon retourne la somme.
Il existe le cas ou le rayon ne touche pas lâobjet alors le diamĂštre du cercle va ĂȘtre trop grand.

Si la distance est trop grande on la renvoie la distance maximale.
Ce qui est important de comprendre câest que lâon va efectuĂ© cette algorithme un trĂšs grand nombre de fois et plusieurs fois par frame. Autant optimiser tout ce qui peut lâĂȘtre. đ
Implémentation
Pour nous faciliter la vie on va définir quelques constantes:
#define MIN_DISTANCE 0.001
#define MAX_DISTANCE 20.
#define MAX_ITERATIONS 50Pui on implĂ©mente lâalgorithme de raymarching en lui-mĂȘme.
float rayMarch(vec3 rayOrigin, vec3 rayDirector) {
// Initialisation des distances
float accumulatedDistance = 0.0;
float computedDistance = 0.0;
vec3 currentPoint;
// Boucle de l'algorithme d'au plus MAX_ITERATIONS
for(int i = 0; i < MAX_ITERATIONS; i++) {
// Calcul du point courant
currentPoint = rayOrigin + rayDirector * accumulatedDistance;
// Calcul de la distance du point courant par rapport Ă une sphĂšre
// de rayon 0.5 et décalé de 0.5 vers la droite et avancé de 1.8 de l'écran
computedDistance = length(currentPoint - vec(0.5, 0, 1.8)) - 0.5;
// On accumule la distance trouvée
accumulatedDistance += computedDistance;
// Si la distance trouvé est suffisamment faible on stoppe l'algorithme
if ( computedDistance < MIN_DISTANCE ) {
break;
}
// Si la distance trouvée est trop grange on retourne la distance
if( accumulatedDistance > MAX_DISTANCE ) {
return MAX_DISTANCE;
}
}
return accumulatedDistance;
}Puis on calcule les paramĂštres de notre algorithme
// Calcule des UV de l'écran
vec2 uv = (fragCoord- 0.5*iResolution.xy) / iResolution.y;
// La caméra est au centre du monde
vec3 eye = vec3(0);
// L'écran est décalé de 1
vec3 pixel = vec3(uv, 1.0);
// Calcul du rayon directeur de l'oeil traversant le pixel
vec3 rayDirector = normalize(pixel - eye);Le rayon directeur doit-ĂȘtre unitaire. DâoĂč lâutilisation de la mĂ©thode normalize.
Pour rappel les UVs sont des coordonnĂ©es dont lâorigine est au centre de lâĂ©cran et dont les coordonnĂ©es sont transformĂ© pour que lâaxe des Y dans un ratio 16:9 soit situĂ©s entre [-1, 1].
On applique en suite lâalgorithme de raymarching avec les paramĂštres calculĂ©s.
// On applique l'algorithme de raymarching Ă ce pixel
float d = rayMarch(eye, rayDirector);On dĂ©sire afficher cette distance sous la forme dâune composante de couleur. On doit donc compresser les distances pour les faire rentrer dans lâintervalle [0, 1].
Et on affiche sous forme de niveau de gris notre sphere.
// On tasse les distances pour les faire rentrer dans l'intervalles [0, 1]
d /= MAX_DISTANCE;
// On affiche
fragColor = vec4(d, d, d, 1.0);
Et cette fois-ci on affiche une sphĂšre et non pas un disque! Bienvenu dans la 3Ăšme dimension !! đ
Pour sâen convaincre on peut booster le contraste et la luminositĂ© de lâimage.

On remarque des cercles concentriques dĂ©calĂ©s vers la gauche. Plus le disque est sombre plus il est proche dâune distance de 0.
On est bien en face dâun objet dans la 3Ăšme dimension.
Plusieurs objets dans notre scĂšne
Notre monde est un peu vide, il est temps de le remplir.
Dessiner un plan horizontal
Un plan horizontal est dĂ©fini par lâensemble des points qui sont Ă la mĂȘme hauteur.

On défini la SDF du plan
float horizontalPlan(float height, vec3 point) {
return point.y - height;
}Elle prend en paramĂštre la hauteur du plan par rapport au plan XZ de notre monde. Ainsi que le point dont on cherche la distance par rapport au plan.
Pour tester notre code on va opérer une petite modification.
On va définir la SDF de ce plan.
float horizontalPlan(float height, vec3 point) {
return point.y - height;
}Puis une fonction scene qui prend en compte le point dont lequel on désire connaßtre la distance par rapport à la scÚne.
float scene(vec3 point) {
// Un plan 0.2 unité plus bas que le plan XZ
return horizontalPlan(-.2, point);
}On va par la mĂȘme occassion se dĂ©finir une SDF pour la sphĂšre.
float sphere(vec3 center, vec3 point, float rayon) {
return length(point - center) - rayon;
}Et on modifie la fonction de raymarching
float rayMarch(vec3 rayOrigin, vec3 rayDirector) {
// Initialisation des distances
float accumulatedDistance = 0.0;
float computedDistance = 0.0;
vec3 currentPoint;
// Boucle de l'algorithme
for(int i = 0; i < MAX_ITERATIONS; i++) {
// Calcul du point courant
currentPoint = rayOrigin + rayDirector * accumulatedDistance;
// Calcule de la distance du point courant par rapport Ă la scene
computedDistance = scene(currentPoint);
// On accumule la distance trouvée
accumulatedDistance += computedDistance;
// Si la distance trouvé est suffisamment faible on stoppe l'algorithme
if ( computedDistance < MIN_DISTANCE ) {
break;
}
// Si la distance trouvée est trop grange on retourne la distance
if( accumulatedDistance > MAX_DISTANCE ) {
return MAX_DISTANCE;
}
}
return accumulatedDistance;
}Ce qui nous permet dâafficher ceci đ

On obtient bien ce qui est attendu. la ligne dâhorizon tend bien vers le plan car elle se trouve Ă une distance trop importante.
Afficher plus dâun Ă©lĂ©ment.
Pour afficher plus dâun Ă©lĂ©ment il faut se reprĂ©senter les choses ainsi

Partant du point P et suivant ce rayon précis la sphÚre est plus proche de P que le plan (orange).
Autrement dit la SDF de la sphĂšre va renvoyer une distance plus faible que le plan.
MathĂ©matiquement ce concept peut-ĂȘtre reprĂ©sentĂ© par le minimum des deux distances.
float scene(vec3 point) {
float dSphere = sphere(vec3(0.5, 0, 2.), point, .7);
float dPlan = horizontalPlan(-.2, point);
return min(dSphere, dPlan);
}Ce qui nous permet dâafficher les deux Ă©lĂ©ments.

Mais il manque quelque choseâŠ
De la couleur ! đ€Ą
Conclusion
On a appris Ă afficher trĂšs sommairement des solides dans un environnement 3D. Et sans bibliothĂšques complexes.
La prochaine fois on rajoutera de la couleur, des ombres et de la lumiĂšre!
Je vous donne aussi le lien de mon shadertoy.
Je vous remercie de mâavoir lu đ.