过程纹理示例#

演示如何从 Python 提供自定义纹理数据。

QtQuick3D Procedural Texture Example

在这个示例中,我们利用 QQuick3DTextureDataTexturetextureData 属性,从 Python 中动态生成纹理数据,而不是从静态资产中获取。

下载 此示例

# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine

from gradienttexture import GradientTexture  # noqa: F401

from pathlib import Path

import os
import sys

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    app.setOrganizationName("QtProject")
    app.setApplicationName("ProceduralTexture")

    engine = QQmlApplicationEngine()
    app_dir = Path(__file__).parent
    engine.addImportPath(os.fspath(app_dir))
    engine.loadFromModule("ProceduralTextureModule", "Main")

    if not engine.rootObjects():
        sys.exit(-1)

    ex = app.exec()
    del engine

    sys.exit(ex)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import Signal, Property, QSize
from PySide6.QtGui import QColor
from PySide6.QtQuick3D import QQuick3DTextureData
from PySide6.QtQml import QmlElement

QML_IMPORT_NAME = "ProceduralTextureModule"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class GradientTexture(QQuick3DTextureData):

    heightChanged = Signal(int)
    widthChanged = Signal(int)
    startColorChanged = Signal(QColor)
    endColorChanged = Signal(QColor)

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self._height = 256
        self._width = 256
        self._startcolor = QColor("#d4fc79")
        self._endcolor = QColor("#96e6a1")
        self.updateTexture()

    @Property(int, notify=heightChanged)
    def height(self):
        return self._height

    @height.setter
    def height(self, val):
        if self._height == val:
            return
        self._height = val
        self.updateTexture()
        self.heightChanged.emit(self._height)

    @Property(int, notify=widthChanged)
    def width(self):
        return self._width

    @width.setter
    def width(self, val):
        if self._width == val:
            return
        self._width = val
        self.updateTexture()
        self.widthChanged.emit(self._width)

    @Property(QColor, notify=startColorChanged)
    def startColor(self):
        return self._startcolor

    @startColor.setter
    def startColor(self, val):
        if self._startcolor == val:
            return
        self._startcolor = val
        self.updateTexture()
        self.startColorChanged.emit(self._startcolor)

    @Property(QColor, notify=endColorChanged)
    def endColor(self):
        return self._endcolor

    @endColor.setter
    def endColor(self, val):
        if self._endcolor == val:
            return
        self._endcolor = val
        self.updateTexture()
        self.endColorChanged.emit(self._endcolor)

    def updateTexture(self):
        self.setSize(QSize(self._width, self._height))
        self.setFormat(QQuick3DTextureData.RGBA8)
        self.setHasTransparency(False)
        self.setTextureData(self.generate_texture())

    def generate_texture(self):
        # Generate a horizontal gradient by interpolating between start and end colors.
        gradientScanline = [
            self.linear_interpolate(self._startcolor, self._endcolor, x / self._width)
            for x in range(self._width)
        ]
        # Convert the gradient colors to a flattened list of RGBA values.
        flattenedGradient = [
            component
            for color in gradientScanline
            for component in (color.red(), color.green(), color.blue(), 255)
        ]
        # Repeat the gradient vertically to form the texture.
        return bytearray(flattenedGradient * self._height)

    def linear_interpolate(self, color1, color2, value):
        output = QColor()

        output.setRedF(color1.redF() + (value * (color2.redF() - color1.redF())))
        output.setGreenF(color1.greenF() + (value * (color2.greenF() - color1.greenF())))
        output.setBlueF(color1.blueF() + (value * (color2.blueF() - color1.blueF())))

        return output
module ProceduralTextureModule
Main 1.0 Main.qml
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
import QtQuick.Controls
import QtQuick.Layouts

import ProceduralTextureModule

