Techniques de rendu avancées en OpenGL Journées de formation

Transcription

Techniques de rendu avancées en OpenGL Journées de formation
Techniques de rendu
avancées en OpenGL
Journées de formation - société OPTIS
Luc Claustres - Freelance Informatique
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Extensions OpenGL
•
Objectif principal
– adéquation implémentation OpenGL et capacités matérielles
•
Chaque extension est documentée à bas niveau pour
– permettre son intégration hardware par les constructeurs
– permettre son implémentation software dans le driver
•
Cycle de vie d’une extension
1) implémentation spécifique (NVidia, ATI, SGI, …)
2) implémentation générique (approbation consensuelle ou ARB)
3) nouvelle fonctionnalité du cœur d’OpenGL (v 1.0 → v 1.1)
Extensions OpenGL
• Nomenclature des extensions
– Génériques
• ARB : extensions officielles approuvées par l’Architectural Review Board
• EXT : extensions communes à plusieurs distributeurs de matériel
– Spécifiques
•
•
•
•
•
•
•
•
•
HP : Hewlett-Packard
IBM : International Business Machines
INTEL : Intel
NVIDIA : NVIDIA Corporation
ATI : ATI Technologies Inc.
MESA : implémentation gratuite de Brian Paul (Linux)
SGIX : Silicon Graphics (généralement experimental)
SUN : Sun Microsystems
WIN : Microsoft Corporation
– Systèmes
• WGL : Microsoft Windows
• GLX : X Window
Extensions OpenGL
• Nom d’une extension
– GL + préfixe de nomenclature + nom : GL_EXT_vertex_array
• Obtenir les extensions disponibles
glGetString(GLenum name)
– GL_VERSION : version de l’implémentation (driver)
– GL_RENDERER : identifiant du matériel (carte 3D)
– GL_VENDOR : nom du fournisseur de l’implémentation
– GL_EXTENSIONS : liste des extensions disponibles
• Vérifier si une extension est présente
GLboolean glCheckExtension(char *name, GLubyte *extensions)
Extensions OpenGL
• En pratique
– obtenir un header intégrant les extensions : glext.h / wglext.h
– l’existence de chaque extension est signalée par des macros
#define GL_EXT_vertex_array
– nouvelles fonctions suffixées par le code de nomenclature
glPointParameterfEXT(), wglQueryPbufferARB()
– nouvelles constantes suffixées par le code de nomenclature
GL_DISTANCE_ATTENUATION_EXT, WGL_TEXTURE_RGBA_ARB
– récupérer un pointeur vers les nouvelles fonctions (Windows)
Extensions OpenGL
• Exemple de code
// vérifier que l’extension est disponible
GLubyte *extensions = glGetString(GL_EXTENSIONS);
…
// pré-définir le profil de la fonction
typedef void (APIENTRY *EXTENSIONPROC)
(GLenum pname, GLfloat param);
// pré-définir la fonction
EXTENSIONPROC glPointParameterfEXT;
// récupérer le pointeur de fonction
glPointParameterfEXT = (EXTENSIONPROC)
wglGetProcAddress("glPointParameterfEXT");
// utiliser la fonction comme n’importe
// quelle autre commande OpenGL
static GLfloat quadratic[3] = {0.25, 0.0, 1/60.0};
glPointParameterfvEXT(
GL_DISTANCE_ATTENUATION_EXT, quadratic);
Extensions OpenGL
• Extensions « promues » dans le cœur de l’API OpenGL
– perdent leurs préfixes/suffixes
– l’ancien mécanisme reste actif
• Liste non exhaustive d’extensions promues essentielles
–
–
–
–
–
–
–
–
–
–
GL_ARB_multitexture : v 1.2
GL_ARB_texture_cube_map : v 1.3
GL_ARB_compressed_texture : v 1.3
GL_ARB_texture_env_combine : v 1.3
GL_ARB_depth_texture : v 1.4
GL_ARB_vertex_buffer_object : v 1.5
GL_ARB_occlusion_query : v 1.5
GL_ARB_vertex_shader : v 2.0
GL_ARB_fragment_shader : v 2.0
GL_ARB_point_sprite : v 2.0
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Rendering pipeline
CPU
écran
affichage
application
GPU
API 3D
(OpenGL/DirectX)
rendering
pipeline
driver
FIFO
BUS
commandes
mémoire système
PCI
AGP
PCIe
commandes
mémoire vidéo
Rendering pipeline
application
pour chaque triangle :
- discrétiser le triangle
- interpoler les attributs
- calculer la visibilité
Transform & Lighting
pour chaque sommet :
- transformer la position 3D en position écran 2D
- calculer ses attributs (couleur, coord. texture, etc.)
Rasterization
Commands
triangles 3D
fragments
triangles 2D
Rendering pipeline
• Sommets = ensemble d’attributs
–
–
–
–
position
normale
coordonnées de textures
…
• Primitives
– assemblées à partir des sommets par le clipping
• Fragments
– produits par la discrétisation des primitives
– interpolation des attributs de sommets
• Pixels
– couleur finale calculée par les opérations sur les fragments
Rendering pipeline
• CPU et GPU ne doivent pas être synchronisés
– tirer profit du parallélisme
– éviter les deadlocks
• Chaque étape dépend de celle qui précède
• Comment identifier un point névralgique (bottleneck) ?
A propos de vitesse
• CPU
– P4 3.5 GHz : 3.5 GHz x 4B x 2 (HT) x 3 (parallélime) = 84 GB/s
• RAM
– FSB : 266 MHz x 4 x 4B = 3.2 GB/s
• Disque
– serial ATA : 20-60 MB/s
• ! Ne jamais faire d’accès disques synchrones !
A propos de vitesse
• Bus Vidéo
–
–
–
–
–
PCI : 33 MHz x 4B = 133 MB/s
AGP : 66 MHz x 4B = 266 MB/s
AGP 2x : 66 MHz x 2 x 4B = 533 MB/s
AGP 8x : 66 MHz x 8 x 4B = 2.1 GB/s
PCI express 16x : 4.2 GB/s
• Coût
– texture : 128x128x32bpp = 64 KB
– géométrie : sommet = au pire 300B
•
•
•
•
position (4f) + normale (4f) + couleur (4f)
matériau (12f)
coordonnées de texture (4f)
…
A propos de vitesse
• Triangles (strips)
–
–
–
–
30 fps sur bus PCI : 133 MB/s ÷ 30 fps ÷ 300 B/t = 13 K t/s
60 fps sur bus PCIe : 4.2 GB/s ÷ 60 fps ÷ 300 B/t = 200 K t/s
ne tient pas compte du transfert des textures/multi-passes
les états OpenGL réduisent le coût par triangle
• Transformation
– GeForce 6800 Ultra : 600 Mv/s ≈ 60 fps à 10 Mv
– beaucoup plus que ce qui est délivré par le bus
• Pixels
– GeForce 6800 Ultra : = 6.4 Gp/s ≈ 60 fps à 100 Mp
• 16 pipelines x 400 Mp/s
– résolution 2560 x 1600 : 4 Mp
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Bottleneck
• Possible à plusieurs niveaux
–
–
–
–
–
–
chargement depuis le disque
calculs géométriques sur le CPU
surcharge mémoire RAM ou Vidéo
surcharge transferts RAM vers Vidéo
calculs géométriques sur la carte 3D
gestion des textures
Bottleneck
• Identifier en modulant la charge des étapes du pipeline
– décharger uniquement l’étape suspectée
• la variation en fps doit être proportionnelle
– décharger toutes les autres étapes
• la variation en fps ne doit pas être significative
! ne pas altérer la charge des autres étapes !
CPU Bottleneck
• Désactiver tout ce qui n’est pas rendu
– simulation physique, IA, calculs, etc.
– ! ne pas altérer la charge du rendu !
• Supprimer tous les appels de tracé ?
– altère la charge du driver
– le driver s’exécute sur le CPU !
• Conserver les appels de tracé
– tracer simplement la première primitive
• Under-clock CPU et mémoire
– performances comparables => le CPU n’est pas le bottleneck
Vertex Bottleneck
• Transfert via le bus (AGP, PCIe)
– vérification : modifier la bande passante
– utiliser des objets résidents
• display lists, texture objects
– organiser les données
• linéarisation des buffers
• taille des données par sommets (x32)
• Calculs par sommets
– vérification : moins de sommets, désactiver l’éclairage
• ! modifie aussi les autres étapes du pipeline !
– désactiver la génération de coordonnées de textures, culling
• Cache misses : post-TnL
– utiliser des strips autant que possible
Vertex Bottleneck
• Simplifier les calculs d’éclairage
– moins de sources, influence limitée
– sources directionnelles
performances QuadroGL (source NVidia)
Raster Bottleneck
• Relativement rare
– ! vérifier en priorité les autres étapes !
• Sauf si les tests élimine la majorité des pixels
– depth
– stencil
– alpha
• Primitives trop grandes
• Trop d’attributs à interpoler
Texture Bottleneck
• Cache misses
• Textures de grande taille
– utiliser des texture 2x2
– maintenir la proportion de passage du test alpha
• Bande passante
– utiliser les mipmaps
– désactiver le filtrage anisotropique
– utiliser des formats compressés
Texture Bottleneck
• Utiliser les formats optimaux de la machine cible
– la conversion format interne → frame buffer/mémoire
application est automatique
30
2,5
25
2
20
1,5
15
1
10
0,5
5
0
0
Machine 1
Machine 2
GL_BYTE
GL_UNSIGNED_BYTE
GL_SHORT
GL_INT
GL_UNSIGNED_INT
GL_FLOAT
Machine 3
GL_UNSIGNED_SHORT
Machine 1
Machine 2
Machine 3
GL_UNSIGNED_SHORT_4_4_4_
GL_UNSIGNED_SHORT_4_4_4_4_REV
GL_UNSIGNED_SHORT_5_5_5_1
GL_UNSIGNED_SHORT_1_5_5_5_REV
GL_UNSIGNED_INT_8_8_8_8
GL_UNSIGNED_INT_8_8_8_8_REV
GL_UNSIGNED_INT_10_10_10_2
GL_UNSIGNED_INT_2_10_10_10_REV
– les types signées ne sont pas optimisés (clamping [0,1])
Fragment Bottleneck
• Calculs par pixel
– vérification : couleur constante par sommet
• ! simplifie aussi l’étape de texturing !
• Plus de fragments que nécessaire ?
– haute complexité selon la composante Z
– élimination limitée via Z-test
• Trier les primitives à la main
– rendu front-to-back
Frame Buffer Bottleneck
• Trop de passes de rendu
• Précision élevée non nécessaire (flottants)
• Nombre et taille des render targets
– réutilisation
– cube maps et shadow maps : une résolution faible suffit
– cube maps → sphere maps
Bottleneck
• En résumé
source ATI GDC’04
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Optimisation
• Changement d’états coûteux
• Désactiver les tests inutiles
– depth, stencil, alpha
• Limiter l’envoi de primitives
– culling
• Limiter les transferts de données
– données résidentes
– compression
• Tirer profit du parallélisme
Objectifs : Optimisation
• Gestion des états
• Culling
• Données résidentes
• Textures compressées
Etats
• Changement d’états : vue naïve
Positionnement
des états
Rendu
Etats
• Changement d’états coûteux
– coût variable suivant l’opération
glEnable(…) : mise à jour d’un flag
glMaterialfv(…) : changement de valeurs dans le contexte de rendu
glTexImage2D(…) : transfert et conversion de données
– un changement d’état peut « casser » le pipeline
• changement de l’algorithme de discrétisation
• activation de la machine du calcul d’éclairage
• re-calcul des données mises en cache
– ! la plupart des changements d’états nécessitent validation !
– éviter d’utiliser glPushAttrib()/glPopAttrib()
– éviter de lire les états glGet*()
Etats
•
Phase de validation = synchronisation
– a lieu avant le rendu : glBegin()
– non nécessaire pour
•
•
•
attributs des sommets
changement de type de primitive
Décomposable en deux étapes
1) déterminer les données à mettre à jour
2) sélectionner les routines de rendu en accord avec les flags
•
Attention aux changements implicites
– GL_COLOR_MATERIAL → glColor()
Etats
• Changement d’états : vue objective
Validation
Positionnement
des états
Rendu
Etats
•
State Sorting : simple et efficace
– minimiser les changements → arranger la séquence de rendu
– grouper les primitives par attributs d’états
– ré-organiser le rendu selon la complexité des changements
changement de texture
plus coûteux
paramètres d’éclairage
opérations matricielles
attributs de sommets
moins coûteux
Objectifs : Optimisation
• Gestion des états
• Culling
• Données résidentes
• Textures compressées
Optimisation
• La complexité dépend du nombre de primitives
• OpenGL rejette les primitives hors du volume de vision
• Couteux
– bande passante : envoi de données inutiles
– calcul : le rejet a lieu après l’étape de transformation
• Rejeter ces primitives au niveau application
– frustum culling
Culling
• Intersection objet/volume de vision trop complexe
– composé de plusieurs milliers de primitives
• Utilisation d’un objet englobant plus simple
–
–
–
–
–
–
sphère
ellipse
boîte alignée aux axes
boîte orientée dans l’espace
cylindre englobant
…
• Compromis entre précision et temps de calcul
Culling
• Exemple avec une sphère
plans du volume de vision
vertex center = V[0];
for (int i=1; i<n; i++)
center += V[i];
center /= n;
vertex diff = V[0]–center;
float radius = diff.length();
for (i=1; i<n; i++)
{
diff = V[i]–center;
float d = diff.length();
if (d > radius)
radius = d;
}
calcul de la sphère
bool function reject(
vertex center, float radius,
vertex N[6], float d[6])
{
for (int i=0; i<6; i++)
{
float d = N[j].center;
d += d[j];
if (d < -radius)
return true;
}
return false;
}
test de rejet
Culling
• Utilisation d’un plan far peu éloigné
– masquage du culling via brouillard ad-hoc
• Utilisation de structures de données dédiées
– kd-tree
– octree
– BSP tree
• Idée générale
– découpage de l’espace en cellules simples
– calcul de la liste des objets appartenant à chaque cellule
– test de rejet effectué sur les cellules (+ objets internes)
Culling
• Masquage inter-objets : occlusion culling
• Difficile à mettre en œuvre au niveau application
– choix délicat de l’ensemble des occludeurs
• Extensions spécifiques disponibles
GL_HP_occlusion_test & GL_NV_occlusion_query
volume de vision
objets
non-visibles
occludeur
Culling
•
Algorithme général
1) trier les objets front-to-back
–
éventuellement par paquets (batching)
2) boucler sur les objets
–
–
–
débuter une requête
tracer l’objet ou son englobant
terminer la requête
3) pour chaque objet
–
–
récupérer le résultat de la requête
si positif alors tracer l’objet
Culling
•
HP_occlusion_test
–
–
–
–
•
déterminer la visibilité d’un ensemble de primitives/objets
indique si le Z-buffer a été ou aurait pu être modifié
si le test échoue, l’objet n’est pas visible
si le test réussi, l’objet est en principe visible (au masque des buffers près)
Algorithme
1) désactiver la mise à jour du frame buffer et les états non nécessaires
glDepthMask(GL_FALSE) / glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
2) activer le test d’occlusion
glEnable(GL_OCCLUSION_TEST_HP)
3) rendre les objets/englobants d’objets
4) désactiver le test d’occlusion
glDisable(GL_OCCLUSION_TEST_HP)
5) analyser les résultats
glGetBooleanv(GL_OCCLUSION_TEST_RESULT_HP, GLboolean *result)
if (result[i]) render ith object
Culling
•
NV_occlusion_query
–
–
–
•
déterminer la visibilité pixel d’un ensemble de primitives/objets
retourne le nombre pixels effectivement rendus
asynchrone (évaluations multiples)
Algorithme
1) désactiver la mise à jour du frame buffer et les états non nécessaires
glDepthMask(GL_FALSE) / glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
2) générer les requêtes d’occlusion
glGenOcclusionQueriesNV(GLuint count, GLuint *queries)
3) effectuer chaque requête : rendre l’objet/englobant d’objet
glBeginOcclusionQueryNV(GLuint query) / glEndOcclusionQueryNV()
•
calculs possibles en // sur le CPU pendant les requêtes
4) restaurer la machine à états
5) analyser les résultats : nombre de pixels effectifs par objet
glGetOcclusionQueryuivNV(GLuint query, GL_PIXEL_COUNT_NV, GLuint *pixel_count)
if (pixel_count > MAX_COUNT) render ith object
Culling
sans occlusion culling : 15 fps
avec occlusion culling : 25 fps
batching : 10% du temps total
Culling
objets occludés en rouge
vue de côté
Culling
•
Problèmes de latence
Ri, Qi, Ci : rendu, requête, culling du i ème objet
•
Adopter une approche incrémentale
–
–
•
pas de requêtes pour les objets visibles pendant quelques frames
maintenir simplement à jour la liste des objets occludés
Applications
–
–
–
culling
optimiser le calcul des ombres et de l’éclairage
pixel count : obtenir des % d’intensité, etc.
Objectifs : Optimisation
• Gestion des états
• Culling
• Données résidentes
• Textures compressées
Optimisation
• La géométrie et les textures transitent par le bus vidéo
– bande passante limitée
• Immediate mode
– les commandes sont exécutées immédiatement
– flexible et dynamique
– coûteux : appels de fonctions + transfert
• Retain mode
– les commandes sont enregistrées et exécutées à la demande
– statique et lent à la création
– données sous forme optimisée
Données résidentes
• Rappel : display lists
– ensemble de commandes OpenGL
– stockées en mémoire vidéo
• géométrie, changements d’états, …
• attention à la surcharge
– identifiées par un handle
• Création-Destruction
GLuint glGenLists(Glsizei n)/glDeleteLists(GLuint list, GLsizei n)
glNewList(GLuint list, GLenum mode) / glEndList()
• Exécution
glCallList(GLuint list)/glCallLists(GLsizei n, GLenum type, GLvoid *lists)
Données résidentes
• Rappel : texture objects
– affecter les données texels d’une texture
– stockées en mémoire vidéo
• format interne pouvant être différent de celui de l’application
• attention à la surcharge
– identifiées par un handle
• Création-Destruction
glGenTextures(GLsizei n, GLuint *names)
glDeleteTextures(GLsizei n, GLuint *names)
• Utilisation
glBindTexture(GLenum target, GLuint name)
Données résidentes
• Les textures ne peuvent parfois pas être résidentes
– surcharge mémoire
glAreTexturesResident(GLsizei n, GLuint *names,
GLboolean *residences)
• Possibilités de sélectionner les textures résidentes
– notion de priorité dans l’intervalle [0,1]
– 0 : priorité la plus faible, 1 : priorité la plus élevée
– priorités semblables : stratégie LRU (least recently used)
glPrioritizeTextures(GLsizei n, GLuint *names,
GLclampf *priorities)
Données résidentes
• Rappel : vertex arrays
– partager les sommets entre primitives
– par attribut de sommet ou entrelacés
– stockés en mémoire application
• Spécifier les données source
glEnableClientState/glDisableClientState(GLenum array)
glArrayPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *data)
glInterleavedArray(GLenum format, GLsizei stride, void *data)
• Utilisation
glArrayElement(GLint ith)
glDrawElements(GLenum mode, GLsizei count, GLenum type, void *indices)
glMultiDrawElements(GLenum mode, GLsizei *count, GLenum type,
void **indices, GLsizei primitive_count)
glDrawArrays(GLenum mode, GLint first, GLsizei count)
glMultiDrawArrays(GLenum mode, GLint *first, GLsizei *count, GLsizei primitive_count)
Données résidentes
• Vertex Buffer Object (VBO)
– concept initialement DirectX → ARB_vertex_buffer_object
– ressource partagée entre l’application et OpenGL
– 3 à 4 fois plus rapide que les vertex arrays
• Concept générique : textures, display lists, P-Buffers
– buffer = zone mémoire accessible via un handle
– client state functions → server state functions
– pointeurs → offsets glArrayPointer(…, offset)
• relatifs à l’adresse de départ du buffer
– partage possible du buffer entre plusieurs clients
Données résidentes
• Création-Destruction
glGenBuffers(GLsizei n, GLuint *names)
glDeleteBuffers(GLsizei n, GLuint *names)
• Spécifier-Modifier les données sources
glBufferData(GLenum target, GLsizei size, void *data, GLenum usage)
glBufferSubData(GLenum target, GLint offset, GLsizei size, void *data)
– GL_ARRAY_BUFFER : attributs de sommets
– GL_ELEMENT_ARRAY_BUFFER : indices de sommets (indexation)
• Utilisation
glBindBuffer(GLenum target, GLuint name)
si zéro, désactive
Données résidentes
• Gestion de la mémoire : usage flags
– combine un flag de gestion et un flag d’accès : GL_STATIC_READ
usage flag
Définition
GL_STATIC_...
les données sont spécifiées à
l’initialisation mais ne sont plus
jamais mises à jour
GL_DYNAMIC_...
les données sont mises à jour
fréquemment par le client
GL_STREAM_...
les données sont mises à jour avant
chaque rendu par le client
GL_..._READ
les données sont accessibles
en lecture par le client
GL_..._COPY
les données sont accessibles
en lecture/écriture par le client
GL_..._DRAW
les données sont accessibles
en écriture par le client
Données résidentes
• Gestion de la mémoire
– il ne s’agit que de recommandations
• OpenGL différencie trois type de mémoire
– mémoire vidéo : espace d’adressage de la carte 3D
– mémoire AGP : espace accessible directement via le bus vidéo
– mémoire système : espace d’adressage de l’application
• OpenGL sélectionne le type de mémoire optimal
Objectifs : Optimisation
• Gestion des états
• Culling
• Données résidentes
• Textures compressées
Textures Compressées
• Transparent via le format interne de la texture
glTexImage2D(…)
gluBuild2DMipmaps(…)
• Packed formats
– utiliser moins de précision
• Compressed formats
– algorithmes (lossy)
format réel
format interne
R
G
B
A
L
I
D
Textures Compressées
•
La compression à lieu lors de l’assignation des texels
glTexImage2D(…) / gluBuild2DMipmaps(…)
•
Vérifier qu’elle a été réalisée correctement
1) créer un proxy
glTexImage(GL_PROXY_TEXTURE_2D, …, NULL)
2) effectuer la requête
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,
GL_TEXTURE_COMPRESSED, GLboolean *compressed)
•
Le proxy permet aussi d’obtenir d’autres informations
– format interne réel, taille réelle, etc…
Textures Compressées
• Exemple de code : compression + sauvegarde
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, width, height, 0,
GL_RGB_EXT, GL_UNSIGNED_BYTE, texels);
// récupérer le résultat de l’algorithme de compression
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED,
&compressed);
// compression réussie ?
if (compressed == GL_TRUE)
{
// récupérer la taille des données compressées
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,
GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &compressed_size);
// récupérer les données compressées en mémoire application
unsigned char *data = (unsigned char *)malloc(compressed_size);
glGetCompressedTexImage(GL_TEXTURE_2D, 0, data);
// sauvegarder les données compressées sur disque
save(width, height, compressed_size, data);
}
Textures Compressées
•
Utiliser des texels compressés comme texture
– chargement depuis le disque
– assignation des texels
glCompressedTexImage2D(GLenum target, GLint level,
GLint internal_format, GLsizei w, GLsizei h,
GLint border, GLsizei size, GLvoid *texels)
•
Les algorithmes de compression sont lents
– à faire en phase d’initialisation
– à faire hors-ligne une fois pour toute
– ! ne jamais compresser en temps réel !
Textures Compressées
•
Formats compressés explicites : FXT1, S3TC/DXTC
– récupérer la liste des formats disponibles
GLint *formats; GLint n;
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS,
&n);
formats = (GLint*)malloc(n * sizeof(GLint));
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS,
formats);
•
Le plus courant aujourd’hui : S3TC
– existe en plusieurs versions (selon la qualité de la composante alpha)
GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
Textures Compressées
RGB
DXT5
DXT1
DXT3
Textures Compressées
•
Algorithme S3TC
– découpage de la texture en blocs 4x4
– calcul de deux couleurs représentatives du bloc
•
deux couleurs supplémentaires sont dérivées par interpolation
– chaque texel utilise un index 2 bits vers cette table
bloc
format de compression S3TC
table
codes
bits/texel
rapport de compression
Textures Compressées
RGB 24 bits : 768 Ko
DXT1 : 128 Ko
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Objectifs : Texturing
• Multi-texturing
• Projective texturing
• Dynamic texturing
Multi-Texturing
• Appliquer plusieurs textures par primitive en une passe
• Formalisation des algorithmes multi-passes
*
=
• Applications (avec register combiners)
–
–
–
–
light mapping
bump mapping
per-pixel lighting
…
Multi-Texturing
• Single-Texturing
– opération : modulation de la couleur du fragment et du texel
couleur du
fragment source
environnement
de texture
couleur de
la texture
couleur finale
Multi-Texturing
• Multi-Texturing
–
–
–
–
décomposition en différentes « unités » de texture
chaque unité effectue une opération de texture
le résultat devient la couleur d’entrée de l’unité suivante
la couleur du fragment source est accessible à chaque unité
couleur du
fragment source
environnement
de texture 0
couleur de
la texture 0
…
environnement
de texture n
couleur de
la texture n
couleur finale
Multi-Texturing
• Sélectionner l’unité de texture courante
glActiveTexture(GLenum unit) : GL_TEXTUREi
• Gérer le texturage pour l’unité courante uniquement
glEnable/Disable(GL_TEXTURE_2D)
• Assigner les coordonnées de texture pour chaque unité
glMultitexCoord{1234}{sifd}v(GLenum unit, TYPE [*] coords)
• Utiliser au sein d’un vertex array
glClientActiveTexture(GLenum unit)
• Autres commandes : similaires au cas du simple texturage
Multi-Texturing
• Exemple de code
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexEnvi(GL_TEXTURE_ENV,
GL_TEXTURE_ENV_MODE,
GL_REPLACE);
…
glBegin(GL_QUADS);
glMultiTexCoord2i(GL_TEXTURE0,0,0);
glMultiTexCoord2i(GL_TEXTURE1,0,0);
glVertex3f(-1,-1,0);
glMultiTexCoord2i(GL_TEXTURE0,1,0);
glMultiTexCoord2i(GL_TEXTURE1,3,0);
glVertex3f( 1,-1,0);
…
glEnd();
Multi-Texturing
• Texture Combiners
– contrôle le mélange entre textures et couleur
– gestion complète du per-pixel engine
– calculs
• produit, somme, produit scalaire, … entre les entrées
• RGB / Alpha séparé
Multi-Texturing
• Rappel : texture compositing
– flot de données linéaire
– fragment source accessible uniquement à la première unité
Fragment
Color
Texture
Fetching
Texture
Environment
Tex0
0
Tex1
Specular Color
Fog Color/Factor
Texture
Environment
1
Specular
Color
Sum
Fog
Application
– travaille sur des valeurs non signées [0,1]
– 5 fonctions de combinaison en mode RGB[A] : glTexEnv(…)
Ct : couleur texture
At : alpha texture
Cf : couleur fragment source
Af : alpha fragment source
Cc : couleur constante de mélange
Multi-Texturing
• Register Combiners : extension GL_NV_register_combiners
– flot de données non-linéaire
– travaillent sur des valeurs signées [-1,1]
– remplace la composition de texture classique
4 RGB Inputs
4 Alpha Inputs
3 RGB Outputs
3 Alpha Outputs
Diffuse Color
4 RGB Inputs
Texture 0
Texture 1
Texture
Fetching
Texture 2
Texture 3
Register Set
Specular Color
Fog Color/Factor
General
Combiner
0
4 Alpha Inputs
3 RGB Outputs
3 Alpha Outputs
4 RGB Inputs
4 Alpha Inputs
3 RGB Outputs
3 Alpha Outputs
Spare 0
Specular Color
6 RGB Inputs
1 Alpha Input
General
Combiner
1
General
Combiner
7
Final
Combiner
RGBA Color Out
Multi-Texturing
• Register Combiner RGB Générique
input registers
RGB
output registers
A
RGB
input
map
primary color
input
map
input
map
B
C
A
input
map
primary color
secondary color
secondary color
A
D
texture 0
texture 0
AB+CD
-orA B mux C D
texture 1
spare 0
spare 1
AB
-orA•B
fog
texture 1
spare 0
spare 1
scale
and
bias
fog
constant color 0
constant color 0
constant color 1
constant color 1
CD
-orC•D
zero
zero
not writeable
not readable
computations
Multi-Texturing
• Register Combiner RGB Générique : opérations
AB mux CD = si (alpha texture0 > 0.5) alors AB sinon CD
Multi-Texturing
• Register Combiner RGB Générique : input mappings
Signed Identity
Unsigned Identity
Expand Normal
Half Bias Normal
f(x) = x
f(x) = max(0, x)
f(x) = 2 * max(0, x) - 1
f(x) = max(0, x) – ½
[-1, 1] → [-1, 1]
[0, 1] → [0, 1]
[0, 1] → [-1, 1]
[0, 1] → [-½, ½]
1
1
1
1
0
0
0
0
-1
-1
-1
-1
-1
0
1
-1
0
1
-1
0
1
-1
0
1
Signed Negate
Unsigned Invert
Expand Negate
Half Bias Negate
f(x) = -x
f(x) = 1-min(max(0,x),1)
f(x) = -2 * max(0, x) + 1
f(x) = -max(0, x) + ½
[-1, 1] → [1, -1]
[0, 1] → [1, 0]
[0, 1] → [1, -1]
[0, 1] → [½, -½]
1
1
1
1
0
0
0
0
-1
-1
-1
0
1
-1
-1
0
1
-1
-1
0
1
-1
0
1
Multi-Texturing
• Register Combiner RGB Générique : output mappings
Scale by ½
No scale
f(x) = x/2
No bias
Scale by 2
f(x) = x
f(x) = 2x
f(x) = 4x
1
1
1
1
0
0
0
0
-1
-1
-1
0
1
-1
-1
0
1
f(x) = x – ½
Bias by –½
Scale by 4
Not supported
-1
-1
0
1
1
0
0
-1
1
Not supported
-1
0
0
f(x) = 2(x – ½)
1
-1
-1
1
-1
0
1
Multi-Texturing
• Register Combiner Alpha Générique
input registers
RGB
output registers
A
RGB
input
map
primary color
input
map
input
map
A
input
map
primary color
secondary color
secondary color
A
B
C
D
texture 0
texture 0
AB+CD
-orA B mux C D
texture 1
spare 0
texture 1
spare 0
spare 1
spare 1
AB
fog
scale
and
bias
fog
constant color 0
constant color 0
constant color 1
constant color 1
CD
zero
not readable
zero
not writeable
Multi-Texturing
• Register Combiner Final : fog + secondary color
input
map
input registers
RGB
input
map
A
primary color
E
secondary color
F
EF
texture 0
spare 0 +
secondary color
texture 1
spare 0
input
map
input
map
input
map
input
map
C
D
input
map
spare 1
A
B
G
fog
constant color 0
A B + ( 1 - A) C + D
constant color 1
zero
G
fragment RGB out
fragment Alpha out
Multi-Texturing
• Activer-Désactiver les register combiners
glEnable/Disable(GL_REGISTER_COMBINERS_NV)
• Spécifier les input mappings
glCombinerInputNV(GL_COMBINERi_NV, GL_{RGB,ALPHA},
GL_VARIABLE_{A,B,C,D}, GLenum input, GLenum mapping)
– input = GL_ZERO, GL_PRIMARY_COLOR_NV, GL_SECONDARY_COLOR_NV,
GL_CONSTANT_COLOR0_NV, GL_CONSTANT_COLOR1_NV, GL_TEXTURE0_ARB,
GL_TEXTURE1_ARB, GL_FOG, GL_SPARE0_NV, GL_SPARE1_NV
– mapping = GL_UNSIGNED_IDENTITY_NV, GL_UNSIGNED_INVERT_NV,
GL_EXPAND_NORMAL_NV, GL_EXPAND_NEGATE_NV,
GL_HALF_BIAS_NORMAL_NV, GL_HALF_BIAS_NEGATE_NV,
GL_SIGNED_IDENTITY_NV, GL_SIGNED_NEGATE_NV
Multi-Texturing
• Spécifier les output mappings
glCombinerOutputNV(GL_COMBINERi_NV, GL_{RGB,ALPHA},
GLenum ab, GLenum cd, GLenum sum, GLenum mapping,
GLenum scale, GLenum bias,
GLboolean ab_dot, GLboolean cd_dot, GLboolean muxsum)
– ab, cd, sum = GL_DISCARD_NV, GL_PRIMARY_COLOR_NV,
GL_SECONDARY_COLOR_NV, GL_TEXTUREi_ARB,
GL_SPARE0_NV, GL_SPARE1_NV
– scale = GL_NONE, GL_SCALE_BY_TWO_NV, GL_SCALE_BY_FOUR_NV,
GL_SCALE_BY_ONE_HALF_NV
– bias = GL_NONE, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV
Multi-Texturing
• Register Combiners
– # de combiners et d’unités de texture dépend de la carte 3D
– « langage » de programmation : utiliser NVparse
nvparse(
“!!RC1.0\n”
{\n"
" rgb {\n"
"
spare0 = expand(col0) . expand(tex1);\n"
"
spare1 = expand(col1) . expand(tex1);\n"
" }\n"
"}\n"
"final_product = spare1 * spare1;\n"
"out.rgb = spare0 * tex0 + final_product;\n"
"out.a = unsigned_invert(zero);\n"
);
for (const char** errors= nvparse_get_errors(); *errors; errors++)
fprintf(stderr, *errors);
glEnable(GL_REGISTER_COMBINERS_NV); // à ne pas oublier
Multi-Texturing
• OpenGL Texture Combiners
–
–
–
–
sous-ensemble fonctionnel des register combiners
supportés par la plupart des cartes modernes
un environnement de texture par unité de texture
le nombre d’unités de texture dépend de la carte graphique
glTexEnv(GLenum target, GL_TEXTURE_ENV_MODE, GL_COMBINE)
glTexEnv(GLenum target, GL_COMBINE_RGB, GLenum function)
glTexEnv(GLenum target, GL_COMBINE_ALPHA, GLenum function)
– une fonction de combinaison par unité calcule
• à partir de trois argument : Arg{0,1,2}
• une valeur de sortie pour l’unité de texture suivante
Multi-Texturing
• OpenGL Texture Combiners
somme spéculaire
+
Cf : couleur fragment source
brouillard
(n’altère pas alpha)
C’f: couleur fragment texturé final
CTi: couleur de la i ème unité de texture
TEi: i ème environnement de texture
Cs : couleur secondaire fragment source
couleur finale
Cs
Multi-Texturing
• OpenGL Texture Combiners : fonctions
spécifiques au mode RGB[A] : résultat copié dans chaque composante
Multi-Texturing
• OpenGL Texture Combiners : arguments
Ct : couleur texture
At : alpha texture
valeur utilisée pour Argn
traitement appliquée sur
la valeur entrante
filtrage
Cs : couleur texture source
As : alpha texture source
Cf : couleur fragment source
Af : alpha fragment source
équivalent pour l’unité
de texture 0
Cc : couleur constante
Cp : couleur précédente
Ap : alpha précédent
n = {0,1,2}
Multi-Texturing
• Exemple de code : interpolation éclairage/texture
Objectifs : Texturing
• Multi-texturing
• Projective texturing
• Dynamic texturing
Perspective-Correct Texturing
•
Coordonnées de texture assignées par sommet
– interpolation nécessaire au niveau pixel
1) le long des arêtes des primitives
2) par ligne entre les arêtes
Perspective-Correct Texturing
• Rappel
– avant projection sommet v = [x, y, z]
– après projection p = Mprojectionv = [x’, y’, z’, w] avec w ≠ 1
• Phase de normalisation : p’= [x’÷w, y’÷w, z’÷w, 1]
• Les points visibles sont dans le cube unité
– (x’,y’,z’) Є [-1,1] x [-1,1] x [0,1]
Perspective-Correct Texturing
• L’interpolation linéaire simple ne fonctionne pas
– la discrétisation a lieu dans l’espace de l’œil
– distortions dues à la perspective
l’espacement régulier des pixels
ne correspond pas à un espacement
régulier des texels
Perspective-Correct Texturing
• Pour chaque sommet calculer
– numérateurs : s/w , t/w
– dénominateur : 1/w
• Interpoler linéairement s/w , t/w, 1/w
• Pour chaque fragment
– s’ = (s/w) / (1/w)
– t’ = (t/w) / (1/w)
• Interpolation correcte dans l’espace de l’œil
Projective Texturing
• Traiter la texture comme une source lumineuse
– analogie du projecteur
Source: Wolfgang Heidrich [99]
Projective Texturing
• Similaire au pipeline de rendu : 3D → 2D
– coordonnées homogènes 3D : (x,y,z,w) → (x/w,y/w,z/w)
– coordonnées homogènes de texture : (s,t,r,q) → (s/q,t/q,r/q)
• La carte sait faire du perspective-correct texturing
– interpolation de q/w au lieu de 1/w, soit par fragment
• s’ = (s/w) / (q/w) = s/q
• t’ = (t/w) / (q/w) = t/q
– reste à multiplier par q pour faire du perspective-correct
• permet d’économiser un diviseur hardware (cher)
• coût supplémentaire de calcul mais par sommet
Projective Texturing
• Pas de spécification explicite des coordonnées
– utiliser les coordonnées 3D comme coordonnées de texture
– utiliser la génération automatique par OpenGL
– travail dans le repère de l’œil en général
• Coordonnée s ↔ x
– il s’agit de la distance au plan (yz) ou x = 0
• De même pour les autres coordonnées t,r,q
Projective Texturing
Projective Texturing
• Exemple de code
GLfloat
GLfloat
GLfloat
GLfloat
Splane[4]
Tplane[4]
Rplane[4]
Qplane[4]
glTexGenfv(GL_S,
glTexGenfv(GL_T,
glTexGenfv(GL_R,
glTexGenfv(GL_Q,
glTexGeni(GL_S,
glTexGeni(GL_T,
glTexGeni(GL_R,
glTexGeni(GL_Q,
=
=
=
=
{1.f,
{0.f,
{0.f,
{0.f,
0.f,
1.f,
0.f,
0.f,
GL_EYE_PLANE,
GL_EYE_PLANE,
GL_EYE_PLANE,
GL_EYE_PLANE,
0.f,
0.f,
1.f,
0.f,
Splane);
Tplane);
Rplane);
Qplane);
GL_TEXTURE_GEN_MODE,
GL_TEXTURE_GEN_MODE,
GL_TEXTURE_GEN_MODE,
GL_TEXTURE_GEN_MODE,
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
0.f};
0.f};
0.f};
1.f};
GL_EYE_LINEAR);
GL_EYE_LINEAR);
GL_EYE_LINEAR);
GL_EYE_LINEAR);
Projective Texturing
Teye
0.5
= 0
0
 0
