警告

此部分包含从C++自动翻译到Python的片段,可能包含错误。

场景图 - 自定义材质#

展示如何在Qt Quick场景图中实现自定义材质。

自定义材质示例展示了如何实现一个使用自定义顶点和片段着色器渲染的项目。

../_images/custom-material-example.jpg

着色器和材质#

主要功能在片段着色器中

<Code snippet "/data/qt5-full-670/6.7.0/Src/qtbase/scenegraph/custommaterial/shaders/mandelbrot.frag" not found>

片段和顶点着色器被组合成了一个QSGMaterialShader子类。

class CustomShader(QSGMaterialShader):

# public
    CustomShader()

        setShaderFileName(VertexStage, ":/scenegraph/custommaterial/shaders/mandelbrot.vert.qsb")
        setShaderFileName(FragmentStage, ":/scenegraph/custommaterial/shaders/mandelbrot.frag.qsb")

    bool updateUniformData(RenderState state,
                           QSGMaterial newMaterial, QSGMaterial oldMaterial) override

QSGMaterial子类封装了着色器以及渲染状态。在这个示例中,我们添加了对应着色器统一的州信息。材质负责通过重写createShader() 来创建着色器。

class CustomMaterial(QSGMaterial):

# public
    CustomMaterial()
    QSGMaterialType type() override
    int compare(QSGMaterial other) override
    QSGMaterialShader createShader(QSGRendererInterface.RenderMode) override

        return CustomShader()

    class():
        center[2] = float()
        zoom = float()
        limit = int()
        dirty = bool()
    } uniforms

为了更新统一数据,我们重实现updateUniformData()

def updateUniformData(self, RenderState state, QSGMaterial newMaterial, QSGMaterial oldMaterial):

    changed = False
    buf = state.uniformData()
    Q_ASSERT(buf.size() >= 84)
    if state.isMatrixDirty():
        m = state.combinedMatrix()
        memcpy(buf.data(), m.constData(), 64)
        changed = True

    if state.isOpacityDirty():
        opacity = state.opacity()
        memcpy(buf.data() + 64, opacity, 4)
        changed = True

    customMaterial = CustomMaterial(newMaterial)
    if oldMaterial != newMaterial or customMaterial.uniforms.dirty:
        memcpy(buf.data() + 68, customMaterial.uniforms.zoom, 4)
        memcpy(buf.data() + 72, customMaterial.uniforms.center, 8)
        memcpy(buf.data() + 80, customMaterial.uniforms.limit, 4)
        customMaterial.uniforms.dirty = False
        changed = True

    return changed

项目和节点#

我们创建一个自定义项目以展示我们的新材质

from PySide6.QtQuick import QQuickItem
class CustomItem(QQuickItem):

    Q_OBJECT
    Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
    Q_PROPERTY(int iterationLimit READ iterationLimit WRITE setIterationLimit NOTIFY iterationLimitChanged)
    Q_PROPERTY(QPointF center READ center WRITE setCenter NOTIFY centerChanged)
    QML_ELEMENT
# public
    CustomItem = explicit(QQuickItem parent = None)
    def zoom():

        return m_zoom

    def iterationLimit():

        return m_limit

    def center():

        return m_center

# public slots
    def setZoom(zoom):
    def setIterationLimit(iterationLimit):
    def setCenter(center):
# signals
    def zoomChanged(zoom):
    def iterationLimitChanged(iterationLimit):
    def centerChanged(center):
# protected
    QSGNode updatePaintNode(QSGNode , UpdatePaintNodeData ) override
    def geometryChange(newGeometry, oldGeometry):
# private
    m_geometryChanged = True
    m_zoom = qreal()
    m_zoomChanged = True
    m_limit = int()
    m_limitChanged = True
    m_center = QPointF()
    m_centerChanged = True

CustomItem声明添加了三个属性,对应我们想要暴露给QML的统一属性。

Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(int iterationLimit READ iterationLimit WRITE setIterationLimit NOTIFY iterationLimitChanged)
Q_PROPERTY(QPointF center READ center WRITE setCenter NOTIFY centerChanged)

与每个自定义Qt Quick项目一样,实现分为两部分:除了在GUI线程中存在的CustomItem外,我们还在渲染线程中创建了一个QSGNode子类。

class CustomNode(QSGGeometryNode):

# public
    CustomNode()

        m = CustomMaterial()
        setMaterial(m)
        setFlag(OwnsMaterial, True)
        g = QSGGeometry(QSGGeometry.defaultAttributes_TexturedPoint2D(), 4)
        QSGGeometry.updateTexturedRectGeometry(g, QRect(), QRect())
        setGeometry(g)
        setFlag(OwnsGeometry, True)

    def setRect(bounds):

        QSGGeometry.updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1))
        markDirty(QSGNode.DirtyGeometry)

    def setZoom(zoom):

        m = CustomMaterial(material())
        m.uniforms.zoom = zoom
        m.uniforms.dirty = True
        markDirty(DirtyMaterial)

    def setLimit(limit):

        m = CustomMaterial(material())
        m.uniforms.limit = limit
        m.uniforms.dirty = True
        markDirty(DirtyMaterial)

    def setCenter(center):

        m = CustomMaterial(material())
        m.uniforms.center[0] = center.x()
        m.uniforms.center[1] = center.y()
        m.uniforms.dirty = True
        markDirty(DirtyMaterial)

节点拥有材质的实例,并具有更新材质状态的逻辑。项目保持相应的QML属性。由于项目和材质位于不同的线程上,因此需要从材质复制信息。

def setZoom(self, zoom):

    if qFuzzyCompare(m_zoom, zoom):
        return
    m_zoom = zoom
    m_zoomChanged = True
    zoomChanged.emit(m_zoom)
    update()

def setIterationLimit(self, limit):

    if m_limit == limit:
        return
    m_limit = limit
    m_limitChanged = True
    iterationLimitChanged.emit(m_limit)
    update()

def setCenter(self, center):

    if m_center == center:
        return
    m_center = center
    m_centerChanged = True
    centerChanged.emit(m_center)
    update()

信息在updatePaintNode()的重写中从项目复制到场景图中。当调用该函数时,两个线程处于同步点,因此可以安全地访问这两个类。

QSGNode CustomItem.updatePaintNode(QSGNode old, UpdatePaintNodeData )

    node = CustomNode(old)
    if not node:
        node = CustomNode()
    if m_geometryChanged:
        node.setRect(boundingRect())
    m_geometryChanged = False
    if m_zoomChanged:
        node.setZoom(m_zoom)
    m_zoomChanged = False
    if m_limitChanged:
        node.setLimit(m_limit)
    m_limitChanged = False
    if m_centerChanged:
        node.setCenter(m_center)
    m_centerChanged = False
    return node

示例的其余部分#

该应用程序是一个简单的QML应用程序,包括一个QGuiApplication和一个我们将.qml文件传递给它的QQuickView。

在QML文件中,我们创建了一个自定义项目,并将其锚定在根处以填充。

CustomItem {
    property real t: 1
    anchors.fill: parent
    center: Qt.point(-0.748, 0.1);
    iterationLimit: 3 * (zoom + 30)
    zoom: t * t / 10
    NumberAnimation on t {
        from: 1
        to: 60
        duration: 30*1000;
        running: true
        loops: Animation.Infinite
    }
}

为了使这个示例更有趣,我们添加了一个动画以改变缩放级别和迭代限制。中心保持不变。

示例项目 @ code.qt.io