
Einführung
Einerseits verfügt die OpenSceneGraph-Engine selbst über ein erweitertes Subsystem zum Verwalten von Fenstern, zum Verarbeiten von Benutzereingabeereignissen sowie zum Senden und Empfangen von Benutzernachrichten. Wir haben in früheren Artikeln dieser Reihe ausführlich darüber gesprochen. Im Allgemeinen reicht dies zusammen mit den Funktionen von C ++ / STL völlig aus, um beliebig komplexe Anwendungen zu entwickeln.
Ein Beispiel für die Integration von OSG in eine von QtDesigner entwickelte Anwendung. Dieses Beispiel wird nachstehend ausführlich erläutert.Um die Entwicklung in C ++ zu beschleunigen, werden sowohl Bibliotheken von Drittanbietern verwendet, die die Funktionen dieser Sprache erweitern (z. B. Boost), als auch ganze Frameworks, die die Entwicklung plattformübergreifender Anwendungen mit breitem Funktionszweck vereinfachen. Ein solches Framework ist das äußerst beliebte Qt. Unabhängig davon, wie sie Qt für seinen Metaobjekt-Compiler und andere Mängel und Unannehmlichkeiten beschimpft haben, liegt die Stärke von Qt in einer umfangreichen Klassenbibliothek, die alle denkbaren Aufgaben der plattformübergreifenden Entwicklung löst, sowie im Konzept der "Signale - Slots", die das Messaging-Subsystem zwischen Klassen implementieren. Die Signale und Slots basieren auch auf den Interaktionsmethoden zwischen der Anwendung und dem Betriebssystem sowie auf der Interprozesskommunikation.
Und zum Teufel, es wäre sehr interessant, zwei Technologien zu kombinieren: Qt und OSG. Mein Team musste ein ähnliches Problem lösen, über das ich bereits in
einer meiner Veröffentlichungen geschrieben habe . Ich möchte diese Frage jedoch etwas weiter öffnen, und dieser Artikel wird sich mit diesem Thema befassen.
Es gibt zwei Möglichkeiten, OSG und Qt zu integrieren:
- Verwenden von Qt-Signalen und -Slots zum Interagieren von Objekten in einer OSG-Anwendung
- Integration des OSG-Viewers in eine in C ++ / Qt entwickelte grafische Oberfläche, einschließlich der Verwendung des QtDesigner-Formulardesigners
Die erste Option ist anwendbar, wenn Sie die von Qt bereitgestellten GUI-Elemente nicht verwenden müssen, aber die Interaktion von Anwendungskomponenten über Signale und Slots sicherstellen möchten. Beispielsweise bestand für mich die Notwendigkeit, eine OSG-Anwendung mit einer Interprozess-Kommunikationsbibliothek über TCP-Sockets unter Verwendung von Qt zu integrieren.
Die zweite Option wird benötigt, wenn die Integration der OSG-Engine und eine mit Qt entwickelte grafische Anwendung erforderlich sind. Signale und Slots stehen uns zur Verfügung, und zusätzlich dazu die gesamte Palette standardisierter GUI-Elemente, die von Qt bereitgestellt werden.
1. Qt-Signale in einem OSG-Fenstersystem
Das erste Beispiel wird etwas synthetisch sein: Wir schreiben eine einfache OSG-Anwendung mit einer primitiven Szene; Erstellen Sie zwei Klassen, von denen eine Tastenanschläge verarbeitet und die andere der Konsole eine Meldung darüber anzeigt, welche Taste gedrückt wird. In diesem Fall generiert der Handler ein Qt-Signal mit einer Meldung über die gedrückte Taste als Parameter.
Um sich in Qt zu integrieren, reicht es aus, die folgenden drei Bedingungen zu erfüllen
- Erben Sie interagierende Klassen von QObject
- Organisieren Sie eine Signalverarbeitungsschleife
- Erstellen Sie eine Instanz der Klasse QApplication (oder QCoreApplication), die während des Anwendungsvorgangs im Speicher vorhanden ist
Den vollständigen Beispielcode finden Sie
hier in
meinem OSG-Unterrichts-Repository , das alle Lektionen für diesen Zyklus enthält.
Erstellen Sie zunächst eine Klasse, die die Signalverarbeitungswarteschlange "verdreht".
qt-events.h#ifndef QT_EVENTS_H #define QT_EVENTS_H #include <osgGA/GUIEventHandler> #include <QCoreApplication> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class QtEventsHandler : public osgGA::GUIEventHandler { public: QtEventsHandler(); virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa); protected: }; #endif // QT_EVENTS_H
qt-events.cpp #include "qt-events.h"
Alles ist sehr einfach - diese Klasse ist ein Standard-OSG-Ereignishandler, der beim Rendern eines Frames die Verarbeitung der Qt-Signalwarteschlange durch Aufrufen initiiert
QCoreApplication::processEvents(QEventLoop::AllEvents);
Erstellen wir nun eine Klasse, die die Tastatur erneut mithilfe des in OSG integrierten Mechanismus verarbeitet, aber dennoch ein Qt-Signal senden kann. Zu diesem Zweck verwenden wir die kürzlich anathematisierte Mehrfachvererbung
keyhandler.h #ifndef KEY_HANDLER_H #define KEY_HANDLER_H #include <osgGA/GUIEventHandler> #include <QObject> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class KeyboardHandler : public QObject, public osgGA::GUIEventHandler { Q_OBJECT public: KeyboardHandler(QObject *parent = Q_NULLPTR) : QObject(parent) , osgGA::GUIEventHandler () { } bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { switch (ea.getEventType()) { case osgGA::GUIEventAdapter::KEYDOWN: emit sendMessage("Pressed key " + QString(ea.getKey())); break; default: break; } return false; } signals: void sendMessage(QString msg); private: }; #endif // KEY_HANDLER_H
Die Klasse verarbeitet eine Nachricht über eine gedrückte Taste und sendet ein Signal mit einer Nachricht, die den Tastencode enthält. Dieses Signal wird von einer Klasse empfangen, die in keiner Weise mit OSG verbunden ist, einem Nachkommen von QObject, das einen einzelnen Steckplatz enthält, der eine Nachricht an die Standardausgabe druckt
Empfänger.h #ifndef RECEIVER_H #define RECEIVER_H #include <QObject> #include <iostream> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class Receiver : public QObject { Q_OBJECT public: Receiver(QObject *parent = Q_NULLPTR) : QObject(parent) {} public slots: void printMessage(QString msg) { std::cout << msg.toStdString() << std::endl; } }; #endif // RECEIVER_H
Fügen Sie nun alles zusammen, indem Sie eine OSG-Anwendung schreiben
main.h #ifndef MAIN_H #define MAIN_H #include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <QCoreApplication> #include "qt-events.h" #include "keyhandler.h" #include "receiver.h" #endif
main.cpp #include "main.h"
Zunächst erstellen wir eine Instanz der QCoreApplication-Klasse.
QCoreApplication app(argc, argv);
Dies ist erforderlich, damit die beschriebene Technologie funktioniert. Die
QCoreApplication :: exec () -Methode wird jedoch nicht aufgerufen ! Stattdessen dreht sich die Signalverarbeitungsschleife in unserer osgViewer :: Viewer :: run () -Schleife, für die wir den entsprechenden Handler erstellen und registrieren
viewer.addEventHandler(new QtEventsHandler);
Wir erstellen Instanzen von Klassen, die über Qt-Signale interagieren und das Signal der einen mit dem Schlitz der anderen verbinden
KeyboardHandler *keyboardHandler = new KeyboardHandler; Receiver *receiver = new Receiver; QObject::connect(keyboardHandler, &KeyboardHandler::sendMessage, receiver, &Receiver::printMessage);
Registrieren Sie den Tastaturhandler
viewer.addEventHandler(keyboardHandler);
Alle führen den Viewer aus
return viewer.run();
und so ein Bild sehen
Ja, das Beispiel ist etwas erfunden, aber es zeigt die Hauptprinzipien der Integration von Code mithilfe von Qt-Mechanismen in eine Anwendung mithilfe von OSG. Diese Idee, die aus dem
OpenSceneGraph 3.-Kochbuch stammt , hat mir und meinem Entwicklungsteam viel Zeit und Nerven gespart und mir ermöglicht, ein Qt-basiertes Modul zu verwenden, das in unserer Codebasis in unserer Codebasis debuggt und standardisiert wurde.
Aber was ist, wenn wir OSG weiterhin in einer Qt-GUI-Anwendung verwenden möchten?
2. osgQt Bibliothek
osgQt ist eine Integrationsbibliothek für:
- Einbetten einer in OSG implementierten 3D-Szene in die grafische Oberfläche einer auf Qt entwickelten Anwendung
- Einbetten von Qt-Widgets in eine 3D-Geometrieoberfläche innerhalb einer OSG-Szene. Ja, Sie haben richtig gehört - Qt-Widgets können in der virtuellen Welt leise arbeiten. Eines Tages werde ich es definitiv demonstrieren
Es gab bestimmte Probleme mit dieser Bibliothek, die wir überwinden konnten, indem wir die beigefügten Beispiele sorgfältig studierten und das bereits erwähnte
OpenSceneGraph 3. Kochbuch lasen
Die Bibliothek sollte zusammengebaut werden, und dieser Vorgang ähnelt dem Zusammenbau des Motors selbst, der im allerersten
Artikel des Zyklus ausführlich beschrieben
wird . Die einzige Bemerkung ist, dass -DCMAKE_INSTALL_PREFIX mit demselben ausgewählt werden sollte, der beim Erstellen der Engine angegeben wurde. Daher wird osgQt neben der Engine installiert und kann während der Entwicklung bequem verwendet werden.
3. Integration von osgViewer :: Viewer in die Qt-GUI
Das folgende Beispiel ist sehr nützlich. Wir werden einen Viewer schreiben, mit dem Sie Modelle im * .osg-Format mit Standard-Qt-Steuerelementen laden können. Darüber hinaus verwenden wir zur Entwicklung einer grafischen Oberfläche QtDeisgner.
Lassen Sie uns ein neues Projekt wie "Qt Widgets Application" erstellen.