0
0.5
0
0
0
0
0.5
0
0.5
0.5 M
M view M eye→ world
projection
0.5
1 
matrice de projection :
forme du projecteur
glFrustum(), gluPerspective()
matrice de biais :
coordonnées normalisées [-1,1]
↓
coordonnées texture [0,1]
permet de spécifier
le projecteur en
coordonnées monde
(génération automatique)
matrice de vue :
positionnement du projecteur
gluLookAt()
Projective Texturing
• A combiner avec des techniques de génération d’ombres
– augmentation sensible du réalisme
Projective Texturing
• Exemple de code
// utiliser la matrice de texture
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
// transformation coordonnées normalisées/texture
glTranslatef(.5f, .5f, 0.f);
glScalef(.5f, .5f, 1.f);
// matrice de projection
gluPerspective(60, (GLfloat)w / (GLfloat)h, 0.001, 1000);
// matrice de modélisation-vue
gluLookAt(position[0], position[1], position[2],
center[0], center[1], center[2],
up[0], up[1], up[2]);
Objectifs : Texturing
• Multi-texturing
• Projective texturing
• Dynamic texturing
Dynamic Texturing
• Génération de textures en temps-réel (par frame)
• Algorithme classique
– rendre une image de façon standard
– créer une texture à partir de cette image
– l’utiliser comme n’importe quelle autre texture
• Applications
– textures procédurales, analyse d’image dynamique, ombres,
réflexions, textures animées, …
• Texture dynamique = mémoire en lecture/écriture
Dynamic Texturing
• Copier le résultat d’un rendu dans une texture :
• glReadPixels() → glTexImage*()
– trop lent : passage par la mémoire application
• glCopyTexImage*()
– mieux : texture résidente mais re-création à chaque frame
• glCopyTexSubImage*()
– optimal : simplement une mise à jour de la texture
• Render-to-Texture : texture = frame buffer
Dynamic Texturing
• Génération dynamique des mipmaps
– 1/3 de charge supplémentaire en les stockant intelligemment
• Construction à la main : lent
gluBuild2DMipmaps(…)
• Extension spécifique : plus rapide
glTexParameteri(GL_TEXTURE_2D,
GL_GENERATE_MIPMAP, GL_TRUE );
Pixel Buffer
• Rendu hors-ligne (offscreen)
– sans passer par le frame buffer pour créer la texture
• Pourquoi
–
–
–
–
ne pas être limité à la résolution de la fenêtre
ne pas être limité par le format du frame buffer (RGB, …)
problème du chevauchement de fenêtres pour la copie
éviter au maximum les changements d’états
• Utilisation d’un Pixel Buffer (P-Buffer)
Pixel Buffer
• P-Buffer ≈ Véritable contexte de rendu
– frame buffer non affiché
– stocké en mémoire vidéo
– intègre un éventuel depth/stencil/accumulation buffer
frame buffer
p-buffer
back buffer
buffer non-visible
front buffer
RGBA n bits
RGB m bits
Pixel Buffer
•
Création du P-Buffer (Windows)
1) obtenir un contexte matériel valide
2) sélectionner un format de pixel
•
•
bits par composante couleur, profondeur, stencil
simple/double buffer (très rarement utilisé → économie de mémoire)
3) créer le buffer
4) obtenir un contexte OpenGL pour le buffer
•
•
utiliser le même que celui de l’application
en créer un nouveau : ! le p-buffer aura sa propre machine à états !
Pixel Buffer
• Exemple de code
// obtenir les contextes courants
HDC dc = wglGetCurrentDC();
HGLRC rc = wglGetCurrentContext();
// définir le format du p-buffer
int attributes[] = { WGL_COLOR_BITS_ARB, 24, WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 16, WGL_STENCIL_BITS_ARB, 8,
WGL_ACCUM_BITS_ARB, 0, WGL_DOUBLE_BUFFER_ARB, FALSE,
0 };
wglChoosePixelFormatARB(hdc, attributes, fattributes, 1, &pixel_format,
&n);
// créer le buffer avec le format correspondant
HPBUFFER pbuffer = wglCreatePbufferARB(dc, pixel_format, w, h,
attributes);
// obtenir le contexte matériel du buffer
HDC pbufferdc = wglGetPbufferDCARB(pbuffer);
// créer un contexte de rendu indépendant pour le buffer
HGLRC pbufferrc = wglCreateContext pbufferdc);
Pixel Buffer
•
Activation du P-Buffer
–
utiliser les contextes du buffer comme contextes courants
wglMakeCurrent(pbufferdc, pbufferrc)
–
•
les primitives rendues le seront hors-ligne, dans le p-buffer
Désactivation du P-Buffer
–
retourner aux contextes de la fenêtre
wglMakeCurrent(windowdc, windowrc)
–
•
retour au rendu en-ligne, dans le frame buffer de la fenêtre
Destruction du P-Buffer
– libérer les contextes
wglDeleteContext(pbufferrc) / wglReleasePbufferDCARB(pbufferdc)
– effacer le buffer
wglDestroyPbufferARB(pbuffer)
Pixel Buffer
•
Copie des données du P-Buffer
– via textures partagées : wglShareLists(windowrc, pbufferrc)
mémoire vidéo
frame buffer
Back
Front
1)
2)
3)
4)
5)
contexte
de rendu
contexte
de rendu
p-buffer
texture
buffer
contexte actif
activer le p-buffer
rendre la scène (off-screen)
copier les donnée : glCopyTexSubImage2D(…)
désactiver le p-buffer (on-screen)
activer la texture et rendre la surface
Pixel Buffer
•
Render-to-Texture : Render Target
– utiliser directement le p-buffer comme texture
– format de création : WGL_BIND_TO_TEXTURE_RGB[A]_ARB
•
Activer le p-buffer en tant que texture
wglBindTexImageARB(pbuffer, WGL_{FRONT, BACK})
•
Désactiver le p-buffer en tant que texture
wglReleaseTexImageARB(pbuffer, WGL_{FRONT, BACK})
– obligatoire pour réutiliser le p-buffer hors-ligne
•
Spécifier le niveau de mipmap à utiliser
wglSetPBufferAttribARB(pbuffer, int *attributes)
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Transparence
• Rappel : blending
– mélange couleur du fragment et couleur du frame buffer
glEnable/Disable(GL_BLEND)
– contrôle de la combinaison source/destination
glBlendEquation(Glenum mode)
– contrôle des facteurs de la combinaison
glBlendFunc(Glenum source_factor,Glenum destination_factor)
• Principalement utilisé pour la gestion de la transparence
Transparence
• Equations de blending
–
–
–
–
–
GL_FUNC_ADD : Cd = SCs + DCd
GL_FUNC_SUBTRACT : Cd = SC s − DCd
GL_FUNC_REVERSE_SUBTRACT : Cd = DCd − SC s
GL_MIN : Cd = min( SC s , DCd )
GL_MAX : Cd = max(SCs , DCd )
• Facteurs de blending (source et destination)
Constante
Facteur concerné
Facteur de blending calculé
GL_ZERO
source ou destination
(0, 0, 0, 0)
GL_ONE
source ou destination
(1, 1, 1, 1)
GL_DST_COLOR
source
(Rd, Gd, Bd, Ad)
GL_SRC_COLOR
destination
(Rs, Gs, Bs, As)
GL_ONE_MINUS_DST_COLOR
source
(1, 1, 1, 1) - (Rd, Gd, Bd, Ad)
GL_ONE_MINUS_SRC_COLOR
destination
(1, 1, 1, 1) - (Rs, Gs, Bs, As)
GL_SRC_ALPHA
source ou destination
(As, As, As, As)
GL_ONE_MINUS_SRC_ALPHA
source ou destination
(1, 1, 1, 1) - (As, As, As, As)
GL_DST_ALPHA
source ou destination
(Ad, Ad, Ad, Ad)
GL_ONE_MINUS_DST_ALPHA
source ou destination
(1, 1, 1, 1) - (Ad, Ad, Ad, Ad)
GL_SRC_ALPHA_SATURATE
source
(f, f, f, 1) avec f = min( As, 1-Ad)
GL_CONSTANT_COLOR
source ou destination
(Rc, Gc, Bc, Ac)
GL_ONE_MINUS_CONSTANT_COLOR
source ou destination
(1, 1, 1, 1) - (Rc, Gc, Bc, Ac)
GL_CONST_ALPHA
source ou destination
(Ac, Ac, Ac, Ac)
GL_ONE_MINUS_CONSTANT_ALPHA
source ou destination
(1, 1, 1, 1) - (Ac, Ac, Ac, Ac)
spécification des couleurs constantes : glBlendColor(r,g,b,a)
Transparence
• La composante d’opacité (alpha) sert de facteur
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Cd = alphas Cs − (1 − alphas )Cd
• le Z-Buffer ne marche pas bien ici (l’ordre importe)
Transparence
•
Algorithme
1) activer le Z-buffer et le Z-test
2) rendre les objets opaques
3) désactiver la mise à jour du Z-buffer
4) activer le blending
5) rendre les objets transparent back-to-front
Transparence
• Imposteur : billboard, sprite
– simuler la complexité géométrique via une texture
– principalement utilisé pour les phénomènes naturels
• arbres, nuages, fumée, …
– technique très utilisée dans les jeux
Transparence
• Afficher un quadrilatère texturé face à l’œil
– récupérer la rotation de la matrice de vue
– appliquer la rotation inverse sur le quadrilatère
• Utiliser la transparence pour « découper » la forme
– color key possible
• Adapté à des objets ayant une symétrie circulaire
– systèmes de particules
GL_ARB_point_sprite
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Ombres : terminologie
source lumineuse
occludeur
pénombre
ombre
récepteur
zone d’ombre
• ombre – région complètement dans l’ombre
• pénombre – région partiellement dans l’ombre
Ombres : terminologie
• Ombres dures / Ombres douces
– dépend du type de source
• ponctuelle ou directionnelle (rendu temps-réel : OpenGL)
• surfacique (rendu réaliste : lancer de rayons, …)
• Les techniques classiques se limitent aux ombres dures
– efficacité
Objectifs : Ombres
• Projective shadows
• Shadow volumes
• Shadow map
• Ombres douces
Projective shadows
• L’algorithme le plus simple
• Ombre projetée par une primitive/objet sur un plan
y
source
ombre
y=0
y
source
ombre
n.x + d =0
Projective shadows
• Il s’agit d’une opération purement géométrique
– la source définit une droite de projection par sommet
– l’X de cette droite avec le plan définit le sommets projeté
l
y
source
v
 p = l + (v − l ) × α

