큐브 OpenGL ES 2.0 예제
사용자 입력으로 텍스처가 있는 3D 큐브를 수동으로 회전하는 방법을 보여줍니다.
Cube OpenGL ES 2.0 예제는 사용자 입력으로 텍스처가 있는 3D 큐브를 수동으로 회전하는 방법을 Qt와 함께 OpenGL ES 2.0을 사용하여 보여줍니다. 다각형 지오메트리를 효율적으로 처리하는 방법과 프로그래밍 가능한 그래픽 파이프라인을 위한 간단한 버텍스 및 프래그먼트 셰이더를 작성하는 방법을 보여줍니다. 또한 3D 객체 방향을 표현하기 위해 쿼터니언을 사용하는 방법도 보여줍니다.
이 예제는 OpenGL ES 2.0용으로 작성되었지만 이 예제는 충분히 간단하고 대부분의 경우 데스크톱 OpenGL API가 동일하기 때문에 데스크톱 OpenGL에서도 작동합니다. OpenGL 지원 없이도 컴파일되지만 OpenGL 지원이 필요하다는 레이블만 표시됩니다.
이 예제는 두 개의 클래스로 구성됩니다:
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").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); }
큐브 지오메트리
OpenGL에서 다각형을 렌더링하는 방법에는 여러 가지가 있지만 가장 효율적인 방법은 삼각형 스트립 프리미티브만 사용하고 그래픽 하드웨어 메모리에서 정점을 렌더링하는 것입니다. OpenGL에는 이 메모리 영역에 버퍼 객체를 생성하고 버텍스 데이터를 이 버퍼로 전송하는 메커니즘이 있습니다. OpenGL 용어로는 이를 버텍스 버퍼 오브젝트(VBO)라고 합니다.
이것이 큐브 면이 삼각형으로 분해되는 방식입니다. 정점은 이러한 방식으로 정렬되어 트라이앵글 스트립을 사용하여 올바른 정점 순서를 갖습니다. OpenGL은 정점 순서에 따라 삼각형의 앞면과 뒷면을 결정합니다. 기본적으로 OpenGL은 앞면에 시계 반대 방향 순서를 사용합니다. 이 정보는 삼각형의 뒷면을 렌더링하지 않음으로써 렌더링 성능을 향상시키는 뒷면 컬링에 사용됩니다. 이렇게 하면 그래픽 파이프라인에서 화면을 향하지 않는 트라이앵글의 면을 렌더링하지 않을 수 있습니다.
버텍스 버퍼 오브젝트를 생성하고 데이터를 전송하는 방법은 QOpenGLBuffer 을 사용하면 매우 간단합니다. MainWidget은 OpenGL 컨텍스트 현재를 사용하여 GeometryEngine 인스턴스가 생성 및 소멸되도록 합니다. 이렇게 하면 생성자에서 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);
© 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.