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
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...