
Présentation
L'une des tâches les plus intéressantes résolues au moyen de graphiques tridimensionnels est la création de "grands mondes" - de longues scènes contenant un grand nombre d'objets avec la possibilité d'un mouvement illimité autour de la scène. La solution à ce problème repose sur les limitations compréhensibles inhérentes au matériel informatique.
Un exemple typique: le "grand monde" lors de la visualisation du chemin de fer sur le moteur OSG. Il ne manque que les langoliers dévorant le monde derrière le train ...À cet égard, il est nécessaire de gérer les ressources des applications, ce qui se résume à une solution évidente: charger uniquement les ressources (modèles, textures, etc.) nécessaires pour former une scène à l'heure actuelle avec la position actuelle de l'observateur; réduction des niveaux de détail des objets distants; le déchargement des objets n'est plus nécessaire de la mémoire système. Pour la plupart, les moteurs graphiques et de jeu fournissent un certain ensemble d'outils pour résoudre de tels problèmes. Aujourd'hui, nous regardons lesquels sont disponibles dans OpenSceneGraph.
1. Utilisation des niveaux de détail (LOD)
La technique d'utilisation des niveaux de détail vous permet d'afficher le même objet avec plus ou moins de détails, selon la distance qui le sépare de l'observateur. L'utilisation de cette technique est basée sur la simple considération que les petits détails d'un modèle tridimensionnel sont indiscernables sur une grande distance, ce qui signifie qu'il n'est pas nécessaire de les dessiner. D'une part, cette technique vous permet de réduire le nombre total de primitives géométriques sorties vers le tampon de trame, et d'autre part, de ne pas perdre la plage d'affichage des objets de scène, ce qui est très utile lors de la création de grands mondes ouverts.
OSG fournit des outils pour implémenter cette technique via la classe osg :: LOD, héritée du même groupe osg ::. Cette classe vous permet de représenter le même objet à plusieurs niveaux de détail. Chaque niveau de détail est caractérisé par une distance minimale et maximale par rapport à l'observateur, à laquelle l'observation de l'objet est commutée dans ce niveau de détail.
osg :: LOD vous permet de spécifier cette plage immédiatement lors de la définition d'un nœud enfant, ou plus tard, à l'aide des méthodes setRange ()
osg::ref_ptr<osg::LOD> lodNode = new osg::LOD; lodNode->addChild(node2, 500.0f, FLT_MAX); lodNode->addChild(node1); . . . lodNode->setRange(1, 0.0f, 500.0f);
Nous continuons à tourmenter Cessna et illustrons la technique décrite avec un exemple
Exemple de Lodmain.h #ifndef MAIN_H #define MAIN_H #include <osg/LOD> #include <osgDB/ReadFile> #include <osgUtil/Simplifier> #include <osgViewer/Viewer> #endif
main.h #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
Tout d'abord, chargez le modèle
osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg");
Vous devez maintenant générer plusieurs modèles (nous nous limiterons à deux exemples), avec un niveau de détail inférieur. Pour ce faire, copiez le nœud chargé deux fois, en utilisant la technique de la copie dite "profonde" de la classe, pour le nœud implémenté par la méthode clone ()
osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL));
Maintenant, nous réduisons la géométrie de ces modèles en utilisant la classe osgUtil :: Simplifer. Le degré de simplification du modèle est défini par la méthode setSampleRatio () de cette classe - plus le paramètre transmis est petit, moins le modèle sera détaillé après l'application de la procédure de réduction
osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer);
Lorsque nous avons des modèles de différents niveaux de détail, nous pouvons les facturer au nœud racine, créé en tant que pointeur intelligent vers osg :: LOD. Pour chaque niveau de détail, définissez la distance d'affichage de ce niveau
osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f);
Par FLT_MAX, on entend en quelque sorte une distance "infiniment grande" à l'observateur. Après avoir démarré la visionneuse, nous obtenons l'image suivante
Niveau de détail 3

Niveau de détail 2

Niveau de détail 1

