"exterior" (1.2Go) extrait de la scene Bistro :
et oui, si le premier test ne permet pas de "séparer" la boite et le frustum, il faut faire le 2ieme test.
remarque : dans le repère projectif homogène, qu'est ce qui se passe avec w, le poids homogène des points projettés ?Il ne reste plus qu'à tester les objets de la scène avant de les
dessiner. Mesh::bounds() calcule les points extrèmes de la boite
englobante alignée sur les axes d'un objet.
exercice 1 : connaissant pmin et pmax, les points extrèmes d'une boite alignée sur les axes, comment calculer les coordonnées des 8 sommets de la boite ?
indications
: pmin.x et pmax.x représentent les bornes de
l'intervalle sur l'axe x, idem pour y et z.
autrement dit, 4 sommets se trouvent à x= pmin.x et 4 autres à x=
pmax.x, idem pour y et z.
exercice 2 : vérifiez que votre test de visibité fonctionne correctement sur une scène de test, comme la grille de cubes du tp précédent.
exercice 3 : sauf que dans la scène "bistro", il n'y a qu'un seul objet... comment faire pour découper cette scène en plusieurs blocs ?
il y a bien sur plusieurs solutions, proposez-en 1 ou 2 et
vérifiez avec les autres binomes, quelle est la meilleure solution
?
exercice 4 : réalisez le
découpage du mesh en boites. testez-les avant affichage des
triangles associés.
est-il plus rapide d'afficher toute la scène sans faire de tests de visibilité ? et en changeant la taille des boites ? que constatez-vous ?
indication : vous pouvez trier les triangles d'un objet avecMesh::groups( const std::vector<unsigned>&
properties )
, en associant un entier à chaque triangle
(par exemple un indice de cellule dans une grille...). groups()
renvoie les triangles tries selon ce critere, par cellule de la
grille... cf MeshGroup et tuto9_groups.cpp
pour afficher les différents groupes.ah ? comment on fait alors ?
facile, mais il va falloir dessiner 2 fois la scène,
mais on "garde" les triangles visibles par la source de lumière,
on sait qu'ils sont éclairés, c'est le résultat de la 1ere étape.
pour chaque pixel de l'image dessinée pour le point de vue de la
source on connait la distance entre la source de lumière et le
triangle visible.
ensuite, lorsque l'on dessine la scène pour le point de vue de la
camera, on transforme le point visible pour la camera dans le
repère / le point de vue de la source. il ne reste plus qu'à
comparer la distance entre le point transformé et la source avec
la distance de la 1ere étape, qui correspond au triangle le plus
proche de la source, ie celui qui est éclairé. si le point est à
la bonne distance / sur le triangle éclairé, il est lui aussi
éclairé, sinon sa distance est plus grande et il est à l'ombre.
exercice 1 : dessinez la scène pour le point de vue de la source de lumière.
on commence par construire les transformations qui permettent d'observer la scène depuis la position de la source de lumière. il faut, comme d'habitude, définir les transformations model, view, projection et image.
le plus direct est d'utiliser une projection ortographique, cf Ortho()
qui "projette" un cube [left .. right] x [bottom .. top] x
[znear .. zfar]
du repère camera vers le repère
projectif.
des exemples complets sont dans la doc en ligne, cf "decals
et projection de textures"
exercice 2 : création du framebuffer et des textures
étape suivante, il faut conserver les triangles visibles par la
source de lumière, ils seront éclairés. ces triangles sont aussi
les plus proches de la source de lumière (par construction), on
veut conserver au minimum le zbuffer, ie la distance entre la
source de lumière et le triangle visible / éclairé.
la création / configuration d'un framebuffer est assez directe,
mais il y a bien sur quelques détails techniques à régler, cf "rendu
multi-passes". vous pouvez utiliser les utilitaires de texture.h
pour créer simplement les textures nécessaires, cf
make_depth_texture() ou make_vec3_texture(), etc. elles sont
faites exactement pour ça !
au final, votre code ressemblera à :
GLuint shadow_map=
make_depth_texture( /* unit */ 0, /* width */ 256, /* height */
256 );
GLuint framebuffer= 0;
glGenFramebuffers(1,
&framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */
GL_DEPTH_ATTACHMENT, shadow_map, /* mipmap */ 0);
// verification de la
configuration du framebuffer
if(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) !=
GL_FRAMEBUFFER_COMPLETE)
return
"error";
// nettoyage...
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
pour dessiner dans le framebuffer, il ne reste plus qu'à le
sélectionner (avant de dessiner la scene), mais attention aux
paramètres implicites du draw() !
(par exemple les dimensions de l'image... cf glViewport())
exercice 3 : et maintenant les ombres !!
et voila, tout est pret !! il ne reste plus qu'à dessiner la
scène depuis le point de vue de la camera et à écrire le test qui
vérifie qu'un point (vu par la camera) est plus près ou plus loin
que le triangle éclairé par la source de lumière. les détails de
la projection et des transformations sont dans "decals
et projection de textures" / section "finir le calcul :
projetter le décal"
attention : dans le shader, la fonction texture(shadow_map,
uv)
renvoie un vec4, la distance stockée dans la texture
se trouve dans la composante .x ou .r (et pas .z qui sera
toujours 0...)
exercice 4 : et maintenant ? des ombres propres !!
et voila ça marche !
ou pas, c'est quand même moche, non ?
argh, mais pourquoi ??
le zbuffer stocke une distance par pixel :
mais cette profondeur est constante : tous les points du triangle
qui se projettent sur le même pixel ont la même distance, et le
triangle n'est plus vraiment une surface, mais une approximation :
et lorsque l'on dessine la scène depuis un autre point de vue, on
utilise la distance d'un autre point du triangle, et ces distances
ne sont pas tout à fait identiques... donc il va falloir modifier
le test...
GLuint make_texture_array( const int unit, const std::vector<ImageData>& images, const GLenum texel_format= GL_RGBA )
{
assert(images.size());
assert(images[0].pixels.size());
// verifie que toutes les images sont au meme format
int w= images[0].width;
int h= images[0].height;
int d= int(images.size());
for(unsigned i= 1; i < images.size(); i++)
{
if(images[i].pixels.size() == 0)
continue; // pas de pixels, image pas chargee ?
if(images[i].width != w)
return 0; // pas la meme largeur
if(images[i].height != h)
return 0; // pas la meme hauteur
}
// alloue le tableau de textures
GLuint texture= 0;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, /* mipmap */ 0,
texel_format, w, h, d, /* border */ 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// transfere les textures
for(unsigned i= 0; i < images.size(); i++)
{
if(images[i].pixels.size() == 0)
continue;
// recupere les parametres de conversion...
GLenum format== GL_RGB;
if(images[i].channels == 4)
format= GL_RGBA;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, /* mipmap */ 0,
/* x offset */ 0, /* y offset */ 0, /* z offset == index */ i,
w, h, 1,
format, GL_UNSIGNED_BYTE, images[i].pixels.data());
}
// mipmaps
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
printf("texture array: %dx%dx%d %dMo\n", w, h, d, 4*w*h*d / 1024 / 1024);
return texture;
}