n. p + d = 0
résolution
ombre
n.x + d =0
p
d + n.l
p=l−
(v − l )
n.(v − l )
Projective shadows
• S’exprime sous la forme d’une matrice « d’ombre »
– à multiplier avec la matrice de vue
∆ − al x
 − bl
x
M =
 − cl x
 − dl x
− al y
∆ − bl y
− cl y
− dl y
− al z
− bl z
∆ − cl z
− dl z
− alw 
− blw 

− clw 
∆ − dlw 
plan receveur : p.n + d = 0 ↔ ax + by + cz + d = 0
position de la source : l = [lx,ly, lz, lw]
∆ = a.lx + b.ly + c.lz + d.lw
Projective shadows
Projective shadows
• Problèmes des ombres projetées
– nécessite un plan infini
• restriction possible via test de stencil
– crée une « pile » de polygone sur le plan : Z-fight
glPolygonOffset()
– double mélange si modulation
• utiliser le stencil buffer comme compteur
tracé hors
du récepteur
conflit de Z
double mélange
rendu correct
Cube Mapping
• Exemple de code : restriction au récepteur via stencil
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
// Tracer le récepteur
…
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// Tracer les ombres
…
Projective shadows
↑ Avantages
– très simple à mettre en oeuvre (sans stencil)
– naturellement dynamique
↓ Inconvénients
– ne fonctionne que pour les receveurs planaires
– dépend de la complexité de la scène
– nécessite un Z-bias
Objectifs : Ombres
• Projective shadows
• Shadow volumes
• Shadow map
• Ombres douces
Shadow volumes
• Une source lumineuse découpe l’espace en deux zones :
– régions dans l’ombre
– régions éclairées
• Shadow volume = frontière entre ces deux zones
Shadow volumes
• Un objet à l’intérieur du volume est dans l’ombre
occludeur
volume d’ombre
source
oeil
surface dans le
volume d’ombre
(ombrée)
surface en dehors du
volume d’ombre
(éclairée)
Shadow volumes
•
Un objet est-il dans le volume d’ombre ?
•
Algorithme intuitif
1) initialiser un compteur
2) lancer un rayon dans la scène
3) incrémenter le compteur si le rayon entre dans le volume
– il traverse une face avant
4) décrémenter le compteur si le rayon sort du volume
– il traverse une face arrière
5) quand un objet est rencontré par le rayon
– si le compteur est > 0, l’objet est dans l’ombre
– sinon l’objet est éclairé au point d’intersection
Shadow volumes
source
occludeur
zéro
zéro
+1
objet dans l’ombre
(compteur > 0)
rayon de vue
+2
oeil
+1
+2
+3
dans l’ombre
Shadow volumes
•
Algorithme temps-réel
– utiliser le stencil buffer → un compteur par pixel
•
Rappel : stencil buffer
– test : contrôle si un fragment est conservé ou non
•
par rapport à une valeur de référence : <, ≤, =, ≥, >, ≠
GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER,
GL_NOTEQUAL, GL_NEVER, GL_ALWAYS
– operation : mise à jour possible suivant le résultat du test
• une opération si le stencil test échoue
• une opération si le depth test échoue
• une opération si le depth test réussi
glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, …
Shadow volumes
•
Deux implémentations possibles
1) Approche Z-pass
– vérifier que le nombre de faces avants et de faces arrières
visibles du volume d’ombre sont égaux
– si oui, le fragment n’est pas dans l’ombre
2) Approche Z-fail
– vérifier que le nombre de faces arrières et de faces avants
invisibles du volume d’ombre sont égaux
– si oui, le fragment n’est pas dans l’ombre
Shadow volumes
•
Algorithme Z-pass
1) rendre la scène avec ambiant+émissif et Z-buffer activé
2) désactiver la mise à jour du Z-buffer et du frame buffer
3) activer le test de stencil et initialiser le stencil buffer
4) tracer le volume d’ombre deux fois en activant le culling
•
•
1ère passe : rendre les faces avants et incrémenter en Z-pass
2nde passe : rendre les faces arrières et décrémenter en Z-pass
5) les pixels du stencil dont la valeur est ≠ 0 sont ombrés
6) rendre la scène avec diffus+spéculaire si stencil = 0
Shadow volumes
•
Algorithme Z-pass
scène avec ombres
contenu du stencil buffer
rouge = valeur du stencil de un
vert = valeur du stencil de 0
Shadow volumes
•
Problèmes de l’approche Z-pass
1) Si le point de vue est à l’intérieur du volume d’ombre
– stencil = 0 ne signifie plus en dehors du volume
– initialiser au nombre de volumes auxquels l’œil appartient
2) Si le plan near intersecte le volume d’ombre
– « boucher » le volume par le near plane
source
near
oeil
faussement
éclairé
far
Shadow volumes
•
Algorithme Z-fail
1) rendre la scène avec ambiant+émissif et Z-buffer activé
2) désactiver la mise à jour du Z-buffer et du frame buffer
3) activer le test de stencil et initialiser le stencil buffer
4) tracer le volume d’ombre deux fois en activant le culling
•
•
1ère passe : rendre les faces arrières et incrémenter en Z-fail
2nde passe : rendre les faces avants et décrémenter en Z-fail
5) les pixels du stencil dont la valeur est ≠ 0 sont ombrés
6) rendre la scène avec diffus+spéculaire si stencil = 0
Shadow volumes
•
Problèmes de l’approche Z-fail
•
Si le plan far intersecte le volume d’ombre
– « boucher » le volume par le far plane : depth clamping
far
source
oeil
near
Shadow volumes
• Construction des volumes d’ombre
– approche simpliste : un volume par primitive
– approche intermédiaire : projection de l’objet sur un plan
– approche évoluée : basée sur la silhouette
Shadow volumes
•
Détection de la silhouette
– une arête appartient à la silhouette si / à la source
1. elle appartient à une face avant
2. elle appartient aussi à une face arrière
source
source
Shadow volumes
↑ Avantages
– algorithme général
– algorithme précis
↓ Inconvénients
–
–
–
–
–
construction efficace difficile
travail important du CPU
clamping délicat en 3D
non naturellement dynamique
nécessite un Z-buffer précis
Objectifs : Ombres
• Projective shadows
• Shadow volumes
• Shadow map
• Ombres douces
Shadow map
• Méthode basée image
–
–
–
–
pas de connaissance géométrique de la scène requise
problèmes d’anti-aliassage
simple à mettre en œuvre
générique
• Très utilisée et éprouvée
– l’idée originale date de 1978 (Lance Williams)
– RenderMan de Pixar utilise cet algorithme
– technique basique pour les ombres de Toy Story, …
Shadow map
•
Méthode en deux passes :
1) Rendre la scène depuis la source lumineuse
– ne conserver que la carte de Z (depth buffer)
– shadow map = texture indiquant la distance des pixels les
plus proches de la source lumineuse (16/24 bits)
2) Rendre la scène depuis le point de vue
– utiliser la shadow map pour détecter les pixels ombrés
Shadow map
•
Pour chaque fragment de l’image finale :
1) déterminer sa position xyz relative à la source lumineuse
! utiliser le volume de vision de la génération de la shadow map !
2) comparer la profondeur à celle de la shadow map en xy
– zm : profondeur stockée dans la shadow map à la position xy
– zl : profondeur du fragment par rapport à la source lumineuse
•
Si zm ≈ zl alors le fragment est éclairé
– il s’agit de l’objet le plus proche
•
Si zl > zm alors le fragment est dans l’ombre
– un objet plus proche de la source « bloque » la lumière
Shadow map
• Cas zm ≈ zl : fragment éclairé
plan image de la shadow map
zm
position de la
source lumineuse
position de l’oeil
plan image du frame buffer
zl
Shadow map
• Cas zl > zm : fragment dans l’ombre
plan image de la shadow map
zm
position de la
source lumineuse
position de l’oeil
plan image du frame buffer
zl
Shadow map
• Imprécision due à la différence d’échantillonnage
résolution de la
shadow map différente
de celle du frame buffer
! ceci conduit à des artéfacts visuels !
Shadow map
source lumineuse
avec ombres
sans ombres
Shadow map
point de vue réel
shadow map
point de vue de la
source lumineuse
Shadow map
projection de la
shadow map
projection de la distance
à la source lumineuse
Shadow map
zones éclairées
pixels vert : zm ≈ zl
zones dans l’ombre
autres pixels : zl > zm
Shadow map
ombres portées
pas de reflets
spéculaires dans les
zones d’ombre
Shadow map
• Grille d’échantillonnage du frame buffer
polygone
X
Z
centres des pixels
Shadow map
• Grille d’échantillonnage de la shadow map
polygone
X
Z
X
Z
centres des pixels
Shadow map
• “Pente” du polygon
X
Z
∂z/∂
∂x
Shadow map
• Si les pixels couvrent des aires identiques
– la pente du polygone peut conduire à des erreurs sur la
composante Z de ±0.5 ∂z/∂x et ± 0.5 ∂z/∂y
– l’erreur totale maximale est donc :
|0.5 ∂z/∂x| + |0.5 ∂z/∂y| ≈ max(|∂z/∂x|,|∂z/∂y|)
• Si les pixels ne couvrent pas des aires identiques
– multiplier par le rapport des aires
• Ces opérations correspondent à celles de glPolygonOffset()
Shadow map
• Comportement de l’offset
– translation de la primitive vers l’oeil
– dépend de la pente du polygone
– dépend de la profondeur (non-linéarité)
glEnable(GL_POLYGON_OFFSET_{POINT, LINE, FILL})
glPolygonOffset(GLfloat factor, GLfloat bias)
constante propre
à l’implémentation
z
=
z
+
offset

