 |
Le Bump Mapping par :
|
Plouf
|
(plouf@win.be ou
theplouf@yahoo.com)
|
|
:
1 Ko |
|
|
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é :) |
. |
0) 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² |
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) |
. |
1) 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
(1Ko) AVANT de poursuivre ce texte) |
. |
2) 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 :)
|
Remarquez 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) |
. |
3) 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. |
. |
4) 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; |
. |
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; |
. |
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. |
|
 |