CPE IMI 2017-2018
TP1 - projection / fragmentation
L'opération
fondamentale réalisée par un pipeline de rendu est le dessin d'une
primitive : déterminer quels pixels permettent de remplir une
forme "simple" dans l'image résultat.
Cette opération est
découpée en plusieurs étapes :
- transformation des sommets de la primitive dans un des repères
liés à la camera,
- identifier les pixels qui font partie de la forme,
- colorier les pixels.
exercice 1 : transformations et
projection
Les
objets et leurs sommets sont décrits dans un repère local, puis
ces objets sont placés et orientés dans le repère de la scène. Un
observateur / camera est également placé et orienté dans le repère
de la scène. Une transformation de projection est aussi associée à
l'observateur. Ces 3 transformations sont classiquement
représentées par des matrices homogenes 4x4 :
Model (transformation du repère local au repère de la scène),
View
(transformation du repère de la scène au repère camera)
Projection (transformation du repère camera au repère projectif
homogène de la camera),
Plus une autre qui
représente les dimensions de l'image résultat :
Viewport (transformation du repère projectif homogène vers le
repère de l'image).
Il est aussi possible de représenter la projection et le passage
dans le repère image directement, comme vu en cours.
Si l'on choisit une
projection directe, ou peut on placer des points qui seront
visibles / associés à un pixel de l'image ?
prise en main de gKit :
installez gKit, les informations
sont sur la page
précédente.
Vous pouvez générer la documentation avec doxygen. Elle sera
consultable dans html/index.html (ou en
ligne). Les classes de bases sont documentées dans la partie
module de la documentation générée.
gKit utilise la classe
Transform pour représenter et manipuler les transformations, (cf mat.h). Les classes Point et
Vector (cf vec.h) permettent de
représenter un point et un vecteur. Les fonctions de construction
des transformations standards sont aussi disponibles : Identity(),
Translation(), Rotate(), Perspective().
La transformation d'un
point s'écrit directement :
#include
"mat.h"
#include "vec.h"
Transform T;
// identité
Transform T= Identity();
// identité
aussi
Transform T= RotateX(30);
// rotation de 30° autour de l'axe X
Transform T= Translate( 0, 0, 50 ); //
translation sur l'axe Z
Point p;
Point q=
T(p);
//
renvoie le point reel transforme
La composition de
transformations est aussi disponible :
Transform A,
B;
Transform C= A
* B;
La transformation
inverse est également calculée :
Transform M=
C.inverse();
Transform M= Inverse(C);
Pour obtenir le point
homogène après la transformation d'un point p :
Point p=
Point(1, 0, 1);
vec4 h= p;
vec4 ph=
M(h);
//
renvoie le point homogene apres la transformation
gKit utilise la classe
Image (cf image.h) pour représenter un ensemble de pixels et
fournit également des fonctions permettant d'enregistrer l'image
dans un fichier.
#include
"image.h"
#include "image_io.h"
Image image(largeur, hauteur);
write_image(image, "resultat.bmp");
Les operateurs () de
Image permettent de lire et de modifier la couleur du pixel de
coordonnées x, y :
Image
image(1024, 512);
image(x, y)= Color(1, 0, 0);
Color pixel=
image(x, y);
La classe de base Color
(cf color.h)
représente une couleur par comme un vecteur à 4 composantes :
rouge, vert, bleu, et transparence / alpha. alpha= 1 pour
une surface opaque.
exemple :
#include "color.h"
// couleur
#include
"image.h" // image
#include
"image_io.h" // entrees / sorties sur les images
int main( )
{
Image image(512, 512); // cree une
image de 512x512 pixels
// parcourir tous les pixels de l'image
for(int y= 0; y < image.height(); y++)
// chaque ligne
for(int x= 0; x < image.width();
x++) // chaque colonne
image(x, y)= Color(1, 0, 0, 1);
// colorie chaque pixel en rouge opaque
// enregistre le resultat
write_image(image, "out.png");
return 0;
}
exercice 2 : un triangle visible
choisissez les coordonnées de 3 sommets visibles par la camera /
projetables.
écrivez le test d'inclusion d'un pixel dans le triangle défini par
ces 3 sommets.
est ce que l'orientation du triangle à une influence sur le résultat
? est-ce vous pouvez dessiner le triange a, b, c et le triangle a,
c, b ?
remplissez tous les pixels du triangle avec une couleur constante.
exercice 3 : interpolation barycentrique
avant de pouvoir dessiner plusieurs triangles, il faut déterminer la
profondeur de chaque fragment / pixel.
modifiez le test d'inclusion pour calculer les coordonnées
barycentriques du fragment.
utilisez les coordonnées pour interpoler la profondeur du fragment.
stockez cette profondeur dans une image de profondeur (vous pouvez
utiliser une Image en n'utilisant qu'un seul canal, rouge, par
exemple).
exercice 4 : Zbuffer et Ztest
modifiez votre programme afin de comparer la profondeur du fragment
avec celle stockée dans le zbuffer.
si le fragment est plus proche, mettez à jour la couleur du pixel et
le zbuffer.
quelle valeur par défaut faut-il choisir pour initialiser le zbuffer
avant de commencer à dessiner ?
que faut-il modifier pour conserver le fragment le plus loin ? le
dernier dessiné ?
exercice 5 : plusieurs triangles
chargez un objet simple et dessinez tous ses triangles.
comment déplacer les sommets afin qu'ils soient tous visibles ?
(quelle transformation appliquer ?)
utilisez read_mesh( ) et la classe Mesh (cf mesh.h
et wavefront.h)
pour charger un fichier .obj.
pour pouvez accéder aux positions des sommets des triangles
directement :
Mesh mesh= read_mesh("data/bigguy.obj");
for(int i= 0; i + 2 < (int)
mesh.positions().size(); i+= 3)
{
Point a=
Point(mesh.positions().at(i));
Point b=
Point(mesh.positions().at(i+1));
Point c=
Point(mesh.positions().at(i+2));
...
}
pour placer l'objet devant la camera, vous aurez besoin de son
englobant (sa boite englobante), vous pouvez la calculer avec
bounds( ), cf mesh.h
vous pouvez utiliser les maillages suivants :
exercice 6 : ca va couper !
vous venez de traiter le cas simple des triangles entièrement
visibles.
modifiez votre programme pour traiter les triangles qui ne se
dessinent pas entièrement dans l'image.
existe-t-il un cas particulier ? que faire ?
exercice 7 : éclairage et orientation
comment calculer la couleur du fragment en fonction de son
orientation par rapport à la camera ?
ou par rapport à une source de lumière placée dans le repère du
monde ?
exercice 8 : c'est trop lent !
tester la totalité des pixels de l'image pour chaque triangle est
très très long.
proposez une solution pour limiter le nombre de pixels testé par
triangle,
ou proposez une solution pour limiter le nombre de triangles testé
par pixel.
comment évaluer la complexité de votre algorithme (en fonction du
nombre de triangles, du nombre de pixels ?)
quelle est la meilleure solution ?
indication : le rectangle englobant les sommets du triangle ? ou un
rectangle de pixels ne contenant pas le triangle ?
peut-on limiter le nombre de triangles à dessiner ?
proposez une solution permettant de ne pas dessiner les triangles à
l'arrière de l'objet (cf back face culling). en moyenne la moitiée
des triangles est éliminée par ce test. vérifiez.
indication : quelle est l'orientation des triangles visibles par la
camera ? des triangles de l'autre coté d'un objet opaque ?
proposez une solution permettant de ne pas dessiner les triangles
qui ne se projettent pas sur l'image (entièrement à gauche, à
droite, etc...) (cf frustum culling)
peut-on facilement déterminer qu'un (morceau d'un) triangle est
entièrement caché / derrière la géométrie déjà dessinée dans le
zbuffer ?
(cf hierarchical depth culling)
bonus : version parallèle
comment utiliser plusieurs threads pour accélérer l'affichage d'un
ensemble de triangles ?
cet algorithme n'est pas si simple à paralléliser. un triangle peut
générer 0 pixel, 1 seul, ou couvrir toute l'image.
associer un thread par triangle génère des conflits lors de
l'écriture dans l'image et la mise à jour du zbuffer.
associer un thread par pixel génère beaucoup de tests (chaque pixel
teste la totalité des triangles), mais cette solution est plus
simple programmer (avec openMP, par exemple)
il faut limiter le nombre de tests par pixel. comment déterminer
(efficacement) le sous ensemble de triangles pouvant influencer un
pixel ?
(cf exercice 8)
pour les curieux :
est ce que tous les triangles ont une influence sur un pixel de
l'image ?
comment éliminer les triangles qui ne peuvent pas avoir d'influence
sur l'image ?
indication : la fragmentation n'est qu'une discrétisation du plan
image...
présentation des solutions utilisées dans le moteur Frostbite
développé par DICE / EA :
"Optimizing
the graphics pipeline with compute" G. Wihlidal, 2016,
une version DX11 opensource développée par AMD est dispo sur
gpuopen.com : GeometryFX