M2 image
TP2 - ca va couper ?
affichage efficace d'un (grand) terrain
Partie 1 : préparation
Créez une structure Region qui servira à regrouper toutes les
informations nécessaires pour afficher une partie du terrain. Créez
également une structure Terrain ou Scene qui permettra de parcourir
toutes les régions.
Une région sera un block de 64x64 cubes, il faudra conserver les
informations permettant d'afficher ces cubes / instances. Par
exemple, la translation permettant de placer chaque instance dans la
scène ainsi que la matière associée. Les coordonnées de l'englobant
seront aussi nécessaires pour les tests de visibilité.
La scène (ou le terrain) sera un simple ensemble de régions.
Comment organiser les données ?
Il y a bien sur plusieurs solutions :
- régions indépendantes : 1 ou plusieurs buffers + vertex array
object décrivants les translations à appliquer à chaque
instance,
- scène : la scène conserve un gros buffer avec les translations
de toutes instances de toutes les régions et 1 seul vao. une
région ne stocke que 2 nombres : l'indice de la première
instance et le nombre d'instances à dessiner.
Pour choisir entre ces solutions (et il y en a d'autres), il faut
imaginer les conséquences pour dessiner le terrain complet.
L'algorithme complet est très simple :
- tester la visibilite de chaque region
- afficher les régions visibles
Selon l'organisation des données, afficher les régions visibles
va nécessiter de reconfigurer le pipeline, ou pas.
remarque : on peut
s'attendre à ce que la meilleure solution soit celle qui utilise
le moins openGL (qui nécessite le moins d'appels de fonctions de
l'api 3d)...
- pour chaque region de la scène
si la region est visible
glBindVertexArray()
glDrawInstanced()
- glBindVertexArray()
pour chaque region de la scene
si la region est visible
glDrawInstancedBaseInstance()
complétez ces 2 solutions en imaginant que chaque région contient
des instances de plusieurs types de matieres, qui nécessitent des
shaders differents (et des textures différentes...) pour être
dessinées. quelle est, a priori, la meilleure solution ?
Construction du terrain
comme présenté en TP, prennez
le temps d'écrire les fonctions qui calculent les pentes et
éventuellement les normales, qui permettent de choisir une unité de
modélisation et d'affecter une matière à chaque instance.
vous pouvez utiliser la fonction Image::sample( const
float x, const float y )
pour évaluer l'altitude d'un
point quelconque, ou lieu de n'utiliser que des coordonnées
entières avec Image::operator() ( const int x, const int y
)
.
Partie 2 : régions et visibilité
Pour déterminer qu'une région est visible, il faut connaitre ses
coordonnées min et max sur les 3 axes, le cube englobant de la
région.
Ecrivez une fonction permettant de déterminer si cet englobant est
séparé du frustum de la camera.
dans quel repère peut-on faire le test ?
comment vérifier que votre test fonctionne correctement ?
indication : si vous avez
besoin de connaitre le poids homogène d'un point transformé dans le
repère projectif (homogène), utilisez :
Point p= { ... };
Transform mvp= { ... };
vec4 ph= mvp(vec4(p));
// ph.w est le poids homogene et ph.xyz sont les
coordonnées
du point p projetté
modifiez la boucle d'affichage de la scène pour n'afficher que les
régions visibles. comptez le nombre de regions affichées et réglez
les distances proche et loin de la projection de la camera pour
rester le plus souvent possible dans le budget de 256x256 cubes
affichés.
remarque : c'est la
fonction Perspective()
de mat.h qui construit la
projection. vous aurez besoin de modifier la camera, cf la classe
Orbiter
indication : n'hésitez pas
à suréchantilloner le terrain pour générer une très grande surface,
4096x4096, par exemple.
bonus : écrivez une classe
camera première personne pour déplacer librement l'observateur dans
la scène, au lieu de tourner autour, comme l'orbiter. vous aurez
besoin de la fonction LookAt()
de mat.h.
rappel : la transformation
View est l'inverse de la transformation qui permet de placer et
d'orienter la camera dans la scène.
Partie 3 : plusieurs matières
chargez le pack de textures de dokucraft, cf tp précédent, les
textures des blocs des matières de minecraft se trouvent le
répertoire assets/minecraft/textures/blocks
. repérez
les textures permettant d'afficher les différents types de terrain
que vous souhaitez : herbe, pierre, terre, eau, etc.
comment afficher des cubes texturés ? reliez le tuto
sur les textures, ou la version
simplifiée, si nécéssaire.
dans un premier temps, utilisez la même texture sur les 6 faces des
cubes, par exemple grass_top.png. que faut-il modifier pour texturer
différemment les faces des cubes ? par exemple : une texture sur la
face supérieure et une texture différente sur les autres faces, cf
grass_top.png, grass_side.png ?
comment afficher des touffes d'herbes, le feuillage des arbres, les
fleurs, etc. ? les textures sont semi-transparentes, certains texels
sont opaques (alpha=1), mais pas tous (alpha < 1)... par exemple
: fern.png, double_plant_grass_top.png et
double_plant_grass_bottom.png.
rappel : mot clé discard
dans GLSL
Partie 4 : et les ombres ?
exercice 1 : modifiez les
fragment shaders afin de calculer la couleur de chaque matière en
fonction de la pente du terrain et de la direction du soleil.
rappel : le résultat dépend du cosinus de l'angle entre la
normale et la direction du soleil :
float cos_theta= dot
(normalize(n),normalize(soleil));
dans quel repère peut-on faire le calcul ? est-il nécessaire de
transformer certaines directions ?
exercice 2 : pour
déterminer qu'un point du terrain est à l'ombre, il faut commencer
par dessiner le terrain depuis le point de vue du soleil.
quelle transformation, point de vue, projection, etc. utiliser pour
observer le terrain depuis la direction du soleil ?
indication : vous aurez probablement besoin d'une
projection orthographique
Transform Ortho( const float
left, const float right, const float bottom, const float top,
const float znear, const float zfar )
{
float tx= - (right + left) /
(right
- left
);
float ty= - (top + bo
ttom)
/ (top - bottom);
float tz= - (zfar + znear) / (zfar
- znear);
return Transform(
2.f / (right -
left),
0,
0, tx,
0, 2.
f / (top -
bottom),
0, ty,
0,
0, -2.f / (zfar - znear), tz,
0,
0,
0, 1);
}
cf glOrtho(),
ou les détails de la construction sur scratchapixel.com,
mais attention à l'orientation des matrices !!
exercice 3 : écrire le
zbuffer dans une texture.
vous aurez besoin de configurer et de dessiner dans un framebuffer
object, ou fbo, cf tuto
rendu multi-passses.
exercice 4 : afficher les
ombres.
il ne reste plus qu'à dessiner "normalement" depuis la camera et
dans la fenêtre (associée au fbo 0).
modifiez les fragment shaders pour récupérer dans le zbuffer la
distance de l'objet visible et éclairé par le soleil et déterminez
si le soleil est visible depuis le point du terrain associé au
fragment.
bonus : comment organiser
la description des régions afin de pouvoir dessiner chaque région
avec un seul draw ? pourquoi ?