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.0Window
{id: rootwidth: Qt.platform.os === "android"? Screen.width: 320height: Qt.platform.os === "android"? Screen.height: 480visible: trueCube{id: cubeanchors.fill: parentParallelAnimation{running: trueNumberAnimation{target: cubeproperty: "rotateAngle"from: 0to: 360duration: 5000}Vector3dAnimation{target: cubeproperty: "axis"from: Qt.vector3d( 0, 1, 0 )to: Qt.vector3d( 1, 0, 0 )duration: 5000}loops: Animation.Infinite}}Rectangle{anchors.centerIn: parentwidth: textField.width * 1.2height: textField.height * 1.5radius: textField.height / 3color: "lightsteelblue"border.color: "white"border.width: 2Text{id: textFieldanchors.centerIn: parenttext: "您好世界!"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_OBJECTQ_PROPERTY( qreal rotateAngle READ RotateAngleWRITE setRotateAngle NOTIFY RotateAngleChanged )Q_PROPERTY( QVector3D axis READ AxisWRITE 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平臺上也可以順利地執行。