avant de vous lancer dans l'affichage de la scène, il est quand même recommandé de passer un peu de temps :
exercice 1 : dessiner plusieurs copies du meme objet
utilisez glDrawInstanced pour afficher plusieurs fois
data/robot.obj à des endroits différents. vous devez être à l'aise
avec la version qui stocke les transformations dans un buffer. cf
la doc,
section "draw instanced, dessiner plusieurs copies", ie configurez
un attribut d'instance.
dans un premier temps, utilisez uniquement une translation, ie un
vec3.
exercice 2 : et avec une matrice ?
il va falloir trouver un moyen d'utiliser une matrice pour placer
et orienter chaque copie de l'objet. comment faire ? il y a au
moins 2 solutions, soit utiliser des attributs d'instance, soit
utiliser un tableau uniform...
mais les uniforms ne peuvent pas occuper plus de 32Ko au total,
ce qui permet de stocker au mieux 512 matrices 4x4 (1 matrice ==
64octets). en pratique, il faut se limiter plutot à 128 ou 256
matrices, pour laisser de la place aux autres uniforms des
shaders.
in vec3 position;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 models[128];
void main( )
{
// recuperer la transform de l'instance
mat4 model= models[gl_InstanceID];
// comme d'habitude...
gl_Position= projection * view * model * vec4(position, 1);
}
// remarque : on peut bien sur composer view et projection dans une seule matrice...
remarque : vous pouvez utiliser program_uniform(
/* program */ , /* uniform */ ,
std::vector<Transform>& transforms ) pour
initialiser ce tableau. le vecteur doit faire la meme taille que
le tableau d'uniforms dans le shader. ce n'est pas nécessaire pour
openGL mais la vérification permet de trouver certaines erreurs
d'indexation. pour eviter l'affichage des messages dans la
console, il suffit d'utiliser glUniformMatrix4fv() directement.
mettez gkit à jour, avec git pull
les fichiers src/gKit/gltf.h et src/gKit/gltf.cpp permettent de
charger une scene au format gltf. la scene est décrite par une
structure toute simple, cf GLTFScene,
qui contient des tableaux de maillages, de transformations pour
dessiner les maillages, etc.
pour les curieux : le format gltf permet de stocker pas
mal de choses, cf glTF
Tutorial.
exemple de parcours des noeuds, des maillages et de leur groupes
de primitives (triangles) :
#include "gltf.h"
GLTFScene scene= read_gltf_scene( "..." );
// parcours les noeuds de la scene
for(unsigned node_id= 0; node_id < scene.nodes.size(); node_id++)
{
const GLTFNode& node= scene.nodes[node_id];
// recupere le mesh du noeud
const GLTFMesh& mesh= scene.meshes[node.mesh_index];
// recupere la transform du noeud
Transform m= node.model;
// parcours les primitives du mesh
for(unsigned primitive_id= 0; primitive_id < mesh.primitives.size(); primitive_id++)
{
const GLTFPrimitives& primitives= mesh.primitives[primitive_id];
// matiere des primitives / groupe de triangles
const GLTFMaterial& material= scene.materials[primitives.material_index];
{ ... }
}
}
il est assez simple de compter combien de fois on change de shader, de texture, etc. dans l'exemple précédent, c'est facile, tout change à chaque itération. c'est a dire 30300 fois... et c'est lent (pour l'application et le driver openGL qui prépare les commandes à exécuter sur la carte graphique).for( node : nodes )
{
mesh= node.mesh
// chaque groupe de triangles est associe a une matiere differente / shader
for( triangles : mesh.primitives )
{
bind_vao(triangles)
material= triangles.material
bind_program(material)
// transformations
uniform(node.model)
uniform(view)
uniform(projection)
// parametres de la matiere
uniform(material.color)
uniform(material.metallic)
uniform(material.roughness)
// textures
bind_texture(material.color_texture)
bind_texture(material.metallic_roughness_texture)
draw(triangles)
}
}
le code d'affichage boucle sur un ensemble de groupes de triangles et avant de changer la valeur d'un paramètre (ce qui n'est pas gratuit) on vérifie qu'il est différent du précédent :struct state
{
shader
primitives
material
mesh
model
}
// construire les parametres
state states[]
for( node : nodes )
{
mesh= node.mesh
model= node.model
for( primitives : mesh.primitives )
{
material= primitives.material
shader= material.shader
states.insert( {shader, primitives, material, mesh, model} )
}
}
mais, on peut faire mieux : pourquoi faut-il changer un paramètre ? le shader par exemple ? il y a 90 matières différentes dans la scène, mais voici leurs paramètres principaux :// trier les parametres, ou pas...
{ ... }
// dessiner dans l'ordre
for( state : states )
{
// shader
if(state.shader != last shader)
{
use_program(state.shader)
// camera
unifom(view)
uniform(projection)
}
// parametres de la matiere
if(state.material != last material)
{
// parametres de la matiere
uniform(state.material.color)
uniform(state.material.metallic)
uniform(state.material.roughness)
}
// textures
if(state.material.color_texture != last color_texture)
bind_texture(state.material.color_texture)
if(state.material.metallic_roughness_texture != last metallic_roughness_texture)
bind_texture(state.material.metallic_roughness_texture);
// buffers+vao
if(state.primitives.vao != last vao)
bind_vao(state.primitives.vao)
draw(state.primitives)
}
(la forme des reflets n'est pas exacte, c'est une approximation... les modèles à micro-facettes plus complexes ne seraient pas utilisés sinon...)diffuse_color= (1 - metal) * color
specular_color = (1 - metal) * Color(0.04) + metal * color
alpha= 2 / (roughness^4) - 2
f_r(p, n, o, l)
= diffuse_color / pi + specular_color * (alpha+8)/ 8pi * (cos theta(n, h))^alpha
avec
theta(u, v) : angle entre les vecteurs u et v, et cos theta(u, v), cosinus de l'angle entre u et v, cf dot(u, v) si u et v sont de longueur 1...
F= specular_color + (Color(1) - specular_color) * (1 - cos theta(o, h))^5 // utilise l'approximation des coeffs de Fresnel...
f_r(p, n, o, l)= F * diffuse_color / pi + (1 - F) * specular_color * (alpha+8)/ 8pi * (cos theta(n, h))^alpha
struct draw_state
{
int shader_type;
int material_index;
int transparent;
int color_texture;
int metallic_roughness_texture;
int node_index;
int mesh_index;
int primitive_index;
int first;
int n;
};
std::vector<draw_state> states;
// construire la liste d'etats
for(unsigned node_id= 0; node_id < m_scene.nodes.size();
node_id++)
{
const GLTFNode& node= m_scene.nodes[node_id];
const GLTFMesh& mesh= m_scene.meshes[node.mesh_index];
for(unsigned primitive_id= 0; primitive_id <
mesh.primitives.size(); primitive_id++)
{
const GLTFPrimitives& primitives=
mesh.primitives[primitive_id];
int material_id= primitives.material_index;
const GLTFMaterial& material= m_scene.materials[material_id];
draw_state state= { };
state.shader_type= -1;
state.material_index= material_id;
state.transparent= material.transmission > 0;
state.color_texture= material.color_texture;
state.metallic_roughness_texture=
material.metallic_roughness_texture;
state.node_index= node_id;
state.mesh_index= node.mesh_index;
state.primitive_index= primitive_id;
states.push_back(state);
}
}
printf("%d states\n", int(states.size()));
// tri par etat
std::sort(states.begin(), states.end(),
[]( const draw_state& a, const draw_state& b )
{
if(a.material_index != b.material_index)
return a.material_index < b.material_index;
// todo comparaison
lexicographique
{ ... }
return false;
}
);
// analyse des changements d'etats en fonction de l'ordre...
{
int materials_count= 0;
int color_texture_count= 0;
int metallic_roughness_texture_count= 0;
int nodes_count= 0; //
transforms...
int primitives_count= 0; // vao + buffers...
int last_material= -1;
int last_color_texture= -1;
int last_metallic_roughness_texture= -1;
int last_node_index= -1;
int last_primitive_index= -1;
for(unsigned i= 0; i < states.size(); i++)
{
const draw_state& state= states[i];
if(state.material_index != last_material)
{
last_material= state.material_index;
materials_count++;
}
if(state.color_texture != last_color_texture)
{
last_color_texture= state.color_texture;
color_texture_count++;
}
if(state.metallic_roughness_texture !=
last_metallic_roughness_texture)
{
last_metallic_roughness_texture= state.metallic_roughness_texture;
metallic_roughness_texture_count++;
}
if(state.node_index != last_node_index)
{
last_node_index= state.node_index;
nodes_count++;
}
if(state.primitive_index != last_primitive_index)
{
last_primitive_index= state.primitive_index;
primitives_count++;
}
}
printf("%d material / %d color texture / %d metallic+roughness
texture / %d node / %d primitive\n",
materials_count, color_texture_count,
metallic_roughness_texture_count, nodes_count, primitives_count);
}
}