exercice 1 : matière diffuse
écrivez un shader permettant de donner un aspect diffus à un
objet.
quelles sont les informations nécessaires ? dans quel repère faire
le calcul ? quel est le meilleur choix ?
dans quel shader faire le calcul ? quelle difference observez-vous
?
exercice 2 : plusieurs sources de lumière
modifiez vos shaders pour éclairer un objet avec plusieurs de
sources de lumière.
quelles sont les informations nécessaires ? comment les
transmettre aux shaders ?
dans quel shader faire le calcul ?
exercice 3 : matière réfléchissante
écrivez un shader permettant de donner un aspect réfléchissant à
un objet.
dans quel shader faire le calcul ? quelle difference observez-vous
?
exercice 4 : brdf
écrivez un shader permettant de donner un aspect partiellement
diffus et réfléchissant à un objet.
éclairez votre objet avec 1 ou plusieurs sources de lumière.
exercice bonus : brdf Disney.
il existe de nombreuses apparences possibles, décrits par autant de modèles de matières. il y a aussi plusieurs manières de combiner plusieurs matières simples afin de décrire et de reproduire une apparence plus complexe. les chercheurs de Disney ont documenté leur approche :
modifiez vos shaders pour utiliser une brdf basée sur la distribution d'orientations GGX, vous pourrez tester avec des parametres de rugosité de l'ordre de 0.5 pour une matiere peu reflechissante ou 0.1 / 0.01 pour des matières très réflechissantes, avec reflets très concentrés.
lorsqu'une scène (ou un objet un peu complexe et étendu) est
affichée de nombreux triangles se dessinent sur le meme pixel. les
fragments shaders sont bien sur exécutés pour chaque triangle,
mais seul le resultat calculé pour le triangle le plus proche de
la camera reste visible, les autres ont ete calcules pour rien...
et les resultats sont assez surprenants, meme sur la scene
d'exemple :
vous pouvez tester avec quelques scenes disponibles sur la page
de M. McGuire :
Meshes, conference, sponza et sibenik par exemple.
attention à la taille des objets, certains pesent 1GB ou plus.
exercice 1 : configurer le zbuffer
la carte graphique dessine scrupuleusement les objets et les triangles dans l'ordre. Le test sur l'orientation des triangles (cf glEnable/Disable(GL_CULL_FACE)) permet d'eliminer a peu pres la moitiee des triangles, mais on peut faire mieux...
par défaut, le pipeline exécute tous les fragments shaders, puis
compare leur profondeur au zbuffer. mais on peut modifier la
configuration du pipeline pour tester la profondeur d'un fragment
avant d'exécuter le shader, et les cartes recentes font meme mieux
en utilisant un test hiérarchique, sur un bloc de pixels. si les
fragments d'un triangle sont tous derriere le zbuffer, les
fragments shaders d'un bloc complet ne seront pas executes. mais
ce n'est efficace que lorsqu'un triangle couvre entièrement un
bloc (et que le bloc est complet, tous les fragments ont deja une
profondeur).
pour les curieux : les solutions réellement utilisées
par les cartes graphiques sont peu documentées (mais tres
brevetees), mais le principe est assez bien exposé par :
la scene d'exemple est tres (trop) maillee et les triangles sont
souvent plus petits que la taille des blocs :
et plus les fragments shader sont longs à executer plus c'est
interressant. il suffit d'ajouter une seule ligne dans le fragment
shader pour activer le test :
layout(early_fragment_tests) in;
verifiez dans quel cas vous observez une difference en utilisant
votre fragment shader de l'exercice 4, utilisez la version avec
plusieurs sources de lumiere (une centaine, par exemple).
utilisez les mesures de temps cpu et gpu (cf tuto_time)
pour faire vos comparaisons. vous pouvez aussi utiliser la classe
AppTime, qui le fait automatiquement.
exercice 2 : aider le zbuffer
dans tous les cas, l'ordre dans lesquel sont dessines les objets (ou les elements d'un meme objet) influence les performances du pipeline : si le test de profondeur (et sa version bloc / hierarchique) peuvent reduire le nombre de fragment shaders, l'affichage est plus rapide. quel est le meilleur ordre pour afficher les triangles ?
indications : aleatoire ? en s'eloignant de la camera ? en
s'approchant de la camera ? d'abord en haut a gauche ? puis a
droite ?
bien sur trier les triangles sur cpu avant de les afficher n'est
(vraiment) pas efficace. quelle approximation peut-on utiliser
pour dessiner a peu pres dans le bon ordre, sans manipuler tous
les triangles ?
indications :
est ce que cette solution est toujours interressante ? dans quels
cas est elle plus lente que l'affichage direct ?
comparez les 2 solutions, l'objectif est que le "tri" + affichage
soit plus rapide que l'affichage seul.
quelle definition de "plus rapide" faut il choisir ?
remarque : une fonction permettant de trier les triangles
par matière est disponible en annexe.
exercice 3 : Z pre pass
une autre solution consiste à dessiner 2 fois les objets de la scène :
meme question que precedemment.
/* affichage d'un mesh par matiere
*/
std::vector<Group> groups;
Mesh data= read_mesh( ... );
Mesh mesh(GL_TRIANGLES);
// construction des groupes de triangles
sort_materials(data, mesh, groups);
// go !
for(int i= 0; i < groups.size(); i++)
{
// recupere la couleur du groupe
program_uniform(m_program, "color",
mesh.mesh_material(m_groups[i].material).diffuse);
// program_uniform(m_program, "color",
Color((i % 100) / 99.f, (i % 10) / 9.f, (i % 4) / 3.f));
// affiche les triangles associes a la
matiere
glDrawArrays(GL_TRIANGLES, groups[i].first,
groups[i].count);
}
// representation d'un groupe de triangles associe a une matiere
struct Group
{
int material;
//!< indice de la matiere
int first;
//!< indice
de depart /
indice du sommet du premier triangle du groupe
int count;
//!< nombre d'indices
Point
pmin; //!< englobant du
groupe de triangles
Point pmax;
Group( const int _id= -1, const
int _first= 0 ) : material(_id), first(_first), count(0) {}
};
// predicat pour std::sort
struct compareMaterial
{
const std::vector<unsigned
int>& materials;
compareMaterial( const
std::vector<unsigned int>& _materials ) :
materials(_materials) {}
bool operator() ( const int
&a, const int& b ) const
{
// compare
l'indice des matieres des triangles a et b
return
materials[a] < materials[b];
}
};
/* tri par matiere :
data : mesh a trier
mesh : resultat trie
groups : sequences de triangles utilisant la
meme matiere
*/
void sort_materials( const Mesh& data, Mesh& mesh,
std::vector<Group>& groups )
{
std::vector<int>
remap(data.triangle_count());
for(int i= 0; i < data.triangle_count();
i++)
remap[i]= i;
// trie les triangles par matiere
std::stable_sort(remap.begin(), remap.end(),
compareMaterial(data.materials()));
// copie les matieres
mesh.mesh_materials(data.mesh_materials());
// separe les groupes de triangles
groups.push_back(
Group(data.materials().at(remap[0]), 0)
); // matiere du triangle 0
// copie les sommets des triangles dans
l'ordre et construit les groupes
// construit aussi la boite englobante de
chaque groupe
Point pmin=
Point(data.positions().at(3*remap[0]));
Point pmax= pmin;
bool has_texcoords=
!data.texcoords().empty();
bool has_normals= !data.normals().empty();
for(int i= 0; i < data.triangle_count();
i++)
{
int triangle_id=
remap[i];
vec3 a=
data.positions().at(3*triangle_id);
vec3 b=
data.positions().at(3*triangle_id +1);
vec3 c=
data.positions().at(3*triangle_id +2);
// matiere
int material=
data.materials().at(triangle_id);
if(material !=
groups.back().material)
{
mesh.material(material);
// termine le groupe precedent
groups.back().count= 3*i - groups.back().first;
groups.back().pmin= pmin;
groups.back().pmax= pmax;
// demarre un nouveau groupe
groups.push_back( Group(material, 3*i) );
pmin= Point(a);
pmax= pmin;
}
// position et
englobant
pmin= Point(
std::min(pmin.x, a.x), std::min(pmin.y, a.y), std::min(pmin.z,
a.z) );
pmax= Point(
std::max(pmax.x, a.x), std::max(pmax.y, a.y), std::max(pmax.z,
a.z) );
pmin= Point(
std::min(pmin.x, b.x), std::min(pmin.y, b.y), std::min(pmin.z,
b.z) );
pmax= Point(
std::max(pmax.x, b.x), std::max(pmax.y, b.y), std::max(pmax.z,
b.z) );
pmin= Point(
std::min(pmin.x, c.x), std::min(pmin.y, c.y), std::min(pmin.z,
c.z) );
pmax= Point(
std::max(pmax.x, c.x), std::max(pmax.y, c.y), std::max(pmax.z,
c.z) );
// termine la
description du sommet
if(has_texcoords)
mesh.texcoord(data.texcoords().at(3*triangle_id));
if(has_normals)
mesh.normal(data.normals().at(3*triangle_id));
mesh.vertex(a);
if(has_texcoords)
mesh.texcoord(data.texcoords().at(3*triangle_id +1));
if(has_normals)
mesh.normal(data.normals().at(3*triangle_id +1));
mesh.vertex(b);
if(has_texcoords)
mesh.texcoord(data.texcoords().at(3*triangle_id +2));
if(has_normals)
mesh.normal(data.normals().at(3*triangle_id +2));
mesh.vertex(c);
}
// termine le dernier groupe
groups.back().count= 3*mesh.triangle_count()
- groups.back().first;
groups.back().pmin= pmin;
groups.back().pmax= pmax;
}