> Graphisme
PrograZine issue #10 - http://www.citeweb.net/discase/10/bump.htm Edito Sommaire Contribution Contacts


L'effet Bump
par Plouf - plouf@win.be http://www.citeweb.net/pmplayer
Initié DOS Pascal/ASM

    Bonsoir à tous (je dis bonsoir car j'ai la tendance à écrire mes tuts le soir car le matin, soit je suis en cour, soit je pieute)

    Ce soir, donc, j'amerais vous entretenir d'un effet génialissime et très peu utilisé (allez savoir pourquoi) : le bump mapping. Il n'est ni lent, ni difficile à programmer, et personne n'arrive à en faire quelque chose de bien (moi j'ai une excuse : je n'ai jamais cherché :)
>Rappels théoriques de géométrie analytique

    Comme ça je suis tranquile, même si ça ne sera pas forcément utile, si vous voulez être sur de savoir de quoi je parle, n'avez qu'à lire ceci :

    Dans l'espace, le produit scalaire entre deux vecteurs (X1,Y1,Z1) et (X2,Y2,Z2) vaut X1X2+Y1Y2+Z1Z2. Ce produit est nul si les deux vecteurs sont orthogonaux. Il peut également se calculer comme le produit des normes de ces vecteurs multiplié par le cosinus de l'angle formé par ceux deux vecteurs. Comme cos(0)=1, on voit que ce produit est maximal quand ces deux vecteurs sont alignés.

    Toujours dans l'espace, la normale en un point à une surface est un vecteur de norme unitaire (=1) et orthogonal à tout autre vecteur tangeant en ce point à cette surface. En clair, la normale en un point à une sphere est dans le prolongement du rayon passant par ce point. Dit encore autrement, c'est le vecteur perpendiculaire au plan tangeant de cette surface en ce point.

    La norme d'un vecteur peut se calculer comme la racine carrée de la somme des carrés de ses trois composantes :
Norme²=X²+Y²+Z²
l

    Dans ce programme, on admettra que la composante en X de la normale vaut la différence entre les hauteurs du point juste à droite et de celui juste à gauche, et que la composante en Y vaut la différence entre les hauteurs du point juste en bas et de celui juste au dessus. On calculera la composante en Z en imposant que la norme de la normale vaut 1.

    Et enfin, la somme de deux vecteurs donne un vecteur dont les composantes sont la somme des composantes des deux premiers vecteurs : (A,B)+(C,D)=(A+C,B+D)
>Alors c'est quoi?

    Le bump mapping c'est l'art de vous faire croire qu'une image possède un relief, et pour mieux vous en persuader, vous pouvez déplacer votre curseur souris pour voir apparaître ici une crête, là un ravin, le tout se déformant en temps réel en fonction de la position de la source lumineuse que constitue votre curseur.

    Ca donne assez bien, c'est toujours assez "tape à l'oeil", et je m'en va vous expliquer comment que ça marche (bien que si vous ne voyez pas très bien de quoi il s'agit, je vous conseille de regarder l'exemple AVANT de poursuivre ce texte)
>Alors comment ksa marche?

    D'abord l'image : en fait nous allons considérer que la valeur de l'octet (et donc du pixel) constitue non plus sa couleur mais bien son "altitude". De ce fait nous perdons notre renseignement de couleur, mais nous en gagnons un autre et de taille.

    Une fois que nous avons ce renseignement, nous pouvons trouver la normale de notre "surface" en tout point, car ses composantes dans le plan de l'écran sont (pour X,Y) [Alt(X+1,Y)-Alt(X-1,Y);Alt(X,Y+1)-Alt(X,Y-1)] où Alt est l'altitude du point. Avec celà, nous pouvons trouver le Z avec une bête règle de pythagore si on impose que la norme de cette normale soit unitaire.

    En effet, on à Norme²=1=X²+Y²+Z² d'où Z²=1-X²-Y² et nous avons donc Z². Nous travaillerons avec Z² et non pas avec Z car ça donne plus joli, c'est tout :)
lRemarquez que ce Z² peut parfois être négatif, cela veut dire qu'il n'y a pas moyen de trouver un vecteur qui remplisse nos conditions. Nous le mettrons donc à zéro, ce qui est un moindre mal.

    Bon, donc en tout point, on à un triplet (dX,dY,Z²) où dX=Alt(X+1,Y)-Alt(X-1,Y) et dY=Alt(X,Y+1)-Alt(X,Y-1).

    Alors à présent, imaginons que la lumière soit à l'infini, très très loin, on peut donc supposer qu'elle arrive à la verticale partout sur l'image. Ce qui fait que l'intensité en tout point peut se calculer en faisant le produit scalaire du vecteur (0,0,-L) représentant la lumière et notre vecteur normal : (dX,dY,Z²). Le résultat vaut -LZ², et si on suppose que la lumière à une norme égale à 1, tout cela se résume à -Z².

    Donc au point (X,Y), on tracera un pixel dont la couleur vaut Z²*255 (car Z² est forcément compris entre 0 et 1)
>Oula, ça devient compliqué, et c'est tout sinon?

    Hélas non :) Car là on a une lumière à l'infini, et le fait de la déplacer en X ou en Y ne change jamais le résultat à l'écran, c'est tout de même assez triste d'avoir fait tout ce travail pour en arriver à un résultat constant!

    Mais réfléchissons un peu. Nous ce que l'on veut simuler, c'est une lumière qui ne se trouve PAS à l'infini. Suposons donc une lumière assez proche qui éclaire (mais pas à la verticale) une surface horizontale. Cette surface fait un angle avec la vertical de la lumière. En fait, c'est un peu la même situation que si la lumière se trouvait à l'infini et que la plaque ne se trouvait plus à la verticale, mais bien penchée... de l'angle dont nous parlions précédement.

    Donc dans notre calcul de dX et dY, nous devons rajouter la "pente" entre la lumière et notre point, soit (Lx-X,Ly-Y) si Lx,Ly sont les coordonées horizontales de notre lumière.
