OpenSceneGraph: Detailebenen (LOD) und Hintergrundladen von Objekten

Bild

Einführung


Eine der interessantesten Aufgaben, die mit Hilfe dreidimensionaler Grafiken gelöst werden, ist die Schaffung "großer Welten" - lange Szenen mit einer großen Anzahl von Objekten mit der Möglichkeit einer unbegrenzten Bewegung auf der Bühne. Die Lösung für dieses Problem beruht auf den verständlichen Einschränkungen, die der Computerhardware inhärent sind.

Ein typisches Beispiel: die "große Welt" bei der Visualisierung der Eisenbahn auf dem OSG-Motor. Es fehlen nur noch die Langoliers, die die Welt hinter dem Zug verschlingen ...


In dieser Hinsicht besteht die Notwendigkeit, Anwendungsressourcen zu verwalten, was auf eine offensichtliche Lösung hinausläuft: Laden nur der Ressourcen (Modelle, Texturen usw.), die zum Erstellen einer Szene zum aktuellen Zeitpunkt mit der aktuellen Position des Beobachters erforderlich sind; Reduzierung der Detailstufen entfernter Objekte; Entladen von nicht mehr benötigten Objekten aus dem Systemspeicher. Zum größten Teil bieten Grafik- und Spiele-Engines eine Reihe von Tools zur Lösung solcher Probleme. Heute schauen wir uns an, welche davon in OpenSceneGraph verfügbar sind.

1. Verwenden von Detailebenen (LOD)


Die Technik der Verwendung von Detailebenen ermöglicht es Ihnen, dasselbe Objekt je nach Entfernung zum Betrachter mehr oder weniger detailliert anzuzeigen. Die Verwendung dieser Technik basiert auf der einfachen Überlegung, dass die kleinen Details eines dreidimensionalen Modells über eine große Entfernung nicht zu unterscheiden sind, was bedeutet, dass sie nicht gezeichnet werden müssen. Mit dieser Technik können Sie einerseits die Gesamtzahl der im Bildspeicher angezeigten geometrischen Grundelemente reduzieren und andererseits den Anzeigebereich von Szenenobjekten nicht verlieren, was beim Erstellen großer offener Welten sehr nützlich ist.

OSG bietet Tools zum Implementieren dieser Technik über die osg :: LOD-Klasse, die von derselben osg :: Group geerbt wurde. Mit dieser Klasse können Sie dasselbe Objekt in mehreren Detailebenen darstellen. Jede Detailebene ist durch einen minimalen und maximalen Abstand zum Betrachter gekennzeichnet, bei dessen Einhaltung die Anzeige des Objekts in dieser Detailebene umgeschaltet wird.

Mit osg :: LOD können Sie diesen Bereich sofort beim Definieren eines untergeordneten Knotens oder später mithilfe der setRange () -Methoden angeben

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); 

Wir quälen Cessna weiterhin und veranschaulichen die beschriebene Technik anhand eines Beispiels

Lod Beispiel
main.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(); } 


Laden Sie zuerst das Modell

 osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); 

Jetzt müssen Sie mehrere Modelle (wir beschränken uns auf zwei Beispiele) mit einem geringeren Detaillierungsgrad generieren. Kopieren Sie dazu den geladenen Knoten zweimal mit der Technik der sogenannten "tiefen" Kopie der Klasse für den von der clone () -Methode implementierten Knoten

 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)); 

Jetzt reduzieren wir die Geometrie dieser Modelle mit der Klasse osgUtil :: Simplifer. Der Grad der Vereinfachung des Modells wird durch die setSampleRatio () -Methode dieser Klasse festgelegt. Je kleiner der übergebene Parameter ist, desto weniger detailliert ist das Modell nach Anwendung des Reduktionsverfahrens

 osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); 

Wenn wir Modelle mit unterschiedlichen Detaillierungsgraden haben, können wir sie dem Stammknoten aufladen, der als intelligenter Zeiger auf osg :: LOD erstellt wurde. Stellen Sie für jede Detailebene den Anzeigeabstand dieser Ebene ein

 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); 

Mit FLT_MAX ist in gewisser Weise eine "unendlich" große Entfernung zum Beobachter gemeint. Nach dem Starten des Viewers erhalten wir das folgende Bild

Detaillierungsgrad 3



Detaillierungsgrad 2



Detaillierungsgrad 1



Es ist zu sehen, dass das Detail der angezeigten Geometrie abnimmt, wenn die Kamera vom Objekt wegbewegt wird. Mit dieser Technik können Sie einen hohen Realismus der Szene bei geringem Ressourcenverbrauch erzielen.

2. Hintergrundladetechnik für Szenenknoten


Die OSG-Engine stellt die Klassen osg :: ProxyNode und osg :: PagedLOD bereit, um die Last beim Rendern der Szene auszugleichen. Beide Klassen erben von osg :: Group.

Ein Knoten vom Typ osg :: ProxyNode reduziert die Startzeit der Anwendung vor dem Rendern, wenn in der Szene eine große Anzahl geladener und angezeigter Modelle von der Festplatte geladen ist. Es fungiert als Schnittstelle zu externen Dateien und ermöglicht das verzögerte Laden von Modellen. Verwenden Sie zum Hinzufügen untergeordneter Knoten die Methode setFileName () (anstelle von addChild), um den Namen der Modelldatei auf der Festplatte festzulegen und dynamisch zu laden.

