이 페이지에서

OpenGL ES 2.0 큐브 예제

사용자 입력을 통해 텍스처가 적용된 3D 큐브를 수동으로 회전하는 방법을 보여줍니다.

'큐브 OpenGL ES 2.0 예제'는 Qt와 OpenGL ES 2.0을 사용하여 사용자 입력에 따라 텍스처가 적용된 3D 큐브를 수동으로 회전시키는 방법을 보여줍니다. 이 예제는 다각형 지오메트리를 효율적으로 처리하는 방법과 프로그래밍 가능한 그래픽 파이프라인을 위한 간단한 버텍스 및 프래그먼트 셰이더를 작성하는 방법을 설명합니다. 또한 3D 객체의 방향을 표현하기 위해 쿼터니언을 사용하는 방법을 보여줍니다.

이 예제는 OpenGL ES 2.0을 위해 작성되었지만, 예제가 충분히 간단하고 데스크톱 OpenGL API가 대부분 동일하기 때문에 데스크톱 OpenGL에서도 작동합니다. OpenGL 지원 없이도 컴파일은 가능하지만, 이 경우 OpenGL 지원이 필요하다는 내용을 담은 레이블만 표시됩니다.

N900에서 실행 중인 Cube 예제의 스크린샷

이 예제는 두 개의 클래스로 구성되어 있습니다:

  • MainWidget QOpenGLWidget 를 상속하며, OpenGL ES 2.0 초기화 및 렌더링, 마우스 및 타이머 이벤트 처리를 담당합니다.
  • GeometryEngine 다각형 지오메트리를 처리합니다. 다각형 지오메트리를 버텍스 버퍼 객체로 전송하고, 버텍스 버퍼 객체에서 지오메트리를 렌더링합니다.

먼저 ` MainWidget`에서 OpenGL ES 2.0을 초기화하는 것으로 시작하겠습니다.

OpenGL ES 2.0 초기화

OpenGL ES 2.0은 더 이상 고정 그래픽 파이프라인을 지원하지 않으므로 직접 구현해야 합니다. 이로 인해 그래픽 파이프라인은 매우 유연해지지만, 동시에 가장 간단한 예제조차 실행하기 위해 사용자가 그래픽 파이프라인을 직접 구현해야 하므로 난이도가 높아집니다. 또한 사용자가 애플리케이션에 필요한 파이프라인 유형을 직접 결정할 수 있으므로 그래픽 파이프라인의 효율성도 높아집니다.

먼저 버텍스 셰이더를 구현해야 합니다. 버텍스 셰이더는 매개변수로 버텍스 데이터와 모델-뷰-투영 행렬(MVP)을 받습니다. MVP 행렬을 사용하여 버텍스 위치를 스크린 공간으로 변환하고, 텍스처 좌표를 프래그먼트 셰이더로 전달합니다. 텍스처 좌표는 폴리곤 면에서 자동으로 보간됩니다.

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

그 다음으로 그래픽 파이프라인의 두 번째 부분인 프래그먼트 셰이더를 구현해야 합니다. 이 연습에서는 텍스처링을 처리하는 프래그먼트 셰이더를 구현해야 합니다. 이 셰이더는 보간된 텍스처 좌표를 매개변수로 받아 주어진 텍스처에서 프래그먼트 색상을 조회합니다.

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

QOpenGLShaderProgram 를 사용하면 셰이더 코드를 컴파일하고, 링크하며, 그래픽 파이프라인에 바인딩할 수 있습니다. 이 코드는 Qt 리소스 파일을 사용하여 셰이더 소스 코드에 접근합니다.

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

다음 코드는 깊이 버퍼링과 뒷면 제거를 활성화합니다.

    // Enable depth buffer
    glEnable(GL_DEPTH_TEST);

    // Enable back face culling
    glEnable(GL_CULL_FACE);

Qt 리소스 파일에서 텍스처 불러오기

