En esta página

Cubo OpenGL ES 2.0 ejemplo

Muestra cómo rotar manualmente un cubo 3D texturizado con la entrada del usuario.

El ejemplo Cube OpenGL ES 2.0 muestra cómo rotar manualmente un cubo 3D texturizado con la entrada del usuario, utilizando OpenGL ES 2.0 con Qt. Muestra cómo manejar geometrías poligonales de forma eficiente y cómo escribir un sombreador de vértices y fragmentos simple para un canal gráfico programable. Además, muestra cómo utilizar cuaterniones para representar la orientación de objetos 3D.

Este ejemplo ha sido escrito para OpenGL ES 2.0 pero funciona también en OpenGL de escritorio porque este ejemplo es lo suficientemente simple y para la mayoría de las partes la API OpenGL de escritorio es la misma. También compila sin soporte OpenGL pero entonces sólo muestra una etiqueta indicando que se requiere soporte OpenGL.

Captura de pantalla del ejemplo Cube en N900

El ejemplo consiste en dos clases:

  • MainWidget extiende QOpenGLWidget y contiene inicialización OpenGL ES 2.0 y manejo de eventos de dibujo, ratón y temporizador.
  • GeometryEngine maneja geometrías poligonales. Transfiere geometría poligonal a objetos de buffer de vértices y dibuja geometrías desde objetos de buffer de vértices.

Comenzaremos inicializando OpenGL ES 2.0 en MainWidget.

Inicializando OpenGL ES 2.0

Dado que OpenGL ES 2.0 ya no soporta un pipeline gráfico fijo, tenemos que implementarlo nosotros mismos. Esto hace que el canal de gráficos sea muy flexible, pero al mismo tiempo se hace más difícil porque el usuario tiene que implementar el canal de gráficos para hacer funcionar incluso el ejemplo más simple. También hace que el canal gráfico sea más eficiente porque el usuario puede decidir qué tipo de canal es necesario para la aplicación.

Primero tenemos que implementar el sombreador de vértices. Obtiene los datos de vértices y la matriz modelo-vista-proyección (MVP) como parámetros. Transforma la posición de los vértices usando la matriz MVP al espacio de la pantalla y pasa las coordenadas de la textura al fragment shader. Las coordenadas de textura se interpolan automáticamente en las caras de los polígonos.

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

Después de eso tenemos que implementar la segunda parte de la tubería de gráficos - fragment shader. Para este ejercicio necesitamos implementar el fragment shader que maneja la textura. Obtiene la coordenada de la textura interpolada como parámetro y busca el color del fragmento a partir de la textura dada.

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

Usando QOpenGLShaderProgram podemos compilar, enlazar y vincular el código del sombreador al canal gráfico. Este código utiliza archivos de recursos Qt para acceder al código fuente del shader.

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

El siguiente código habilita el buffering de profundidad y el back face culling.

    // Enable depth buffer
    glEnable(GL_DEPTH_TEST);

    // Enable back face culling
    glEnable(GL_CULL_FACE);

Carga de texturas desde archivos de recursos Qt

La interfaz QOpenGLWidget implementa métodos para cargar texturas desde QImage a la memoria de texturas OpenGL. Aún necesitamos utilizar las funciones proporcionadas por OpenGL para especificar la unidad de textura OpenGL y configurar las opciones de filtrado de texturas.

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

Geometría cúbica

Hay muchas formas de renderizar polígonos en OpenGL, pero la más eficiente es utilizar sólo primitivas de banda triangular y renderizar los vértices desde la memoria del hardware de gráficos. OpenGL tiene un mecanismo para crear objetos búfer a esta área de memoria y transferir datos de vértices a estos búferes. En la terminología de OpenGL se denominan objetos de búfer de vértices (VBO).

Caras y vértices del cubo

Así es como las caras de los cubos se descomponen en triángulos. Los vértices se ordenan de esta manera para que el orden de los vértices sea el correcto utilizando tiras de triángulos. OpenGL determina la cara frontal y posterior del triángulo basándose en el orden de los vértices. Por defecto OpenGL utiliza el orden contrario a las agujas del reloj para las caras frontales. Esta información es utilizada por el "back face culling" que mejora el rendimiento del renderizado al no renderizar las caras traseras de los triángulos. De esta forma, la línea gráfica puede omitir el renderizado de las caras del triángulo que no están orientadas hacia la pantalla.

La creación de objetos de búfer de vértices y la transferencia de datos a ellos es bastante simple usando QOpenGLBuffer. MainWidget se asegura de que la instancia GeometryEngine es creada y destruida con el contexto OpenGL actual. De esta forma podemos utilizar recursos OpenGL en el constructor y realizar una limpieza adecuada en el destructor.

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

Dibujar primitivas a partir de VBOs y decirle al pipeline gráfico programable cómo localizar los datos de vértices requiere unos pocos pasos. Primero necesitamos enlazar los VBOs a utilizar. Después de eso vinculamos los nombres de los atributos del programa shader y configuramos qué tipo de datos tiene en el VBO vinculado. Finalmente dibujaremos primitivas triangulares usando los índices del otro 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);
}

Proyección en Perspectiva

Usando los métodos de ayuda de QMatrix4x4 es realmente fácil calcular la matriz de proyección perpectiva. Esta matriz se utiliza para proyectar los vértices al espacio de la pantalla.

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

Orientación del Objeto 3D

Los cuaterniones son una forma práctica de representar la orientación de un objeto 3D. Los cuaterniones implican matemáticas bastante complejas pero afortunadamente todas las matemáticas necesarias detrás de los cuaterniones están implementadas en QQuaternion. Eso nos permite almacenar la orientación del cubo en cuaterniones y rotar el cubo alrededor de un eje dado es bastante simple.

El siguiente código calcula el eje de rotación y la velocidad angular basándose en los eventos del ratón.

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 se utiliza para animar la escena y actualizar la orientación del cubo. Las rotaciones se pueden concatenar simplemente multiplicando cuaterniones.

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

La matriz modelo-vista se calcula usando el quaternion y moviendo el mundo por el eje Z. Esta matriz se multiplica con la matriz de proyección para obtener la matriz MVP para el programa shader.

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

Proyecto de ejemplo @ 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.