ApplicationWindow {
    id: window
    width: 480
    height: 320
    visible: true
    title: "Procedural Texture Example"

    QtObject {
        id: applicationState
        property int size: size256.checked ? 256 : 16
        property color startColor: "#00dbde"
        property color endColor: "#fc00ff"
        property int filterMode: size === 256 ? Texture.Linear : Texture.Nearest
        property Texture texture: pythonModeRadio.checked ? textureFromPython : textureFromQML

        function randomColor() : color {
            return Qt.rgba(Math.random(),
                           Math.random(),
                           Math.random(),
                           1.0);
        }
    }

    View3D {
        anchors.fill: parent

        DirectionalLight {
        }

        PerspectiveCamera {
            z: 300
        }

        Texture {
            id: textureFromPython

            minFilter: applicationState.filterMode
            magFilter: applicationState.filterMode
            textureData: gradientTexture

            GradientTexture {
                id: gradientTexture
                startColor: applicationState.startColor
                endColor: applicationState.endColor
                width: applicationState.size
                height: width
            }
        }

        Texture {
            id: textureFromQML
            minFilter: applicationState.filterMode
            magFilter: applicationState.filterMode
            textureData: gradientTextureDataQML

            ProceduralTextureData {
                id: gradientTextureDataQML

                property color startColor: applicationState.startColor
                property color endColor: applicationState.endColor
                width: applicationState.size
                height: width
                textureData: generateTextureData()

                function linearInterpolate(startColor : color, endColor : color, fraction : real) : color{
                    return Qt.rgba(
                                startColor.r + (endColor.r - startColor.r) * fraction,
                                startColor.g + (endColor.g - startColor.g) * fraction,
                                startColor.b + (endColor.b - startColor.b) * fraction,
                                startColor.a + (endColor.a - startColor.a) * fraction
                                );
                }

                function generateTextureData() {
                    let dataBuffer = new ArrayBuffer(width * height * 4)
                    let data = new Uint8Array(dataBuffer)

                    let gradientScanline = new Uint8Array(width * 4);

                    for (let x = 0; x < width; ++x) {
                        let color = linearInterpolate(startColor, endColor, x / width);
                        let offset = x * 4;
                        gradientScanline[offset + 0] = color.r * 255;
                        gradientScanline[offset + 1] = color.g * 255;
                        gradientScanline[offset + 2] = color.b * 255;
                        gradientScanline[offset + 3] = color.a * 255;
                    }

                    for (let y = 0; y < height; ++y) {
                        data.set(gradientScanline, y * width * 4);
                    }

                    return dataBuffer;
                }
            }
        }

        Model {
            source: "#Cube"

            materials: [
                PrincipledMaterial {
                    baseColorMap: applicationState.texture
                }
            ]

            PropertyAnimation on eulerRotation.y {
                from: 0
                to: 360
                duration: 5000
                loops: Animation.Infinite
                running: true
            }
        }
    }

    Pane {
        ColumnLayout {

            GroupBox {
                title: "Size:"

                ButtonGroup  {
                    id: sizeGroup
                }

                ColumnLayout {
                    RadioButton {
                        id: size256
                        text: "256x256"
                        checked: true
                        ButtonGroup.group: sizeGroup
                    }
                    RadioButton {
                        id: size512
                        text: "16x16"
                        checked: false
                        ButtonGroup.group: sizeGroup
                    }
                }
            }

            GroupBox {
                title: "Backend:"

                ButtonGroup {
                    id: backendGroup
                }

                ColumnLayout {
                    RadioButton {
                        id: pythonModeRadio
                        text: "Python"
                        checked: true
                        ButtonGroup.group: backendGroup
                    }
                    RadioButton {
                        id: qmlModeRadio
                        text: "QML"
                        checked: false
                        ButtonGroup.group: backendGroup
                    }
                }

            }

            Button {
                text: "Random Start Color"
                onClicked: applicationState.startColor = applicationState.randomColor();
            }
            Button {
                text: "Random End Color"
                onClicked: applicationState.endColor = applicationState.randomColor();
            }
        }
    }
}
<RCC>
    <qresource prefix="/qt/qml/ProceduralTextureModule">
        <file>qmldir</file>
        <file>Main.qml</file>
    </qresource>
</RCC>