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代码。本例非常easy。仅仅有一个界面。没有不论什么界面的跳转。我们在前面显示一个矩形,上面写了”您好世界!

”的文字。后面显示的是一个旋转的矩形。依照规定。先显示的内容在最底层显示。于是我们将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平台上也可以顺利地执行。

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

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

    Qt移动应用开发(六):QML与C++互动 上一篇文章讲到了在Qt Quick中实现场景切换的一种可能的方法,场景切换是诸如游戏等应用在内必需要面临的技术难点,所以场景切换并没有通行的方法,依据自己的 ...

  2. 【Qt编程】基于Qt的词典开发系列&lt;八&gt;--用户登录及API调用的实现

    在上一篇文章<调用网络API>中,我仅仅讲述了怎样直观的使用API接口以及调用API后返回的结果,本文则从程序实现的角度来实现API的调用.当然本程序的实现也是借助于扇贝网的API接口文档 ...

  3. Qt for Android开发环境搭建及测试过程记录

    最近学习了Qt的QML编程技术,感觉相较于以前的QtGUI来说更方便一些,使用QML可以将界面与业务逻辑解耦,便于开发. QML支持跨平台,包括支持Android平台,因此可以使用Qt的QML进行An ...

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

    初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引 ...

  5. 【Qt编程】基于Qt的词典开发系列<二>--本地词典的设计

    我设计的词典不仅可以实现在线查单词,而且一个重大特色就是具有丰富的本地词典库:我默认加入了八个类型的词典,如下所示: 由于是本人是通信专业,因此加入了华为通信词典.电子工程词典,又由于我喜爱编程,也加 ...

  6. 采用QT技术,开发OFD电子文档阅读器

    前言 ofd作为板式文档规范,相当于国产化的pdf.由于pdf标准制定的较早,相关生态也比较完备,市面上的pdf阅读器种类繁多.国内ofd阅读器寥寥无几,作者此前采用wpf开发了一款阅读器,但该阅读器 ...

  7. 快速全面了解QT软件界面开发技术

    快速全面了解QT软件界面开发技术     目录 前言 一. 学习QT可能的目的是什么? 只想体验一下QT? 当前的项目选择了用QT. 为将来做QT技术储备. 二. QT的核心技术优势是什么? QT在软 ...

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

    讲到Qt的IDE开发环境,本人一直在Windows下使用VC6.0 + Qt4.3.1开发程序.但转到Linux下,使用Fedora中自带的KDevelop + Qt4.3.1开发程序. 最近一直做Q ...

  9. Qt For Android 开发环境配置

    想了想,还是再写一篇关于Qt for Android开发环境配置的教程. 准备:Java jdk,Android sdk,Android adb,Android ndk,Android ant,Qt ...

随机推荐

  1. udev和rules使用规则

    本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本文会使那 ...

  2. gtk+学习笔记(二)

    如何创建一个按钮呢,直接贴代码把,有详细的注释. #include<gtk/gtk.h> gint data_count=; void on_button_clicked (GtkWidg ...

  3. JavaWeb知识回顾-servlet生命周期。

    Servlet生命周期 生命周期,很容易理解,拿人来说,就是你从出生到离开的这一过程.无论是什么技术,只要谈到生命周期都可以这样理解. Servlet的生命周期就是从它被创建到毁灭的过程,整个过程可以 ...

  4. 回文词(UVa401)

    详细题目描述见:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_prob ...

  5. CentOS7.5 firefox Flash插件更新

    CentOS7自带的firefox没有flash插件,所以是没有办法在网页上看视频的,需要自己手动安装 1.下载 打开flash官网https://get.adobe.com/flashplayer/ ...

  6. web_reg_save_param_regexp函数的用法

    关联从服务器返回的所有的内容: 本例通过一个使用HTTP/HTML协议发送.获取服务器数据的vuser脚本,分析LoadRunner如何进行HTTP关联. 下面这个例子包括两个事务:上传数据到服务器. ...

  7. run-time setting 中设置simulate browser cache 选项详解

    Browser  Emulation: Simulate  browser  cache:配置Vuser模拟带缓存的浏览器.缺省缓存是被允许的, 可以通过禁止该选项来使得所有VUser模拟的浏览器都不 ...

  8. asp.net form 验证方式的使用(转载)

    如何运用 Form 表单认证 ASP.NET 的安全认证,共有“Windows”“Form”“Passport”“None”四种验证模式.“Windows”与“None”没有起到保护的作用,不推荐使用 ...

  9. Screen 常用命令+VNC 启动停止命令总结

    screen -S 名称:创建一个新的会话 screen -r 会话ID:恢复一个Detach状态的会话 screen -xr 会话ID:强制恢复一个Attach状态的会话,常用于掉线时上次的会话没有 ...

  10. 洛谷P1486 [NOI2004]郁闷的出纳员 [STL,平衡树]

    题目传送门 郁闷的出纳员 题目描述 OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反 ...