variation maximale de profondeur

sur le polygone selon x ou y
offset = m. factor + r.bias
Shadow map
• Valeur de biais à trouver en fonction de la scène
– shadow map précise => biais faible
trop élevé :
les ombres commencent à
apparaître trop tard
trop faible :
tout porte des ombres
bonne valeur : ombres correctes
Shadow map
•
Réalisation pratique :
1) Rendre la scène et récupérer le depth-buffer
2) Rendre la scène à nouveau : pour chaque sommet
– (x,y,z,w)oeil → (x,y,z,w)lumière
– (x,y,z,w)lumière → (s,t,r,q)map : projective texturing
Shadow map
• Après projection
– (s/q , t/q) est la coordonnée du fragment dans la shadow map
– r/q est la distance linéaire du fragment au plan image de la
shadow map transposée dans l’intervalle [0,1]
• Etape de comparaison
– si depth[s/q , t/q] ≈ r/q le fragment est éclairé
– si depth[s/q , t/q] < r/q le fragment est dans l’ombre
Hardware shadow mapping
• Test d’ombre géré comme une opération de filtrage
– lecture de la valeur de la texture en (s/q , t/q)
– comparaison avec la valeur r/q
– génère 1 ou 0 suivant le résultat de la comparaison
– à moduler avec la couleur du fragment : noire ou inchangée
glTexParameteri(…, GL_TEXTURE_COMPARE_MODE_ARB,
GL_COMPARE_R_TO_TEXTURE)
glTexParameteri(…, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL)
• Format spécifique pour les textures de profondeur
– composante de profondeur haute précision : 16/24/32 bits
glTexImage2D(…,GL_DEPTH_COMPONENT,…)
– texture résidente ou copie possible vers la mémoire application
glCopySubTexImage2D(…)
Shadow map
• Filtrage traditionnel : moyenne des valeurs
– ne fonctionne pas correctement sur la profondeur
couverture des texels
de la shadow map
position de l’oeil
Z du pixel = 0.57
Z du texel = 0.25
Z du texel = 0.63
0.25 0.25
0.63 0.63
moyenne(0.25, 0.25, 0.63, 0.63) = 0.44
0.57 > 0.44 => pixel faussement dans l’ombre
Rien n’est à une profondeur de 0.44 : Z = 0.25 ou 0.57
Shadow map
• Percentage Closer Filtering : moyenne des comparaisons
couverture des texels
de la shadow map
position de l’oeil
Z du pixel = 0.57
Z du texel = 0.25
Z du texel = 0.63
ombré
éclairé
moyenne(0.57>0.25, 0.57>0.25, 0.57<0.63, 0.57<0.63) = 50%
le pixel est naturellement à 50% dans l’ombre
(l’opération est supportée directement par la carte 3D)
Shadow map
GL_NEAREST: effet de bloc
GL_LINEAR: anti-aliassage
Shadow map
• Attention à la projection arrière
fausses ombres
générées par la
projection arrière
source lumineuse
de type spot
projection arrière du cone
d’illumination de la source
cone d’illumination de la source
(formation des ombres réelles)
Shadow map
• Utiliser un plan de clipping
– fonction de la position/direction de la source lumineuse
• Ne pas tracer la géométrie arrière
• Texture 1D encodant la distance linéaire à la source
– coordonnée s générée de façon automatique
– distances négatives/positives → 0/1
• Moduler par le résultat de l’éclairage issue de la source
Shadow map
↑ Avantages
–
–
–
–
–
pas d’accès nécessaire à la géométrie (sommets)
simple à implémenter
naturellement dynamique
s’intègre aisément dans un algorithme multi-passes
complexité indépendante de celle de la scène
↓ Inconvénients
– problèmes d’aliassage
– utilise un volume de vision (source omni-directionnelle → 6)
• ne tient pas compte des objets hors du volume
– nécessite une passe supplémentaire + transfert de texture
– optimale pour deux volumes de vision proches (oeil et source)
• lampe frontale : ! mais dans ce cas on ne voit que très peu d’ombres !
Shadow map
minification
oeil
projection depuis
l’oeil avec
code couleur
magnification
source
projection vue
depuis la source
Shadow map
oeil
la région la plus
petite du point de
vue de la source est
la plus grande du
point de vue de l’oeil
↓
source
nécessite une
shadow map de très
haute résolution
pour éviter les
problèmes
d’aliassage
Objectifs : Ombres
• Projective shadows
• Shadow volumes
• Shadow map
• Ombres douces
Ombres douces
• Méthode simpliste
– échantillonnage de la source surfacique
– moyenne de n source ponctuelle réparties sur la surface
• utiliser l’accumulation buffer
– temps de calcul x n
4 ombres dures
1024 ombres dures
Ombres douces
• Adaptation de la shadow map
– utiliser un filtrage de la texture
• Adaptation des shadow volumes
– échantillonnage de la source : n volumes différents à gérer
– générer un volume de pénombre
• temps de calcul x 2
Ombres : Conclusion
• Le choix d’un algorithme dépend de
– la complexité de la scène
– le réalisme attendu
– les ressources CPU
– les ressources GPU
• Possibilité de combiner plusieurs techniques
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Réflexion
• Réflexion plane équivalente à
– un oeil virtuel réfléchie par rapport au plan
– une scène virtuelle réfléchie par rapport au plan
réflexion miroir de l’oeil
réflexion miroir de la scène
Réflexion
• Approche scène virtuelle
1.
2.
3.
4.
5.
6.
appliquer la transformation de réflexion
tracer les objets non réflecteurs (scène réfléchie)
annuler la transformation de réflexion
tracer l’objet réflecteur avec stencil
effacer le frame buffer (élimine objets en dehors du réflecteur)
tracer les objets non réflecteurs (scène réelle)
• Approche œil virtuel
1.
2.
3.
4.
rendu de la scène depuis l’œil virtuel
copie du résultat dans une texture
tracer les objets non réflecteurs
tracer l’objet réflecteur en projetant la texture calculée
Réflexion
scène réelle
scène réfléchie
scène réfléchie avec stencil
scène réfléchie + scène réelle
Cube Mapping
• Exemple de code : restriction au réflecteur via stencil
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 1);
glEnable(GL_STENCIL_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// Tracer le reflecteur
…
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_NOTEQUAL);
glClear(GL_COLOR_BUFFER_BITS);
glDisable(GL STENCIL TEST);
Réflexion
• Transformation de réflexion
– s’exprime sous la forme d’une matrice de réflexion
– à multiplier avec la matrice de vue
1 − 2a 2
 − 2ba
