Qt移動應用開發(八):實現跨平臺的QML和OpenGL混合渲染css
上一篇文章講到了利用C++這個橋樑,咱們實現了QML和Java的交互。Qt 5大力推崇的QML/JS開發,讓輕量、高速開發的QML/JS打頭陣,讓重量的C++撐腰,差點兒什麼技術均可以實現。接下來的這篇文章講的是咱們使用QML。藉助Qt庫和OpenGL。實現了使用着色器定義OpenGL的渲染方式,爲你們呈現混合渲染的效果。android
原創文章,反對未聲明的引用。緩存
原博客地址:http://blog.csdn.net/gamesdev/article/details/38024327app
本文難度偏大。適合有經驗的Qt開發同行學習交流。ide
演示程序下載地址:這裏函數
源碼下載地址:這裏oop
演示程序的截圖例如如下(Android):學習
首先咱們來看簡單的QML代碼。本例很是easy。僅僅有一個界面。沒有不論什麼界面的跳轉。咱們在前面顯示一個矩形,上面寫了」您好世界!ui
」的文字。後面顯示的是一個旋轉的矩形。依照規定。先顯示的內容在最底層顯示。因而咱們將Cube放在前面,Rectangle放在了後面。this
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平臺上也能夠順利地執行。