Qt移动应用开发(八):实现跨平台的QML和OpenGL混合渲染

上一篇文章讲到了利用C++这个桥梁,我们实现了QML和Java的交互。Qt 5大力推崇的QML/JS开发,让轻量、快速开发的QML/JS打头阵,让重量的C++撑腰,几乎什么技术都能够实现。接下来的这篇文章讲的是我们使用QML,借助Qt库和OpenGL,实现了使用着色器定义OpenGL的渲染方式,为大家呈现混合渲染的效果。

原创文章,反对未声明的引用。原博客地址:http://blog.csdn.net/gamesdev/article/details/38024327

本文难度偏大,适合有经验的Qt开发同行学习交流。

演示程序下载地址:这里

源代码下载地址:这里

演示程序的截图如下(Android):

首先我们来看简单的QML代码。本例很简单,只有一个界面,没有任何界面的跳转。我们在前面显示一个矩形,上面写了”您好世界!”的文字,后面显示的是一个旋转的矩形。按照规定,先显示的内容在最底层显示,于是我们将Cube放在前面,Rectangle放在了后面。

import QtQuick 2.2
import QtQuick.Window 2.2
import OpenGLCube 1.0

Window
{
    id: root
    width: Qt.platform.os === "android"? Screen.width: 320
    height: Qt.platform.os === "android"? Screen.height: 480
    visible: true

    Cube
    {
        id: cube
        anchors.fill: parent
        ParallelAnimation
        {
            running: true
            NumberAnimation
            {
                target: cube
                property: "rotateAngle"
                from: 0
                to: 360
                duration: 5000
            }

            Vector3dAnimation
            {
                target: cube
                property: "axis"
                from: Qt.vector3d( 0, 1, 0 )
                to: Qt.vector3d( 1, 0, 0 )
                duration: 5000
            }
            loops: Animation.Infinite
        }
    }

    Rectangle
    {
        anchors.centerIn: parent
        width: textField.width * 1.2
        height: textField.height * 1.5
        radius: textField.height / 3
        color: "lightsteelblue"
        border.color: "white"
        border.width: 2
        Text
        {
            id: textField
            anchors.centerIn: parent
            text: "您好世界!"
            font.pixelSize: root.width / 20
        }
    }
}

我们发现Cube类并不是Qt Quick自带的,而是我们自定义的一个QML模块OpenGLCube。按照第六篇文章上面的方法,我们通过在C++注册QML类实现了让QML访问C++代码。下面是主函数的实现:

#include <QApplication>
#include <QQmlApplicationEngine>
#include "Cube.h"

int main( int argc, char** argv )
{
    QApplication app( argc, argv );

    qmlRegisterType<Cube>( "OpenGLCube", 1, 0, "Cube" );

    QQmlApplicationEngine engine;
    engine.load( QUrl( QStringLiteral( "qrc:///main.qml" ) ) );

    return app.exec( );
}

主函数中通过qmlRegisterType函数向QML环境注册了一个QML类。接下来就是Cube类的定义和实现了。

Cube.h


#ifndef CUBE_H
#define CUBE_H

#include <QVector3D>
#include <QMatrix4x4>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QQuickItem>
#include <QQuickWindow>

#define DECLRARE_Q_PROPERTY( aType, aProperty ) protected:    aType m_ ## aProperty; public:    aType aProperty( void ) { return m_ ## aProperty; }     void set ## aProperty( aType _ ## aProperty )     {        m_ ## aProperty = _ ## aProperty;        if ( window( ) != Q_NULLPTR )        {            window( )->update( );        }    }

class Cube: public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY( qreal rotateAngle READ RotateAngle
                WRITE setRotateAngle NOTIFY RotateAngleChanged )
    Q_PROPERTY( QVector3D axis READ Axis
                WRITE setAxis NOTIFY AxisChanged )
public:
    explicit Cube( void );
signals:
    void RotateAngleChanged( void );
    void AxisChanged( void );
protected slots:
    void Render( void );
    void OnWindowChanged( QQuickWindow* pWindow );
    void Release( void );