M =
− 2ca
 − 2ad

− 2ab
2
1 − 2b
− 2cb
− 2bd
− 2ac
− 2bc
2
1 − 2c
− 2cd
plan receveur : p.n + d = 0 ↔ ax + by + cz + d = 0
0
0
0
1
Réflexion
scène réelle
scène réfléchie
scène réfléchie avec stencil
scène réfléchie + scène réelle
Environement Mapping
• Idée générale
–
–
–
–
un objet est petit par rapport à son environnement
f(v) donne la contribution de celui-ci pour une direction v
f est encodé dans une texture d’environnement
si l’environnement est statique, ne dépend que du point de vue
environment map
position de l’oeil
Sphere Mapping
• Réflexion de l’environnement
– vue par un observateur à l’infini
– sur une hémisphère parfaitement réfléchissante
– ne représente pas ce qui est derrière l’objet
Sphere Mapping
• Ce qui parvient à l’œil provient de la direction miroir
– symétrique du vecteur de vue par rapport à la normale
– calcul des coordonnées de texture (s,t) à partir de R
N
I
θ
θ
R
I = vecteur d’incidence
N = normale locale de la surface
R = vecteur de réflexion
R = I - 2 (N NT) I
• Calculs fait dans le repère de l’œil par OpenGL
Sphere Mapping
• Calcul automatique de (s,t) à partir de R : GL_SPHERE_MAP
R = [x, y, z] avec x2 + y2 + z2 = 1
et x, y, z dans [-1,1]
(0.5, 0.5)
1.0
s’ = x / √ x2 + y2 + (z+1)2
t’= y / √ x2 + y2 + (z+1)2
(s’, t’) dans [-1,1] → [0,1]
s = s’/2 + 0.5
t = t’/2 + 0.5
t
0.0
0.0
s
1.0
– si R est normalisé, le calcul assure un point de la sphère
Sphere Mapping
• Exemple de code
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
Cube Mapping
• Cube map : objet enfermé dans une « boîte »
– cube unité centré sur l’origine
– chaque face est texturée
– un texel indique la valeur pour les directions le traversant
haut
gauche
avant
bas
droite
arrière
Cube Mapping
• Découpage de l’espace des directions
– chaque face définie un ensemble de directions selon chaque axe
-z
py
+y
+y
+z
nx
+x
+y
+x
pz
px
+z
+x
ny
+y
-z
nz
-x
Cube Mapping
• Calcul automatique : GL_REFLECTION_MAP
• Utilisation de trois coordonnées de texture
– (x,y,z) → (s,t,r)
– la coordonnée de + grande magnitude indique la face du cube
– considérons qu’il s’agit de –t, on en déduit s’ et r’
s’ = s / -t
r’ = r / -t
• Activation-Désactivation
glEnable/Disable(GL_TEXTURE_CUBE_MAP)
Cube Mapping
• Exemple de code
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
Cube Mapping
• Construction d’une cube map
– caméra positionnée au centre de l’objet
– fov de 90°
– faire pointer la camera selon chaque axe ± X, ± Y, ± Z
– faire une rendu de la scène et l’utiliser comme texture
glTexImage2D(GLenum cube_face, …)
• Si la cube map est générée à chaque frame
– environnement dynamique possible
– seules manques les auto-réflexions
Cube Mapping
Cube Mapping
• Exemple de code
glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_px );
glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_nx );
glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_py );
glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_ny );
glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_pz );
glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT, 0,
GL_RGB8, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, face_nz );
Cube Mapping
• Même technique pour construire une sphere map
– déformer les faces pour les plaquer sur la sphère
Cube Mapping
• Noter l’inversion correcte
rouge à gauche
vert à droite
vert à gauche
rouge à droite
Cube Mapping
• Noter la singularité due à la vue arrière
Objectifs
• Introduction
–
–
–
–
utilisation des extensions OpenGL
récapitulatif du rendering pipeline
points névralgiques d’une application 3D temps-réel
optimisation d’une application 3D temps-réel
• Techniques de rendu avancées
–
–
–
–
–
textures
transparence
ombres
environnement
éclairage
Objectifs : Eclairage
• Light mapping
• Bump mapping
• BRDF-based rendering
• HDR rendering
Light Mapping
• Pré-calcul de l’éclairage diffus dans une texture
– indépendant du point de vue
– faible résolution suffit (lisse + filtrage)
• A combiner avec (multi-texturing)
– matériau
– texture
– éclairage spéculaire
• Peut intégrer des effets globaux (radiosité, etc…)
– éclairage indirect
– ombres
• Limité à un éclairage statique
– sinon intégrer la modification des coordonnées de textures correspondante
Light Mapping
×
(modulate)
texture
light map
=
scène finale
Light Mapping
•
Génération
1)
regrouper les primitives coplanaires
2)
leur affecter une light map (texture)
3)
calculer les informations texel dans le monde
–
position, normale, …
4)
en déduire l’éclairage global des texels → lumels
5)
rassembler les différentes maps dans une seule texture
–
–
•
économie substantielle de mémoire/transfert
gestion des coordonnées de texture plus complexe (logiciels dédiés)
Technique très utilisée dans les jeux (Quake II, …)
Objectifs : Eclairage
• Light mapping
• Bump mapping
• BRDF-based rendering
• HDR rendering
Bump mapping
• But : simuler la micro-géométrie d’une surface
– généralement simulée via une texture pour les matériaux
• Intègre en plus
– les variations locale de l’éclairage sur la surface
– simulées via une perturbation locale de la normale
– nécessite un calcul par pixel et non par sommet
sans
avec
Bump mapping
• Tangent space
– repère local à un point de la surface (T,B,N)
•
•
•
•
tangente (X)
binormale (Y)
normale (Z)
TBN matrix : object space → tangent space
• Carte de normale
– construite à partir d’une carte d’altitude / surface de réf.
– (s,t,s^t) : texture space ≈ tangent space
– par différences finies
[dh / ds, dh / dt , f ] = [1,0,−dh / ds ] × [0,1,−dh / dt ]
changement en
altitude selon l’axe s
changement en
altitude selon l’axe t
t
facteur d’échelle
s
Bump mapping
• Stocker la carte de normale
– coordonnée ↔ composante : (x,y,z) → (r,g,b)
– nécessite biais + mise à l’échelle : [-1,1] → [0,1]
• {r,g,b} = ( {x,y,z} + 1 ) / 2
– dans le repère local la normale perturbée pointe vers Z
• coordonnées z ↔ b => les normal maps sont généralement bleutées
– si normalisée on peut reconstruire z = √1-x2-y2
• utilisation de textures en composantes luminance + alpha
• Suivant les cas on utilise
– Textures 2D
– Cube maps
Bump mapping
• Calcul de l’éclairage
– fait par OpenGL au niveau des sommets
– à « remplacer » par un calcul au niveau des pixels
– utilise typiquement le modèle de Blinn
C f = C d × ( N .L ) + C s × ( N .H )
couleur pixel finale
couleur diffuse
n
N = vecteur normal
L = vecteur lumière
H = vecteur bissecteur
couleur spéculaire
– ce calcul peut avoir lieu dans n’importe quel espace
• utiliser le tangent space
Bump mapping
• En pratique
– encoder les normales dans une normal map
• utiliser la normale perturbée N’ pour le calcul de l’éclairage
– encoder la couleur diffuse dans une texture classique
– calculer les vecteurs L et H aux sommets et les interpoler
– calculer les produits scalaires via texture combiners
Bump mapping
• Interpolation des vecteurs L et H
1) Spécifier normalisé & attribut de sommets
– nécessite une transformation [-1,1] → [0,1]
glColor3f( ½(Lx+1), ½(Ly+1), ½(Lz+1) )
– nécessite une re-normalisation lors du calcul
2) Spécifier non-normalisé & cordonnée de texture (s,t,r)
– nécessite une normalisation avant le calcul
– utilisation d’une cube map pour cette tâche
Bump mapping
• Une cube map encode une fonction f(v)
• Une normalization cube map encode : f(v) = v ÷ ||v||
• Un texel stocke en RGB la direction depuis l’origine
– le cube mapping est indépendant de la magnitude de (s,t,r)
+Y face
-X face
+Z face
-Y face
+X face
- Z face
Bump mapping
• Exemple de code : normalization cube map (X)
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
unsigned char *data = new unsigned char[size*size*3];
float offset = 0.5f;
float halfSize = size * 0.5f;
vertex direction[3];
unsigned int index = 0;
for (unsigned int j=0; j<size; j++)
{
for (unsigned int i=0; i<size; i++)
{
direction[0] = halfSize;
direction[1] = (j+offset-halfSize);
direction[2] = -(i+offset-halfSize);
normalize(direction);
scale_to_01(direction);
data[index++] = (unsigned char)(direction[0] * 255.0f);
data[index++] = (unsigned char)(direction[1] * 255.0f);
data[index++] = (unsigned char)(direction[2] * 255.0f);
}
}
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB8,
size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
Bump mapping
• Exemple de code
// normalization cube map
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, ncm);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE) ;
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE) ;
// normal map
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normal);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB) ;
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS) ;
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE) ;
// diffuse map
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, diffuse);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
// rendre la géométrie avec L en coordonnée de texture 0
Bump mapping
• Hypothèse pour que la technique soit valide
– distance surface/lumière >> distance surface réelle/surface simulée
surface simulée
(polygone)
normale pixel
surface réelle
vecteur lumière
du calcul N’•L
vecteur lumière de
la surface réelle
OK ☺
KO Objectifs : Eclairage
• Light mapping
• Bump mapping
• BRDF-based rendering
• HDR rendering
BRDF-based Rendering
• Rendu physiquement réaliste
– contribution d’une source ponctuelle
Lo = BRDF (θ i , φi , θ o , φo ) Li cos θ i
intensité
réfléchie
matériau de
la surface
ωi direction
d’incidence
ωo direction
de réflexion
intensité
de la source
– contributions de plusieurs sources
n
Lo = ∑ BRDF (θ i j , φi j , θ o , φo ) Lij cos θ i j
j =1
BRDF-based Rendering
• La BRDF est une fonction 4D
– les textures 4D ne sont pas disponibles sur le matériel actuel
– décomposition utilisant un produit de fonctions 2D
• homomorphic factorization, singular value/normalized decomposition
BRDF (θ i , φi , θ o , φo ) = G (θ i , φi ) ⋅ H (θ o , φo ) + ε
Lo = G (θ i , φi ) ⋅ H (θ o , φo ) Li cos θ i
– 1 accès texture 4D ↔ 2 accès cube map ou texture 2D
• Algorithme
1. preprocess : pré-calcul de la décomposition dans 2 textures
2. run-time : reconstruction et calcul de l’équation d’éclairage
BRDF-based Rendering
• Normalized Decomposition
1. projeter la fonction 4D dans un espace 2D
2. des opérateurs mathématiques fournissent la décomposition
• Projection
– construire la matrice BRDF
– chaque ligne correspond à une direction de réflexion constante
– chaque colonne correspond à une direction d’incidence
constante
θ o 0 , φo 0
θ o 0 , φo1
θ o1 , φo 0
θ o1 , φo1
θ i 0 , φi 0
F(θ i 0 , φi 0 , θ o 0 , φo 0 )
 F(θ i 0 , φi 0 ,θ o 0 , φo1 )
 F(θ , φ ,θ , φ )
 i 0 i 0 o1 o 0
 F(θ i 0 , φi 0 , θ o1 , φo1 )
