Architecture d'un moteur de rendu
ou quelques idées pour en écrire un ...
les commentaires / remarques /
compléments / correction d'erreurs, etc. sont les bienvenus : mailto:jean-claude.iehl@liris.cnrs.fr
hypothèses :
on suppose que le monde est trop gros pour être
entièrement chargé en mémoire.
on suppose que le monde est découpé en cellules et que
l'on peut localiser un point dans le monde (trouver la cellule dans
laquelle il se trouve) et connaître les cellules voisines.
dans le cas d'un monde ouvert (exterieurs) : un
quadrillage régulier en 2D ou 2D5,
dans le cas d'un monde fermé
(intérieurs) : un système de cellules / portails, ou un
bsp
on connait les caractéristiques du gpu :
nombre de triangles affichables ("budget triangle")
quantité de mémoire
dédiée au stockage
des triangles + données (attributs).
quantité de mémoire
dédiée aux textures ("buget texture").
étapes :
1. localiser le point de vue dans le monde : déterminer la
cellule contenant le point et les cellules adjacentes
objectif : déterminer l'ensemble de cellules sur lequel on
travaille
2. charger le contenu de l'ensemble de cellules
dans chaque cellule : ensemble d'objets (affichables ou pas)
dans chaque objet affichable : ensemble de "ressources 3D"
géométrie
animations (squelette, poids de skinning, etc.)
textures
matières
effets / shaders
+ version procédurale (systèmes de
particules, forêts, etc.)
3. estimation des objets visibles par la caméra ("frustum
culling")
objectif : réduire la quantité de données à
charger
4. sélection du niveau de détail géométrique
objectif : trouver le niveau de détail de chaque objet à
afficher pour tenir le "budget triangle"
5. estimation exacte des objets visibles : "occlusion culling"
objectif : ne pas charger sur le gpu la géométrie des
objets non visibles pour augmenter le niveau de détail des
objets visibles en restant dans le "budget triangle". réevaluer
le niveau de détail des objets visibles.
l'objectif des étapes 3, 4, 5 est de sélectionner le
meilleur niveau de détail des objets à afficher qui
respecte le "budget triangle".
6. charger la géométrie sur le gpu
compresser la géométrie :
soit remailler l'objet à la volée
(générer la géométrie sur le gpu) cf.
tesselation,
soit afficher une géometrie simple et ajouter
des détails (lancer de rayons sur gpu), cf. relief mapping
compresser / quantifier les attributs de la géométrie
pour limiter les données à charger lors de
l'exécution des shaders
7. afficher la géométrie brute sans activer les shaders :
"ZPass" avec un parcours de l'ensemble d'objets en s'éloignant
de la caméra (utilisation efficace du Zbuffer)
objectif : remplir le Zbuffer pour accélérer
l'étape 11.
8. estimer le niveau de détail (les niveaux de mip map
utilisés) des textures / données des objets
affichés à l'étape 7.
objectif : tenir le "budget texture"
9. rendu du niveau de détail des textures par le gpu.
objectif : ne pas charger sur le gpu les niveaux de mip map des
textures qui ne seront pas utilisés pour l'affichage.
même idée que pour la géométrie et
l'occlusion culling. permet de remonter le niveau de détail des
autres textures.
l'objectif des étapes 8 et 9 est de tenir le "budget texture" en
sélectionnant le meilleur niveau de détail pour chaque
objet à afficher.
compression des textures.
compression / filtrge des normal maps
10. charger les textures sur le gpu
11. rendu final avec les shaders activés
objectif : je me demande bien ...
réordonner les shaders pour limiter le nombre de changements de l'état du pipeline,
l'étape 7 à déjà remplit le Zbuffer ce qui
permet "d'oublier" d'afficher les objets en s'éloignant de la
caméra (normalement pour utiliser efficacement le Zbuffer).
gestion des shaders : combien de sources, de quel type, combien de
shadow map, quel effet (normal map, parallax map, relief map, ambient
occlusion, etc.), quel post process (hdr, profondeur de champ, flou de
mouvement, etc.)
limiter "l'explosion" du nombre de combinaisons de shaders "élémentaires" ...
12. limiter le nombre de batchs / appels de DrawPrimitive /
glDrawElements
pourquoi ? parce que c'est la première cause de ralentissement
du rendu gpu.
cf. "DirectX 9 Performance",
en résumé, il est de bon gout de ne pas dépasser
500 ~ 1000 batchs pour le rendu complet de la scène.
estimation rapide du nombre de batchs
:
hypothèses :
un objet est composé d'une ou plusieurs
matières,
une matière est composée d'une ou
plusieurs textures et d'un ou de plusieurs shaders
un objet est donc composé de plusieurs
maillages associés chacun à une matière (texture +
shader).
pour afficher une scène
composée de plusieurs objets et éclairée par
plusieurs sources de lumières :
afficher la scène éclairée par l'énergie
ambiente
pour chaque source
pour chaque objet
pour chaque maillage
(associé à une matière)
activer la
matière ambiente
afficher le
maillage pour le rendu ambient
calculer les shadow maps
pour chaque source
pour chaque objet
pour chaque maillage
afficher le
maillage
afficher la scène éclairée
pour chaque source
activer la shadow map
pour chaque objet
pour chaque maillage
(associé à une matière)
activer la
matière complète
afficher le
maillage pour le rendu final
Le nombre de batchs est dépendant du nombre
de sources, du nombre d'objets visibles et du nombre de matières
des objets. Ce qui offre plusieurs stratégies de
réduction du nombre de batchs : travailler sur la
représentation des objets et d'un groupe d'objets, travailler
sur le nombre d'objets concerné par chaque étape, et
travailler sur le nombre de sources de lumières.
La simplification la plus directe consiste à
utiliser le même shader pour calculer les aspects ambient et
complet de chaque matière, ce qui permet d'éliminer la
première étape ...
- utiliser l'instanciation pour afficher plusieurs objets avec un
seul batch : ce n'est possible que lorsque certains objets sont
affichés plusieurs fois (des arbres, de l'herbe, des
unités dans un
rts, etc.)
- éliminer le plus d'objets possible de chaque étape,
- cf. visibilité, frustum culling, occlusion culling,
- cf. gestion de ressources : par exemple, pour calculer les
shadow maps, seule la forme des objets est nécessaire, il est
possible de ré-organiser les maillages pour dessiner l'objet
avec un seul batch
- construire l'ensemble d'objets visible par la caméra,
l'ensemble d'objets visible pour chaque source, l'ensemble d'objets
projettant une ombre sur les objets visibles par la caméra.
- limiter le nombre de maillages par objet,
- n'utiliser qu'une seule (mega-) texture qui couvre toute la
scène,
cf. "Unified
Texture Management for Arbitrary Meshes" et id tech 5,
"Sparse Virtual Textures" gdc 08
- n'utiliser qu'un seul shader capable de traiter toutes les
matières / sources de lumière (restriction du type de matières ?),
- utiliser un atlas de textures (regouper plusieurs textures dans
une seule) et ré-organiser les objets /
maillages / coordonnées de textures.
- limiter le nombre de sources de lumières et de shadow map à calculer :
utiliser une light map : précalculer les shadow maps des sources de lumières fixes
- accumuler les shadow maps de plusieurs sources dans la meme texture pour limiter le nombre de passes.
- ne dessiner qu'une seule fois la scène : "deferred
rendering" et stocker toutes les informations nécessaires au
rendu dans un / plusieurs buffers (position, normale, matière,
etc.).
- utiliser du post process sur l'image de la scène : screen
space ambient occlusion, depth of field, motion blur, rendu HDR,
modifications colorimétriques,
le temps de rendu ne dépend, généralement, que de
la taille du frame buffer au lieu d'etre lié au nombre d'objets, et surtout ils sont déjà dessinés.
objets animés par vertex shader ?
Render to Vertex Buffer, ati
pixel_buffer_objects, extension opnGL nvidia ok, ati
?
Stream Out, SM4, extension opengl nvidia sur geforce
8, directx 10
pour la suite :
intégration du streaming ... cf. "Texture Streaming", insomniac 2007, "Advanced Virtual Texture Topics", crytek 2008
gestion des
ressources mémoire,
gestion des ressources gpu (textures, vertex buffers, index buffers, etc.) :
"Segment Buffering", "Optimizing Ressource Management with Multistreaming", gpu gems 2
intégration avec le reste du moteur du jeu :
gestion des entités ...
interactions entre occlusion queries (augmente le
nombre de batchs) pour éviter d'afficher les objets
cachés et réduction du nombre de batchs ??
autres questions :
pour quel objet est-il rentable
d'utiliser une occlusion query ? (les éléments du
décors, ...)
est-ce qu'un système logiciel peut etre compétitif ?
Références triées par étape :
les références sont organisées différement
sur la page "programmation
openGL"
étape 1, découpage du monde :
- classique : un octree pour une scène 3D, quadtree pour une scène 2D ou 2D5
- cells and portals :
étape 2, charger le contenu des cellules :
- sérialisation des objets C / C++, cf. TD
- lecture d'un fichier de config
étape 3, frustum culling :
étape 4, niveau de détail, LOD, générer des
détails sur des objets peu détaillés :
- LOD, simplification de maillages
- imposteurs
- ajouter des détails (techniques basées images)
- normal mapping
- parallal mapping
- relief mapping
- ajouter des détails (techniques géométriques)
- génération de maillages de subdivision
étape 5, occlusion culling :
étape 6, charger la géométrie sur le gpu, vertex
buffers et réordonnancement de sommets :
étape 7, Zpass :
étape 8, estimer les niveaux de mip map utilisés pour
afficher une texture :
étape 9, rendu du niveau de mip map sur le gpu :
étape 10, charger les textures sur le gpu, gestion de textures :
étape 11, rendu final et effets :
- minimiser le nombre de changements d'états du pipeline
- modèles d'éclairement direct : lambert,
half-lambert, phong, fresnel
- réflexions, environement map
- ombres, shadow map, pénombres
- ambient occlusion constant, directionnel (statique, dynamique)
- éclairement indirect (précalculé, dynamique)
- hdr "HDR : the bungie way", bungie 2007
- post processing colorimétrique
- depth of field :
- motion blur :
- ...
- gestion des shaders
étape 12, limiter le nombre de passes de rendu :