热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Qt新渲染底层SceneGraph研究(三)

Qt新渲染底层SceneGraph研究(三)上一篇文章介绍了QtQuick和SceneGraph的一些理论上的内容。这也是我最新的研究成果。接下来我要介绍一下如何使用SceneGr

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
#include

#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
#include
#include
#include
#include
#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( oldNode );
geometry = node->geometry( );
QSGNode::DirtyState dirtyState = QSGNode::DirtyGeometry;

if ( m_TextureNeedsReconstruct )
{
QSGOpaqueTextureMaterial* material =
static_cast( 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( 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 {
// 这里由于坐标系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", 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对象都觉得是扁平一片。可能我半年前制作的示例更有实用价值吧。


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
author-avatar
詹慧君874
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有