θ i 0 , φi1
F(θ i 0 , φi1 , θ o 0 , φo 0 )
F(θ i 0 , φi1 , θ o 0 , φo1 )
F(θ i 0 , φi1 , θ o1 , φo 0 )
F(θ io , φi1 ,θ o1 , φo1 )
θ i1 , φi 0
F(θ i1 , φi 0 ,θ o 0 , φo 0 )
F(θ i1 , φi 0 ,θ o 0 , φo1 )
F(θ i1 , φi 0 ,θ o1 , φo 0 )
F(θ i1 , φi 0 , θ o1 , φo1 )
θ i1 , φi1
F(θ i1 , φi1 ,θ o 0 , φo 0 )
F(θ i1 , φi1 ,θ o 0 , φo1 )
F(θ i1 , φi1 ,θ o1 , φo 0 )

F(θ i1 , φi1 , θ o1 , φo1 ) 
BRDF-based Rendering
• Exemple de code : projection 4D
double deltat = (0.5 * PI) / (N-1);
double deltap = (2.0 * PI) / N;
double theta_i, phi_i, theta_o, phi_o;
for (int h = 0; h < n; h++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
{
theta_o = h * deltat;
phi_o
= i * deltap;
theta_i = j * deltat;
phi_i
= k * deltap;
// Calcul de la valeur de BRDF courante
value = f(theta_i, phi_i, theta_o, phi_o)
// stockage dans la texture 4D
BRDF[h][i][j][k] = value;
}
BRDF-based Rendering
• Exemple de code : projection 2D
double deltat = (0.5 * PI) / (N-1);
double deltap = (2.0 * PI) / N;
double theta_i, phi_i, theta_o, phi_o;
for (int h = 0; h < N; h++)
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
{
theta_o = h * deltat;
phi_o
= i * deltap;
theta_i = j * deltat;
phi_i
= k * deltap;
// Calculer la valeurde BRDF courante
value = f(theta_i, phi_i, theta_o, phi_o);
// stockage dans la texture 2D
BRDFMatrix[h*N+i][j*N+k] = value;
}
BRDF-based Rendering
• Decomposition
1. calculer la norme de chaque ligne
2. diviser chaque valeur de colonne par la norme correspondante
3. calculer la valeur moyenne de chaque colonne
ωin
normes (H)
H
ωout
matrice
BRDF
×
matrice
approchée
GxH
moyennes normalisées (G)
décomposition
G
reconstruction
BRDF-based Rendering
• Algorithme de décomposition
 m1,1
 m 2,1

 M
m n −1,1
 m n ,1
matrice
BRDF
m1, 2
m 2, 2
M
m n −1 ,2
m n,2
L
L
M
L
L



n×n 




1× n
m1,n −1
m 2,n −1
M
m n −1,n −1
m n ,n −1
m1,n 
m 2,n 

M 
m n −1,n 
m n ,n 
vecteur nx1
de normes
 norm(m1,1 , m1, 2 ,L, m1, n −1 , m1, n )

 norm (m , m ,L, m

)
,
m
2
,
1
2
,
2
2
,
n
−
1
2
,
n




M


norm(m n −1,1 , m n −1, 2 ,L, m n −1, n −1 , m n −1, n )
 norm(m n ,1 , m1, 2 ,L, m n , n −1 , m n , n )



m1,1
m 2,1
M
m1, 2
m 2, 2
M
L
L
M
m1,n −1
m 2,n −1
M
m n −1,1
m n ,1
m n −1 ,2
m n,2
L
L
m n −1,n −1
m n ,n −1
 1 n m k ,1
 ∑
 n k =1 nvec k ,1
1 n m k ,2
∑
n k =1 nvec k , 2
moyenne
de chaque
colonne
1 n m n −1,1
L
∑
n k =1 nvec n −1,1





m n −1,n 
m n ,n 
m1,n
m 2, n
M
1 n m n ,1 

∑
n k =1 nvec n ,1 
BRDF-based Rendering
ωin
• Exemple
ωout
H
nxn
H
nx1
1xn
G
×
décomposition
GxH
G
reconstruction
BRDF-based Rendering
• Calcul de la matrice d’erreur
matrice
originale
H
G
_
GxH
matrice
d’erreur
BRDF-based Rendering
• Limiter la dynamique
– BRDF : [0,∞[ ↔ OpenGL : [0,1]
– normaliser via valeurs maximales de H et G
BRDF = maxG . maxH . (G/maxG) . (H/maxH)
BRDF = D . G’ . H’
• BRDF, H, G définies sur un hémisphère
– n’utiliser qu’une ½ cube map
– utiliser une texture 2D
H
BRDF-based Rendering
•
Rendu
1. pour chaque sommet : calculer les coordonnées de texture G
2. pour chaque sommet : calculer les coordonnées de texture H
3. activer l’éclairage pour la partie diffuse (Licosθi)
4. activer la texture G sur l’unité 0
5. activer la texture H sur l’unité 1
6. la combinaison de texture pour calculer Cf*t0*t1*D
7. rendre l’objet avec les coordonnées de texture calculées en 1-2
BRDF-based Rendering
• Exemple de code : calcul des coordonnées de texture
// Pour chaque sommet
for (unsigned int i = 0; i < n; i++ )
{
// calcul de la direciton d’incidence
// et de réflexion en coordonnées monde
vl = light_position - vertex[i];
ve = eye_position - vertex[i];
// transformations dans l’espace tangent local
G.x = dot(vl, tangent[i]);
G.y = dot(vl, normal[i]);
G.z = dot(vl, binormal[i]);
H.x = dot(ve, tangent[i]);
H.y = dot(ve, normal[i]);
H.z = dot(ve, binormal[i]);
// stocker les coordonnées de texture
texcoordsG[i] = G;
texcoordsH[i] = H;
}
BRDF-based Rendering
lancer
de rayons
OpenGL
• Optimisations
– décomposition de la matrice d’erreur
BRDF (θ i , φi , θ o , φo ) = G1 (θ i , φi ) ⋅ H1 (θ o , φo ) + G1 (θ i , φi ) ⋅ H 2 (θ o , φo ) + ε '
– re-paramétrisation
• vecteur bissecteur
Objectifs : Eclairage
• Light mapping
• Bump mapping
• BRDF-based rendering
• HDR rendering
HDR Rendering
• High Dynamic Range Rendering
– dynamic range = rapport entre la valeur mesurable la plus
élevée et la plus faible d’un signal
– pour la luminance réelle des scènes, peut être 100 000 : 1
• OpenGL standard
– l’intensité des pixels est restreinte à [0,1]
– les calculs sont généralement effectués sur 8 bits
• Apports
– les zones sombres/brillantes le sont réellement
– les détails sont perceptibles dans les deux cas
HDR Rendering
HDR Rendering
HDR Rendering
HDR Rendering
• Nécessite toute la chaîne d’affichage en flottants
–
–
–
–
–
–
calculs arithmétiques
render targets
blending
textures
filtrage
affichage
• Deux formats
– flottants IEEE-32 bits (s23e8)
– flottants OpenERX 16 bits (s10e5) : half
GL_ARB_half_float_pixel
HDR Rendering
• 16 bits sont généralement suffisants
– 16 bits x 4 = 64 bits par texel
– déjà deux fois la bande passante RGBA 32 bits
– seize fois celle de textures compressées DXT1
• 32 bits consomment trop de bande passante
– 32 bits x 4 = 128 bits par texel
• La taille est ici le point névralgique
– bande passante moins performante que les capacités de calcul
• Précision inutile pour les textures classiques
– pourcentage de lumière réfléchie : albédo
HDR Rendering
• GeForce 6800/Radeon 9600
– textures R32G32B32A32F, R16G16B16A16F, L32F, L16F
• 2D, 3D, cube maps
– blending flottant 16 bits
– filtrage flottant 16 bits
• Seul manque l’affichage aujourd’hui
– composantes encodées sur 8 bits
– certains travaux sont en cours (SIGGRAPH’2004)
http://www.cs.ubc.ca/~heidrich/Projects/HDRDisplay
– algorithmes de conversion flottants → 8 bits : tone mapping
HDR Rendering
• Fonction de correspondance : L f ( x, y ) =
α
Laverage
L ( x, y )
• Nécessite
1. conversion de l’image HDR en luminance
2. le calcul de la luminance image moyenne : down-filtering 1x1
HDR Rendering
• Eblouissement
– inspiré par l’effet photographique et réel du système visuel
– les zones claires de l’image « déteignent » alentour
– semble globalement lisser l’image
• Algorithme
1.
2.
3.
4.
rendre la scène dans une texture
seuil sur les parties brillantes
filtrage (blur)
combiner avec l’image finale
HDR Rendering
• Eblouissement : optimisations
– travailler sur une image réduite et reconstruire via filtrage
• filtre 32 pixels → filtre 8 pixels sur image 4 fois moins résolue
– utiliser des filtres séparables
• blur en X puis blur en Y
• 2n accès à la texture au lieu de n2
– filtre à trous
• ne traiter qu’un pixel sur deux
• les pixels manquant seront déduit par interpolation lors du filtrage
HDR Rendering
• Le matériel permet aujourd’hui un début d’exploitation
• Quelques outils sont disponibles
– OpenEXR, HDRShop, Photosphere
• Difficultés dues aux spécificités flottantes
–
–
–
–
NaN, +Inf, -Inf
se propagent via convolutions
vérifier les divisions par zéro
vérifier les overflows
Patron
• Tous les tampons sont en lecture-seule ou lecture-écriture
glColorMask(), glDepthMask(), glStencilMask()
• Sélection d’un tampon chromatique
– pour les opérations de lecture : glReadBuffer(GLenum buffer)
– pour les opérations d’écriture : glDrawBuffer(GLenum buffer)
– tampons disponibles :
GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT,
GL_FRONT_LEFT, GL_FRONT_RIGHT,
GL_BACK_LEFT, GL_BACK_RIGHT, GL_AUXi,
GL_FRONT_AND_BACK, GL_NONE
Liens & Livres
• Liens
www.delphi3d.net/hardware/index.php : cartes + extensions
www.performanceopengl.com : optimisation
www.openexr.com, www.debevec.org : HDR
www.nvidia.com, www.ati.com : examples de codes avancés
• Livres
Advanced Graphics Programming Techniques Using OpenGL
Advanced Graphics Game Programming
Graphics Gems I, II, III, IV, V

Documents pareils

TP OpenGL : Multitextures et Bump Mapping

TP OpenGL : Multitextures et Bump Mapping normale tirée de la normal map et d’effectuer les calculs d’eclairage avec cette normale perturbée. La question qu’il convient de se poser est : Dans quel système de coordonnées sont exprimées...

Plus en détail