警告
此部分包含从C++自动翻译到Python的片段,可能包含错误。
场景图 - 自定义材质#
展示如何在Qt Quick场景图中实现自定义材质。
自定义材质示例展示了如何实现一个使用自定义顶点和片段着色器渲染的项目。
着色器和材质#
主要功能在片段着色器中
<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 } }
为了使这个示例更有趣,我们添加了一个动画以改变缩放级别和迭代限制。中心保持不变。