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.

Das Beispiel besteht aus zwei Klassen:
MainWidgeterweitert „ QOpenGLWidget “ und enthält die Initialisierung sowie das Zeichnen für OpenGL ES 2.0 sowie die Verarbeitung von Maus- und Timer-EreignissenGeometryEngineverarbeitet 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.

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