Qt新渲染底层Scene Graph研究(三)

Qt新渲染底层Scene Graph研究(三)

上一篇文章介绍了Qt Quick和SceneGraph的一些理论上的内容。这也是我最新的研究成果。接下来我要介绍一下如何使用Scene Graph来制作一些好玩的效果。这也是我进行一次SceneGraph的尝试。

我的目标是希望在Scene Graph这一套渲染框架下实现一个带有纹理的立方体,并且旋转。花了几天,虽然不是那么满意,但是已经告一段落了。

蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/43196787。欢迎同行前来探讨。

本文难度偏大,适合有经验的同行进行交流。

首先,对比C++,QML这边的代码稍微简单一些,那么从最简单开始说起吧。

import QtQuick 2.4
import QtQuick.Window 2.2
import TexturedCube 1.0

Window
{
    title: qsTr( "Scene Graph Textured Object" )
    width: isMobileDevice( )? Screen.width: 480
    height: isMobileDevice( )? Screen.height: 320
    visible: true

    Rectangle
    {
        anchors.fill: parent
        color: "orange"

        Cube
        {
            id: theCube
            anchors.centerIn: parent
            length: 60
            source: "../image/avatar.jpg"

            property int rotateAngle: 0
            transform:
                [
                Rotation
                {
                    angle: theCube.rotateAngle
                    axis
                    {
                        x: 1
                        y: 1
                    }
                }
            ]

            NumberAnimation on rotateAngle
            {
                from: 0
                to: 360
                duration: 1000
                loops: Animation.Infinite
            }

            Timer
            {
                interval: 3000
                repeat: true
                property int tCount: 0
                running: true
                onTriggered:
                {
                    var sourceList = [ "../image/soul.png",
                             "../image/avatar.jpg" ];
                    theCube.source = sourceList[tCount++ % 2];
                }
            }
        }
    }

    function isMobileDevice( )// 判断是否是移动平台
    {
        return  Qt.platform.os === "android" ||
                Qt.platform.os === "blackberry" ||
                Qt.platform.os === "ios" ||
                Qt.platform.os === "winphone";
    }
}

一个普通的窗口,背景是橙色的,在上面显示了我们的Cube。我希望我的Cube沿着一个轴进行旋转,所以设定了NumberAnimationon rotateAngle。此外,我希望每隔三秒Cube更换纹理,所以设定了一个Timer来更换纹理。每一个Item都有transform成员,它表示Item经过什么样的转换,目前transform支持Translation、Rotation以及Scale,有人想要让MouseArea成为不规则的,其实如果官方提供了Shear这个类,那么就更方便了。

我们看到的QML代码仅仅是表象,其实在幕后,是一个较为复杂的C++类:TexturedCube。下面我们再来看看TexturedCube.h的内容:


#ifndef TEXTUREDCUBE
#define TEXTUREDCUBE

#include <QUrl>
#include <QQuickItem>

#define DECLRARE_QUICKITEM_PROPERTY( aType, aProperty ) protected:    aType m_ ## aProperty; public:     aType aProperty( void ) { return m_ ## aProperty; }     void set ## aProperty( aType _ ## aProperty )     {        if ( m_ ## aProperty == _ ## aProperty ) return;         m_ ## aProperty = _ ## aProperty;         emit aProperty ## Changed( _ ## aProperty );         update( );     }

class TexturedCube: public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY( qreal length READ Length
                WRITE setLength NOTIFY LengthChanged )
    Q_PROPERTY( QUrl source READ Source
                WRITE setSource NOTIFY SourceChanged )
public:
    explicit TexturedCube( void );

    QSGNode* updatePaintNode( QSGNode* oldNode,
                              UpdatePaintNodeData* );
private slots:
    void checkTextureReconstruct( QUrl source );
signals:
    void LengthChanged( qreal length );
    void SourceChanged( QUrl source );
private:
    QString source2Path( QUrl source );

    QUrl        m_LastSource;
    bool        m_TextureNeedsReconstruct;
