Qt Quick Scene Graph 学习2:纹理

Qt Quick Scene Graph 相关文档:https://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html

参照示例 twotextureproviders

本文完整代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/LearnQSG_20210624_Texture

上一节学习了画线,通过设置顶点参数也可以绘制任意几何图元,这一节学习纹理的基本操作。

回顾下基本知识,一个几何节点 Node 由顶点 Geometry 和材质 Material 组成;QQuickItem 如果是可视类型需要设置 "setFlag(ItemHasContents, true);";Geometry、Material 及其他更新后单独设置 dirty 标志,这样在刷新时判断对应的 dirty 标志可以减少不必要的操作。

如果只是简单的展示一个纹理,如图片等,可以使用 QSGTextureMaterial,基本操作如下:

#pragma once
#include <QQuickItem>

//学习QSGTextureMaterial的基本使用
class MyTextureItem : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QQuickItem *source READ getSource WRITE setSource NOTIFY sourceChanged)
public:
    explicit MyTextureItem(QQuickItem * parent = nullptr);

    //设置提供纹理的item
    QQuickItem *getSource() const { return source; }
    void setSource(QQuickItem *item);

protected:
    //更新渲染节点时调用
    QSGNode *updatePaintNode(QSGNode *oldNode, 
QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;

signals:
    void sourceChanged();

private:
    //提供纹理的对象
    QQuickItem *source = nullptr;
    //变更source后需要重置texture
    bool sourceChange = false;
};
#include "MyTextureItem.h"
#include <QSGGeometryNode>
#include <QSGGeometry>
#include <QSGMaterial>
#include <QSGTextureProvider>
#include <QSGTextureMaterial>

MyTextureItem::MyTextureItem(QQuickItem *parent)
    : QQuickItem(parent)
{
    //要渲染的节点需要设置此标志
    setFlag(ItemHasContents, true);
}

void MyTextureItem::setSource(QQuickItem *item)
{
    if(source == item)
        return;
    source = item;
    //变更source后需要重置texture
    sourceChange = true;
    emit sourceChanged();
    update();
}

QSGNode *MyTextureItem::updatePaintNode(QSGNode *oldNode, 
QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
    //基本逻辑copy自Qt那几个scene graph的示例
    Q_UNUSED(updatePaintNodeData)

    bool abort = false;
    //未设置纹理源或者该Item不能创建纹理,就不显示
    if (!source || !source->isTextureProvider()) {
        qDebug() << "source is null or not a texture provider";
        abort = true;
    }
    if (abort) {
        delete oldNode;
        return nullptr;
    }

    //一个node包含geometry和material,类似于别的架构中的entity、mesh、material
    //node可以是要渲染的物体,也可以是透明度等
    //geometry定义了网格、顶点、结构等,比如坐标点
    //material定义了填充方式
    QSGGeometryNode *node = nullptr;
    QSGGeometry *geometry = nullptr;

    if (!oldNode) {
        //如果只是显示基本的线和纹理,用一个QSGGeometryNode即可
        node = new QSGGeometryNode;

        //使用纹理顶点,4个表示四个角
        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
        //绘制模式,设置为连续三角,这样只用四个点就可以表示一个矩形
        geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); //GL_TRIANGLE_STRIP
        //添加到node
        node->setGeometry(geometry);
        //此设置表示该节点拥有Geometry实例的所有权,并且会在该节点被销毁或重新设置时将其释放
        node->setFlag(QSGNode::OwnsGeometry);

        //创建纹理
        QSGTextureMaterial *material = new QSGTextureMaterial;
        //设置纹理数据,同一个item的纹理内容更新了会自动触发更新
        //但是更换item后需要重新设置texture
        material->setTexture(source->textureProvider()->texture());
        //添加到node
        node->setMaterial(material);
        //此设置表示该节点拥有Material实例的所有权,并且会在该节点被销毁或重新设置时将其释放
        node->setFlag(QSGNode::OwnsMaterial);
    } else {
        node = static_cast<QSGGeometryNode *>(oldNode);
        geometry = node->geometry();
        //如果item重置了就重新设置纹理
        if(sourceChange) {
            sourceChange = false;
            QSGTextureMaterial *material = static_cast<QSGTextureMaterial*>(node->material());
            material->setTexture(source->textureProvider()->texture());
        }
    }

    QSGGeometry::TexturedPoint2D *vertices = geometry->vertexDataAsTexturedPoint2D();
    //四个顶点,每个顶点为屏幕坐标xy+纹理坐标xy
    //opengl纹理坐标默认左下角为0点,但是QSG默认左上角为0点
    //(注意更新纹理的时候,width和height也要绑定更新)
    vertices[0].set(0,0,0,0);
    vertices[1].set(0,0+height(),0,1);
    vertices[2].set(0+width(),0,1,0);
    vertices[3].set(0+width(),0+height(),1,1);
    //效果同上
    //QSGGeometry::updateTexturedRectGeometry(geometry, boundingRect(), QRectF(0, 0, 1, 1));
    //标记为dirty后才会刷新节点对应内容
    node->markDirty(QSGNode::DirtyGeometry);
    node->markDirty(QSGNode::DirtyMaterial);
    return node;
}
                MyTextureItem{
                    id: texture
                    width: source.width
                    height: source.height
                    source: txt
                }

