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.

Screenshot of the Cube example running on N900

Das Beispiel besteht aus zwei Klassen:

  • MainWidget erweitert QOpenGLWidget und enthält OpenGL ES 2.0 Initialisierung und Zeichnen sowie Maus- und Timer-Ereignisbehandlung
  • GeometryEngine 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.

Cube faces and vertices

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

Beispielprojekt @ code.qt.io

© 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.