protected:
    DECLRARE_QUICKITEM_PROPERTY( qreal, Length )
    DECLRARE_QUICKITEM_PROPERTY( QUrl, Source )
};

#endif // TEXTUREDCUBE

在头文件我首先定义了一个方便的宏,包含了属性:Getter-Setter,以及Notifier。和QQuickItem的大多数属性一样,一旦某个属性发生了改变,那么就要发出改变的信号,也就是我们称的notifier,并且要告诉QQuickItem进行下一次更新。这里的update()并不是立即更新的意思,而是告诉QQuickItem在下个循环周期之前调用updatePaintNode()这个函数。接下来我们看看TexturedCube.cpp文件。

// TexturedCube.cpp
#include <QQmlFile>
#include <QSGGeometryNode>
#include <QSGGeometry>
#include <QSGOpaqueTextureMaterial>
#include <QQuickWindow>
#include "TexturedCube.h"

#define VERTEX_COUNT        36

TexturedCube::TexturedCube( void )
{
    m_Length = 10.0;
    m_Source = "";
    setFlag( ItemHasContents, true );
    connect( this, SIGNAL( SourceChanged( QUrl ) ),
             this, SLOT( checkTextureReconstruct( QUrl ) ) );
}

struct TexturedCubeVertex
{
    QVector3D               position;
    QVector2D               texCoord;
};

