OpenGL-Fenster-Beispiel
Dieses Beispiel zeigt, wie man eine minimale QWindow basierte Anwendung für den Zweck der Verwendung von OpenGL erstellt.
Hinweis: Dies ist ein Beispiel auf niedriger Ebene für die Verwendung von QWindow mit OpenGL. In der Praxis sollten Sie die höherwertige Klasse QOpenGLWindow verwenden. Siehe das Hello GLES3 Beispiel für eine Demonstration der QOpenGLWindow convenience class.
OpenGLWindow Superklasse
Unsere OpenGLWindow-Klasse fungiert als API, die dann untergeordnet wird, um das eigentliche Rendering durchzuführen. Sie verfügt über Funktionen, mit denen der Aufruf von render() angefordert werden kann, entweder sofort mit renderNow() oder sobald die Ereignisschleife die Verarbeitung des aktuellen Stapels von Ereignissen mit renderLater() beendet hat. Die OpenGLWindow-Unterklasse kann entweder render() für OpenGL-basiertes Rendering oder render(QPainter *) für Rendering mit QPainter neu implementieren. Verwenden Sie OpenGLWindow::setAnimating(true), damit render() mit der vertikalen Aktualisierungsrate aufgerufen wird, vorausgesetzt, die vertikale Synchronisation ist in den zugrunde liegenden OpenGL-Treibern aktiviert.
In der Klasse, die das OpenGL-Rendering durchführt, werden Sie typischerweise von QOpenGLFunctions erben wollen, wie es unser OpenGLWindow tut, um plattformunabhängigen Zugriff auf OpenGL ES 2.0-Funktionen zu erhalten. Durch die Vererbung von QOpenGLFunctions erhalten die darin enthaltenen OpenGL-Funktionen Vorrang, und Sie müssen sich nicht um die Auflösung dieser Funktionen kümmern, wenn Sie möchten, dass Ihre Anwendung sowohl mit OpenGL als auch mit OpenGL ES 2.0 arbeitet.
class OpenGLWindow : public QWindow, protected QOpenGLFunctions { Q_OBJECT public: explicit OpenGLWindow(QWindow *parent = nullptr); ~OpenGLWindow(); virtual void render(QPainter *painter); virtual void render(); virtual void initialize(); void setAnimating(bool animating); public slots: void renderLater(); void renderNow(); protected: bool event(QEvent *event) override; void exposeEvent(QExposeEvent *event) override; private: bool m_animating = false; QOpenGLContext *m_context = nullptr; QOpenGLPaintDevice *m_device = nullptr; };
Der Oberflächentyp des Fensters muss auf QSurface::OpenGLSurface gesetzt werden, um anzuzeigen, dass das Fenster für das OpenGL-Rendering und nicht für das Rendering von Rasterinhalten mit QPainter unter Verwendung eines QBackingStore verwendet werden soll.
OpenGLWindow::OpenGLWindow(QWindow *parent) : QWindow(parent) { setSurfaceType(QWindow::OpenGLSurface); }
Jede benötigte OpenGL-Initialisierung kann durch Überschreiben der Funktion initialize(), die einmal vor dem ersten Aufruf von render() aufgerufen wird, mit einer gültigen aktuellen QOpenGLContext durchgeführt werden. Wie im folgenden Codeschnipsel zu sehen ist, sind die Standardimplementierungen von render(QPainter *) und initialize() leer, während die Standardimplementierung von render() eine QOpenGLPaintDevice initialisiert und dann render(QPainter *) aufruft.
void OpenGLWindow::render(QPainter *painter) { Q_UNUSED(painter); } void OpenGLWindow::initialize() { } void OpenGLWindow::render() { if (!m_device) m_device = new QOpenGLPaintDevice; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); m_device->setSize(size() * devicePixelRatio()); m_device->setDevicePixelRatio(devicePixelRatio()); QPainter painter(m_device); render(&painter); }
Die Funktion renderLater() ruft einfach QWindow::requestUpdate() auf, um eine Aktualisierung für den Zeitpunkt zu planen, zu dem das System bereit ist, neu zu malen.
Wir rufen auch renderNow() auf, wenn wir ein expose-Ereignis erhalten. Das exposeEvent() ist die Benachrichtigung an das Fenster, dass sich seine Exposition, d. h. Sichtbarkeit, auf dem Bildschirm geändert hat. Wenn das expose-Ereignis empfangen wird, können Sie QWindow::isExposed() abfragen, um herauszufinden, ob das Fenster derzeit sichtbar ist oder nicht. Rendern Sie nicht auf ein Fenster und rufen Sie QOpenGLContext::swapBuffers() nicht auf, bevor es sein erstes expose-Ereignis erhalten hat, da bis dahin seine endgültige Größe unbekannt sein könnte, und außerdem könnte das, was gerendert wird, gar nicht auf dem Bildschirm erscheinen.
void OpenGLWindow::renderLater() { requestUpdate(); } bool OpenGLWindow::event(QEvent *event) { switch (event->type()) { case QEvent::UpdateRequest: renderNow(); return true; default: return QWindow::event(event); } } void OpenGLWindow::exposeEvent(QExposeEvent *event) { Q_UNUSED(event); if (isExposed()) renderNow(); }
In renderNow() kehren wir zurück, wenn wir derzeit nicht exponiert sind, in diesem Fall wird das Rendern verzögert, bis wir tatsächlich ein exponiertes Ereignis erhalten. Wenn wir dies noch nicht getan haben, erstellen wir QOpenGLContext mit der gleichen QSurfaceFormat, die auf dem OpenGLWindow eingestellt wurde, und rufen initialize() für die Unterklasse und initializeOpenGLFunctions() auf, damit die Superklasse QOpenGLFunctions mit der richtigen QOpenGLContext verbunden wird. In jedem Fall machen wir den Kontext aktuell, indem wir QOpenGLContext::makeCurrent() aufrufen, rufen render() auf, um das eigentliche Rendering durchzuführen, und planen schließlich, dass der gerenderte Inhalt sichtbar gemacht wird, indem wir QOpenGLContext::swapBuffers() mit dem OpenGLWindow als Parameter aufrufen.
Sobald das Rendering eines Frames unter Verwendung eines OpenGL-Kontextes durch den Aufruf von QOpenGLContext::makeCurrent() eingeleitet wurde, wobei die Oberfläche, auf der gerendert werden soll, als Parameter angegeben wird, können OpenGL-Befehle ausgegeben werden. Die Befehle können entweder direkt durch das Einbinden von <qopengl.h> erteilt werden, das auch die OpenGL-Header des Systems enthält, oder durch die Verwendung von QOpenGLFunctions, das entweder der Einfachheit halber geerbt werden kann oder auf das mit QOpenGLContext::functions() zugegriffen werden kann. QOpenGLFunctions ermöglicht den Zugriff auf alle OpenGL-Aufrufe auf OpenGL ES 2.0-Ebene, die nicht bereits Standard sowohl in OpenGL ES 2.0 als auch in Desktop OpenGL sind. Weitere Informationen über die OpenGL- und OpenGL ES-APIs finden Sie in der offiziellen OpenGL Registry und der Khronos OpenGL ES API Registry.
Wenn die Animation mit OpenGLWindow::setAnimating(true) aktiviert wurde, rufen wir renderLater() auf, um eine weitere Aktualisierungsanforderung zu planen.
void OpenGLWindow::renderNow() { if (!isExposed()) return; bool needsInitialize = false; if (!m_context) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); needsInitialize = true; } m_context->makeCurrent(this); if (needsInitialize) { initializeOpenGLFunctions(); initialize(); } render(); m_context->swapBuffers(this); if (m_animating) renderLater(); }
Durch die Aktivierung der Animation wird auch eine Aktualisierungsanforderung geplant, wie im folgenden Codeschnipsel gezeigt.
void OpenGLWindow::setAnimating(bool animating) { m_animating = animating; if (animating) renderLater(); }
Beispiel einer OpenGL-Rendering-Unterklasse
Hier zeigen wir mit der Unterklasse OpenGLWindow, wie man mit OpenGL ein rotierendes Dreieck rendert. Durch die indirekte Unterklassifizierung von QOpenGLFunctions erhalten wir Zugang zu allen Funktionen der OpenGL ES 2.0 Ebene.
class TriangleWindow : public OpenGLWindow { public: using OpenGLWindow::OpenGLWindow; void initialize() override; void render() override; private: GLint m_matrixUniform = 0; QOpenGLBuffer m_vbo; QOpenGLShaderProgram *m_program = nullptr; int m_frame = 0; };
In unserer Hauptfunktion initialisieren wir QGuiApplication und instanziieren unser TriangleOpenGLWindow. Wir geben ihm eine QSurfaceFormat, die angibt, dass wir vier Samples von Multisample-Antialiasing sowie eine Standardgeometrie wollen. Da wir eine Animation wünschen, rufen wir die oben erwähnte Funktion setAnimating() mit einem Argument von true auf.
int main(int argc, char **argv) { QGuiApplication app(argc, argv); QSurfaceFormat format; format.setSamples(16); TriangleWindow window; window.setFormat(format); window.resize(640, 480); window.show(); window.setAnimating(true); return app.exec(); }
Der folgende Codeschnipsel zeigt das in diesem Beispiel verwendete OpenGL-Shader-Programm. Die Vertex- und Fragment-Shader sind relativ einfach und führen Vertex-Transformation und interpolierte Vertex-Färbung durch.
static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n" "attribute lowp vec4 colAttr;\n" "varying lowp vec4 col;\n" "uniform highp mat4 matrix;\n" "void main() {\n" " col = colAttr;\n" " gl_Position = matrix * posAttr;\n" "}\n"; static const char *fragmentShaderSource = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n";
Hier ist der Code, der die Shader lädt und das Shader-Programm initialisiert. Durch die Verwendung von QOpenGLShaderProgram anstelle von rohem OpenGL erhalten wir die Bequemlichkeit, dass die highp-, mediump- und lowp-Qualifizierer bei Desktop-OpenGL wegfallen, wo sie nicht Teil des Standards sind. Wir speichern die Attribut- und Uniform-Positionen in Mitgliedsvariablen, um zu vermeiden, dass wir bei jedem Frame die Position nachschlagen müssen.
void TriangleWindow::initialize() { static const GLfloat vertices_colors[] = { +0.0f, +0.707f, 1.0f, 0.0f, 0.0f, -0.5f, -0.500f, 0.0f, 1.0f, 0.0f, +0.5f, -0.500f, 0.0f, 0.0f, 1.0f }; m_vbo.create(); m_vbo.bind(); m_vbo.allocate(vertices_colors, sizeof(vertices_colors)); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), reinterpret_cast<void *>(2 * sizeof(GLfloat))); m_program = new QOpenGLShaderProgram(this); m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); m_program->bindAttributeLocation("posAttr", 0); m_program->bindAttributeLocation("colAttr", 1); m_program->link(); m_program->bind(); m_matrixUniform = m_program->uniformLocation("matrix"); Q_ASSERT(m_matrixUniform != -1); }
Zum Schluss sehen Sie unsere render()-Funktion, mit der wir OpenGL verwenden, um das Ansichtsfenster einzurichten, den Hintergrund zu löschen und ein rotierendes Dreieck zu rendern.
void TriangleWindow::render() { const qreal retinaScale = devicePixelRatio(); glViewport(0, 0, width() * retinaScale, height() * retinaScale); glClear(GL_COLOR_BUFFER_BIT); m_program->bind(); QMatrix4x4 matrix; matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f); matrix.translate(0, 0, -2); matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0); m_program->setUniformValue(m_matrixUniform, matrix); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); m_program->release(); ++m_frame; }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.