Würfel OpenGL ES 2.0 Beispiel
Zeigt, wie ein texturierter 3D-Würfel mit Benutzereingabe manuell gedreht werden kann.
Das Cube OpenGL ES 2.0 Beispiel zeigt, wie man einen texturierten 3D-Würfel mit Benutzereingabe manuell drehen kann, unter Verwendung von OpenGL ES 2.0 mit Qt. Es zeigt, wie man Polygongeometrien effizient handhabt und wie man einen einfachen Vertex- und Fragment-Shader für eine programmierbare Grafikpipeline schreibt. Darüber hinaus wird gezeigt, wie man Quaternionen zur Darstellung der 3D-Objektorientierung verwendet.
Dieses Beispiel wurde für OpenGL ES 2.0 geschrieben, funktioniert aber auch mit Desktop-OpenGL, da das Beispiel einfach genug ist und die Desktop-OpenGL-API größtenteils gleich ist. Es lässt sich auch ohne OpenGL-Unterstützung kompilieren, aber dann wird nur ein Hinweis angezeigt, dass OpenGL-Unterstützung erforderlich ist.
Das Beispiel besteht aus zwei Klassen:
MainWidget
erweitert QOpenGLWidget und enthält OpenGL ES 2.0 Initialisierung und Zeichnen sowie Maus- und Timer-EreignisbehandlungGeometryEngine
behandelt Polygon-Geometrien. Überträgt Polygongeometrien auf Vertexpufferobjekte und zeichnet Geometrien aus Vertexpufferobjekten.
Wir beginnen mit der Initialisierung von OpenGL ES 2.0 in MainWidget
.
Initialisierung von OpenGL ES 2.0
Da OpenGL ES 2.0 keine feste Grafikpipeline mehr unterstützt, muss diese von uns selbst implementiert werden. Dies macht die Grafikpipeline sehr flexibel, aber gleichzeitig wird es schwieriger, weil der Benutzer die Grafikpipeline implementieren muss, um selbst das einfachste Beispiel zum Laufen zu bringen. Es macht die Grafikpipeline auch effizienter, weil der Benutzer entscheiden kann, welche Art von Pipeline für die Anwendung benötigt wird.
Zuerst müssen wir den Vertex-Shader implementieren. Er erhält Scheitelpunktdaten und die Model-View-Projection-Matrix (MVP) als Parameter. Er transformiert die Scheitelpunktposition mithilfe der MVP-Matrix in den Bildschirmraum und gibt die Texturkoordinaten an den Fragment-Shader weiter. Die Texturkoordinaten werden automatisch auf die Polygonflächen interpoliert.
void main() { // Calculate vertex position in screen space gl_Position = mvp_matrix * a_position; // Pass texture coordinate to fragment shader // Value will be automatically interpolated to fragments inside polygon faces v_texcoord = a_texcoord; }
Danach müssen wir den zweiten Teil der Grafikpipeline implementieren - den Fragment-Shader. Für diese Übung müssen wir einen Fragment-Shader implementieren, der die Texturierung übernimmt. Er erhält die interpolierte Texturkoordinate als Parameter und sucht die Fragmentfarbe aus der gegebenen Textur heraus.
void main() { // Set fragment color from texture gl_FragColor = texture2D(texture, v_texcoord); }
Mit QOpenGLShaderProgram können wir den Shader-Code kompilieren, verlinken und an die Grafikpipeline binden. Dieser Code verwendet Qt-Ressourcen-Dateien, um auf den Shader-Quellcode zuzugreifen.
void MainWidget::initShaders() { // Compile vertex shader if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl")) close(); // Compile fragment shader if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl")) close(); // Link shader pipeline if (!program.link()) close(); // Bind shader pipeline for use if (!program.bind()) close(); }
Der folgende Code ermöglicht Tiefenpufferung und Backface Culling.
// Enable depth buffer glEnable(GL_DEPTH_TEST); // Enable back face culling glEnable(GL_CULL_FACE);
Laden von Texturen aus Qt-Ressourcendateien
Die Schnittstelle QOpenGLWidget
implementiert Methoden zum Laden von Texturen von QImage in den OpenGL-Texturspeicher. Wir müssen immer noch die von OpenGL bereitgestellten Funktionen verwenden, um die OpenGL-Textureinheit zu spezifizieren und die Texturfilteroptionen zu konfigurieren.
void MainWidget::initTextures() { // Load cube.png image texture = new QOpenGLTexture(QImage(":/cube.png").mirrored()); // Set nearest filtering mode for texture minification texture->setMinificationFilter(QOpenGLTexture::Nearest); // Set bilinear filtering mode for texture magnification texture->setMagnificationFilter(QOpenGLTexture::Linear); // Wrap texture coordinates by repeating // f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2) texture->setWrapMode(QOpenGLTexture::Repeat); }
Würfelgeometrie
Es gibt viele Möglichkeiten, Polygone in OpenGL zu rendern, aber der effizienteste Weg ist, nur Dreiecksstrip-Primitive zu verwenden und Scheitelpunkte aus dem Speicher der Grafikhardware zu rendern. OpenGL verfügt über einen Mechanismus zur Erstellung von Pufferobjekten für diesen Speicherbereich und zur Übertragung von Scheitelpunktdaten in diese Puffer. In der OpenGL-Terminologie werden diese als Vertex Buffer Objects (VBO) bezeichnet.
Auf diese Weise werden Würfelflächen in Dreiecke zerlegt. Die Scheitelpunkte werden auf diese Weise geordnet, um die richtige Anordnung der Scheitelpunkte mit Hilfe von Dreiecksstreifen zu erhalten. OpenGL bestimmt die Vorder- und Rückseiten von Dreiecken anhand der Anordnung der Scheitelpunkte. Standardmäßig verwendet OpenGL die Reihenfolge gegen den Uhrzeigersinn für die Vorderseiten. Diese Information wird vom Backface Culling verwendet, das die Renderingleistung verbessert, indem es die Rückseiten der Dreiecke nicht rendert. Auf diese Weise kann die Grafikpipeline das Rendern von Seiten des Dreiecks, die nicht in Richtung Bildschirm zeigen, auslassen.
Die Erstellung von Vertex-Buffer-Objekten und die Übertragung von Daten an diese Objekte ist mit QOpenGLBuffer recht einfach. MainWidget stellt sicher, dass die GeometryEngine-Instanz mit dem aktuellen OpenGL-Kontext erstellt und zerstört wird. Auf diese Weise können wir OpenGL-Ressourcen im Konstruktor verwenden und im Destruktor eine ordnungsgemäße Bereinigung durchführen.
GeometryEngine::GeometryEngine() : indexBuf(QOpenGLBuffer::IndexBuffer) { initializeOpenGLFunctions(); // Generate 2 VBOs arrayBuf.create(); indexBuf.create(); // Initializes cube geometry and transfers it to VBOs initCubeGeometry(); } GeometryEngine::~GeometryEngine() { arrayBuf.destroy(); indexBuf.destroy(); } // Transfer vertex data to VBO 0 arrayBuf.bind(); arrayBuf.allocate(vertices, 24 * sizeof(VertexData)); // Transfer index data to VBO 1 indexBuf.bind(); indexBuf.allocate(indices, 34 * sizeof(GLushort));
Das Zeichnen von Primitiven aus VBOs und die Anweisung an die programmierbare Grafikpipeline, wie die Vertexdaten zu finden sind, erfordert einige Schritte. Zunächst müssen wir die zu verwendenden VBOs binden. Danach binden wir die Attributnamen des Shader-Programms und konfigurieren, welche Art von Daten es in den gebundenen VBOs hat. Schließlich zeichnen wir Dreiecksstreifen-Primitive mit Indizes aus dem anderen VBO.
void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program) { // Tell OpenGL which VBOs to use arrayBuf.bind(); indexBuf.bind(); // Offset for position quintptr offset = 0; // Tell OpenGL programmable pipeline how to locate vertex position data int vertexLocation = program->attributeLocation("a_position"); program->enableAttributeArray(vertexLocation); program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData)); // Offset for texture coordinate offset += sizeof(QVector3D); // Tell OpenGL programmable pipeline how to locate vertex texture coordinate data int texcoordLocation = program->attributeLocation("a_texcoord"); program->enableAttributeArray(texcoordLocation); program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData)); // Draw cube geometry using indices from VBO 1 glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr); }
Perspektivische Projektion
Mit Hilfe der QMatrix4x4
-Hilfsmethoden ist es sehr einfach, die perspektivische Projektionsmatrix zu berechnen. Diese Matrix wird verwendet, um die Eckpunkte auf den Bildschirm zu projizieren.
void MainWidget::resizeGL(int w, int h) { // Calculate aspect ratio qreal aspect = qreal(w) / qreal(h ? h : 1); // Set near plane to 3.0, far plane to 7.0, field of view 45 degrees const qreal zNear = 3.0, zFar = 7.0, fov = 45.0; // Reset projection projection.setToIdentity(); // Set perspective projection projection.perspective(fov, aspect, zNear, zFar); }
Ausrichtung des 3D-Objekts
Quaternionen sind eine praktische Methode, um die Ausrichtung des 3D-Objekts darzustellen. Quaternionen beinhalten eine recht komplexe Mathematik, aber glücklicherweise ist die gesamte notwendige Mathematik hinter den Quaternionen in QQuaternion
implementiert. Dadurch können wir die Ausrichtung eines Würfels in Quaternionen speichern und die Drehung des Würfels um eine bestimmte Achse ist recht einfach.
Der folgende Code berechnet die Drehachse und die Winkelgeschwindigkeit auf der Grundlage von Mausereignissen.
void MainWidget::mousePressEvent(QMouseEvent *e) { // Save mouse press position mousePressPosition = QVector2D(e->position()); } void MainWidget::mouseReleaseEvent(QMouseEvent *e) { // Mouse release position - mouse press position QVector2D diff = QVector2D(e->position()) - mousePressPosition; // Rotation axis is perpendicular to the mouse position difference // vector QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized(); // Accelerate angular speed relative to the length of the mouse sweep qreal acc = diff.length() / 100.0; // Calculate new rotation axis as weighted sum rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized(); // Increase angular speed angularSpeed += acc; }
QBasicTimer
wird verwendet, um die Szene zu animieren und die Würfelausrichtung zu aktualisieren. Rotationen können einfach durch Multiplikation von Quaternionen verkettet werden.
void MainWidget::timerEvent(QTimerEvent *) { // Decrease angular speed (friction) angularSpeed *= 0.99; // Stop rotation when speed goes below threshold if (angularSpeed < 0.01) { angularSpeed = 0.0; } else { // Update rotation rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation; // Request an update update(); } }
Die Matrix der Modellansicht wird mit Hilfe der Quaternionen und durch Verschieben der Welt um die Z-Achse berechnet. Diese Matrix wird mit der Projektionsmatrix multipliziert, um die MVP-Matrix für das Shader-Programm zu erhalten.
// Calculate model view transformation QMatrix4x4 matrix; matrix.translate(0.0, 0.0, -5.0); matrix.rotate(rotation); // Set modelview-projection matrix program.setUniformValue("mvp_matrix", projection * matrix);
© 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.