L3IF Images
année 2005-2006
http://www710.univ-lyon1.fr/~jciehl/
TD3 - Création d'un monde
Partie 1 : le terrain
Il est relativement
naturel de représenter un terrain par une fonction 2D et de la
stocker sous forme d'une image en niveau de gris. Le blanc
représentera l'altitude maximale et le noir, l'altitude minimale.
Ce "champ d'altitude" doit ensuite être affiché, sous forme de triangles, ou de quadrangles, comme d'habitude. Voici un exemple :
exercice :
Générez une grille
régulière et affichez-la. Les sommets nécessaires
à l'affichage seront stockés dans un tableau.
Réutilisez le TD1 pour vous déplacer
au dessus de la grille. Il serait également assez pratique de
"plaquer" la caméra sur le sol.
exercice :
Générez
une fonction d'altitude et stockez les sommets dans un tableau
équivalent au premier exercice.
exercice :
Utilisez le programme de génération de
terrain. Les explications correspondantes sont disponibles sur
gamedev.
Les valeurs stockées dans l'image sont
comprises entre 0 et 255, il sera peut être nécessaire de
modifer leur amplitude.
Il existe de nombreuses méthodes de
génération, plus ou moins réalistes, vous pouvez
parcourir "
Real Time Procedural Terrain Generation" pour vous faire une idée et l'
archive bibliographique associée.
exercice :
Réalisez un affichage efficace du terrain
(cf. TD 2) et testez-le avec des dimensions plus importantes (128x128,
256x256, 512x512, 1024x1024, ...).
Une méthode simple consiste à
superposer un quad-tree au dessus de la grille de sommets et à
ne dessiner que les cellules potentiellement visibles.
Il n'est pas nécessaire de parcourir la
totalité de la hiérarchie : un bloc 16x16
représente peu de géométrie à tracer, pour
openGL. Il n'est pas non plus nécessaire de construire la
représentation mémoire du quad-tree, un simple algorithme
récursif fournit des résultats très corrects.
exercice :
Observez l'exemple ci-dessous, il est possible de
supprimer énormément de triangles sans dégrader la
perception du relief.
Il existe de nombreuses méthodes. Le point de
départ consiste à sous échantillonner
régulièrement la grille de sommets en fonction de la
distance de l'utilisateur. L'idée est de considérer que
le terrain est composé de blocs 16x16 (par exemple) et de
choisir une précision correcte pour les afficher : 16x16, 8x8,
4x4, 2x2. Proche de l'horizon un bloc 16x16 pourra être
dessiné avec 2 triangles (les 4 extrémités du bloc
16x16) sans introduire d'erreurs importantes.
Si vous observez plus attentivement l'exemple
ci-dessus, vous verrez que les blocs de précisions
différentes sont connectés par une fine bande de
triangles, pourquoi ??
Partie 2 : l'animation des personnages
L'idée est d'interpoler les données de
l'animation en fonction de la fréquence d'affichage. Les
données représentent fréquemment une animation
mesurée à 24 images par secondes. Il ne reste plus
qu'à générer les images intermédiaires.
Par exemple : le modèle bigguy
(nvidia SDK)

|

|
t= 8, Image
8 de l'animation
|
t= 10, Image
10 de l'animation |
Une des nombreuses techniques d'animation consiste
à déformer le maillage de l'objet dans le temps. Le
sommet 0 se déplace donc en fonction du temps et
représente toujours le même point de l'objet (ce qui n'est
pas nécessairement le cas avec d'autres méthodes
d'animations).
Pour construire le maillage à l'instant t,
il ne reste plus qu'à interpoler la position du sommet entre les
instants t0 et t1.
exercice :
Charger les maillages à deux instants assez
éloignés (bigguy_00.obj et bigguy_10.obj, par exemple) et
écrivez une fonction d'affichage capable d'interpoler la
position de chaque sommet du maillage en fonction du temps.
Des remarques sur la qualité de
l'interpolation ? Quels peuvent être les problèmes ?
Comment les régler ?
Vous aurez sans doute besoin de glutGet(GLUT_ELAPSED_TIME) pour
vous repérer dans le temps (cette fonction renvoie le temps en
millisecondes depuis le début de l'éxécution du
programme, ou depuis sa première utilisation).
exercice :
Charger tous les maillages et reconstruisez
l'animation complète.
Annexe : la structure MODEL
Pour interpoler la position des sommets du maillage,
il va falloir parcourir la structure qui les stocke : MODEL, définit dans model.h.
Un MODEL est composé de plusieurs tableaux
contenant chacun des informations différentes : sommets, faces, normales, couleurs, coordonnées de textures, et
attributs de faces.
typedef
struct
{
VERTEX
*v; // sommets
(coordonnees)
int v_n;
FACE
*faces; // faces
int faces_n;
ATTR
*attr; // liste
d'attributs des faces
int attr_n;
VERTEXT
*tex; // textures
(coordonnees) associees aux sommets
int tex_n;
VERTEXN
*norm; // normales aux sommets
int norm_n;
} MODEL;
Les types VERTEX, VERTEXT et VERTEXN
représentent respectivement un sommet, des coordonnées de
textures, et une normale. Ce sont des vecteurs de réels à
4, 2, et 3 coordonnées.
Pour accéder à tous les sommets de
MODEL, il suffit de parcourir le tableau v (de taille logique v_n) :
void
f(MODEL *model)
{
int i;
for(i= 0; i
< model->v_n; i++)
{
printf("sommet %d: %f %f %f\n", i,
model->v[i][0],
model->v[i][1],
model->v[i][2]);
}
}
Chaque face est décrite par une
structure FACE (cf. le tableau faces de MODEL, taille logique faces_n) :
typedef
struct
{
int
attr; // indice du premier
attribut
int
n; // nombre
d'attributs
} FACE;
Les n sommets d'une maille sont décrits par 3
attributs : leur position géométrique (gl_vertex), leur
coordonnées de texture (gl_tex)
et leur normale (gl_norm).
Ces informations sont stockées dans le tableau ATTR de MODEL.
Seuls des indices sont présents dans le tableau ATTR, les
valeurs sont en fait stockées dans les tableaux principaux de
MODEL (v, tex, et norm).
Exemple, si a
est un indice d'attribut (le premier de la face, par exemple),
l'accès aux autres données est très simple :
int v= model->attr[a][gl_vertex]; // indice des coordonnees du sommet
int t= model->attr[a][gl_tex]; // indice des coordonnees de texture
int n= model->attr[a][gl_norm]; // indice des coordonnees de la normale
Exemple : accéder à tous les
sommets d'une face :
void
f(MODEL *model, int f)
{
FACE *face;
int a;
int v, t, n;
int i;
face=
&model->faces[f];
printf("face %d :\n", f);
for(i=
0; i < face->n; i++)
{
a= face->attr + i;
v= model->attr[a][gl_vertex];
printf(" sommet %d : %f %f
%f, ", i,
model->v[v][0], model->v[v][1], model->v[v][2]);
/* et pour les autres attributs :
t= model->attr[a][gl_tex];
printf("texture %f %f, ",
model->tex[t][0], model->tex[t][1]);
n= model->attr[a][gl_norm];
printf("normale %f %f %f",
model->norm[n][0], model->norm[n][1], model->norm[n][2]);
*/
}
}