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.

carte d'altitude    les cubes placés à la bonne altitude...

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 :
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 :
"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.