QSGNode *TexturedCube::updatePaintNode( QSGNode* oldNode,
                                        QQuickItem::UpdatePaintNodeData* )
{
    QSGGeometryNode* node = Q_NULLPTR;
    QSGGeometry* geometry = Q_NULLPTR;

    if ( oldNode == Q_NULLPTR )
    {
        // 创建几何体
        static QSGGeometry::Attribute attributes[] =
        {
            QSGGeometry::Attribute::create(
            0,                  // 数组元素的下标
            3,                  // 元的个数
            GL_FLOAT,           // 元的类型
            true ),             // 是不是顶点位置元素
            QSGGeometry::Attribute::create(
            1,                  // 数组元素的下标
            2,                  // 元的个数
            GL_FLOAT,           // 元的类型
            false )             // 是不是顶点位置元素
        };
        static QSGGeometry::AttributeSet attributeSet =
        {
            2,                              // 属性的个数
            sizeof( TexturedCubeVertex ),   // 个性化顶点结构的大小
            attributes                      // 属性的数组
        };
        geometry = new QSGGeometry(
                    attributeSet,       // 属性集
                    VERTEX_COUNT );     // 顶点个数
        geometry->setDrawingMode( GL_TRIANGLES );
        geometry->setVertexDataPattern( QSGGeometry::DynamicPattern );
        geometry->allocate( VERTEX_COUNT );

        // 创建材质
        QImage image( source2Path( m_Source ) );
        QSGTexture* texture = window( )->createTextureFromImage(
                    image.mirrored( ) );
        texture->setParent( window( ) );
        QSGOpaqueTextureMaterial* material = new QSGOpaqueTextureMaterial;
        material->setTexture( texture );

        // 创建节点
        node = new QSGGeometryNode;
        node->setGeometry( geometry );
        node->setMaterial( material );
        node->setFlag( QSGNode::OwnsGeometry );
        node->setFlag( QSGNode::OwnsOpaqueMaterial );
        node->markDirty( QSGNode::DirtyGeometry | QSGNode::DirtyMaterial );
    }
    else
    {
        node = static_cast<QSGGeometryNode*>( oldNode );
        geometry = node->geometry( );
        QSGNode::DirtyState dirtyState = QSGNode::DirtyGeometry;

        if ( m_TextureNeedsReconstruct )
        {
            QSGOpaqueTextureMaterial* material =
                    static_cast<QSGOpaqueTextureMaterial*>( node->material( ) );
            if ( material != Q_NULLPTR )
            {
                QSGTexture* texture = material->texture( );
                texture->setParent( Q_NULLPTR );
                delete texture;
                QImage image( source2Path( m_Source ) );
                texture = window( )->createTextureFromImage(
                                    image.mirrored( ) );
                texture->setParent( window( ) );
                material->setTexture( texture );
                dirtyState |= QSGNode::DirtyMaterial;
            }
        }

        node->markDirty( dirtyState );
    }

    TexturedCubeVertex* v = static_cast<TexturedCubeVertex*>( geometry->vertexData( ) );

    // 设置顶点坐标
    qreal semi = m_Length / 2.0;
    const QVector3D basicVertices[] =
    {
        QVector3D( semi, -semi, semi ),
        QVector3D( semi, -semi, -semi ),
        QVector3D( -semi, -semi, -semi ),
        QVector3D( -semi, -semi, semi ),
        QVector3D( semi, semi, semi ),
        QVector3D( semi, semi, -semi ),
        QVector3D( -semi, semi, -semi ),
        QVector3D( -semi, semi, semi )
    };

    // 前面
    v[0].position = basicVertices[6]; v[0].texCoord = QVector2D( 0.0, 0.0 );
    v[1].position = basicVertices[2]; v[1].texCoord = QVector2D( 0.0, 1.0 );
    v[2].position = basicVertices[5]; v[2].texCoord = QVector2D( 1.0, 0.0 );
    v[3].position = basicVertices[2]; v[3].texCoord = QVector2D( 0.0, 1.0 );
    v[4].position = basicVertices[1]; v[4].texCoord = QVector2D( 1.0, 1.0 );
    v[5].position = basicVertices[5]; v[5].texCoord = QVector2D( 1.0, 0.0 );

    // 后面
    v[6].position = basicVertices[4]; v[6].texCoord = QVector2D( 0.0, 0.0 );
    v[7].position = basicVertices[0]; v[7].texCoord = QVector2D( 0.0, 1.0 );
    v[8].position = basicVertices[7]; v[8].texCoord = QVector2D( 1.0, 0.0 );
    v[9].position = basicVertices[0]; v[9].texCoord = QVector2D( 0.0, 1.0 );
    v[10].position = basicVertices[3]; v[10].texCoord = QVector2D( 1.0, 1.0 );
    v[11].position = basicVertices[7]; v[11].texCoord = QVector2D( 1.0, 0.0 );

    // 上面
    v[12].position = basicVertices[2]; v[12].texCoord = QVector2D( 0.0, 0.0 );
    v[13].position = basicVertices[3]; v[13].texCoord = QVector2D( 0.0, 1.0 );
    v[14].position = basicVertices[1]; v[14].texCoord = QVector2D( 1.0, 0.0 );
    v[15].position = basicVertices[3]; v[15].texCoord = QVector2D( 0.0, 1.0 );
    v[16].position = basicVertices[0]; v[16].texCoord = QVector2D( 1.0, 1.0 );
    v[17].position = basicVertices[1]; v[17].texCoord = QVector2D( 1.0, 0.0 );

    // 下面
    v[18].position = basicVertices[7]; v[18].texCoord = QVector2D( 0.0, 0.0 );
    v[19].position = basicVertices[6]; v[19].texCoord = QVector2D( 0.0, 1.0 );
    v[20].position = basicVertices[4]; v[20].texCoord = QVector2D( 1.0, 0.0 );
    v[21].position = basicVertices[6]; v[21].texCoord = QVector2D( 0.0, 1.0 );
    v[22].position = basicVertices[5]; v[22].texCoord = QVector2D( 1.0, 1.0 );
    v[23].position = basicVertices[4]; v[23].texCoord = QVector2D( 1.0, 0.0 );

    // 左面
    v[24].position = basicVertices[7]; v[24].texCoord = QVector2D( 0.0, 0.0 );
    v[25].position = basicVertices[3]; v[25].texCoord = QVector2D( 0.0, 1.0 );
    v[26].position = basicVertices[6]; v[26].texCoord = QVector2D( 1.0, 0.0 );
    v[27].position = basicVertices[3]; v[27].texCoord = QVector2D( 0.0, 1.0 );
    v[28].position = basicVertices[2]; v[28].texCoord = QVector2D( 1.0, 1.0 );
    v[29].position = basicVertices[6]; v[29].texCoord = QVector2D( 1.0, 0.0 );

    // 右面
    v[30].position = basicVertices[5]; v[30].texCoord = QVector2D( 0.0, 0.0 );
    v[31].position = basicVertices[1]; v[31].texCoord = QVector2D( 0.0, 1.0 );
    v[32].position = basicVertices[4]; v[32].texCoord = QVector2D( 1.0, 0.0 );
    v[33].position = basicVertices[1]; v[33].texCoord = QVector2D( 0.0, 1.0 );
    v[34].position = basicVertices[0]; v[34].texCoord = QVector2D( 1.0, 1.0 );
    v[35].position = basicVertices[4]; v[35].texCoord = QVector2D( 1.0, 0.0 );

    const QRectF bounding = boundingRect( );
    const float factor = m_Length;
    for ( int i = 0; i < VERTEX_COUNT; ++i )// 调整位置
    {
        // 这里由于坐标系x向右增大,y向下增大,符合屏幕坐标系,那么根据OpenGL右手坐标系,
        // Z轴朝里面增大。
        float x = v[i].position.x( ) + bounding.width( ) / 2;
        float y = v[i].position.y( ) + bounding.height( ) / 2;
        float z = ( v[i].position.z( ) + semi ) / factor;
        v[i].position.setX( x );
        v[i].position.setY( y );
        v[i].position.setZ( z );
    }

    return node;
}