protected:
    bool RunOnce( void );

    QMatrix4x4                  m_ModelViewMatrix;
    QMatrix4x4                  m_ProjectionMatrix;
    QOpenGLBuffer               m_VertexBuffer, m_IndexBuffer;
    QOpenGLBuffer               m_ColorBuffer;
    QOpenGLShaderProgram        m_ShaderProgram;

    DECLRARE_Q_PROPERTY( qreal, RotateAngle )
    DECLRARE_Q_PROPERTY( QVector3D, Axis )
};

#endif // CUBE_H

在Cube.h中,我们让Cube继承QQuickItem。因为Cube也是一个Qt Quick的显示对象。这里顺便说一下,C++的QQuickItem对应QML的Item类,而C++的QObject则是对应QML的QtObject类。在C++中,QQuickItem继承于QObject,在QML中,Item继承QtObject。在类的定义中,我使用了QOpenGLBuffer来保持各种绘图缓存(缓冲区),使用QOpenGLShaderProgram来方便地载入着色器数据。最后我使用了一个方便的宏来定义受QML属性系统控制的成员变量。当这些变量发生变化的时候,让其通知父窗口(QQuickWindow)进行更新。

Cube.cpp


// Cube.cpp
#include "Cube.h"

Cube::Cube( void ):
    m_VertexBuffer( QOpenGLBuffer::VertexBuffer ),
    m_IndexBuffer( QOpenGLBuffer::IndexBuffer ),
    m_ColorBuffer( QOpenGLBuffer::VertexBuffer ),
    m_RotateAngle( 0.0f ),
    m_Axis( 1.0f, 1.0f, 0.0f )
{
    // 初始化
    connect( this, SIGNAL( windowChanged( QQuickWindow* ) ),
             this, SLOT( OnWindowChanged( QQuickWindow* ) ) );
}

void Cube::OnWindowChanged( QQuickWindow* pWindow )
{
    if ( pWindow == Q_NULLPTR ) return;
    connect( pWindow, SIGNAL( beforeRendering( ) ),
             this, SLOT( Render( ) ), Qt::DirectConnection );
    pWindow->setClearBeforeRendering( false );
}

void Cube::Render( void )
{
    static bool runOnce = RunOnce( );
    Q_UNUSED( runOnce );

    // 运动
    m_ModelViewMatrix.setToIdentity( );
    m_ModelViewMatrix.translate( 0.0f, 0.0f, -60.0f );
    m_ModelViewMatrix.rotate( m_RotateAngle, m_Axis.x( ),
                              m_Axis.y( ), m_Axis.z( ) );

    // 渲染
    glViewport( 0, 0, window( )->width( ), window( )->height( ) );
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glEnable( GL_DEPTH_TEST );
    glEnable( GL_CULL_FACE );
    glFrontFace( GL_CW );

    m_ShaderProgram.bind( );
    m_VertexBuffer.bind( );
    int posLoc = m_ShaderProgram.attributeLocation( "position" );
    m_ShaderProgram.enableAttributeArray( posLoc );
    m_ShaderProgram.setAttributeBuffer( posLoc,                 // 位置
                                        GL_FLOAT,               // 类型
                                        0,                      // 偏移
                                        3,                      // 元大小
                                        0 );                    // 迈

    m_ColorBuffer.bind( );
    int colorLoc = m_ShaderProgram.attributeLocation( "color" );
    m_ShaderProgram.enableAttributeArray( colorLoc );
    m_ShaderProgram.setAttributeBuffer( colorLoc,               // 位置
                                        GL_FLOAT,               // 类型
                                        0,                      // 偏移
                                        4,                      // 元大小
                                        0 );                    // 迈
    m_IndexBuffer.bind( );
    m_ShaderProgram.setUniformValue( "modelViewMatrix", m_ModelViewMatrix );
    m_ShaderProgram.setUniformValue( "projectionMatrix", m_ProjectionMatrix );
    glDrawElements( GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, Q_NULLPTR );

    m_ShaderProgram.disableAttributeArray( posLoc );
    m_ShaderProgram.disableAttributeArray( colorLoc );
    m_IndexBuffer.release( );
    m_VertexBuffer.release( );
    m_ShaderProgram.release( );
}

