bin/tuto7_camera
il est aussi recommandé de passer un peu de temps à (re-) lire les explications sur le fonctionnement de tout ça :
les exercices suivants permettent de manipuler chaque étape.
exercice 1 : modifez le
code de départ pour charger 2 objets, data/cube.obj et
data/robot.obj, ou data/bigguy.obj, par exemple, et affichez-les.
relisez "transformations et affichage" cf doc, si necessaire
comment afficher un cube rouge et un autre cube bleu, par exemple ?
créer 2 objets différents n'est pas une option viable. on veut un
parametre quelque part...
exercice 2 : écrivez un
couple de shaders qui dessine les objets avec une couleur
uniforme.
utilisez shader_kit pour écrire et tester les shaders, sans modifier l'application.
(ou est la doc de shader_kit ?)
dans un premier temps, la couleur sera une constante dans le
fragment shader.
que faudra-t-il modifier (dans l'application et dans le shader) pour dessiner un objet
rouge et un autre vert, par exemple ?
dessiner un objet avec une couleur uniforme est un peu limité,
pour faire mieux : il faut une matière de base et une source de
lumière. la matière la plus simple est une constante, ca tombe
bien, ce n'est pas difficile à calculer.
par contre, il faut quand
même calculer le cosinus de l'angle entre la normale d'un fragment
/ d'un point de la surface de l'objet et la direction vers la source
de lumière.
Color fragment_shader( const
Vector& n, const Vector& l )
{
float cos_theta= dot(normalize(n),
normalize(l));
return Color(cos_theta);
}
écrivez le shader en utilisant shader_kit. quels sont ses
paramètres ?
ou se trouve la doc sur toutes les fonctions de calcul de GLSL ? ie
comment calculer un produit scalaire, comment normaliser un vecteur,
etc.
est ce que ce shader pourra fonctionner avec tous les objets 3d ? pourquoi ? ou pas ?
indication : quelle
direction utiliser pour la source de lumière ? vers la camera, par exemple,
fonctionne correctement. c'est tout simplement l'axe Z (0, 0, 1) dans le
repere camera.
rappel : calculs sur les
points et les vecteurs, toutes les coordonnées doivent être dans
le même repère, sinon c'est faux et souvent très bizarre... et
oui, il faudra sans doute transformer les coordonnées de certains
points ou vecteurs...
faudra-t-il modifier l'application pour que les shaders
obtiennent ces informations ?
exercice 4 : et avec un peu de style ?
modifiez votre shader précédent, toujours dans shader_kit, pour réaliser un effet "toon shading", avec des aplats de couleurs plutot qu'un rendu plus lisse comme dans l'exercice précédent.
exercice 5 : modifiez
votre application pour dessiner avec les shaders de l'exercice
precedent.
relisez la doc, si necessaire.
si ces derniers shaders sont trop compliqués à utiliser, commencez par celui de l'exercice 2.
exercice 6 : dessinez
avec openGL, créez un ou plusieurs buffers, un vao, etc... la
totale.
ou est la doc pour faire ça ?
tous les objets ne sont pas constitués d'un seul groupe de triangles de même couleur, par exemple data/robot.obj, comment peut on dessiner ces triangles avec la bonne couleur ? bien sur, il y a plusieurs solutions, les exercices suivants proposent de réaliser et de comparer 2 solutions.
std::vector<TriangleGroup> Mesh::groups()
trie
les triangles d'un Mesh par couleur / matière et renvoie les groupes
de triangles. TriangleGroup
est une structure toute simple : struct TriangleGroup
{
int 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, ce qui permet de dessiner le groupe de
triangles.const Materials& Mesh::materials()
permet de les récupérer. cf Materials
et Material
pour la description d'une matière.Mesh m_objet;
std::vector<TriangleGroup> m_groups;
init( ):
m_objet=
read_mesh("data/robot.obj");
//
charge le fichier .obj et la description des matieres (fichier
.mtl), 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 de
l'objet
const
Materials& materials=
m_objet
.materials();
// dessine chaque
groupe de triangles, 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.index].diffuse;
// 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 / solution 2 : Mesh définit aussi un attribut de sommet
supplémentaire, l'indice de la matiere / couleur du triangle.
connaissant cet indice, comment le fragment shader peut-il
récupérer la couleur du triangle ?
en dessinant avec Mesh::draw(), le vertex shader peut récupérer l'indice de la matière / couleur : cf
mesh.draw(program,
/* use position */
true
,
/* use texcoord */
false
,
/* use normal */
true
,
/* use color */
false
,
/* use material index */
true
);
il faut déclarer l'indice de la matière dans le vertex shader en respectant la convention utilisée par Mesh, cf doc, section "et les attributs",
par contre, cet attribut n'est pas un réel, c'est juste un entier, et il n'est pas vraiment interpolable par le pipeline (entre la sortie du vertex shader et l'entrée du fragment shader), il faut prendre quelques précautions lors de la déclaration du varying. la décoration flat indique que le varying est constant à la surface du triangle :layout(location= 4) in uint material;
Reste une dernière étape : déclarer un tableau de couleurs dans le fragment shader et l'initialiser avec les couleurs des matières (dans l'application).// vertex shader
// attributs
layout(location= 0) in vec3 position;
layout(location= 2) in vec3 normal;
layout(location= 4) in uint material;
...
// sorties / varyings
flat out
uint vertex_material;
/* décoration
flat
: le varying est un entier, donc pas vraiment interpolable... il faut le déclarer explicitement */
void main( )
{
gl_Position= ...;
vertex_material= material;
}
// fragment shader
flat in
uint vertex_material; // !! decoration flat, le varying est marque explicitement comme non interpolable !!
...
void main( )
{
gl_FragColor= ...;
}
indication : oui, il faut déclarer la taille du tableau.
le plus simple est de déclarer un tableau de 16 couleurs par
exemple, seules les premières seront utilisées, il suffit
d'initialiser les autres qui ne seront pas utilisées (normalement) avec
une couleur par défaut bien visible, comme du
magenta par exemple : Color(1, 0, 1).
pourquoi une couleur bien moche ? pour debugger facilement, s'il y
a un problème avec les indices dans le shader...
exercice 3 : quelle est la meilleure solution ? pour l'application ? pour le gpu ?
avec une texture plaquée sur l'objet, que faut-il modifier dans
les shaders ? dans l'application ? peut on texturer n'importe quel
objet chargé ?
indication : cf utilisation des textures et des shaders
avec Mesh::draw(), doc
section "et avec une texture ?"
et avec plusieurs groupes de triangles qui utilisent des textures
différentes ? quelle semble être la meilleure solution ?
il existe des tableaux de textures (cf texture2DArray) en plus des
textures classiques (texture2D), est ce que ca change le type de
solution applicable ?
si ce n'est pas déjà fait, reprenez les exercices précédents et
modifiez l'application correspondante pour dessiner directement avec
openGL.
c'est à dire : créer les buffers nécessaires pour stocker les sommets et leurs propriétés, configurer un vertex array object (VAO) pour décrire comment sont stockés ces propriétés, puis au moment de dessiner : affecter une valeur à chaque paramètre uniform des shaders et enfin dessiner avec glDrawArrays().
comme précisé dans la doc, une application va ressembler à ça :
init( ) :
chargement de l'objet 3d,
création des buffers,
configuration du vao,
chargement et compilation des shaders,
état minimal d'openGL (resolution,
zbuffer et ztest, orientation des triangles et culling, couleur et z par
défaut)
render( ):
glBindVertexArray(vao);
glUseProgram(program);
glUniform( ... );
...
glDrawArrays(GL_TRIANGLES, ... );
m_objet= read_mesh("bistro-small/exterior.obj");
if(m_objet.materials().count() == 0)
// pas de matieres, pas d'affichage
return -1;
printf("%d materials.\n", m_objet.materials().count());
if(m_objet.has_texcoord() == false)
// pas de texcoords, pas d'affichage
return -1;
Materials& materials= m_objet.materials();
if(materials.filename_count() == 0)
// pas de textures, pas d'affichage
return -1;
// charge les textures referencees par les matieres
m_textures.resize(materials.filename_count(), 0);
{
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);
// meilleure qualite de filtrage...
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8);
}
printf("%d textures.\n", materials.filename_count());
}
// charge aussi une texture neutre pour les matieres sans texture...
m_white_texture=
read_texture(0, "data/grid.png"); // !! utiliser une vraie image
blanche...
// trie les
triangles par matiere et recupere les groupes de triangles utilisant la
meme matiere.
m_groups= m_objet.groups();
Mesh m_objet;
std::vector<GLuint> m_textures;
GLuint m_white_texture;
// configurer le pipeline
glUseProgram(m_program);
program_uniform(m_program, "mvpMatrix", mvp);
program_uniform(m_program, "mvMatrix", mv);
// afficher chaque groupe
const Materials& materials= m_objet.materials();
for(unsigned i= 0; i < m_groups.size(); i++)
{
const Material& material= materials(m_groups[i].index);
// couleur de la matiere du groupe de triangle
program_uniform(m_program, "material_color", material.diffuse);
// texture definie par la matiere, ou texture neutre
sinon...
if(material.diffuse_texture != -1)
program_use_texture(m_program, "material_texture", 0,
m_textures[material.diffuse_texture]);
else
program_use_texture(m_program, "material_texture", 0, m_white_texture);
// 1 draw par groupe de triangles...
m_objet.draw(m_groups[i].first, m_groups[i].n, m_program, /* use
position */ true,
/* use texcoord */ true, /* use normal */ true, /*
use color */ false, /* use material index*/ false);
}