void TexturedCube::checkTextureReconstruct( QUrl source )
{
    m_TextureNeedsReconstruct = m_LastSource != source;
}

QString TexturedCube::source2Path( QUrl source )
{
    QUrl url( source );
    if ( url.isRelative( ) )
    {
        QUrl baseURL( "file:///" + qApp->applicationFilePath( ) );
        url = baseURL.resolved( url );
    }
    return QQmlFile::urlToLocalFileOrQrc( url );
}

经过实践,要渲染一个带六个纹理面的立方体,需要36个顶点。所以我设定了一个宏VERTEX_COUNT,值是36。此外,QtScene Graph默认的渲染是AoS(Array of Structure),这一点和Direct3D9一样的,所以我们需要定义自己的顶点格式。structTexturedCubeVertex里面包含了顶点位置以及纹理坐标(又称uv坐标)。在TexturedCube的构造函数,我们通过设定setFlag(ItemHasContents, true );来告诉Scene
Graph,此项目有内容,需要调用updatePaintNode()函数,然后当source改变的时候,为了调用相应的处理函数,需要连接处理器。这里的核心就是updatePaintNode()函数。这个函数的作用类似于一个中转站,火车从远处来,进站,如果有什么变化,比如说上下客,那么车内的人员也会变化。最后火车驶离车站,开往下一个目的地。此函数颇有这一个意思。第一次,我们发现oldNode指针为空,那么我们创建几何体、材质和纹理。否则oldNode指针不为空,我们认为这不是第一次渲染了,我们取出它的几何体和纹理。如果纹理路径发生了改变,那么我们也需要进行重新载入,通过source2Path()来解析路径。这里可能没有Qt源码那么复杂,因为Qt里面如果来自网络,即以http协议开始的,那么需要调用QQmlEngine里面的QNetworkAccessManager,此外QtQuick的Image类还有cache功能,我们这个例子没有那么复杂,能根据source的改变同步改变纹理内容就可以了。

由于Qt的SceneGraph的灵活性,我们需要使用许多类一起写作才能按照我们的要求创建几何体。首先我们要创建QSGGeometry::Attribute的一个数组,以便告诉SceneGraph和OpenGL,我们需要什么样的顶点缓存。注意这个数组绝对不能在栈上,因为要在updatePaintNode()函数以外用到这个数组,因此我标记为static类型。随后,我们需要QSGGeometry::AttributeSet结构的对象标识我们所需要的属性。

