M2 - Images


TP3 - pipeline 



mise à jour : tutos wavefront (cf menu modules dans la doc) et gkit (git pull; git update)

archive gkit2 03/10/2017




Partie 1: matières et lumières

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 ?

aspect diffus


remarque :
si vos objets n'ont pas de normales, il suffit de les calculer, tuto_wavefront.


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 ?

aspect reflechissant

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.


aspect mixte : diffus et reflechissant

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.


Partie 2 : pipeline

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 :

overdraw

remarque : vous pouvez utiliser tutos/tuto_texture_storage.cpp pour reproduire ce resultat.

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 ce test supplementaire ne permet pas toujours de faire mieux que la solution de base...


sur une autre scene (cf crytek sponza), moins maillee, avec des triangles plus gros, c'est beaucoup plus efficace :


(à gauche : affichage classique, à droite : early fragment tests)

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.




exercice 4 : et avec plusieurs shaders.

quelle serait la meilleure solution lorsque de nombreux shaders sont presents dans la scene ?




un peu de lecture :



Annexe : tuto read_mesh_data(), buffers( ) et normals()

cf tuto_wavefront



Annexe : tri par matière



/* 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;
}