天天看点

QT5.14.2自带Examples:OpenGL Window

概述

本示例需要入门级OpenGL基础,推荐教程:https://ke.qq.com/course/package/25480?flowToken=1021922

QT5.14.2自带Examples:OpenGL Window

本示例展示了如何创建一个支持OpenGL,并且基于QWindow的简单应用程序。

注意:这是一个如何将QWindow与OpenGL结合使用的底层示例。实际开发中,更多使用更高级别的QOpenGLWindow类。

实现步骤

首先我们需要一个支持OpenGL的窗口类:

OpenGLWindow 类

类定义

#include <QtGui/QWindow>
#include <QtGui/QOpenGLFunctions>

QT_BEGIN_NAMESPACE
class QPainter;
class QOpenGLContext;
class QOpenGLPaintDevice;
QT_END_NAMESPACE


//protected继承,导致在子类里降级。
//也就是QOpenGLFunctions里能直接调用的public函数在OpenGLWindow里成了protect
//QOpenGLFunctions:OpenGL ES 2.0API,
class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLWindow(QWindow *parent = 0);
    ~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;

    QOpenGLContext *m_context;
    //QOpenGLPaintDevice使用当前的QOpenGL上下文来呈现QPainter的画图命令。
    QOpenGLPaintDevice *m_device;
};
           

类实现

#include "openglwindow.h"

#include <QtCore/QCoreApplication>

#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QPainter>


OpenGLWindow::OpenGLWindow(QWindow *parent)
    : QWindow(parent)
    , m_animating(false)
    , m_context(0)
    , m_device(0)
{
    //指示窗口将用于OpenGL渲染
    setSurfaceType(QWindow::OpenGLSurface);
}

OpenGLWindow::~OpenGLWindow()
{
    delete m_device;
}

void OpenGLWindow::render(QPainter *painter)
{
    Q_UNUSED(painter);
}
//在第一次调用render()之前调用一次,使用当前有效的QOpenGLContext
void OpenGLWindow::initialize()
{
}
//初始化QOpenGLPaintDevice,然后调用render(qpanter*)
//函数里的代码在 本例中没有用到,子类将会重载这部分代码。
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());
    //devicePixelRatio返回窗口的物理像素与设备无关像素之间的比率。
    //普通显示器上的常见值为1.0,苹果“视网膜”显示器上的常见值为2.0。
    m_device->setDevicePixelRatio(devicePixelRatio());

    QPainter painter(m_device);
    render(&painter);
}
//发出一个更新请求事件,在系统准备好重新绘制时执行
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();
}

void OpenGLWindow::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;
    //第一次渲染
    if (!m_context) {
        m_context = new QOpenGLContext(this);
        //返回本窗口需要的surface format
        m_context->setFormat(requestedFormat());
        //使用当前的设置,生成一个上下文
        m_context->create();
        //因为是第一次所以需要初始化
        needsInitialize = true;
    }
    //在给定的表面上(this),使上下文成为当前线程中的当前上下文。
   //也就是将当前上下文与当前的窗口绑定
    m_context->makeCurrent(this);

    if (needsInitialize) {
        //初始化当前上下文的OpenGL函数解析。
        initializeOpenGLFunctions();
        initialize();
    }
    //渲染引擎的准备工作都做好了,可以开始渲染了
    //这里使用了模板方法设计模式
    render();
    //交换前后缓冲,把绘制好的显示出来
    m_context->swapBuffers(this);
    //如果已使用OpenGLWindow::setAnimating(true)启用动画
    //则调用renderLater()调度另一个更新请求。
    if (m_animating)
        renderLater();
}

void OpenGLWindow::setAnimating(bool animating)
{
    m_animating = animating;

    if (animating)
        renderLater();
}
           

基于上面的基类,创建一个子类,用于完成本示例的效果:

TriangleWindow 类

#include "openglwindow.h"

#include <QtGui/QGuiApplication>
#include <QtGui/QMatrix4x4>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QScreen>

#include <QtCore/qmath.h>


class TriangleWindow : public OpenGLWindow
{
public:
    TriangleWindow();

    void initialize() override;
    void render() override;

private:
    //位置、验收属性
    GLuint m_posAttr;
    GLuint m_colAttr;
    //需要给shader的矩阵
    GLuint m_matrixUniform;

    QOpenGLShaderProgram *m_program;
    int m_frame;
};

TriangleWindow::TriangleWindow()
    : m_program(0)
    , m_frame(0)
{
}
//shader是运行在GPU上的小程序,
//顶点着色器:将顶点数据引入GPU的渲染管线
//highp是精度限制,可以忽略
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";

void TriangleWindow::initialize()
{
    //编译着色器小程序
    m_program = new QOpenGLShaderProgram(this);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->link();
    //找到顶点着色器需要的参数的“代理”
    m_posAttr = m_program->attributeLocation("posAttr");
    m_colAttr = m_program->attributeLocation("colAttr");
    m_matrixUniform = m_program->uniformLocation("matrix");
}

void TriangleWindow::render()
{
    const qreal retinaScale = devicePixelRatio();
    //设置视口范围
    glViewport(0, 0, width() * retinaScale, height() * retinaScale);
    //我们只需要使用颜色缓冲,所以只需要每个周期清空颜色缓冲
    glClear(GL_COLOR_BUFFER_BIT);
    //将此着色器程序绑定到活动的QOpenGLContext并使其成为当前着色器程序
    m_program->bind();

    QMatrix4x4 matrix;
    //透视投影(角度,比例,近,远)
    matrix.perspective(60.0f, 4.0f/3.0f, 0.1f, 100.0f);
    //相当于摄像机的位置在Z=2的位置,实际上是让所有的内容都后移了2个单位
    matrix.translate(0, 0, -2);
    //相对于Y轴旋转
    matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);

    m_program->setUniformValue(m_matrixUniform, matrix);

    GLfloat vertices[] = {
        0.0f, 0.707f,
        -0.5f, -0.5f,
        0.5f, -0.5f
    };

    GLfloat colors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };

    //设置缓冲的值
    glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(0);

    m_program->release();

    ++m_frame;
}
           

main函数

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