In diesem Fall wird das Hauptanwendungsfenster mit einer Menüleiste, einer Symbolleiste und einer Statusleiste generiert. Fügen Sie in QtDesigner diesem Fenster eine QFrame-Komponente hinzu.

Wir werden den OSG-Viewer in diesem Rahmen platzieren. Der OSG-Viewer wird im Wesentlichen ein Qt-Widget sein. Für seine Implementierung schreiben wir die QViewerWidget-Klasse. Ich werde die vollständigen Quellen auf den Spoiler setzen, um die Präsentation nicht mit Codeblättern zu verwischen
qviewerwidget.h #ifndef QVIEWER_WIDGET_H #define QVIEWER_WIDGET_H #include <QWidget> #include <osgViewer/Viewer> #include <osgQt/GraphicsWindowQt> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class QViewerWidget : public QWidget { public: QViewerWidget(const QRect &geometry); virtual ~QViewerWidget(); osg::Group *getScene(); osgViewer::Viewer *getViewer(); protected: osg::ref_ptr<osg::Group> scene; osgViewer::Viewer viewer; private: osgQt::GraphicsWindowQt *createGraphicsWindow(const QRect &geometry); void initCamera(const QRect &geometry); void paintEvent(QPaintEvent *); }; #endif // QVIEWER_WIDGET_H
qviewerwidget.cpp include "qviewerwidget.h" #include <osgViewer/ViewerEventHandlers> #include <osgGA/TrackballManipulator> #include <QGridLayout> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ QViewerWidget::QViewerWidget(const QRect &geometry) : QWidget() , scene(new osg::Group) { initCamera(geometry); viewer.setSceneData(scene); viewer.addEventHandler(new osgViewer::StatsHandler); viewer.setCameraManipulator(new osgGA::TrackballManipulator); viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); } } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ QViewerWidget::~QViewerWidget() { } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Group *QViewerWidget::getScene() { return scene.get(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgViewer::Viewer *QViewerWidget::getViewer() { return &viewer; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgQt::GraphicsWindowQt *QViewerWidget::createGraphicsWindow(const QRect &geometry) { osg::DisplaySettings *ds = osg::DisplaySettings::instance().get(); osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; traits->windowName = ""; traits->windowDecoration = false; traits->x = geometry.x(); traits->y = geometry.y(); traits->width = geometry.width(); traits->height = geometry.height(); if (traits->height == 0) traits->height = 1; traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); return new osgQt::GraphicsWindowQt(traits.get()); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void QViewerWidget::initCamera(const QRect &geometry) { osg::Camera *camera = viewer.getCamera(); osg::ref_ptr<osgQt::GraphicsWindowQt> gw = createGraphicsWindow(geometry); gw->setTouchEventsEnabled(true); camera->setGraphicsContext(gw.get()); const osg::GraphicsContext::Traits *traits = gw->getTraits(); camera->setClearColor(osg::Vec4(0.7f, 0.7f, 0.7f, 1.0f)); camera->setViewport(0, 0, traits->width, traits->height); double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height); camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void QViewerWidget::paintEvent(QPaintEvent *) { viewer.frame(); }
Die Hauptidee der Implementierung ist die Verwendung der osgQt :: GraphicsWindow-Klasse, die ein Grafikfenster basierend auf der QGLWidget-Klasse erstellt. Verwenden Sie die Methode, um dieses Fenster zu erstellen
osgQt::GraphicsWindowQt *QViewerWidget::createGraphicsWindow(const QRect &geometry) { osg::DisplaySettings *ds = osg::DisplaySettings::instance().get(); osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; traits->windowName = ""; traits->windowDecoration = false; traits->x = geometry.x(); traits->y = geometry.y(); traits->width = geometry.width(); traits->height = geometry.height(); if (traits->height == 0) traits->height = 1; traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); return new osgQt::GraphicsWindowQt(traits.get()); }
Das Fenster wird gemäß den an die Eingabe übertragenen Geometrieparametern und den erforderlichen Einstellungen des OSG 3D-Renderings konfiguriert. Der zurückgegebene Zeiger ist der OSG-Grafikkontext, der an die Kamera übergeben werden soll. Daher besteht der nächste Schritt darin, die Kamera zu initialisieren
void QViewerWidget::initCamera(const QRect &geometry) { osg::Camera *camera = viewer.getCamera(); osg::ref_ptr<osgQt::GraphicsWindowQt> gw = createGraphicsWindow(geometry); gw->setTouchEventsEnabled(true); camera->setGraphicsContext(gw.get()); const osg::GraphicsContext::Traits *traits = gw->getTraits(); camera->setClearColor(osg::Vec4(0.7f, 0.7f, 0.7f, 1.0f)); camera->setViewport(0, 0, traits->width, traits->height); double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height); camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0); }
Der eigentliche Anruf
camera->setGraphicsContext(gw.get());
und übergibt den gewünschten Kontext, der dem QGLWidget-Widget zugeordnet ist, an die Kamera. Wir haben die gesamte Widget-Routine in den Klassenkonstruktor eingefügt
QViewerWidget::QViewerWidget(const QRect &geometry) : QWidget() , scene(new osg::Group) { initCamera(geometry); viewer.setSceneData(scene); viewer.addEventHandler(new osgViewer::StatsHandler); viewer.setCameraManipulator(new osgGA::TrackballManipulator); viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); } }
Hier konfigurieren wir den Viewer und achten besonders auf die Herausforderung
viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
Umschalten des Viewers in den Single-Threaded-Modus. Dies ist eine notwendige Maßnahme bei der Integration von OSG in Qt, da das Programm bei einigen Linux-Distributionen abstürzt, wenn das von OSG standardmäßig verwendete Multithread-Rendering verwendet wird. Die Gründe hierfür erfordern eine separate Nachbesprechung. Beachten Sie daher diesen Code
osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); }
In diesem Bereich erstellen wir eine Ebene, die ein vom Grafikkontext der Kamera zurückgegebenes QGLWidget stört und in einen osgQt :: GraphicsWindows-Zeiger konvertiert wird. Wir fügen die erstellte Ebene unserem Widget QViewerWidget durch Aufrufen hinzu
this->setLayout(layout);
Damit unser Widget und damit die Szene aktualisiert werden, wenn das Fenster aktualisiert wird, müssen Sie den QPaintEvent-Ereignishandler neu definieren
void QViewerWidget::paintEvent(QPaintEvent *) { viewer.frame(); }
In diesem Fall initiieren wir das Rendern des Frames durch Aufrufen der Methode osgViewer :: Viewer :: frame ().
Ok, der Code unseres Widgets ist fertig, jetzt binden wir ihn in einen Rahmen ein, der sich auf dem Formular befindet. Dazu schreiben wir im Konstruktor der MainWindow-Klasse solchen Code
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , qviewer(Q_NULLPTR) { ui->setupUi(this); QGridLayout *layout = new QGridLayout; qviewer = new QViewerWidget(QRect(0, 0, ui->frame->width(), ui->frame->height())); layout->addWidget(qviewer); ui->frame->setLayout(layout); this->setCentralWidget(ui->frame); connect(&timer, &QTimer::timeout, this, &MainWindow::update); timer.start(40); connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::quit); connect(ui->actionClean, &QAction::triggered, this, &MainWindow::clean); connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::open); this->setWindowTitle("QViewerWidget example"); }
oder besser gesagt, wir sind immer noch an diesem Teil interessiert
QGridLayout *layout = new QGridLayout; qviewer = new QViewerWidget(QRect(0, 0, ui->frame->width(), ui->frame->height())); layout->addWidget(qviewer); ui->frame->setLayout(layout); this->setCentralWidget(ui->frame);
Wenn Sie eine Ebene erstellen, erstellen Sie unser Widget mit Abmessungen, die der Größe des Rahmens entsprechen, fügen Sie das erstellte Widget der Ebene hinzu und hängen Sie die Ebene an den Rahmen an. Um das Layout in diesem Beispiel nicht zu beeinträchtigen, dehnen wir den Rahmen auf den gesamten Client-Bereich des Fensters aus und machen ihn zum zentralen Widget.
Zum Rendern sollten Sie eine regelmäßige Fensteraktualisierung nach Timer veranlassen. Erstellen Sie dazu einen Timer mit einem Intervall von 40 Millisekunden (25 Bilder pro Sekunde) und ordnen Sie das Zeitlimitsignal dem Fensteraktualisierungssteckplatz zu. Ich mache es so mit Qt5-Syntax
connect(&timer, &QTimer::timeout, this, &MainWindow::update); timer.start(40);
zuvor den Aktualisierungssteckplatz für die Fensterklasse auf diese Weise definiert haben
void MainWindow::update() { QMainWindow::update(this->geometry()); }
Warum, weil Sie das Timer-Signal direkt mit dem QMainWindow :: Update-Slot verknüpfen können, wie es die meisten osgQt-Beispiele zeigen
connect(&timer, SIGNAL(timeout), this, SLOT(update));
Tatsache ist, dass die Syntax mit den Makros SIGNAL () und SLOT () veraltet ist und im Vorgriff auf den Übergang zu Qt6 aufgegeben werden muss. Gleichzeitig hat die QMainWindow-Klasse keine Überladung des update () - Slots ohne Parameter, was zu einem Fehler beim Bind-Aufruf während der Kompilierung führt. Dazu musste ich meinen update () - Slot ohne Parameter definieren, die Basis QMainWindow :: update () darin aufrufen und dort den Client-Bereich des Fensters übergeben.
Wenn Sie diesen Ort ergänzen und das Programm ausführen, erhalten Sie ein bestimmtes Ergebnis

