* * *
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

 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 :


 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 :


 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