QOpenGLWidget 인터페이스는 QImage 에서 OpenGL 텍스처 메모리로 텍스처를 불러오는 메서드를 구현합니다. OpenGL 텍스처 유닛을 지정하고 텍스처 필터링 옵션을 구성하기 위해서는 여전히 OpenGL에서 제공하는 함수를 사용해야 합니다.

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

큐브 지오메트리

OpenGL에서 다각형을 렌더링하는 방법은 여러 가지가 있지만, 가장 효율적인 방법은 삼각형 스트립 프리미티브만을 사용하고 그래픽 하드웨어 메모리에서 정점을 렌더링하는 것입니다. OpenGL에는 이 메모리 영역에 버퍼 객체를 생성하고 정점 데이터를 이러한 버퍼로 전송하는 메커니즘이 있습니다. OpenGL 용어에서는 이를 정점 버퍼 객체(VBO)라고 합니다.

정육면체의 면과 꼭짓점

다음은 큐브의 면이 삼각형으로 분할되는 방식입니다. 삼각형 스트립을 사용하여 정점 순서를 올바르게 맞추기 위해 정점들이 이러한 순서로 정렬됩니다. OpenGL은 정점 순서를 기반으로 삼각형의 앞면과 뒷면을 결정합니다. 기본적으로 OpenGL은 앞면을 시계 반대 방향 순서로 처리합니다. 이 정보는 뒷면 켈링(back face culling)에 사용되며, 삼각형의 뒷면을 렌더링하지 않음으로써 렌더링 성능을 향상시킵니다. 이를 통해 그래픽 파이프라인은 화면을 향하지 않는 삼각형의 면을 렌더링에서 생략할 수 있습니다.

QOpenGLBuffer 를 사용하면 정점 버퍼 객체를 생성하고 데이터를 전송하는 것이 매우 간단합니다. MainWidget은 GeometryEngine 인스턴스가 현재 OpenGL 컨텍스트 상태에서 생성 및 소멸되도록 보장합니다. 이를 통해 생성자에서 OpenGL 리소스를 사용하고 소멸자에서 적절한 정리 작업을 수행할 수 있습니다.

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

VBO에서 프리미티브를 그리기 위해 프로그래밍 가능한 그래픽 파이프라인에 정점 데이터의 위치를 알려주려면 몇 가지 단계가 필요합니다. 먼저 사용할 VBO를 바인딩해야 합니다. 그 다음 셰이더 프로그램 속성 이름을 바인딩하고, 바인딩된 VBO에 어떤 종류의 데이터가 포함되어 있는지 구성합니다. 마지막으로 다른 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);
}

원근 투영

QMatrix4x4 의 헬퍼 메서드를 사용하면 투시 투영 행렬을 매우 쉽게 계산할 수 있습니다. 이 행렬은 정점을 화면 공간으로 투영하는 데 사용됩니다.

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

3D 객체의 방향

쿼터니언은 3D 오브젝트의 방향을 표현하는 편리한 방법입니다. 쿼터니언은 꽤 복잡한 수학을 포함하지만, 다행히 QQuaternion 에는 쿼터니언의 기반이 되는 모든 필수 수학이 구현되어 있습니다. 이를 통해 큐브의 방향을 쿼터니언으로 저장할 수 있으며, 주어진 축을 중심으로 큐브를 회전시키는 것도 매우 간단합니다.

다음 코드는 마우스 이벤트를 기반으로 회전축과 각속도를 계산합니다.

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 이 코드는 씬을 애니메이션화하고 큐브의 방향을 업데이트하는 데 사용됩니다. 회전은 쿼터니언을 곱하기만 하면 간단히 연쇄적으로 적용할 수 있습니다.

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

모델-뷰 행렬은 쿼터니언을 사용하고 Z축을 따라 월드 좌표계를 이동시켜 계산됩니다. 이 행렬에 투영 행렬을 곱하여 셰이더 프로그램용 MVP 행렬을 얻습니다.

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

예제 프로젝트 @ 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.