L'objectif de cette partie est d'afficher les objets en fonction
de leurs matières.
Chaque triangle d'un Mesh
est associé à une matière, cf la classe
Material. cf Mesh::materials() pour recuperer l'ensemble de
matieres et la description de la classe Materials.
les indices des matieres des triangles sont egalement stockes dans
Mesh, utilisez const std::vector<unsigned>&
Mesh::material_indices()
pour recuperer tous les indices,
ou Mesh::triangle_material_index() pour connaitre l'indice de la
matiere d'un triangle.
Il existe bien sur plusieurs solutions pour dessiner les
triangles en tenant compte de leur matiere.
exercice 1 :
Modifiez votre shader pour ajouter un parametre supplementaire :
une couleur. comment modifier le shader ? faut-il modifier
l'application ? comment ?
bien sur, il est possible de recuperer la matiere de chaque triangle et de dessiner l'objet triangle par triangle, mais ce n'est vraiment pas efficace...
une solution courante et plus rapide consiste à trier les triangles de l'objet par matière, puis à afficher chaque groupe de triangles avec les bons parametres.std::vector<TriangleGroup> Mesh::groups()
trie
les triangles d'un Mesh par matiere et renvoie les groupes de
triangles à dessiner. struct TriangleGroup
{
int
material_index; // indice de la matiere dans
Mesh::materials()
int
first;
// indice du premier sommet a dessiner
int
n;
// nombre d'indices
};
first
et n
?
ce sont les parametres de glDrawArrays(GL_TRIANGLES, /* first
*/, /* n */ )
qui dessine des triangles avec n sommets
(donc n/3 triangles) à partir du sommet d'indice first dans le
tableau de sommets du Mesh.init( ):
m_objet=
read_mesh("data/robot.obj");
//
charge le fichier .obj et le .mtl associe (s'il existe)
if(m_objet.materials().count() == 0)
return -1; // pas de matieres, pas d'affichage
// trie les
triangles par matiere et recupere les groupes de triangles
utilisant la meme matiere.
m_groups=
m_objet.groups();
render( ):
// ensemble de matieres
const
Materials& materials=
m_objet
.materials();
// dessine chaque
groupe de triangle, avec sa matiere
for(unsigned
i= 0; i < m_groups.size(); i++)
{
const TriangleGroup& group= m_groups[i];
// recuperer la couleur de la matiere du groupe
Color color=
materials[group.material_index].diffuse_color;
// parametrer le shader pour dessiner avec la
couleur
glUseProgram(m_program);
{ ... }
// dessiner les triangles du groupe
glDrawArrays(GL_TRIANGLES, group.first,
group.n);
}
exercice 2 :
est-il réellement nécessaire de faire plusieurs affichages ?
peut-on ré-organiser les "informations" différemment pour
afficher tous les triangles et leurs matieres, en une seule fois /
avec le même fragment shader ?
testez votre solution et comparez.
la solution à ce problème est classique et très simple : il
suffit d'utiliser une indirection simple, ou une indirection
double...
avec openGL, on peut faire la même chose en declarant les
tableaux dans le fragment shader, et utiliser gl_PrimitiveID comme
indice de triangle.
mais il y a une contrainte à respecter. les tableaux d'uniforms
sont statiques. il faut déclarer leur taille et au total, les
uniforms déclarés par un shader ne peuvent pas occupper plus de
32Ko... du coup, il est plus raisonnable d'utiliser la version
avec une seule indirection qui n'utilise qu'un (petit) tableau de
matieres. par contre, il va falloir trouver une autre solution
pour obtenir l'indice de la matiere du triangle dans le fragment
shader.
remarque : vu la limite sur le nombre de matieres, il est
raisonnable d'utiliser un GL_UNSIGNED_BYTE, un entier non signe
sur 8bits, comme attribut de sommet (et oui, ca limite le nombre
de matieres à 256. mais stocker 256 descriptions de matières
occuppe une bonne partie des 32Ko disponibles...)
indications :
le fragment shader connait l'indice de la
primitive qu'il dessine., cf gl_PrimitiveID
on peut utiliser des tableaux d'uniforms,
on peut ajouter des attributs aux sommets des
triangles : pour configurer un attribut entier (l'indice de la
matière par exemple), il faut utiliser glVertexAtttribIPointer(),
au lieu de glVertexAttribPointer()
on peut utiliser des tableaux de textures 2d :
cf GL_TEXTURE_2D_ARRAY
bonus : peut on utiliser la même stratégie avec les matières
de plusieurs objets ? quelles seraient les contraintes à respecter
?
on peut dessiner plusieurs objets avec un seul
glDraw(), cf glDrawArraysInstanced() et
glMultiDrawArraysIndirect()...
qui gère les ressources openGL ? chaque objet ?
la technique d'affichage ? faut-il créer une gestion centralisee
ou peut-on laisser chaque objet décider ?
Mesh m_objet;
std::vector<GLuint> m_textures;
init( ) :
m_objet=
read_mesh("bistro/exterior.obj");
if(m_objet.materials().count() == 0)
// pas de matieres, pas d'affichage
return -1;
Materials& materials= mesh.materials();
m_textures.resize(materials.filename_count(), 0);
// charge
toutes les textures
{
printf("%d materials.\n", materials.count());
for(unsigned i= 0; i < m_textures.size(); i++)
{
m_textures[i]= read_texture(0, materials.filename(i));
// repetition des textures si les texccord sont > 1, au lieu de
clamp...
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT);
}
printf("%d textures.\n", materials.filename_count());
}
render( ) :
// ensemble
de matieres
const
Materials& materials= m_objet.materials();
// dessine
chaque groupe de triangle, avec sa matiere
for(unsigned
i= 0; i < m_groups.size(); i++)
{
const TriangleGroup& group= m_groups[i];
// recuperer la couleur de la matiere du groupe
Color material_color=
materials[group.material_index].diffuse_color;
// et recuperer la texture couleur
int texture_id=
materials[group.material_index].diffuse_texture;
GLuint material_texture=
m_textures[texture_id];
// parametrer le shader pour dessiner avec la couleur et une
texture, cf program_use_texture() par exemple...
glUseProgram(m_program);
{ ... }
// dessiner les triangles du groupe
glDrawArrays(GL_TRIANGLES, group.first, group.n);
}
init( ) :
// charge toutes les textures
et reduit leur
taille...
int
n= 0;
int size= 0;
for(int i=
0; i < materials.filename_count(); i++)
{
// charge l'image
ImageData image= read_image_data(materials.filename(i));
// resize
ImageData level= mipmap_resize(image);
size+= level.pixels.size();
// cree la texture sur l'unite 0
m_textures[i]= make_texture(0, level);
// repetition des textures si les texccord sont > 1, au lieu de
clamp...
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
n++;
}
printf("%d
textures. %dMB\n", n, size / 1024 / 1024);
ImageData mipmap_resize( const ImageData& image )
{
assert(image.size == 1);
ImageData level=
ImageData(image.width/2, image.height/2, image.channels,
image.size);
int w= level.width;
int h= level.height;
int c= level.channels;
for(int y= 0; y < h; y++)
for(int x= 0; x < w; x++)
for(int i= 0; i < c; i++)
{
int m= 0;
m= m +
image.pixels[image.offset(2*x, 2*y, i)];
m= m +
image.pixels[image.offset(2*x +1, 2*y, i)];
m= m +
image.pixels[image.offset(2*x, 2*y +1, i)];
m= m +
image.pixels[image.offset(2*x +1, 2*y +1, i)];
level.pixels[level.offset(x, y, i)]= m / 4;
}
return level;
}