OpenGL 창 예제
이 예제는 OpenGL을 사용하기 위한 최소한의 QWindow 기반 애플리케이션을 만드는 방법을 보여줍니다.

참고: 이 예제는 OpenGL과 함께 QWindow 를 사용하는 방법에 대한 저수준 예제입니다. 실제로는 더 높은 수준의 QOpenGLWindow 클래스를 사용하는 것을 고려해야 합니다. QOpenGLWindow 편의 클래스의 사용 예시는 Hello GLES3 예제를 참조하십시오.
OpenGLWindow 슈퍼 클래스
이 OpenGLWindow 클래스는 API 역할을 하며, 실제 렌더링을 수행하기 위해 이 클래스를 상속받아 서브클래스를 생성합니다. 이 클래스에는 render() 호출을 요청하는 함수가 포함되어 있는데, renderNow()를 사용하면 즉시 호출되고, renderLater()를 사용하면 이벤트 루프가 현재 이벤트 배치를 모두 처리하는 대로 호출됩니다. OpenGLWindow 하위 클래스는 OpenGL 기반 렌더링을 위해 render()를 재구현하거나, QPainter 를 사용한 렌더링을 위해 render(QPainter *)를 재구현할 수 있습니다. 기본 OpenGL 드라이버에서 수직 동기화가 활성화되어 있다고 가정할 때, render()가 수직 재생 빈도로 호출되도록 하려면 OpenGLWindow::setAnimating(true)를 사용하십시오.
OpenGL 렌더링을 수행하는 클래스에서는 일반적으로 OpenGLWindow가 그러하듯이 QOpenGLFunctions 를 상속받아, 플랫폼에 구애받지 않고 OpenGL ES 2.0 함수에 접근할 수 있도록 해야 합니다. QOpenGLFunctions 을 상속하면 해당 클래스에 포함된 OpenGL 함수가 우선적으로 적용되므로, 애플리케이션이 OpenGL과 OpenGL ES 2.0 모두에서 작동하도록 하려는 경우 해당 함수들을 해결하는 데 신경 쓸 필요가 없습니다.
class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit OpenGLWindow(QWindow *parent = nullptr);
~OpenGLWindow();
virtual void render(QPainter *painter);
virtual void render();
virtual void initialize();
void setAnimating(bool animating);
public slots:
void renderLater();
void renderNow();
protected:
bool event(QEvent *event) override;
void exposeEvent(QExposeEvent *event) override;
private:
bool m_animating = false;
QOpenGLContext *m_context = nullptr;
QOpenGLPaintDevice *m_device = nullptr;
};창의 표면 유형은 QSurface::OpenGLSurface 로 설정되어야 하며, 이는 해당 창이 OpenGL 렌더링에 사용될 것이며, QBackingStore 를 사용하여 QPainter 로 래스터 콘텐츠를 렌더링하는 데 사용되지 않음을 나타냅니다.
OpenGLWindow::OpenGLWindow(QWindow *parent)
: QWindow(parent)
{
setSurfaceType(QWindow::OpenGLSurface);
}필요한 OpenGL 초기화는 render()가 처음 호출되기 전에 한 번 호출되는 initialize() 함수를 오버라이드하여 수행할 수 있으며, 이때 유효한 현재 QOpenGLContext 가 전달되어야 합니다. 다음 코드 스니펫에서 볼 수 있듯이, 기본 render(QPainter *) 및 initialize() 구현은 비어 있는 반면, 기본 render() 구현은 QOpenGLPaintDevice 를 초기화한 다음 render(QPainter *)를 호출합니다.
void OpenGLWindow::render(QPainter *painter)
{
Q_UNUSED(painter);
}
void OpenGLWindow::initialize()
{
}
void OpenGLWindow::render()
{
if (!m_device)
m_device = new QOpenGLPaintDevice;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_device->setSize(size() * devicePixelRatio());
m_device->setDevicePixelRatio(devicePixelRatio());
QPainter painter(m_device);
render(&painter);
}renderLater() 함수는 단순히 QWindow::requestUpdate()를 호출하여 시스템이 다시 그리기(repaint)할 준비가 되었을 때 업데이트가 실행되도록 예약합니다.
또한 expose 이벤트를 수신하면 renderNow()를 호출합니다. exposeEvent()는 창이 화면에서 노출(가시성) 상태가 변경되었음을 창에 알리는 알림입니다. expose 이벤트를 수신하면 QWindow::isExposed()를 쿼리하여 창이 현재 노출되어 있는지 여부를 확인할 수 있습니다. 창이 첫 번째 expose 이벤트를 수신하기 전에는 해당 창에 렌더링을 수행하거나 QOpenGLContext::swapBuffers()을 호출해서는 안 됩니다. 그 이전에는 창이 최종적으로 어떤 크기가 될지 알 수 없을 뿐만 아니라, 렌더링된 내용이 화면에 표시되지 않을 수도 있기 때문입니다.
void OpenGLWindow::renderLater()
{
requestUpdate();
}
bool OpenGLWindow::event(QEvent *event)
{
switch (event->type()) {
case QEvent::UpdateRequest:
renderNow();
return true;
default:
return QWindow::event(event);
}
}
void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
Q_UNUSED(event);
if (isExposed())
renderNow();
}renderNow()에서는 현재 노출 상태가 아닐 경우 반환하며, 이 경우 렌더링은 실제로 expose 이벤트를 수신할 때까지 지연됩니다. 아직 수행하지 않았다면, OpenGLWindow에 설정된 것과 동일한 QSurfaceFormat 를 사용하여 QOpenGLContext 를 생성하고, 서브클래스를 위해 initialize()를 호출하며, QOpenGLFunctions 슈퍼클래스가 올바른 QOpenGLContext 와 연관되도록 initializeOpenGLFunctions()를 호출합니다. 어느 경우든 QOpenGLContext::makeCurrent()를 호출하여 컨텍스트를 현재 컨텍스트로 설정하고, render()를 호출하여 실제 렌더링을 수행한 후, 마지막으로 OpenGLWindow를 매개변수로 전달하여 QOpenGLContext::swapBuffers()를 호출함으로써 렌더링된 내용이 표시되도록 스케줄링합니다.
QOpenGLContext::makeCurrent()를 호출하여 렌더링할 표면을 매개변수로 전달함으로써 OpenGL 컨텍스트를 사용한 프레임 렌더링이 시작되면, OpenGL 명령을 실행할 수 있습니다. 명령어는 시스템의 OpenGL 헤더도 포함하는 <qopengl.h>를 포함시켜 직접 실행하거나, 편의를 위해 상속받을 수도 있고 QOpenGLContext::functions()를 사용하여 액세스할 수도 있는 QOpenGLFunctions 를 통해 실행할 수 있습니다. QOpenGLFunctions 는 OpenGL ES 2.0 및 데스크톱 OpenGL 모두에서 아직 표준으로 채택되지 않은 모든 OpenGL ES 2.0 수준의 OpenGL 호출에 대한 액세스를 제공합니다. OpenGL 및 OpenGL ES API에 대한 자세한 내용은 공식 OpenGL 레지스트리 및 Khronos OpenGL ES API 레지스트리를 참조하십시오.
OpenGLWindow::setAnimating(true)를 통해 애니메이션이 활성화된 경우, renderLater()를 호출하여 또 다른 업데이트 요청을 예약합니다.
void OpenGLWindow::renderNow()
{
if (!isExposed())
return;
bool needsInitialize = false;
if (!m_context) {
m_context = new QOpenGLContext(this);
m_context->setFormat(requestedFormat());
m_context->create();
needsInitialize = true;
}
m_context->makeCurrent(this);
if (needsInitialize) {
initializeOpenGLFunctions();
initialize();
}
render();
m_context->swapBuffers(this);
if (m_animating)
renderLater();
}애니메이션을 활성화하면 다음 코드 스니펫에서 볼 수 있듯이 업데이트 요청도 예약됩니다.
void OpenGLWindow::setAnimating(bool animating)
{
m_animating = animating;
if (animating)
renderLater();
}OpenGL 렌더링 서브클래스 예제
여기서는 회전하는 삼각형을 렌더링하는 OpenGL 구현 방법을 보여주기 위해 OpenGLWindow를 상속받은 서브클래스를 만듭니다. QOpenGLFunctions 을 간접적으로 상속함으로써 모든 OpenGL ES 2.0 수준의 기능을 사용할 수 있게 됩니다.
class TriangleWindow : public OpenGLWindow
{
public:
using OpenGLWindow::OpenGLWindow;
void initialize() override;
void render() override;
private:
GLint m_matrixUniform = 0;
QOpenGLBuffer m_vbo;
QOpenGLShaderProgram *m_program = nullptr;
int m_frame = 0;
};main 함수에서 QGuiApplication 를 초기화하고 TriangleOpenGLWindow를 인스턴스화합니다. 멀티샘플 앤티앨리어싱을 위해 4개의 샘플을 지정하는 QSurfaceFormat 와 기본 지오메트리를 전달합니다. 애니메이션을 구현하기 위해 앞서 언급한 setAnimating() 함수를 true 인자로 호출합니다.
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QSurfaceFormat format;
format.setSamples(16);
TriangleWindow window;
window.setFormat(format);
window.resize(640, 480);
window.show();
window.setAnimating(true);
return app.exec();
}다음 코드 조각은 이 예제에서 사용된 OpenGL 셰이더 프로그램을 보여줍니다. 버텍스 셰이더와 프래그먼트 셰이더는 비교적 단순하며, 버텍스 변환과 보간된 버텍스 채색을 수행합니다.
static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n"
"attribute lowp vec4 colAttr;\n"
"varying lowp vec4 col;\n"
"uniform highp mat4 matrix;\n"
"void main() {\n"
" col = colAttr;\n"
" gl_Position = matrix * posAttr;\n"
"}\n";
static const char *fragmentShaderSource = "varying lowp vec4 col;\n"
"void main() {\n"
" gl_FragColor = col;\n"
"}\n";다음은 셰이더를 로드하고 셰이더 프로그램을 초기화하는 코드입니다. 원시 OpenGL 대신 QOpenGLShaderProgram 를 사용함으로써, 표준의 일부가 아닌 데스크톱 OpenGL에서 highp, mediump, lowp 한정자를 자동으로 제거하는 편의성을 얻을 수 있습니다. 매 프레임마다 위치를 조회할 필요가 없도록, 속성 및 유니폼 위치를 멤버 변수에 저장합니다.
void TriangleWindow::initialize()
{
static const GLfloat vertices_colors[] = { +0.0f, +0.707f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.500f, 0.0f, 1.0f, 0.0f,
+0.5f, -0.500f, 0.0f, 0.0f, 1.0f };
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(vertices_colors, sizeof(vertices_colors));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat),
reinterpret_cast<void *>(2 * sizeof(GLfloat)));
m_program = new QOpenGLShaderProgram(this);
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
m_program->bindAttributeLocation("posAttr", 0);
m_program->bindAttributeLocation("colAttr", 1);
m_program->link();
m_program->bind();
m_matrixUniform = m_program->uniformLocation("matrix");
Q_ASSERT(m_matrixUniform != -1);
}마지막으로, OpenGL을 사용하여 뷰포트를 설정하고, 배경을 지우며, 회전하는 삼각형을 렌더링하는 render() 함수를 소개합니다.
void TriangleWindow::render()
{
const qreal retinaScale = devicePixelRatio();
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
glClear(GL_COLOR_BUFFER_BIT);
m_program->bind();
QMatrix4x4 matrix;
matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
matrix.translate(0, 0, -2);
matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);
m_program->setUniformValue(m_matrixUniform, matrix);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
m_program->release();
++m_frame;
}© 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.