Der Knoten osg :: PagedNode erbt die osg :: LOD-Methoden und lädt und entlädt Detailebenen so, dass eine Überlastung der OpenGL-Pipeline vermieden und ein reibungsloses Rendern der Szene sichergestellt wird.

3. Dynamisches Laden (Laufzeit) des Modells


Mal sehen, wie der Prozess des Ladens des Modells mit osg :: ProxyNode abläuft.

Proxynode-Beispiel
main.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(); } 


Der Download-Vorgang hier ist etwas anders

 osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); 

Anstatt das Kuhmodell explizit zu laden, geben wir dem Stammknoten den Namen der Datei an, in der sich das Modell befindet, und den Index des untergeordneten Knotens, in dem dieses Modell nach dem Laden platziert werden soll. Bei der Ausführung des Programms erhalten wir dieses Ergebnis



Es ist zu erkennen, dass der Blickwinkel nicht optimal gewählt wurde - die Kamera ruht direkt auf der Spiegelseite der Kuh. Dies geschah, weil das Modell nach dem Starten des Renderns und dem Initialisieren der Kamera geladen wurde, als Knoten 0 noch nicht sichtbar war. Der Betrachter konnte die notwendigen Kameraparameter einfach nicht berechnen. Das Modell wurde jedoch geladen und wir können den Anzeigemodus durch Manipulieren der Maus konfigurieren



Was passiert im obigen Beispiel? osg :: ProxyNode und osg :: PagedLOD arbeiten in diesem Fall als Container. Der interne Datenmanager von OSG sendet Anforderungen und lädt Daten in das Szenendiagramm, wenn Modelldateien und Detailebenen erforderlich sind.

Dieser Mechanismus funktioniert in mehreren Hintergrundströmen und steuert das Laden statischer Daten in Dateien auf der Festplatte sowie dynamischer Daten, die während der Programmausführung generiert und hinzugefügt werden.

Die Engine verarbeitet automatisch Knoten, die nicht im aktuellen Ansichtsfenster angezeigt werden, und entfernt sie aus dem Szenendiagramm, wenn das Rendern überladen ist. Dieses Verhalten wirkt sich jedoch nicht auf die osg :: ProxyNode-Knoten aus.

Wie der Proxy-Knoten verfügt auch die osg :: PagedLOD-Klasse über eine setFileName () -Methode zum Angeben des Pfads zum geladenen Modell. Sie müssen jedoch den Sichtbarkeitsbereich dafür festlegen, wie für den osg :: LOD-Knoten. Vorausgesetzt, wir haben eine cessna.osg-Datei und ein Low-Poly-Modell der Ebene L1, können wir den ausgelagerten Knoten wie folgt organisieren

 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 ); 

Sie müssen verstehen, dass der Knoten modelL1 nicht aus dem Speicher entladen werden kann, da dies ein normaler untergeordneter Nicht-Proxy-Knoten ist.

Beim Rendern ist der Unterschied zwischen osg :: LOD und osg :: PagedLOD von außen nicht sichtbar, wenn Sie nur eine Detailebene des Modells verwenden. Eine interessante Idee wäre, mit der Klasse osg :: MatrixTransform einen großen Cluster von Cessna-Modellen zu organisieren. Hierfür können Sie beispielsweise eine solche Funktion verwenden

 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(); } 

Ein Beispielprogramm, das das Laden von 10.000 Flugzeugen im Hintergrund implementiert

main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/PagedLOD> #include <osg/MatrixTransform> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Node *createLODNode(const std::string &filepath, const osg::Vec3 &pos) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->setFileName(0, filepath); pagedLOD->setRange(0, 0.0f, FLT_MAX); osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix(osg::Matrix::translate(pos)); mt->addChild(pagedLOD.get()); return mt.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Group> root = new osg::Group; float dist = 50.0f; int N = 100; for (int i = 0; i < N; ++i) { float x = i * dist; for (int j = 0; j < N; ++j) { float y = j * dist; osg::Vec3 pos(x, y, 0.0f); osg::ref_ptr<osg::Node> node = createLODNode("../data/cessna.osg", pos); root->addChild(node.get()); } } osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 

Es wird angenommen, dass sich das Flugzeug in einem Flugzeug mit einem Intervall von 50 Koordinateneinheiten befindet. Beim Laden werden wir sehen, dass nur die Cessna geladen werden, die in den Rahmen gelangen. Die Ebenen, die aus dem Rahmen verschwinden, verschwinden aus dem Szenenbaum.



Fazit


Diese Lektion in der OpenSceneGraph-Reihe ist die letzte im How To-Format. In zwölf Artikeln konnten die Grundprinzipien der Arbeit und Verwendung von OpenSceneGraph in die Praxis umgesetzt werden. Ich hoffe wirklich, dass dieser Motor dem russischsprachigen Entwickler klarer geworden ist.

Dies bedeutet nicht, dass ich das Thema OpenSceneGraph in der Ressource abschließe. Im Gegenteil, es ist geplant, zukünftige Artikel fortgeschritteneren Techniken und Methoden zur Verwendung von OSG bei der Entwicklung grafischer Anwendungen zu widmen. Dafür müssen Sie jedoch gutes Material sammeln und viele englischsprachige Quellen verarbeiten, was einige Zeit in Anspruch nimmt.

Aber ich verabschiede mich nicht, danke für Ihre Aufmerksamkeit und bis bald!

Source: https://habr.com/ru/post/de438454/


All Articles