On peut voir que lorsque la caméra est éloignée de l'objet, le détail de la géométrie affichée diminue. En utilisant cette technique, vous pouvez obtenir un réalisme élevé de la scène avec une faible consommation de ressources.
2. Technique de chargement en arrière-plan pour les nœuds de scène
Le moteur OSG fournit les classes osg :: ProxyNode et osg :: PagedLOD, conçues pour équilibrer la charge lors du rendu de la scène. Les deux classes héritent d'osg :: Group.
Un nœud de type osg :: ProxyNode réduit le temps de lancement de l'application avant le rendu, si la scène a un grand nombre de modèles chargés et affichés à partir du disque. Il fonctionne comme une interface vers des fichiers externes, permettant un chargement différé des modèles. Pour ajouter des nœuds enfants, utilisez la méthode setFileName () (au lieu de addChild) pour définir le nom du fichier modèle sur le disque et le charger dynamiquement.
Le nœud osg :: PagedNode hérite des méthodes osg :: LOD et charge et décharge les niveaux de détails de manière à éviter de surcharger le pipeline OpenGL et d'assurer un rendu fluide de la scène.
3. Chargement dynamique (runtime) du modèle
Voyons comment se déroule le processus de chargement du modèle à l'aide d'osg :: ProxyNode.
Exemple de Proxynodemain.h #ifndef MAIN_H #define MAIN_H #include <osg/ProxyNode> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
Le processus de téléchargement ici est un peu différent
osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg");
Au lieu de charger explicitement le modèle de vache, nous indiquons au nœud racine le nom du fichier où se trouvent le modèle et l'index du nœud enfant, où ce modèle doit être placé après son chargement. Lors de l'exécution du programme, nous obtenons ce résultat

On peut voir que le point de vue n'a pas été choisi de la meilleure façon - la caméra repose directement sur le côté miroir de la vache. Cela est dû au fait que le modèle a été chargé après le démarrage du rendu et l'initialisation de la caméra, alors que le nœud 0 n'était pas encore visible. Le spectateur n'a tout simplement pas pu calculer les paramètres de caméra nécessaires. Cependant, le modèle est chargé et nous pouvons configurer le mode d'affichage en manipulant la souris

Que se passe-t-il dans l'exemple ci-dessus? osg :: ProxyNode et osg :: PagedLOD fonctionnent dans ce cas comme des conteneurs. Le gestionnaire de données interne de l'OSG enverra des demandes et chargera des données dans le graphique de la scène lorsque le besoin se fera sentir de fichiers de modèle et de niveaux de détail.
Ce mécanisme fonctionne dans plusieurs flux d'arrière-plan et contrôle le chargement des données statiques situées dans les fichiers sur le disque et des données dynamiques générées et ajoutées lors de l'exécution du programme.
Le moteur traite automatiquement les nœuds qui ne sont pas affichés dans la fenêtre courante et les supprime du graphique de scène lorsque le rendu est surchargé. Toutefois, ce comportement n'affecte pas les nœuds osg :: ProxyNode.
Comme le nœud proxy, la classe osg :: PagedLOD a également une méthode setFileName () pour spécifier le chemin d'accès au modèle chargé, cependant, vous devez définir la plage de visibilité pour celui-ci, comme pour le nœud osg :: LOD. Pourvu que nous ayons un fichier cessna.osg et un modèle low-poly de niveau L1, nous pouvons organiser le nœud paginé comme suit
osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->addChild(modelL1, 200.0f, FLT_MAX ); pagedLOD->setFileName( 1, "cessna.osg" ); pagedLOD->setRange( 1, 0.0f, 200.0f );
Vous devez comprendre que le nœud modelL1 ne peut pas être déchargé de la mémoire, car il s'agit d'un nœud enfant non proxy normal.
Lors du rendu, la différence entre osg :: LOD et osg :: PagedLOD n'est pas visible lors de l'utilisation d'un seul niveau de détail du modèle. Une idée intéressante serait d'organiser un énorme cluster de modèles Cessna en utilisant la classe osg :: MatrixTransform. Pour cela, vous pouvez utiliser par exemple une telle fonction
osg::Node* createLODNode( const osg::Vec3& pos ) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; … osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix( osg::Matrix::translate(pos) ); mt->addChild( pagedLOD.get() ); return mt.release(); }
Un exemple de programme qui implémente le chargement en arrière-plan de 10 000 avions
main.h #ifndef MAIN_H #define MAIN_H #include <osg/PagedLOD> #include <osg/MatrixTransform> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
Il est supposé que l'avion sera situé sur un avion avec un intervalle de 50 unités de coordonnées. Lors du chargement, nous verrons que seuls les cessna qui entrent dans le cadre sont chargés. Les avions qui disparaissent du cadre disparaissent de l'arbre de la scène.

Conclusion
Cette leçon de la série OpenSceneGraph sera la dernière au format How To. Au cours de douze articles, il a été possible d'adapter les principes de base du travail et de l'utilisation d'OpenSceneGraph dans la pratique. J'espère vraiment que ce moteur est devenu plus clair pour le développeur russophone.
Cela ne signifie pas que je ferme le sujet d'OpenSceneGraph sur la ressource, au contraire, il est prévu de consacrer de futurs articles à des techniques et méthodes plus avancées d'utilisation d'OSG dans le développement d'applications graphiques. Mais pour cela, vous devez accumuler du bon matériel et traiter beaucoup de sources anglophones, et cela prend du temps.
Mais je ne dis pas au revoir, merci de votre attention et à
bientôt!