>Pouet, ça devient insuivable, c'est tout, hein?

    Oui, à peu près. Résumons la situation :
  • Pour chaque pixel, nous calculons dX,dY, qui vaut [Alt(X+1,Y)-Alt(X-1,Y);Alt(X,Y+1)-Alt(X,Y-1)]+[Lx-X;Ly-Y]
  • Grâce à ce dX,dY, nous calculons Z²=1-dX²-dY² et mettons Z² à 0 si il est négatif.
  • Nous affichons en X,Y un pixel d'une couleur égal à Z²*255

>5) Et en pratique?

    En pratique, le calcul de Z² est assez lourd, car il faut se trainer des mises au carré. Faisons une nouvelle suposition : que la pente sera toujours comprise entre (-127,-127) et (127,127). Bien sur en pratique ça ne sera pas toujours le cas et il faudra vérifier cette valeur.

    Puisque nous avons des valeurs limites, nous pouvons à présent créer une table, au début du programme, de deux dimensions, qui à (dX,dY) retourne Z²*255, donc un Array[-127..127,-127..127] Of Byte que l'on calcuerait comme suit :
For X:=-127 To 127 Do Begin
  For Y:=-127 To 127 Do Begin
    I:=Trunc((1-Sqr(X/128)-Sqr(Y/128))*255);
    If I<0 then i:=0>
    Normale[X,Y]:=I;
  End;
End;
l

    La division par 128 permet de limiter la "force" de la pente, car si on prenait les valeurs brutes, on aurait des valeurs énormes. En jouant sur cette valeur, on peut obtenir un effet plus ou moins prononcé. Il s'agit donc ici d'un paramètre arbitraire.

    Pour le reste, pour calculer l'effet, il reste :
For X:=1 To 318 Do Begin
  For Y:=1 To 198 Do Begin
    dX:=Image[X+1,Y]-Image[X-1,Y]+Lx-X;
    dY:=Image[X,Y+1]-Image[X,Y-1]+Ly-Y;
    If dX<-127 then dx:=-127>
    If dY<-127 then dy:=-127>
    If dX>127 Then dX:=127;
    If dY>127 Then dY:=127;
    Nouvelle_Image[X,Y]:=Normale[dX,dY];
  End;
End;
l

    Et ouf...
>Le programme
{
 Démonstration de l'effet bump mapping par Plouf
 plouf@win.be
}
Program Bump;
Uses Crt;
Type
  TScreen=Array[0..199,0..319] Of Byte;
  TNorm=Array[-127..127,-127..127] Of Byte;
Var
  Buf1,Buf2:Pointer;
  I,J:Integer;
  X,Y:Integer;
  Lx,Ly:Integer;
  Norm:Pointer;
Procedure SetColor(N,R,V,B:byte);Assembler;
ASM
  MOV dx, 3c8h
  MOV al, N
  OUT dx, al
  INC dx
  MOV al, R
  OUT dx, al
  MOV al, V
  OUT dx, al
  MOV al, B
  OUT dx, al
End;
Function Mousex:Integer;Assembler;
ASM
  MOV ax, 03h
  INT 33h
  MOV ax, cx
End;
Function Mousey:Integer;Assembler;
ASM
  MOV ax, 03h
  INT 33h
  MOV ax, dx
End;
Begin
  GetMem(Buf1,64000);
  GetMem(Buf2,64000);
  GetMem(Norm,255*255);
  {La table}
  For Y:=-127 To 127 Do Begin
    For X:=-127 To 127 Do Begin
      I:=Trunc((1-Sqr(X/128)-Sqr(Y/128))*255);
      If I<0 then i:=0>
      TNorm(Norm^)[Y,X]:=I;
    End;
  End;
  {Créons une petite image de type plasma}
  For Y:=0 To 199 Do Begin
    For X:=0 To 319 Do Begin
      TScreen(Buf1^)[Y,X]:=Abs(Trunc((Cos(X/10)+Sin(Y/10)+Sin(X/20)+Cos(Y/20))*63));
    End;
  End;
  FillChar(Buf2^,64000,0);
  ASM
    MOV ax, 13h
    INT 10h
  End;
  For I:=0 To 255 Do SetColor(I,I Div 4,I Div 4,I Div 4);
  Repeat
    Lx:=Mousex Div 2;
    Ly:=Mousey;
    For Y:=1 To 198 Do Begin
      For X:=1 To 318 Do Begin
        I:=TScreen(Buf1^)[Y,X+1]-TScreen(Buf1^)[Y,X-1]+Lx-X;
        J:=TScreen(Buf1^)[Y+1,X]-TScreen(Buf1^)[Y-1,X]+Ly-Y;
        If I<-127 then i:=-127>
        If J<-127 then j:=-127>
        If I>127 Then I:=127;
        If J>127 Then J:=127;
        TScreen(Buf2^)[Y,X]:=TNorm(Norm^)[J,I];
      End;
    End;
    Move(Buf2^,Ptr($a000,0)^,64000);
  Until KeyPressed;
  ASM
    MOV ax, 03h
    INT 10h
  End;
  FreeMem(Buf1,64000);
  FreeMem(Buf2,64000);
  FreeMem(Norm,255*255);
End.
l
Cet article est la propriété de Plouf. La copie et la diffusion sont libres sauf dans un but lucratif sans accord explicite de l'auteur.