在设置上、下、左、右、前、后面的顶点位置坐标以及纹理坐标之后,需要对坐标进行一次适配。因为QQuickItem毕竟是一个矩形的控件,所以指定了width和height之后,boundingRect()就有相应的值了。这里需要说明的是z值。如上面几篇文章所述,要将坐标值限制在x∈[0,Screen.width],y∈[0,Screen.height],z∈[0,stackLayer](其中stackLayer为Item堆叠的层数),这样才能在视口中看见。最后是Qt的source2Path()方法,用来处理路径的。

main.cpp文件也比较简单,这里我只介绍主要部分:

……
int main( int argc, char** argv )
{
	QApplication app( argc, argv );
	……
	// 注册一些类
    qmlRegisterType<TexturedCube>( "TexturedCube", 1, 0, "Cube" );

	QQmlApplicationEngine engine;
	engine.load( QUrl( "qrc:///qml/main.qml" ) );
	……
	return app.exec( );
}

最后就是效果了。在Windows下的效果如下图所示:

大家可能觉得奇怪,本来不是设置的是立方体吗?怎么看起来像一个面片?其实如果设置QSG_VISUALIZE=overdraw的话,我们就明白了。

其实是有一个立方体显示的,只是因为视立方体的范围是x∈[0,Screen.width],y∈[0,Screen.height],z∈[0,stackLayer](其中stackLayer为Item堆叠的层数),在这种情况下,左右和上下共四个面被挤压得很小,所以我们几乎感觉不到它们的存在。

后记:也正是因为这样的原因,让我认识到,在Scene Graph下绘制出3D模型有些不合适。由于z和视口限制,所以让我们看3D对象都觉得是扁平一片。可能我半年前制作的示例更有实用价值吧。

时间: 2024-11-10 15:20:24

Qt新渲染底层Scene Graph研究(三)的相关文章

Qt新渲染底层Scene Graph研究(一)

Qt新渲染底层Scene Graph研究(一) Qt 5提出了一个新的渲染底层,以替代Qt4时期的Graphics View,这个渲染底层就是Scene Graph.其实这个底层的作用和Open Scene Graph是差不多的,但是由于是不同的团队进行开发的,所以两者没有必然的联系.Scene Graph主要利用OpenGL ( ES )2的渲染优势,在2D和3D以非常流畅的速度进行渲染,满足日益增长的界面效果需求,同时Scene Graph预留了各种各样的接口,满足大家定义显示和渲染效果的需

Qt新渲染底层Scene Graph研究(二)

上一篇文章初步介绍了Qt新渲染底层Scene Graph,我们该如何利用这个框架为应用程序增添绚丽的效果呢?首先,我们要明确利用Scene Graph开发的目的是什么.如果是简单的,纯粹的显示2D图形界面,那么直接利用构建在Scene Graph之上的Qt Quick和Qt Quick Widget即可.如果觉得Qt Quick为我们提供的功能不够,在QML这一层无法很好地实现,那么我们或许需要考虑更低一层的Scene Graph了.一个使用Scene Graph的常见需求就是实现3D模型的渲染

JAVA 虚拟机深入研究(三)——Java内存区域

JAVA 虚拟机深入研究(一)--关于Java的一些历史 JAVA 虚拟机深入研究(二)--JVM虚拟机发展以及一些Java的新东西 JAVA 虚拟机深入研究(三)--Java内存区域 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的围城,城外的人想进去,城里的人想出来. Java运行的时候会把内存分为若干个,他们各有各的用途,每块区域的创建和销毁都是相对独立的,有的跟虚拟机一起混,有的则抱着用户的大腿同生共死. 按照第七版的<Java虚拟机规范>规定,JVM所管理的内存包括以下

qt新进程工作目录的设置(工作目录确实是被子进程继承的,但也可以设置)