bool Cube::RunOnce( void )
{
    // 初始化着色器
    m_ShaderProgram.addShaderFromSourceFile( QOpenGLShader::Vertex,
                                             ":/shader/Shader.vsh" );
    m_ShaderProgram.addShaderFromSourceFile( QOpenGLShader::Fragment,
                                             ":/shader/Shader.fsh" );
    m_ShaderProgram.link( );

    // 初始化顶点缓存
    const GLfloat length = 10.0f;
    const GLfloat vertices[] =
    {
        length, -length, length,
        length, -length, -length,
        -length, -length, -length,
        -length, -length, length,
        length, length, length,
        length, length, -length,
        -length, length, -length,
        -length, length, length
    };

    m_VertexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_VertexBuffer.create( );
    m_VertexBuffer.bind( );
    m_VertexBuffer.allocate( vertices, sizeof( vertices ) );

    // 初始化颜色的缓存
    const GLfloat colors[] =
    {
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f
    };
    m_ColorBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_ColorBuffer.create( );
    m_ColorBuffer.bind( );
    m_ColorBuffer.allocate( colors, sizeof( colors ) );

    // 初始化索引缓存
    GLubyte indices[] =
    {
        0, 1, 2, 0, 2, 3,// 下面
        7, 6, 4, 6, 5, 4,// 上面
        7, 4, 3, 4, 0, 3,// 左面
        5, 6, 1, 6, 2, 1,// 右面
        4, 5, 0, 5, 1, 0,// 前面
        3, 2, 6, 3, 6, 7,// 背面
    };

    m_IndexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_IndexBuffer.create( );
    m_IndexBuffer.bind( );
    m_IndexBuffer.allocate( indices, sizeof( indices ) );

    // 设定模型矩阵和投影矩阵
    float aspectRatio  = float( window( )->width( ) ) / float( window( )->height( ) );
    m_ProjectionMatrix.perspective( 45.0f,
                                    aspectRatio,
                                    0.5f,
                                    500.0f );

    connect( window( )->openglContext( ),
             SIGNAL( aboutToBeDestroyed( ) ),
             this, SLOT( Release( ) ),
             Qt::DirectConnection );

    return true;
}

void Cube::Release( void )
{
    qDebug( "Vertex buffer and index buffer are to be destroyed." );
    m_VertexBuffer.destroy( );
    m_IndexBuffer.destroy( );
    m_ColorBuffer.destroy( );
}

类的实现较复杂。大致分为构造阶段、初始化阶段、渲染阶段和释放空间阶段。这里我们使用了OpenGL ES 2.0常用的buffer + attribute array方式来进行高效渲染。有关上述OpenGL的知识,感兴趣的同行们可以看看《OpenGL ES 2.0 Programming Guide》、Qt书籍有关OpenGL的部分、KDAB博客中有关OpenGL的知识以及我的其它博客以获得相关知识。

上述程序载入了顶点着色器和片断着色器。它们如下所示:


// Shader.vsh
attribute highp vec3 position;
attribute highp vec4 color;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

varying highp vec4 v_Color;

void main( void )
{
    gl_Position = projectionMatrix *
            modelViewMatrix *
            vec4( position, 1.0 );
    v_Color = color;
}


// Shader.fsh
varying highp vec4 v_Color;

void main( void )
{
    gl_FragColor = v_Color;
}

本例在三大桌面平台上运行正常,同时在Android平台上也能够顺利地运行。

时间: 2024-10-09 22:40:00

Qt移动应用开发(八):实现跨平台的QML和OpenGL混合渲染的相关文章

Qt移动应用开发(六):QML与C++互动

Qt移动应用开发(六):QML与C++互动 上一篇文章讲到了在Qt Quick中实现场景切换的一种可能的方法,场景切换是诸如游戏等应用在内必需要面临的技术难点,所以场景切换并没有通行的方法,依据自己的使用习惯进行设计就可以. 本文主要介绍的是怎样使用QML和C++进行交互,难度略微偏大,适合有经验的Qt开发人员进行学习交流. Qt 5吸收了Qt 4的declarative模块的长处,对底层进行了更改,新建了QPA层,隔离了不同操作系统API和上层Qt代码.同一时候QML/QtQuick也能够顺利

Qt移动应用开发(七):QML与Java的交互

上一篇文章讲到了如何实现QML和C++的交互,QML和C++的交互方法有很多,它们分别为使用上下文变量.注册QML类以及注册QML单例.那么这一次我们要了解如何使QML和Java进行交互.这里主要讲的是在Android系统下的实现,不适用于桌面平台J2SE的JVM. 原创文章,反对未声明的引用.原博客地址:http://blog.csdn.net/gamesdev/article/details/37997555 Qt5中针对不同的平台适配并开发了不同的模块,比如说Qt Windows Extr