Qt 提供的基本类型只给了单个纹理设置的接口,如果需要多个纹理同时处理则可以参照示例:twotextureproviders

但是这个示例有点小问题,很多版本都有改进该示例(如 5.12、5.15、5.1 代码都有差异),所以代码有差异。由于老的示例使用了废弃的接口,而新的示例又使用了 Qt RHI 的接口,所以我选择了 5.15 的示例进行学习。

这里有个小插曲就是我觉得示例这个棋盘背景挺有意思的,设计巧妙,所以复制粘贴过来了。

    ShaderEffect {
        id: bg
        anchors.fill: parent

        property real tileSize: 16
        property color color1: Qt.rgba(0.9, 0.9, 0.9, 1);
        property color color2: Qt.rgba(0.8, 0.8, 0.8, 1);
        property size pixelSize: Qt.size(width / tileSize, height / tileSize);

        fragmentShader:
            "
        uniform lowp vec4 color1;
        uniform lowp vec4 color2;
        uniform highp vec2 pixelSize;
        varying highp vec2 qt_TexCoord0;
        void main() {
            highp vec2 tc = sign(sin(3.14159265358979323846 * qt_TexCoord0 * pixelSize));
            if (tc.x != tc.y)
                gl_FragColor = color1;
            else
                gl_FragColor = color2;
        }
        "
    }

下面进入正题。

首先是继承 QQuickItem 然后重写 updatePaintNode 接口,在接口内实例化了一个自定义的 Node 对象,而这个 Node 对象的功能就是将两个纹理进行异或操作,都有颜色的地方变为透明。

Node 设置了 setFlag(QSGNode::UsePreprocess, true),在每次刷新的时候会回调 preprocess 接口,我们就在该接口内更新纹理。Node 的顶点使用基本类型 QSGGeometry,注意顶点参数为 4 ,GL_TRIANGLE_STRIP 时表示两个连续的三角,一般组合为矩形,纹理就在这个矩形范围内渲染:

//4个顶点表示绘制一个矩形,使用GL_TRIANGLE_STRIP
m_node.setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
m_node.setFlag(QSGNode::OwnsGeometry);

//顶点值在改变rect后才设置
QSGGeometry::updateTexturedRectGeometry(m_node.geometry(), m_rect, QRectF(0, 0, 1, 1));

Node 的材质使用的自定义类型,以支持两个纹理的导入。这里面主要是给材质自定义了一个 Shader 类,在初始化时创建并绑定相应的 GL 属性:

void XorBlendShader::initialize()
{
    m_matrix_id = program()->uniformLocation("qt_Matrix");
    m_opacity_id  = program()->uniformLocation("qt_Opacity");
    //相当于绑定到了GL_TEXTURE0上,后面设置纹理数据直接操作GL_TEXTURE0
    program()->setUniformValue("uSource1", 0); // GL_TEXTURE0
    program()->setUniformValue("uSource2", 1); // GL_TEXTURE1
}

然后刷新的时候将纹理 bind 并进行渲染:

void XorBlendShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
    XorBlendMaterial *material = static_cast<XorBlendMaterial *>(newEffect);

    if (state.isMatrixDirty())
        program()->setUniformValue(m_matrix_id, state.combinedMatrix());

    if (state.isOpacityDirty())
        program()->setUniformValue(m_opacity_id, state.opacity());

    QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
    //先active再bind,步骤和使用Qt封装的OpenGL使用方式一样
    f->glActiveTexture(GL_TEXTURE1);
    material->state.texture2->bind();
    f->glActiveTexture(GL_TEXTURE0);
    material->state.texture1->bind();
}

如果你熟悉 Qt 封装的 OpenGL 类,会感觉很熟悉,使用方式基本差不多。知道了流程后,我们就可以扩展为任意个纹理的组合。

最后再学习下示例异或纹理显示的片段着色器代码,取出该纹理坐标像素的颜色后根据透明度来处理,带入计算得,p1 透明 p2 不透明则显示 p2,p1 p2 都不透明则透明度为 0 都不显示,如果 p1 p2 都透明则透明度也为 0 都不显示。

uniform lowp float qt_Opacity;
uniform lowp sampler2D uSource1;
uniform lowp sampler2D uSource2;

varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 p1 = texture2D(uSource1, vTexCoord);
    lowp vec4 p2 = texture2D(uSource2, vTexCoord);
    gl_FragColor = (p1 * (1.0 - p2.a) + p2 * (1.0 - p1.a)) * qt_Opacity;
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值