Durch Drücken von "S" können wir den OSG-Statistikmonitor aktivieren und sicherstellen, dass unser Widget ordnungsgemäß funktioniert und eine leere Szene zeichnet.
Welche Art von Statistikmonitor?Um den Artikel nicht zu überladen, werde ich hier darüber schreiben. OSG verfügt über einen integrierten Monitor, der Motorstatistiken in Echtzeit anzeigt. Um es dem Viewer hinzuzufügen, fügen wir die Header-Datei hinzu
#include <osgViewer/ViewerEventHandlers>
und fügen Sie dem Viewer einen Handler hinzu
viewer.addEventHandler(new osgViewer::StatsHandler);
dann jederzeit durch Drücken von "S", um viele nützliche Informationen anzuzeigen.
4. Beenden Sie unseren Viewer: Fügen Sie ein Menü hinzu
Im Formular-Designer passen wir das Menü mithilfe der "mausorientierten" Programmierung an (was mir gleichgültig ist, aber ja, manchmal ist es praktisch). Am Ende werden wir so etwas bekommen

Jetzt starten wir die entsprechenden Prozessor-Slots, sodass Sie das Modell über den im Dialogfeld ausgewählten Pfad laden, die Szene löschen und die Anwendung beenden können
Danach erhalten wir einen sehr praktischen Viewer für Modelle im * .osg-Format.

Eine Demonstration seiner Arbeit zeigt das Video am Anfang des Artikels. Der vollständige Quellcode für dieses Beispiel ist
hier verfügbar.Fazit
Wie wir gesehen haben, ist die Integration von OSG und Qt weder beim Verständnis noch bei der Implementierung besonders schwierig. Dies ist eine große Hilfe beim Erstellen plattformübergreifender Anwendungen für die technische Visualisierung und möglicherweise für Spiele.
Dieser Artikel eröffnet eine Fortsetzung der OSG-Reihe, in der komplexe Entwicklungstechniken beschrieben werden. Ich denke, sie ist erfolgreich herausgekommen. Vielen Dank und bis bald!