经过试验,qt启动一个新的进程时,这个进程的工作目录是继承父进程的,无论是通过start还是startDetached来启动. 其实对于linux系统,qt底层应该也是调用fork.exec之类的函数,对于fork,参看apue中文版第三版,有以下解析: 在f o r k之后处理文件描述符有两种常见的情况:(1) 父进程等待子进程完成.在这种情况下,父进程无需对其描述符做任何处理.当子进程终止后,它曾进行过读.写操作的任一共享描述符的文件位移量已做了相应更新.(2) 父.子进程各自执行不同的程序

RDIFramework.NETV2.9版本 Web新增至14套皮肤风格+三套界面组合(共42套皮肤组合)

RDIFramework.NETV2.9版本 Web新增至14套皮肤风格+三套界面组合(共42套皮肤组合) 客户的心声是最重要的,RDIFramework.NET V2.9版本不仅对WinForm版做了大的调整,Web版也彻彻底底的底翻上的优化了一篇,不仅增加了很多的新功能.新特色,用户最期望的界面风格也进行了海量增加.全新改变.这次算对得起观众了!下面我们就展示下Web版本中的皮肤界面风格吧-! RDIFramework.NET ━ .NET快速信息化系统开发框架钜献 V2.9 版本震撼发布

碧桂园营销院长朱晓波-新常态下拉动业绩的三驾马车-业务链、人才链、文化链标杆房企的顶层设计解码

新常态下拉动业绩的三驾马车-业务链.人才链.文化链标杆房企的顶层设计解码课程背景:2015年房地产榜单已经揭晓,万科以2600亿的成绩继续领跑:恒大以49%的增长成为最大黑马:TOP100房企的入榜门槛为104亿元,同比增长48%.恒大海花岛以日销售122亿的世界纪录成为年度神盘.一连串的数据显示白银时代不缺赚钱效应,新常态下依旧演绎强者恒强的逻辑.数字背后留下的是一连串的思考:标杆房企跨越式的业绩增长是如何形成的?支撑这种增长的背后动因是什么?成功的关键因素是什么?标杆房企的内在基因是什么?究

Java面试准备之JVM详细研究三(类加载机制)

类加载过程 一个类从编写完成后,编译为字节码之后,它要装载进内存有七个阶段: 加载 => (验证-> 准备-> 解析)=> 初始化=> 使用=> 卸载 括号中的三个步骤可以整合成为 “连接”步骤.其中的步骤并不是一个阶段结束,一个阶段才开始的.只是说他们的开始阶段基本遵循此顺序(解析阶段更是可能在使用的时候才发生,目的是配合动态绑定),这些阶段都是互相交叉的混合式进行的,通常会在一个阶段执行过程中调用或激活另一个阶段. 1.加载 ”加载“的过程是”类加载“过程的一个阶段

Unity5.1 新的网络引擎UNET(三) UNET NetworkManager

孙广东   2015.7.12 我们先来看看这第一个大类的 定义:http://docs.unity3d.com/ScriptReference/Networking.NetworkManager.html 直接继承自  MonoBehaviour,  还有就是被设计成了单例  singleton NetworkManager 网络管理器是一个方便的HLAPI 类,用于管理网络系统 . 对于简单的网络应用NetworkManager 网络管理器可以使用HLAPI控制 .它提供了简单的方法来  启

CEF3研究(三)

一.Off-Screen Rendering 脱屏绘制 CEF的脱屏渲染并不创建源生的浏览器窗口,而是CEF提供主应用程序在无效区域和像素buffer里渲染,然后主应用程序通过鼠标.键盘和焦点事件通知CEF.脱屏渲染现在不支持图层混合加速图层混合加速.脱屏渲染要窗口浏览器一样接受相同的通知,包括生命周期通知等,为了使用脱屏渲染: 实现CefRenderHandler接口,所有方法是必需的,除非另有指示. 调用CefWindowInfo::SetAsOffScreen()函数和将CefWindow