本示例需要入门级OpenGL基础,推荐教程:https://ke.qq.com/course/package/25480?flowToken=1021922
本示例展示了如何创建一个支持OpenGL,并且基于QWindow的简单应用程序。
注意:这是一个如何将QWindow与OpenGL结合使用的底层示例。实际开发中,更多使用更高级别的QOpenGLWindow类。
首先我们需要一个支持OpenGL的窗口类:
#include
#include
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
#include
#include
#include
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();
}
基于上面的基类,创建一个子类,用于完成本示例的效果:
#include "openglwindow.h"
#include
#include
#include
#include
#include
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;
}
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();
}