【Qt for Android】OpenGL ES 绘制彩色立方体
Qt 内置对OpenGL ES的支持,选用Qt进行OpenGL ES的开发是非常方便的,许多辅助类都已经具备。从Qt 5.0开始增加了一个QWindow类,该类既可以使用OpenGL绘制3D图形,也可以使用QPainter绘制2D传统的GDI+图形,5.0以前的QGLWidget不推荐再使用。在即将到来(官方时间是今年秋天)Qt 5.4会完全废弃QGLWidget,作为替代将会新增QOpenGLWidget和QOpenGLWindow类来方便OpenGL的编程。
好了废话不多说了,今天我会使用OpenGL ES绘制一个彩色立方体,先在桌面平台编译运行成功后,再针对Android平台编译一次即可,下面是在Android上的运行效果,基于同一个着色器绘制了四个相同的彩色立方体。
为了使用OpenGL,我从QWindow类和QOpenGLFunctions类派生出了一个OpenGLWindow类,该类声明如下:
class OpenGLWindow : public QWindow, protected QOpenGLFunctions { Q_OBJECT public: explicit OpenGLWindow(QWindow *parent = 0); ~OpenGLWindow(); virtual void initialize(); virtual void render(QPainter *painter); virtual void render(double currentTime = 0.0); // elapsed seconds from program started. protected: void exposeEvent(QExposeEvent *event); private: QOpenGLPaintDevice *m_device; QOpenGLContext *m_context; QTime startTime; GLuint m_program; // QOpenGLShaderProgram *m_shaderProgram; };
由于继承自QWindow因此可以使用QWindow提供的OpenGL环境,不需要EGL来控制本地窗口显示图形。同时由于继承自QOpenGLFunctions,所以在OpenGLWindow类的成员函数中可以直接使用 gl* 风格的原生的OpenGL API。在Qt中提供了很多封装好的OpenGL便捷类,如QOpenGLShaderProgram可以很方便的对着色器程序进行操作,但这样做可能对不熟悉Qt的人不友好,所以这里我不用Qt提供的便捷类,而直接使用原生的C风格的 OpenGL API进行操作,这完全是可以的(这也是我喜欢Qt的原因之一:提供自身类库的同时,允许你使用非Qt的类,并提供二者之间的转换,如Qt中的容器类QVector、QMap、QString可以和C++标准库中的相应容器相互转换)。甚至你可以混合使用Qt的OpenGL类和原生的OpenGL API。
下面看看几个关键的函数。首先是initialize()负责着色器的创建、编译、链接等操作,并设置背景色。代码如下,其中被注释的部分是使用Qt自带类库实现相同的功能。
void OpenGLWindow::initialize() { const char *vertexShaderSrc = "attribute vec4 a_position; \n" "uniform mat4 u_mvp; \n" "varying vec4 v_color; \n" "void main() \n" "{ \n" " v_color = a_position*0.7 + 0.5; \n" " gl_Position = u_mvp * a_position; \n" "} \n"; const char *fragmentShaderSrc = "varying vec4 v_color; \n" "void main() \n" "{ \n" " gl_FragColor = v_color; \n" "} \n"; GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSrc, NULL); glCompileShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSrc, NULL); glCompileShader(fragmentShader); m_program = glCreateProgram(); glAttachShader(m_program, vertexShader); glAttachShader(m_program, fragmentShader); glLinkProgram(m_program); // // add vertex shader(compiled internal) // m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); // // add fragment shader(compiled internal) // m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); // // link shaders to program // m_shaderProgram->link(); // set the background clear color. glClearColor(0.0f, 0.0f, 0.0f, 1.0f); }
再来看看QWindow的QExposeEvent事件,当QWindow需要重绘时会调用该事件的处理函数exposeEvent(),这里我对该事件的处理函数进行了重写。isExposed()用来判断当前窗口是否显示在屏幕上(onScreen or offScreen),只有显示在屏幕上时才重绘(虽然窗口需要重绘,但是由于没有显示在屏幕上,重绘了也没人看得见,所以加这个判断可以减少不必要的绘图操作)。首先创建OpenGL上下文,然后进行调用相应的初始化函数,这两步只在第一次被执行,以后不会再执行。接下来是该函数的核心部分,调用render()函数在后端缓冲区进行图形渲染,然后交换前端和后端缓冲区,让后端缓冲区图形显示到界面上。
void OpenGLWindow::exposeEvent(QExposeEvent *event) { Q_UNUSED(event) static bool needInit = true; // Returns true if this window is exposed in the windowing system. if (isExposed()) { if (m_context == nullptr) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); } m_context->makeCurrent(this); if (needInit) { initializeOpenGLFunctions(); this->initialize(); needInit = false; } // calculate elapsed seconds from program started. double duration = startTime.msecsTo(QTime::currentTime()) / 1000.0; render(duration); m_context->swapBuffers(this); } }
最后看看render()渲染函数,这个也是学习OpenGL的主要部分。
在render()函数的开始部分创建了一个QOpenGLPaintDevice实例,该示例用于绘制QPainter的绘图操作,这里可以忽略,删掉也可以。接下来就是定义立方体的顶点位置,以及顶点索引。创建2个顶点缓冲区对象(vertex buffer object),通过glBufferData()函数将立方体的顶点位置和顶点所以放到顶点缓冲区对象中,将顶点位置通过glVertexAttribPointer()传递给顶点着色器。计算model/view/projection,然后将结果通过glUniformMatrix4fv()传递给顶点着色器中的mvp,最后使用glDrawElement()绘制立方体。立方体每个点的颜色由其所在的位置决定,所以不同位置的顶点具有不同颜色。
void OpenGLWindow::render(double currentTime) { if (m_device == nullptr) m_device = new QOpenGLPaintDevice; m_device->setSize(this->size()); static GLfloat vCubeVertices[] = { -0.5f, 0.5f, 0.5f, // v0 -0.5f, -0.5f, 0.5f, // v1 0.5f, -0.5f, 0.5f, // v2 0.5f, 0.5f, 0.5f, // v3 0.5f, -0.5f, -0.5f, // v4 0.5f, 0.5f, -0.5f, // v5 -0.5f, 0.5f, -0.5f, // v6 -0.5f, -0.5f, -0.5f, // v7 }; static GLushort vCubeIndices[] = { 0, 1, 2, 0, 2, 3, // front face 5, 6, 7, 4, 5, 7, // back face 0, 1, 7, 0, 6, 7, // left face 2, 3, 4, 3, 4, 5, // right face 0, 3, 5, 0, 5, 6, // top face 1, 2, 4, 1, 4, 7 // bottom face }; glViewport(0, 0, width(), height()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(m_program); GLuint vbos[2]; glGenBuffers(2, vbos); glBindBuffer(GL_ARRAY_BUFFER, vbos[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vCubeVertices), vCubeVertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vCubeIndices), vCubeIndices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, vbos[0]); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindAttribLocation(m_program, 0, "a_position"); static GLfloat angle = 0.0; GLuint mvpLoc = glGetUniformLocation(m_program, "u_mvp"); QMatrix4x4 model, view, projection, mvp; model.rotate(angle + 5, QVector3D(1,0,0)); model.rotate(angle - 5, QVector3D(0,1,0)); model.scale(0.5, 0.5, 0.5); view.translate(0.5, 0.5, 0); angle += 10; mvp = projection * view * model; glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData()); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0); /* draw another cube in different place with the same shader */ view.translate(-1.0, 0, 0); mvp = projection * view * model; glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData()); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0); view.translate(0.0, -1.0, 0); mvp = projection * view * model; glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData()); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0); view.translate(1.0, 0, 0); mvp = projection * view * model; glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData()); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0); QPainter painter(m_device); render(&painter); glDeleteBuffers(2, vbos); }
好了大致就这样了~
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。