Quelques notes techniques
sur les effets "spéciaux" utilisé dans glSkel.


Basé sur les travaux de Mark Kilgard (mjk@sgi.com) et les SIGGRAPH 95, 96 et 97.

Voir aussi la page sur OpenGL et glut.

La réflection
Les ombres portées
Les caustics
Contrast et Brillance
Les Quaternions
Environment Mapping / Textures transparentes

Les explications qui suivent impliquent une connaissance minimale sur l'API OpenGL.


LA REFLECTION

Comment simuler en OpenGL la réflection en temps réel ?

Si l'on veut rendre la réflection d'un objet sur un plan il suffit de dessiner ce même objet sous ce plan (glScalef(1.0, -1.0, 1.0) dans le cas d'une réflection sur un plan d'équation y=0) et de dessiner le plan transparent.

Le fait d'effectuer une symétrie (glScalef(1.0, -1.0, 1.0)) produit un effet indésirable, l'inversion des normales. Pour remédier à cela il suffit d'activer la normalisation des normales et d'activer le CullFace sur les faces avants à la place des faces arrières (l'inversion du sens de parcours des Vertex par glFrontFace(GL_CCW) devrait marcher aussi).

glEnable(GL_NORMALIZE);
glCullFace(GL_FRONT);
draw();
glDisable(GL_NORMALIZE);
glCullFace(GL_BACK);

Dessiner le plan transparent s'effectue suivant la séquence : Activation du Blending et réglage de la fonction de Blending de façon à ce qu'elle tienne compte de la composante alpha du plan (ici 0.7 soit 30% de transparence, le plan est dessiné en tenant compte de 30% de la couleur de l'objet et de 70% de la couleure du plan (ici vert)).

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0, 0.7, 0.0, 0.7);
drawFloor();
glDisable(GL_BLEND);

D'autre part un détail reste à régler, l'objet réfléchi dépasse de dessous le plan suivant le point de vue, pour résoudre ce probleme on utilisera alors le Stencil Buffer d'OpenGL (non disponible dans Direct3D). L'idée est de dessiner la réflection de l'objet uniquement sur les points vues à travers le plan. On dessinera alors le plan dans un Stencil Buffer :

Tous les points du plan prendrons la valeure 1 dans le Stencil Buffer.

glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
drawFloor();

Puis il suffit de dessiner la réflection uniquement dans les zones ou le Stencil Buffer vaut 1 (donc aux endroits où se trouve le plan) :

glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
drawReflect();
/* On efface les Buffers (par défaut en noir) */
glClear(GL_COLOR_BUFFER_BIT | (useDepth ? GL_DEPTH_BUFFER_BIT : 0) | GL_STENCIL_BUFFER_BIT);
glPushMatrix(); /* <- */
/* On règle la position du ou des lumières */
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
/* On désactive le z-buffer */
glDisable(GL_DEPTH_TEST);
/* Et on désactive le Color Buffer en positionnant un masque de bits à faux sur les composantes Red, Green, Blue et Alpha */
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
/* Activation du Stencil Buffer L'objet (ici le plan) est dessiné dans le Stencil (valeure de référence=1 positionnée) */
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
drawFloor();
/* On réactive le Color et Depth Buffer */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
/* Maintenant on ne dessine uniquement dans les zones ou le Stencil vaut 1, la valeure courante est positionnée */
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix(); /* <- réflection */
glScalef(1.0, -1.0, 1.0);
/* On postionne les lumieres reflechis */
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
/* Attention les normales sont inversés */
glEnable(GL_NORMALIZE);
glCullFace(GL_FRONT);
/* Le ou les objets réfléchis */
draw();
glDisable(GL_NORMALIZE);
glCullFace(GL_BACK);
glPopMatrix(); /* -> réflection */
/* On repositionne les lumières à leurs places */
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
/* On désactive le Stencil Buffer */
glDisable(GL_STENCIL_TEST);
/* On dessine le dessous du sol d'une couleure différente */
glFrontFace(GL_CW);
glColor4f(0.1, 0.1, 0.7, 1.0);
drawFloor();
glFrontFace(GL_CCW);
/* Dessin du sol transparent */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0, 0.7, 0.0, 0.7);
drawFloor();
glDisable(GL_BLEND);
/* Dessin du ou des objets (les vraies, pas la réflection) */
draw();

Voila, facile non ?


LES OMBRES PORTES

Un autre effet intérressant est le rendu des ombres des objets de la scène (toujours en temps réel).

Dessiner l'objet en passant par une matrice de projection (monde -> plan, fonction des lumières).

La matrice est la suivante :

[ b*ly+c*lz+d*lw -a*ly -a*lz -a*lw ]
[ -b*lx a*lx+c*lz+d*lw -b*lz -b*lw ]
[ -c*lx -c*ly a*lx+b*ly+d*lw -c*lw ]
[ -d*lx -d*ly -d*lz a*lx+b*ly*c*lz ]

Avec (lx, ly, lz, lw) la position de la lumière (ou du vecteur lumière), b*dx + b*dy + c*dz + d =0 équation du plan.

De la même façon on active un Stencil Buffer et on trace le plan :

(ici la valeure positionnée dans le Stencil est 3 si le point passe avec succès du Z-Buffer, sinon les valeures déjà présentes sont gardées).

glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 3, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
drawFloor();

On dessine alors le ou les objets, puis on rend les ombres :

