Par l'équipe Codermind. |
|
Vous avez toujours voulu savoir pourquoi vous souffriez d'artefacts de profondeur ("z-fighting") même lorsque vous utilisiez un zbuffer 24 bits dans votre application 3D ? Ou êtes vous intéressé de connaitre la représentation interne de votre Z-Buffer et le souci du détail ne vous fait pas peur ? Alors lisez attentivement ce qui suit.
Ce qu'il faut savoir
Tout d'abord il est nécessaire de rappeler que la valeur qui est stockée dans un tampon de profondeur ("zbuffer") hardware ou software est une valeur discrète et non pas continue. La plupart des gens l'auront deviné (au moins les programmeurs). Il y a généralement deux versions utilisés en hardware, une qui utilise 16 bits de précision et une utilisant 24 bits. Certaines cartes graphiques supportent un tampon de profondeur de 32 bits, mais ce n'est pas à cette date habituel.
Les valeurs stockés le sont sous une forme opaque (tout ce qu'on sait c'est qu'on peut les comparer entre elles), mais pour des raisons de confort pour la suite et par convention on va dire que c'est une valeur normalisée qui varie entre 0 et 1 inclus avec une distribution de valeur uniforme (si n est le nombre de bits de précision, alors on peut dire que la valeur stockée dans le zbuffer est un nombre à précision fixe qui varie par incréments de 1/(2^n -1)).
Maintenant pour rendre les choses plus compliquées : après que la carte graphique a fait le transform and lighting, votre hardware va devoir translater les coordonnées résultantes x,y,z,w en une valeur qui tient dans notre z buffer.
C'est ici que la valeur znear (z le plus proche) et zfar (z le plus lointain) vont être pris en compte.
Contrairement à ce que vous auriez pu deviner d'après son nom, le z buffer ne contient pas la coordonnée z de votre jeu de coordonnées homogènes. Mais il contient la valeur de Z divisé par w, z/w.
Pour quelle raison a-t-on z/w ?
Une des raisons est que la valeur z/w est interpolée linéairement sur un triangle (contrairement aux autres valeurs qui doivent respecter la correction perspective). Cela veut dire que si l'on a les valeurs de z/w aux trois sommets d'un triangle, on peut stocker les valeurs interpolées linéairement sur le triangle dans le z buffer. Cela ne serait pas possible avec seulement z ou seulement w. Pour ces valeurs là vous auriez à faire la correction perspective, c'est à dire interpoler linéairement z/w et 1/w dans l'espace écran et les diviser entre eux à chaque pixel pour reobtenir la "vraie" valeur de z. Pourquoi ne pas stocker directement la valeur non perspective correcte de z ? Parce que is l'on fait cela l'intersection et l'occlusion ne pourraient plus être perspective correct et les lignes d'intersection en théorie droites pourraient apparaître cassée par exemple. Bien entendu, sur le hardware moderne faire la correction perspective n'est pas si cher que cela, sachant qu'il faut déjà le faire pour un grand nombre de données de couleur, de coordonnées de textures, d'éclairage etc. (Il est probable qu'historiquement c'était probablement un gain de zapper la correction perspective)
L'autre raison est que stocker une telle valeur aide à apporter une plus grande précision près de l'observateur (où les polygones couvrent une plus grande portion de l'écran à taille égale). Alors que stocker un z "linéaire" vous donnerait une précision uniforme.
(C'est là qu'il existe des alternatives, comme le w buffer qui stocker le "z linéaire" (ou le "w linéaire") mais ils ont été dépréciés et rendu obsolètes sur les hardwares graphique depuis quelques temps.)
Maintenant pourquoi cette redistribution de la précision ?
Ok, donc on a retenu z/w et que se passe-t-il alors ? Comme je l'ai dit précédemment le zbuffer contient des nombres entre 0 et 1. Cela vous donne deux extrémités, une qui doit être proche de l'observateur et une qui doit être éloignée. Si le résultat de la division de z par w est moindre que zéro alors le sommet doit être coupé : c'est le plan "proche" (near plane). Si le résultat de la division de z/w est plus que un alors le vertex est également coupé : c'est le plan "lointain" (far plane).
De ce qui précède vous retirez deux équations. La première est que z/w = 0 pour le plan proche. La deuxième est que z/w = 1 pour le plan lointain. Maintenant si vous exprimez z comme une variation linéaire de w (ce qui est toujours le cas dans la projection classique utilisée par les cartes graphiques), cela vous donne la relation dérivée suivante :
Vous devez simplement résoudre les deux équations données précédemment pour retomber sur ce résultat (nous laissons la résolution de l'équation comme un excercice pour le lecteur).
Ici vous pouvez voir ce qui se passe si l'on trace la variation de z et de w en fonction de la distance par rapport au plan de l'observateur :

La forme de z/w
Donc la valeur que l'on stocke dans le z buffer est égale à :

(L'image représente une courbe hyperbolique).
Comme vous pouvez le voir, la courbe part de 0 pour w = wnear et elle arrive à 1 pour w = wfar, et à mesure que vous vous approchez de l'infini, la courbe tend assymptotiquement vers la valeur wfar/(wfar - wnear).
Bien entendu la courbe n'a pas l'air si mauvaise que ça sur l'image précédente, la portion qui est entre wfar et wnear n'est pas une rampe linéaire mais s'en approche suffisamment pour que la distribution des valeurs soit raisonnable.
Mais imaginez maintenant que nous approchions wnear de 0, ceci sans déplacer wfar. Alors la limite wfar/(wfar - wnear) tend à s'approcher de 1. Et le facteur de mise à l'échelle (wnear * wfar)/(wfar - wnear) se rapproche de zero. Donc le résultat est une courbe hyperbolique très fortement compressée verticalement, compressée vers la valeur 1.
Encore une fois, pas tout à fait exact, mais pour vous donner une image générale de la forme de la courbe :

Le plus vous approchez wnear de zero, le plus vous compresserez votre fonction hyperbole. Si vous essayez de comparer la différence de précision pour les objets proches et les objets loins, vous remarquez que quasiment toutes les valeurs possibles du z buffer couvrent les objets les plus proches et seulement un très petit pourcentage couvre les objets distants.
Exemples rapides
Imaginez que vous mettiez la valeur wnear à 1,0 et la valeur wfar à 10,0. À quelle distance se trouve le point médian ? Après un calcul rapide vous déterminez que la moitié des valeurs possibles prises par le z buffer sont situés entre le point 1,0 et 1,8. Le reste étant consommé par l'intervalle 1,8 à 10,0.
Maintenant imaginez que vous mettiez la valeur wnear à 0,01 et la valeur wfar toujours à 10,0. Après le même calcul .01 et cette fois ci vous arrivez à la conclusion que la moitié des valeurs de votre z buffer sont consommées entre 0.01 et 0.02. Pour en rajouter sachez que 90% des valeurs prises par le z buffer sont consommées entre 0.o1 et 0.1.
Vous pouvez voir que si votre scène 3D a la majorité de ses objets plus lointains que 0.1 ou aucun objet plus proche que 0.1 du plan qui contient l'observateur, alors 90% de la précision de votre tampon est gachée.
Que se passe-t-il si l'on bouge la valeur wfar à la place ? Gardons donc wnear à 1.0 et repoussons wfar à 1000.0. Dans ce cas la moitié des valeurs de profondeurs seront consommés entre 1.0 et 2.0.
Pour parler plus généralement, la distance médiane est simplement :
Ce qui est plus intéressant c'est de connaitre quelle fraction de l'espace couvert par notre z buffer (espace visible entre wnear et wfar) cela représente, pour cela vous pouvez calculer le pourcentage suivant :
Vous l'aviez probablement deviné, cette fraction est une fonction à la fois de wfar et wnear. Elle se réduit quand wnear devient plus petit et aussi lorsque wfar devient plus grand. Plus précisemment quand wnear << wfar (très petit comparé à) ce pourcentage est quasiment égal à wnear/wfar. Quand wnear ~ wfr (presque équivalent à) ce pourcentage tend vers 50% mais le résultat c'est que les valeurs prises par z sont très compressé et l'intervalle visible est plutot petit.
Comment garder votre z buffer heureux ?
Dans une certaine mesure il faut jouer sur la courbe, il est inévitable d'avoir plus de précision dans l'espace proche à cause de la nature du zbuffer comme expliqué au début et d'une certaine façon c'est bénéfique, mais en gardant les valeurs wnear et wfar raisonnables, les objets lointains ne souffrent pas trop de z fighting. Bien entendu, le contrôle de la courbe est limité par le fait que wnear et wfar ont une signification bien particulière en dehors du paramétrage de la courbe. Ce sont les distances des plans de clipping.
Ce qui en ressort donc c'est qu'avec un z buffer classique vous ne pouvez pas tracer des objets qui sont très loins et d'autres qui sont très près en même temps. Si vous voulez des objets très loins il faut sacrifier votre vision proche en repoussant le plan de clipping proche le plus loin possible. Pour limiter les artefacts de clipping (lorsqu'un objet est suffisamment proche pour intercepter le plan de clipping de manière visible), vous pouvez entourer la caméra d'une enveloppe de collision suffisamment large pour que votre clip plane n'ait aucune chance d'intersecter l'un de ces objets. Il est parfois possible de faire disparaitre graduellement les objets qui sont trop près de votre clipping plane.
Parfois il est inévitable d'avoir besoin d'afficher des objets très proches et des objets très lointains (montagnes, planêtes) et ceci sans artefacts de profondeur. Dans ces cas là il faudra parfois séparer les deux rendus, d'abord les objets lointains, puis avec un nouveau z buffer remis à zéro les objets proches. Ce qui nécessite de pouvoir trier les deux par la distance.




