M2 - Images
TP2 - openGL
mise à jour 13/10 export_gkit
L'objectif du tp est d'afficher et de se déplacer dans un monde
composé de cubes, un minecraft un peu simplifié.
Le minecraft originel décrit le monde comme une immense grille 3d,
en stockant le type de chaque cellule : vide ou occupée par de la
pierre, du bois, de l'herbe, etc.
Dans un premier temps, on ne représentera que la surface du monde.
Une solution directe est d'utiliser une carte 2d ou chaque cellule
stocke son altitude et son type, ce qui est simple à stocker dans 1
ou 2 images standards.
Partie 1 : affichage direct
Comment afficher le monde ? La solution directe consiste à dessiner
un cube pour chaque cellule de la grille.
exercice 1 :
Chargez une image, puis affichez une copie de cube.obj pour chaque
cellule. Il suffit de calculer la translation permettant de placer
chaque cube "au bon endroit" et à la bonne altitude.
Comment placer une camera ? en fonction du sol ? comment se déplacer
?
exercice 2 : c'est un peu lent, non ?
Chaque appel à glDraw() nécessite pas mal de traitements, ce qui
limite le nombre de cubes que l'on peut afficher de cette manière.
Bien que soit ne soit pas totalement intuitif, ce n'est pas le gpu
qui bloque dans ce cas. Une carte graphique entrée de gamme peut
afficher sans problèmes quelques millions de triangles.
Vous pouvez jetter un oeil sur tuto_time
qui explique comment mesurer le temps d'exécution pour le cpu et le
gpu.
Une autre solution consiste à construire un seul maillage
représentant toute la géometrie à dessiner et à l'afficher avec un
seul draw.
Partie 2 : ca va couper !
Les solutions précédentes permettent d'afficher une carte de taille
raisonnable, mais ne permettront pas de parcourir un monde bien
grand.
Les 2 premières solutions sont des extrèmes : 1 draw par cube, ou 1
draw pour le monde.
Il faut trouver un compromis : pour afficher efficacement il faut un
maillage assez grand, mais s'il est trop grand, l'affichage devient
lent, alors qu'une bonne partie du maillage n'est pas visible...
L'idée est donc de découper le monde en blocs, ou régions, de
générer un maillage par région et de n'afficher que les régions
visibles par la camera.
remarque :
minecraft fait exactement la même chose, mais avec des
voxels 16x16x16 qui sont ensuite regroupés par block, puis par
chunk, puis par map, puis par region.
exercice 3 :
Découpez le monde en blocs de 64x64 cubes et générez un maillage
pour chaque bloc.
question :
qu'elle est la bonne taille de bloc ?
exercice 4 :
Calculez la boite englobante de chaque bloc, et écrivez la fonction
permettant de savoir si un bloc est, au moins, partiellement
visible.
Utilisez cette fonction pour n'afficher que les blocs visibles par
la camera.
rappel :
s'il existe un plan séparant la boite englobante du bloc
et le volume de vision de la camera, le bloc ne pas être visible.
bonus :
comment déplacer la camera, sans passer à travers le
terrain ?
bonus :
utilisez glDrawArraysInstanced() pour générer à la volée
chaque bloc. il y a 2 solutions, cf tuto
Draw de gKit :
- soit le vertex shader accède à une texture stockant
l'altitude de chaque cube et gl_InstanceID permet d'identifier
le cube en cours de transformation,
- soit le vertex shader accède à un buffer stockant un
attribut pour chaque instance : l'altitude et la position de
chaque cube du bloc.
- mesurez aussi les performances de cette solution, par
rapport à la solution "maillage". cf tuto_time.
bonus :
et si on modifie le terrain à la volée, en creusant
certains "voxels" ? comment modifier le terrain, quel bloc faut-il
modifier ?
Partie 3 : c'est un peu sombre non ?
exercice 5 :
Modifez vos shaders pour donner une couleur représentant
l'orientation du cube par rapport à la camera.
Même question pour une lumière venant d'une direction, ou d'un point
?
rappel : float cos
_
theta
= dot( normalize(u), normalize(v) )
;
les coordonnées des points et des vecteurs/directions
doivent être connues dans le même repère... choisissez un repère
et transformez toutes les coordonnées.
bonus :
choisissez une couleur pour chaque cube en fonction de
l'altitude, de la pente, d'une texture...
exercice 6 :
les ombres...
il y a 2 manières de faire des calculs de visibilité pour déterminer
qu'un point est éclairé ou à l'ombre :
- en utilisant le pipeline graphique, la technique s'appelle
"shadow mapping",
"Casting
curved shadows on curved surfaces"
L. Williams, 1978
- ou le lancer de rayon :
dans le cas d'un terrain / d'une surface, il y a une variante du
lancer de rayons qui est très pratique : le sphere tracing.
l'idée est de savoir à quelle distance un point du rayon se
trouve de la surface et d'avancer en fonction de cette distance.
bien sur, il faut un critère pour être sur de ne pas passer à
travers la surface... c'est à dire ralentir lorsque le point
s'approche de la surface.
"Sphere
tracing :
a geometric method for the antialiased ray tracing of implicit
surfaces"
J.C. Hart, 1996
et selon comment on règle les paramètres (la constante de
Lipschitz, lambda), on obtient un résultat interressant, ou pas
vraiment.
exemple, on avance 256 fois le long du rayon en partant de son
origine :
chaque méthode permet d'avancer jusqu'à l'horizon, ou pas... en
gris/bleu, une intersection a été trouvée, en rouge,
l'intersection se trouve plus loin le long du rayon...
à gauche : lambda adaptatif, au milieu : lambda global constant
(calculé d'après le gradient de la surface), à droite : lambda
constant, mal choisi, mauvaise précision et distance parcourue
trop faible.