"exterior" (1.2Go) extrait de la scene Bistro :
mtllib bistro.mtl
par
mtllib export.mtl
dans bistro.obj pour les charger
automatiquement avec read_mesh() ou read_indexed_mesh().Material::specular
)
utilisent une convention particulière : ce ne sont pas vraiment des
couleurs, mais 3 valeurs entre 0 et 1. le canal rouge représente
l'ambient occlusion, le canal vert la rugosité de la matière et le
canal bleu permet de décrire les métaux. au minimum, il faut évaluer
la concentration des reflets Blinn-Phong à partir de la rugosité : ns=
(2 / rugosite**2 - 2)
et oui, si le premier test ne permet pas de "séparer" la boite et le frustum, il faut faire le 2ieme test.
remarque : dans le repère projectif homogène, qu'est ce qui se passe avec w, le poids homogène des points projettés ?Il ne reste plus qu'à tester les objets de la scène avant de les
dessiner. Mesh::bounds() calcule les points extrèmes de la boite
englobante alignée sur les axes d'un objet.
exercice 1 : connaissant pmin et pmax, les points extrèmes d'une boite alignée sur les axes, comment calculer les coordonnées des 8 sommets de la boite ?
indications
: pmin.x et pmax.x représentent les bornes de
l'intervalle sur l'axe x, idem pour y et z.
autrement dit, 4 sommets se trouvent à x= pmin.x et 4 autres à x=
pmax.x, idem pour y et z.
exercice 2 : vérifiez
que votre test de visibité fonctionne correctement sur une scène
de test, comme la grille de cubes du tp précédent.
exercice 3 : sauf que dans exterior.obj, il n'y a qu'un seul objet... comment faire pour découper la scène en plusieurs blocs ?
il y a bien sur plusieurs solutions, proposez-en 1 ou 2 et
vérifiez avec les autres binomes, quelle est la meilleure solution
?
exercice 4 : réalisez le
découpage du mesh en boites. testez-les avant affichage des
triangles associés.
est-il plus rapide d'afficher toute la scène sans faire de tests de visibilité ? et en changeant la taille des boites ? que constatez-vous ?
indication : vous pouvez trier les triangles d'un objet
avec Mesh::groups( const std::vector<unsigned>&
properties )
, en associant un entier à chaque triangle
(par exemple un indice de cellule dans une grille...). groups()
renvoie les triangles tries selon ce critere, par cellule de la
grille... cf MeshGroup et tuto9_groups.cpp
pour afficher les différents groupes.
il y a ~20000 triangles qui émettent de la lumière dans
exterior.obj. vous pouvez vous amuser à modifier un fragment
shader pour évaluer la couleur du fragment en calculant la lumière
réfléchie par chaque source et en sommant le tout. ca va etre un
peu long, mais ça fonctionnera.
peut-on limiter le nombre de sources de lumière que l'on évalue
dans le fragment shader ? sans introduire trop d'erreur dans le
résultat...
indication : si la
source est trop loin son influence est très faible.
rappel : le flux reçu par
un point à une distance d de la source dépend du carré de d...
comment déterminer le sous ensemble de sources de lumière qui
peut éclairer un fragment ? une région de la scène ? comment
passer cette information au fragment shader ?
exercice 1 : modifiez votre fragment shader pour gérer N
sources. comment transmettre cette information au shader ?
pour tester, créez N sources placées aléatoirement au dessus de la
scène.
exercice 2 : on peut aussi utiliser le découpage spatial de la scène de la partie 2 pour sélectionner les sources qui peuvent éclairer chaque région. comment transmettre ces informations au shader ? comment le fragment shader peut-il retrouver l'ensemble de sources pouvant éclairer un fragment ?
question : serait-il interressant d'utiliser un découpage
spatial plus fin, ie des régions plus petites, pour construire
l'ensemble de sources associé à chaque région ?
peut-on éviter de faire des calculs inutiles ?
question : quels calculs sont inutiles ? comment éviter de
les faire ?
pensez vous qu'un rendu avec une Z prepass soit interressant dans
ce cas ? ou un rendu différé ? pourquoi ?
exercice : modifiez votre programme pour tester une des 2
techniques. que constatez-vous ? avec plus de sources de lumière ?
avec moins ?
indications : parmi les choses inutiles, combien de vertex
shaders s'exécutent si la scène est décrite par des triangles non
indexés ? et avec des triangles indexés ?
rappel : il existe read_mesh() et read_indexed_mesh() pour
vérifier (oui c'est récent...)
indications : on peut dessiner des objets différents (y
compris des instances) avec un seul draw, cf glMultiDraw() et
glMultiDrawIndirect()
indications : combien de positions uniques dans l'objet ?
combien de sommets au total dans l'objet ? pourquoi ? peut-on ne
dessiner que les positions uniques ?
parmi les choses inutiles, il y a : dessiner des objets
entièrement cachés par d'autres. Oui, dessiner la scène avec une Z
prepass ou du rendu différé limite le problème : les fragment
shaders ne sont exécutés que sur les fragments visibles dans
l'image. Mais, le gpu a quand même passé un temps non négligeable
à exécuter les vertex shaders, à tester l'inclusion des pixels
dans chaque triangle (cf unité de fragmentation / raterization)
puis à exécuter (ou pas) les fragment shaders.
lisez "Practical,
dynamic visibility for games", section 5
quel problème adresse cette méthode de rendu ?
la solution proposée semble-t-elle adaptée à ce tp ? pouvez-vous
estimer la quantité de géométrie dessinée pour rien dans la scène
de test (malgré les tests sur le frustum) ?
Combien de temps gpu pourrait-on gagner avec ce type de méthode ?
même question avec des scènes différentes / pour quel type de
scène ce test semble-t-il nécessaire ?
indication : pourquoi constuire le HZB / zbuffer
hiérarchique ? combien de pixels faut-il tester sans la version
hiérarchique ? et avec la version hiérarchique ?
question : dans quels cas ça ne fonctionnera pas ? quelle
est l'hypothèse principale ? ou : pourquoi ça marche ?
indications : pour relire le zbuffer depuis l'application,
utilisez glReadPixels(), (ou regardez comment fonctionne screenshot()...)
std::vector<float>
zbuffer(width*height);
glReadBuffer(GL_BACK);
glReadPixels(0, 0, width, height,
GL_DEPTH_COMPONENT, GL_FLOAT, zbuffer.data());
indication : construire les mipmaps, la version
hiérarchique du zbuffer, se fait très facilement sur cpu (avec des
shaders, c'est rigolo aussi, mais ce sera pour plus tard).
GLuint make_texture_array( const int unit, const
std::vector<ImageData>& images, const GLenum
texel_format= GL_RGBA )
{
assert(images.size());
// verifie que toutes les images
sont au meme format
int w= images[0].width;
int h= images[0].height;
int c= images[0].channels;
int s= images[0].size;
int d= int(images.size());
for(unsigned i= 1; i <
images.size(); i++)
{
if(images[i].pixels.size() == 0)
continue; // pas de pixels, image pas chargee ?
if(images[i].width != w)
return 0; // pas la meme largeur
if(images[i].height != h)
return 0; // pas la meme hauteur
}
// alloue le tableau de textures
GLuint texture= 0;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D_ARRAY,
texture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, /*
mipmap */ 0,
texel_format, w, h, d, /* border */ 0,
GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
// transfere les textures
for(unsigned i= 0; i <
images.size(); i++)
{
if(images[i].pixels.size() == 0)
continue;
// recupere
les parametres de conversion...
GLenum format;
switch(
images[i].channels
)
{
case 1: format= GL_RED; break;
case 2: format= GL_RG; break;
case 3: format= GL_RGB; break;
case 4: format= GL_RGBA; break;
default: format= GL_RGBA;
}
GLenum type;
switch(
images[i].size
)
{
case 1: type= GL_UNSIGNED_BYTE; break;
case 4: type= GL_FLOAT; break;
default: type= GL_UNSIGNED_BYTE;
}
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, /* mipmap */ 0,
/* x offset */ 0, /* y offset */ 0, /* z offset == index */ i,
w, h, 1,
format, type, images[i].pixels.data());
}
// dimension max des textures 2d
int max_2d= 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE,
&max_2d);
int max_2d_array=0;
// dimension max d'un tableau de
textures 2d
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max_2d_array);
printf("texture 2d maxsize %dx%d\n",
max_2d,max_2d);
printf("texture 2d array maxsize
%dx%dx%d\n", max_2d,max_2d,max_2d_array);
printf("texture array: %dx%dx%d
%dMo\n", w, h, d, 4*w*h*d / 1024 / 1024);
//mipmaps
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
return texture;
}
// charge un objet et ses matieres
Mesh mesh= read_mesh(
"..." );
const Materials& materials=
mesh.materials();
// charge les textures
std::vector<ImageData> images;
for(int i= 0; i <
materials.filename_count(); i++)
{
ImageData
image= read_image_data(materials.filename(i));
images.emplace_back(image);
}
// cree le texture 2d array
GLuint texture_array=
make_texture_array(0, images);
render( ):
// selectionne le texture 2d array
sur une unite de texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY,
texture_array);
glBindVertexArray(vao);
// configure le shader
glUseProgram(program);
// indice de l'unite de texture
associee au texture 2d array, cf le ActiveTexture() au dessus...
program_uniform(program,
"texture_array", 0);
{ ... } // autres parametres du
shader
// go !
glDrawArrays(GL_TRIANGLES, ... );
in vec2 vertex_texcoord;
//< attribut de
sommet exporte par le vertex shader
uniform sampler2DArray texture_array;
//< acces au tableau de textures...
vec4 fragment_color;
void main( )
{
vec4 color= texture(texture_array,
vec3(vertex_texcoord, <indice de la texture
dans le tableau>));
if(color.a < 0.3)
discard;
fragment_color= color * ... ;
}