
Programmation : Ombrages

Et oui, un article sur les ombrages de Lambert, Gouraud et Phong. Bien que
cet article est sur la lignée des articles sur les points, lignes et autres
polygones, les techniques utilisée sont plus proches de l'infographie en 3D
que de l'infographie en 2D. Ainsi on manipule des vecteurs en 3 dimensions.
Commençons par le commencemment :
Ombrage de Lambert
L'ombrage de Lambert (ou flat-shading dans la littérature anglo-
saxonne traduit en français) contient deux choses distinctes :
- le modèle d'éclairage (modèle de Lambert)
- l'ombrage
Le modèle d'éclairage
Y a peut-être une question qui vous turlupine : comment représenter la
lumière. Et bien pour Lambert (et pour d'autre), la lumière, ou plutôt sa
direction est représentée par un vecteur. Un petit schéma :
* <- source lumineuse
\
\ / <- lumière réfléchie
\ /
\/
----------- <- surface de réflexion
La lumière est soit absorbée par l'objet, soit transmise, soit réfléxie.
Un objet qui absorbe 100% de la lumière est un objet qui paraît toujours
noir. A l'inverse un objet qui n'absorbe pas beaucoup de lumière, mais qui
en réfléxi beaucoup paraît brillant. L'idée de Lambert est de savoir quel
est la quantité de lumière qui est réfléchie pour pouvoir donné une couleur
plus ou moins claire à l'objet. Ainsi l'intensité lumineuse est calculé par
:
I = Il * kd * cos(theta)
Avec theta l'angle entre le vecteur lumineux et le vecteur normal de la
surface. Donc pour chaque face, I est l'intensité lumineuse de la face.
Commençons par voir ce que signifie Il : Il est l'intensité de la source
lumineuse, plus cette valeur est grande, plus la source est forte (dans le
sens lumineuse). Kd est le coefficient de diffusion, c'est à dire le
pourcentage de lumière qui est transmise de manière diffuse. Pour voir ce
qu'est la diffusion : regarder un mur blanc éclairé, si ce dernier est
brillant (donc faible indice de diffusion), vous verrez un point lumineux
brillant et le reste du mur plus terne. Car la lumière que vous recevrez est
plutôt de la lumière réfléchie, et pour que cette lumière touche votre oeil,
c'est quelle provient de la réflexion de la source lumineuse. Un petit
schéma pour bien voir le truc :
source lumineuse
+-> * ~ <- votre oeil
\ /\
\ / \
\/ \ <- lumière reçue par diffusion du point éclairé
------------------------------
Par contre si votre mur est mat (indice de diffusion élevé), il n'y aura
pas de tache lumineuse, mais le mur aura une luminosité constante. Il ne
nous reste plus qu'a définir theta :
theta
\ __| <- normale au plan de réflexion
+----> \/ | /
lumière incidente \ | /
\ | /
\|/
------------- <- plan de réflexion
Donc theta est l'angle entre le vecteur de direction lumineuse et le
vecteur normal au plan de réflexion. Si theta est élevé, la lumière est
rasante, et donc la lumière réfléchie par la lumière est faible. Au
contraire, si theta est faible, la lumière n'est pas rasante et donc la
quantité de lumière réfléchie est importante. Mais comment calculer theta,
ou plutôt cos(theta)? Et bien c'est très simple, il suffit de calculer le
produit scalaire des deux vecteurs. Le produit scalaire calcul le produit
des deux normes (longueurs) des vecteurs et du cosinus de l'angle qu'il y a
entre les deux vecteurs. Ainsi, soit u et v deux vecteurs, leur produit
scalaire a est :
a = |u| * |v| * cos(theta)
avec |u| la norme du vecteur u. Et là, vous me dites que l'on toujours pas
plus avancé, et bien si, car il y a un autre moyen de calculer un produit
scalaire : soit (x1,y1,z1) les coordonnées de u et (x2,y2,z2) les
coordonnées de v, le produit scalaire est défini par :
a = x1*x2 + y1*y2 + z1*z2
De même la norme de u est :
___________________ <- racine carrée
|u| = V x1^2 + y1^2 + z1^2
^
+- x1 au carré
De plus si nos vecteurs u et v sont normés (norme égale à 1), alors le
produit scalaire nous donne directement le cosinus de l'angle. Et comme
c'est le cosinus qui nous intéresse, ça tombe bien! Bon il nous reste à
définir le vecteur normal à un plan. Un vecteur normal à un plan, est un
vecteur qui est perpendiculaire au plan. Pour calculer un vecteur normal, et
bien il y a là aussi plusieurs solutions :
- produit vectoriel de deux vecteurs du plan
- à partir de l'équation du plan.
Le produit vectoriel est le plus simple à calculer, en
prenant les mêmes coordonnées pour u et v, w (x3,y3,z3) , le
vecteur normal au plan formé par u et v est :
w = u*v
x3 = y1*z2 - z1*y2
y3 = z1*x2 - x1*z2
z3 = x1*y2 - y1*x1
On remarque que u*v est différent de v*u et que les deux vecteurs u*v et
v*u sont opposés. Si on a l'équation du plan, qui est de la forme :
k = x*x3 + y*y3 + z*z3
Alors un vecteur normal peut-être w = (x3,y3,z3). (k est une constante).
Une première remarque s'impose : il n'y a pas de lumière ambiante, c'est à
dire que si un objet n'est pas éclairé, il est de couleur noir, ce qui n'est
pas très réaliste. Ainsi si on veut rajouter de la lumière ambiante, il
suffit d'utiliser la nouvelle formule pour I :
I = Ia * ka + Il * kd * cos (theta)
Avec Ia, l'intensité de la lumière ambiante et ka la constante de
diffusion de la lumière ambiante. Maintenant revenons à l'ombrage de
Lambert. L'idée est la suivante, pour chaque face, on détermine l'intensité
lumineuse émise par cette face, en fonction de la direction de la lumière et
du vecteur normal à la face. On dessine la face avec la couleur
correspondante à l'intensité, et l'on répète la même chose pour chaque face.
Une remarque, si un cosinus est négatif, la lumière provient de derrière la
face, donc elle n'est pas éclairée. Mais ce cosinus peut-être négatif si le
vecteur normal au plan est dirigé vers l'intérieur de l'objet, donc il faut
faire attention à la direction de ce dernier. L'algorithme n'est pas très
compliqué, et le calcul de la lumière ne pose pas trop de problème. Le
source d'exemple est le suivant : lambert.s, il fait tourner le vecteur de
direction lumineuse autour de l'objet. Une petite remarque sur la
représentation de l'objet. Il y a un tableau (objet_point) qui contient tous
les points en coordonnées 3D sur 16 bits plus un mot vide. Chaque points est
donc stocké sur 8 octets, et donc grâce à l'adressage indexé, on prend un
facteur d'échelle de 8 et l'on accède directement aux coordonnées du point.
Chaque face est elle même représentée par la liste des points qui la
constitue (liste terminée par -1, tableau objet_face). L'ensemble des
normales des faces est stocké en coordonnées 3D sur 16 bits, dans le tableau
objet_normal. Voilà pour l'ombrage de Lambert, passons à la deuxième partie
:
Ombrage de Gouraud
L'ombrage de Lambert coloriait une face de manière uniforme,
mais il s'avère que dans la vraie vie, les surfaces n'ont pas
une luminosité uniforme. L'ombrage de Gouraud est sensé
être plus réaliste. Le principe est le suivant : au lieu
d'avoir une couleur par face, on dégrade les couleurs pour obtenir
un changement progressif de couleur. A chaque sommet de l'objet, on
calcul un vecteur normal, représentant la moyenne des vecteurs
normaux des faces qui passent par ce point. Ce vecteur normal peut-
être précalculé sans aucun problème. Lorsque que
l'on affiche une face, on calcul les intensités lumineuses pour
chaque sommet de la face. Lors de l'affichage du polygone, on interpole la
couleur sur les arêtes du polygone, puis pour chaque ligne de
balayage, on interpole la couleur sur une ligne. De ce fait, on obtient
une bi-interpolation linéaire de la couleur et donc un joli
dégradé.
Dans le source d'exemple (gouraud.s), j'utilise toujours la même
représentation des nombres (16 bits partie entière, 16 bits partie
décimale), pour la "pente" de la couleur sur une arête ainsi que la "pente"
de la couleur sur une ligne de balayage. La structure qui contenait les
intersections d'arêtes a trois champ nouveaux :
- couleur : indice de la couleur du point d'intersection
- pente_c : "pente" de la couleur sur l'arête
- accu_c : accumulateur pour l'interpolation des couleurs
De plus, la signification du tableau objet_normal a changé, il ne contient
plus les normales au plan, mais les normales aux sommets de l'objet; de plus
cette structure est aussi stockée sur 8 octets, pour les mêmes raisons que
les coordonnées des points.
On peut remarquer que l'ombrage de Gouraud donne un aspect moins
faceté de l'objet, mais par contre l'interpolation linéaire
est désagréable au regard. Ce dernier point est assez
particulier, bien que la différence de couleur entre deux pixels
voisins est assez faible, on a l'impression qu'elle est importante ; ce
phénomène provient de l'effet de bande de Mach. Prenez un
dégradé de gris, et bien sur une bande de couleur
égale, vous avez l'impression que le bord proche d'une surface
plus sombre est plus clair que le bord proche d'une surface moins sombre.
Dans le genre des effets bizarre, il y a aussi le contraste
simultané. Dans le source gouraud.s, supprimez la ligne de
boucle (dbra d0,prg_init_b) de la routine prg_init, l'objet en gouraud
sera sur fond blanc, et vous aurez l'impression qu'il est plus foncé
que sur fond noir!
Dans un prochain article, on verra peut-être d'autre modèle de lumière (du
flat-shading avec modèle de Phong?) et l'ombrage de Phong !
Bon code et bonne lecture !
Golio Junior