Qt移动应用开发(六):QML与C++的交互

Qt移动应用开发(六):QML与C++的交互 上一篇文章讲到了在Qt Quick中实现场景切换的一种可能的方法,场景切换是诸如游戏等应用在内必须要面临的技术难点,所以场景切换并没有通行的方法,根据自己的使用习惯进行设计即可. 本文主要介绍的是如何使用QML和C++进行交互,难度稍微偏大,适合有经验的Qt开发者进行学习交流. Qt 5吸收了Qt 4的declarative模块的优点,对底层进行了更改,新建了QPA层,隔离了不同操作系统API和上层Qt代码,同时QML/QtQuick也可以顺利在不同

QT开发(五十三)———QML基本元素

QT开发(五十三)---QML基本元素 一.基本可视化项 可视元素具有几何坐标,会在屏幕上占据一块显示区域. Item             基本的项元素,所有可视化项都继承Item Rectangle        基本的可视化矩形元素 Gradient         定义一个两种颜色的渐变过程 GradientStop     定义个颜色,被Gradient使用 Image         在场景中使用位图 BorderImage     (特殊的项) 定义一张图片并当做边界 Anima

[Android游戏开发]八款开源 Android 游戏引擎 (巨好的资源)

初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情. 事实上,这种想法完全是没有必要且不符合实际的,作为能和苹果iOS分庭抗礼的Android(各种意义上),当然也会有相当数量的游戏引擎存

【Qt编程】基于Qt的词典开发系列&lt;二&gt;--本地词典的设计

我设计的词典不仅可以实现在线查单词,而且一个重大特色就是具有丰富的本地词典库:我默认加入了八个类型的词典,如下所示: 由于是本人是通信专业,因此加入了华为通信词典.电子工程词典,又由于我喜爱编程,也加入了c语言基本函数词典.下面介绍如何设计本地词典: 词典类型的选择 当然是txt格式的最好了,因为我们可以用程序直接进行读取.可是网上词典一般都是用mdx格式.ld2格式的,我无法用Qt来直接读取.最终,经过不断摸索,网上查找,发现我们可以将mdx格式的词典通过软件转化为txt格式的! mdx词典的

Qt For Android 开发环境配置

想了想,还是再写一篇关于Qt for Android开发环境配置的教程. 准备:Java jdk,Android sdk,Android adb,Android ndk,Android ant,Qt 支持 安卓的版本,比如qt-opensource-windows-x86-android-5.6.2. 前言:在Qt的版本更新中,Qt支持的功能越来越多,安装包也越来越大,在Qt5.9出来之前,Qt的每个版本都对应的编译环境,而在Qt5.9后Qt官方将好多编译环境放到了一起,而在这里我要说的是要配置

Qt的IDE开发环境(KDevelop,MonKey Studio,QDevlop,Dev-cpp,Cobras,Edyuk)

讲到Qt的IDE开发环境,本人一直在Windows下使用VC6.0 + Qt4.3.1开发程序.但转到Linux下,使用Fedora中自带的KDevelop + Qt4.3.1开发程序. 最近一直做Qt程序,今天在网上看见讲述Qt的IDE开发环境的文章,介绍了几款Qt的IDE开发环境.在此加以总结: VC6.0.VS2005 —— Windows下知名开发环境,不多说了~ KDevelop —— Fedora下自带的集成开发环境,支持Qt4.据说是用Qt3开发出来的,不知道真伪~ MonKey

Qt for Android 开发大坑

Qt for Android 开发大坑 作者: qyvlik Qt 5.5.1 这里说一说比较常见的 Qt 开发安卓的大坑.希望同学们不要做无谓的挣扎,跳过这些坑. 输入框 首当其冲的是输入框,Qt 的输入在安卓上表现不佳. 无法支持安卓原生的输入法访问 Qt 的输入框,就是安卓输入法无法复制,粘贴,剪切 Qt 输入框中的文本. 无法支持使用触摸的方式选中 Qt 输入框中的文字. 如果输入框的位置处于应用底部,类似于 IM 那种聊天工具,应当注意. a. 如果应用 Activity 设置为 an