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

好了大致就这样了~

时间: 2024-08-23 08:46:25

【Qt for Android】OpenGL ES 绘制彩色立方体的相关文章

Android OpenGL ES(八)绘制点Point ..

上一篇介绍了OpenGL ES能够绘制的几种基本几何图形:点,线,三角形.将分别介绍这几种基本几何图形的例子.为方便起见,暂时在同一平面上绘制这些几何图形,在后面介绍完OpenGL ES的坐标系统和坐标变换后,再介绍真正的3D图形绘制方法. 在Android OpenGL ES(六):创建实例应用OpenGLDemos程序框架 创建了示例应用的程序框架,并提供了一个“Hello World”示例. 为避免一些重复代码,这里定义一个所有示例代码的基类OpenGLESActivity,其定义如下:

初学Android OpenGL ES之使用纹理 八十三

在网上发现这些讲纹理的文章,非常不错 android 游戏导引(4. 简单纹理贴图) http://www.cnblogs.com/shengdoushi/archive/2011/01/13/1934181.html Android OpenGL es 纹理坐标设定与贴图规则 http://blog.csdn.net/cjkwin/article/details/6016224 Android OpenGL | ES给立方体进行纹理映射 http://www.ourunix.org/andro

Android OpenGL ES 开发教程 从入门到精通

From:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ES 简明开发教程一:概述 Android OpenGL ES 简明开发教程二:构造OpenGL ES View Android OpenGL ES 简明开发教程三:3D绘图基本概念 Android OpenGL ES 简明开发教程四:3D 坐标变换 Android OpenGL ES 简明开发教程五

[工作记录] Android OpenGL ES 2.0: square texture not supported on some device

npot texture: non-power-of-two texture.rectangle texture: non-square (height != wdith) 在测试Samsumg Galaxy S4的时候, 发现rectangle texture支持不好, 虽然创建成功, 但是绘制有问题. 不同的模块出现类似的情况: 纹理采样出的颜色是(0,0,0,1). 在排除了所有可能的runtime问题以后, 尝试性改了纹理格式无果, 又尝试性的改了下尺寸, 竟然好了. 我擦... 而Ga

Android OpenGL ES 画球体

最近因为兴趣所向,开始学习OpenGL绘图.本文以"画球体"为点,小结一下最近所学. > 初识OpenGL ES 接触OpenGL是从Android开始的.众所周知,Android View 是线程不安全的,于是只允许在主线程中对View进行操作.然而假如我们需要实现复杂的界面,特别是开发游戏,在主线程中画大量图像,会耗费比较长的时间,使得主线程没能及时响应用户输入,甚至出现ANR.于是Android提供了一个 SurfaceView类,通过双缓冲机制(两块画布?三块画布?),允

[转]Android OpenGL ES 开发教程 从入门到精通

本文转自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ES 简明开发教程一:概述 Android OpenGL ES 简明开发教程二:构造OpenGL ES View Android OpenGL ES 简明开发教程三:3D绘图基本概念 Android OpenGL ES 简明开发教程四:3D 坐标变换 Android OpenGL ES 简明开发教程五

Android OpenGL ES(十二):三维坐标系及坐标变换初步 .

OpenGL ES图形库最终的结果是在二维平面上显示3D物体(常称作模型Model)这是因为目前的打部分显示器还只能显示二维图形.但我们在构造3D模型时必须要有空间现象能力,所有对模型的描述还是使用三维坐标.也就是使用3D建模,而有OpenGL ES库来完成从3D模型到二维屏幕上的显示. 这个过程可以分成三个部分: 坐标变换,坐标变换通过使用变换矩阵来描述,因此学习3D绘图需要了解一些空间几何,矩阵运算的知识.三维坐标通常使用齐次坐标来定义.变换矩阵操作可以分为视角(Viewing),模型(Mo

Android OpenGL ES(七)基本几何图形定义 .

在前面Android OpenGL ES(六):创建实例应用OpenGLDemos程序框架 我们创建了示例程序的基本框架,并提供了一个“Hello World”示例,将屏幕显示为红色. 本例介绍OpenGL ES 3D图形库支持的几种基本几何图形,通常二维图形库可以绘制点,线,多边形,圆弧,路径等等.OpenGL ES 支持绘制的基本几何图形分为三类:点,线段,三角形.也就是说OpenGL ES 只能绘制这三种基本几何图形.任何复杂的2D或是3D图形都是通过这三种几何图形构造而成的. 比如下图复

Android OpenGL ES

1.Android OpenGL ES 简明开发教程3D 坐标变换: http://www.linuxidc.com/Linux/2011-10/45756p4.htm