Auf dieser Seite

Beispiel: Würfel in OpenGL ES 2.0

Zeigt, wie ein texturierter 3D-Würfel anhand von Benutzereingaben manuell gedreht wird.

Das OpenGL ES 2.0-Beispiel „Cube“ zeigt, wie man einen texturierten 3D-Würfel mithilfe von Benutzereingaben manuell dreht, wobei OpenGL ES 2.0 mit Qt verwendet wird. Es veranschaulicht, wie man Polygongeometrien effizient verarbeitet und wie man einen einfachen Vertex- und Fragment-Shader für eine programmierbare Grafikpipeline schreibt. Zudem wird gezeigt, wie Quaternionen zur Darstellung der Ausrichtung von 3D-Objekten verwendet werden.

Dieses Beispiel wurde für OpenGL ES 2.0 geschrieben, funktioniert aber auch mit Desktop-OpenGL, da es einfach genug ist und die Desktop-OpenGL-API größtenteils identisch ist. Es lässt sich auch ohne OpenGL-Unterstützung kompilieren, zeigt dann jedoch lediglich eine Meldung an, dass OpenGL-Unterstützung erforderlich ist.

Screenshot des „Cube“-Beispiels, das auf dem N900 läuft

Das Beispiel besteht aus zwei Klassen:

  • MainWidget erweitert „ QOpenGLWidget “ und enthält die Initialisierung sowie das Zeichnen für OpenGL ES 2.0 sowie die Verarbeitung von Maus- und Timer-Ereignissen
  • GeometryEngine verarbeitet Polygongeometrien. Sie überträgt die Polygongeometrie in Vertex-Buffer-Objekte und zeichnet die Geometrien aus diesen Vertex-Buffer-Objekten.

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 selbst implementiert werden. Dies macht die Grafikpipeline sehr flexibel, erschwert die Umsetzung jedoch gleichzeitig, da der Anwender die Grafikpipeline implementieren muss, um selbst das einfachste Beispiel zum Laufen zu bringen. Es macht die Grafikpipeline zudem effizienter, da der Anwender entscheiden kann, welche Art von Pipeline für die Anwendung benötigt wird.

Zunächst müssen wir den Vertex-Shader implementieren. Er erhält als Parameter Vertex-Daten und die Model-View-Projection-Matrix (MVP). Er transformiert die Vertex-Position mithilfe der MVP-Matrix in den Bildschirmraum und übergibt die Texturkoordinaten an den Fragment-Shader. Die Texturkoordinaten werden auf den Polygonflächen automatisch 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 interpolierten Texturkoordinaten als Parameter und ermittelt die Fragmentfarbe aus der angegebenen Textur.

void main()
{
    // Set fragment color from texture
    gl_FragColor = texture2D(texture, v_texcoord);
}

Mit „ QOpenGLShaderProgram “ können wir den Shader-Code kompilieren, verknüpfen und an die Grafikpipeline binden. Dieser Code nutzt Qt-Ressourcendateien, 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 aktiviert die Tiefenpufferung und die Backface-Culling-Funktion.

    // 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 aus „ QImage “ in den OpenGL-Texturspeicher. Wir müssen weiterhin die von OpenGL bereitgestellten Funktionen verwenden, um die OpenGL-Textureinheit festzulegen und die Texturfilteroptionen zu konfigurieren.

void MainWidget::initTextures()
{
    // Load cube.png image
    texture = new QOpenGLTexture(QImage(":/cube.png").flipped());

    // 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, doch am effizientesten ist es, ausschließlich Dreiecksstreifen-Primitive zu verwenden und Scheitelpunkte aus dem Speicher der Grafikhardware zu rendern. OpenGL verfügt über einen Mechanismus, um Pufferobjekte für diesen Speicherbereich zu erstellen und Scheitelpunktdaten in diese Puffer zu übertragen. In der OpenGL-Terminologie werden diese als Vertex-Buffer-Objekte (VBO) bezeichnet.

Würfelseiten und -ecken

So lassen sich die Flächen eines Würfels in Dreiecke zerlegen. Die Scheitelpunkte werden auf diese Weise angeordnet, um bei der Verwendung von Dreiecksstreifen die richtige Scheitelpunktreihenfolge zu gewährleisten. OpenGL bestimmt anhand der Scheitelpunktreihenfolge, welche Seite eines Dreiecks die Vorder- und welche die Rückseite ist. Standardmäßig verwendet OpenGL für Vorderseiten eine Reihenfolge gegen den Uhrzeigersinn. Diese Information wird für das Backface-Culling genutzt, das die Rendering-Leistung verbessert, indem die Rückseiten der Dreiecke nicht gerendert werden. Auf diese Weise kann die Grafikpipeline das Rendern von Seiten des Dreiecks überspringen, die nicht zum Bildschirm hin ausgerichtet sind.

Das Erstellen von Vertex-Buffer-Objekten und das Übertragen von Daten in diese ist mithilfe von „ QOpenGLBuffer “ recht einfach. „MainWidget“ stellt sicher, dass die „GeometryEngine“-Instanz im aktuellen OpenGL-Kontext erstellt und zerstört wird. Auf diese Weise können wir OpenGL-Ressourcen im Konstruktor nutzen 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 Grafik-Pipeline, wie die Scheitelpunktdaten zu finden sind, erfordert nur wenige 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 sich im gebundenen VBO befinden. Schließlich zeichnen wir Dreiecksstreifen-Primitive unter Verwendung von 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

Mithilfe der Hilfsmethoden von „ QMatrix4x4 “ lässt sich die perspektivische Projektionsmatrix ganz einfach berechnen. Diese Matrix wird verwendet, um Scheitelpunkte auf den Bildschirmraum 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 Orientierung des 3D-Objekts darzustellen. Quaternionen beinhalten recht komplexe Mathematik, aber glücklicherweise sind alle notwendigen mathematischen Grundlagen hinter Quaternionen in „ QQuaternion “ implementiert. Das ermöglicht es uns, die Orientierung des Würfels als Quaternion zu speichern, und das Drehen 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 Ausrichtung des Würfels zu aktualisieren. Drehungen lassen sich einfach durch Multiplikation von Quaternionen verketten.

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 Model-View-Matrix wird mithilfe des Quaternions und durch Verschieben der Welt entlang der 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

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