/* L'objet le vrai le beau */
draw();
/* On affiche (ici l'ombre) ssi la valeure courante est >=3,
dans les autres cas on remplace par la valeure 2,
ainsi on ne dessine pas les ombres qui se recouvrent */
glStencilFunc(GL_LESS, 2, 0xffffffff);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
/* Ici on dessine des ombres transparentes */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/* On éteind les lumières (ombres noires) */
glDisable(GL_LIGHTING);
/* On force la couleure des ombres à noir (on sait jamais) */
glColor4f(0.0, 0.0, 0.0, 0.5);
glPushMatrix(); /* <- shadows */
glMultMatrixf((GLfloat*)floorShadow);
/* La matrice de projection */
draw();
glPopMatrix(); /* -> shadows */
glDisable(GL_BLEND);
glEnable(GL_LIGHTING);
glDisable(GL_STENCIL_TEST);

Enfin un petit détail, comme l'ombre est dessinée sur le plan il y a un effet indésirable d'aliasing entre l'ombre et le plan. Pour éviter cet effet on utilise une fonction positionnant un offset sur les points :

glEnable(GL_POLYGON_OFFSET_FILL);

Cette fonction n'est disponible que dans OpenGL 1.1 :

glPolygonOffset(-2.0, -1.0);

Sinon utiliser, s'elle est supportée, la fonction (extensions OpenGL) :

glPolygonOffsetEXT(-0.1, -0.002);
avec : glEnable(GL_POLYGON_OFFSET_EXT);


CAUSTICS

Comment simuler les environnements sous-marins avec des effets de caustics, s'effectue très facilement grace aux possibilités de génération de coordonnées de textures d'OpenGL (existe depuis la version 1.0).

La méthode implique une seconde passe sur tout les objets de la scène (couteuse en temps) avec la fonction de blending :

glEnable(GL_BLEND);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);

Et on active la génération des coordonnées de textures :

GLfloat sPlane[4] = { 0.05, 0.03, 0.0, 0.0 };
GLfloat tPlane[4] = { 0.0, 0.03, 0.05, 0.0 };
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGenfv(GL_S, GL_OBJECT_PLANE, sPlane);
glTexGenfv(GL_T, GL_OBJECT_PLANE, sPlane);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_2D);

Et le tour est joué.

Cette fonction n'est pas utilisé dans notre programme pour gain de rapidité.


CONTRAST ET BRILLANCE (scale & bias)

Pour simuler le réglage du contrast et de la brillance d'une texture (sans recharger la texture) ou d'une scène, un moyen simple de faire est d'utiliser les fonctions glCopyPixels et glPixelTransfert d'OpenGL.

Cependant pour un gain en rapidité, il est plus efficace de simuler l'opération avec la fonction blenging. On peut aller 4 à 25 fois plus vite suivant l'implémentation de la fonction de blending , le nombre de pixels traités et les valeurs de contrast et de brillance.

La méthode est simple, il suffit de dessiner un plan (couvrant tous le champ visuel) avec les bons paremetre de couleur (bias=brillance) et de transparence (scale=contrast) :

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
draw();
/* */
glPushMatrix();
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
/* */
glEnable(GL_BLEND);
glColor4f(bias, bias, bias, scale);
/* RGB = bias * 1 + scale * RGBprevious */
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
/* Tracé d'un rectangle de la taille de l'écran (cf glFrustrum ou gluPerspective) */
glRectf(-width/2.0, -height/2.0, width, height);
glDisable(GL_BLEND);
/* */
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glPopMatrix();

On remarquera que pour gagner encore un peu de temps on pourra utiliser un Stencil Buffer pour n'appliquer le stencil que sur les zones utiles de la scene.


QUATERNIONS

Tout d'abord d'un point de vue mathématique et d'après ce que j'en ai compris, un quaternion est un réel et trois complexes que l'on notera :

q = w + x i + y j + z k = w + v
avec :
i2 = j2 = k2 = -1
i j = - j i = k
j k = - k j = i
k i = - i k = j

Sans s'éterniser sur les lois qui régissent l'espace des quaternions on va s'intéresser aux quaternions unitaires (|q| = w2 + x2 + y2 +z2 = 1) que l'on notera uq. En effet sous ces conditions un quaternion permet de représenter une rotation 3D.

uq = cos (t) + v sin (t) qui est la rotation d'angle 2t autour du vecteur v.

L'interet d'utiliser des quaternions pour représenter des rotations est le gain en nombre d'opérations pour composer des rotations et la facilitée pour interpoler entre deux quaternions.

La première approche pour interpoler la position intermédiaire entre deux quaternions est la Spherical Linear intERPolation (SLERP) donné par la relation :

SLERP(p, q, t) = 1/sin(tetha) [ p sin((1-t)tetha) + q sin(t tetha) ]

D'autres méthodes d'interpolation existent comme Spherical Cubic intERPolation (SCERP) ou Spline intERPolation (SERP) que l'on ne détaillera pas ici.


ENVIRONMENT MAPPING & TEXTURE TRANSPARENTE

On va voir ici comment faire de l'environment mapping sur un objet, et comment appliquer une texture transparente.

L'environment mapping s'effectue comme pour les caustics en utilisant les possibilités de génération de coordonnées de texture d'OpenGL.

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_GEN_S);
glEnable(GL_GEN_T);
glEnable(GL_TEXTURE_2D);

Ensuite il ne reste plus qu'a dessiner l'objet avec la texture courante.

Pour ce qui est des textures transparentes, il faut tout d'abord charger une texture avec le canal alpha en mémoire (soit il est déjà présent dans l'image, soit on l'ajoute artificiellement), puis appliquer la texture sur l'objet en activant le blending :

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);

Grace à cette méthode on peut donc créer une nappe de brouillard artificielle comme le montre l'image ci-dessus.

C'est aussi de la meme façon que l'on peut recréer le ciel de Quake...


[ home ] [ bookmarks ]