Qt移動應用開發(八):實現跨平臺的QML和OpenGL混合渲染

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平臺上也能夠順利地執行。

相關文章
相關標籤/搜索