C

Qt Quick Ultralite 视图变换示例

展示如何在 Qt Quick Ultralite 应用程序中实现透视(2.5D)变换。

概览

perspective_transforms 示例使用 Matrix4x4 QML 变换类型实现透视变换和准 3D 效果。它通过音乐专辑封面流动组件来演示这一点。

示例的 UI 在顶部包含一组单选按钮,可以切换以下封面流动样式

  • 旋转木马
  • 圆形
  • 圆形 2D(使用正交变换)
  • 透视

封面流动组件在中间渲染。它根据所选的样式动态地显示专辑封面运动。

在最底部,一组滑动控件允许用户调整摄像头参数,如升降角度或倾斜角度。

此外,示例还具有“演示”模式,当用户交互超过5秒时启用。在此模式下,动画在可用的封面和视图类型之间切换。当用户开始与应用程序交互时,动画停止。

目标平台

项目结构

应用程序项目结构没有与透视变换相关的特殊功能,但它比最小更复杂。它包含以下子目录

  • controls - 包含示例中使用的自定义 UI 控件的实现,例如 CheckBox、Slider 或 RadioButton
  • imports - 在项目中使用 QML 模块的内容 - 主要用于全局常量(使用单例)
  • resources - 图形资产 - 大多数是专辑封面
CMake 项目文件

本示例的 CMakeLists.txtperspective_transforms 定义为主要可执行目标。

...
    qul_add_target(perspective_transforms
        QML_PROJECT
        mcu_perspective_transforms.qmlproject
        SELECTORS no_controls
        GENERATE_ENTRYPOINT
    )
elseif(NOT QUL_PLATFORM MATCHES "^stm32f769i" AND NOT QUL_PLATFORM MATCHES "^stm32f469i" AND NOT QUL_PLATFORM MATCHES "^mimxrt1170-evkb")
    qul_add_target(perspective_transforms
        QML_PROJECT
        mcu_perspective_transforms.qmlproject
        SELECTORS small_controls
        GENERATE_ENTRYPOINT
    )
...
QmlProject 文件

所有相关的 QML 文件都在 QmlProject 文件中指定。

...
        QmlFiles {
                files: [
                "CoverFlow.qml",
                "CoverFlowState.qml",
                "Cover.qml",
                "IdleTimer.qml",
                "perspective_transforms.qml"
                ]
        }

        QmlFiles {
                files: [
                "controls/CheckBox.qml",
                "controls/RadioButton.qml",
                "controls/Slider.qml"
                ]
        }
...

所有图像资源都已添加带有 ImageFiles.MCU.resourceOptimizeForRotation 属性启用。这将提高支持平台上转换性能。

        ImageFiles {
                files: [
                "resources/cover0.jpg",
                "resources/cover1.jpg",
                "resources/cover2.jpg",
                "resources/cover3.jpg",
                "resources/cover4.jpg",
                "resources/cover5.jpg",
                "resources/cover6.jpg",
                "resources/cover7.jpg",
                "resources/cover8.jpg",
                "resources/cover9.jpg",
                ]
                MCU.base: "resources"

                // Optimize all assets for transformations
                MCU.resourceOptimizeForRotation: true

此外,使用 constants.qmlproject 定义了一个包含全局常量的模块并添加到主项目文件中。

        ModuleFiles {
                files: [
        "imports/constants/constants.qmlproject",
        "configuration/configuration.qmlproject"
        ]
                MCU.qulModules: ["Controls"]
        }
应用程序 UI

perspective_transforms.qml 文件定义了应用程序的用户界面。

它布局了主要的 UI 组件,如单选按钮和滑块。这些都是定义在 controls 子目录中的自定义控件。

import "controls"

IdleTimer 在检测到用户不活动时负责动画 UI。这是为没有触摸屏的平台设计的。它公开了两个信号,用于独立切换封面流类型和当前专辑选择。

    IdleTimer {
        id: idleTimer
        property int coverDir: 1
        onSwitchCover: {
            ...
        }
        onSwitchFlowType: {
            ...
        }
    }

最相关的是封面流组件的实例化。这从创建一个持有 CoverFlow 组件当前状态的对象开始。

    CoverFlowState {
        id: currentState
        screenWidth: root.width
        screenHeight: root.height
    }
    ...

然后,CoverFlow 组件负责实际的渲染。

    CoverFlow {
        anchors.fill: parent
        currentState: currentState
    }
CoverFlowState

CoverFlowState.qml 文件包含所有影响 CoverFlow 渲染方式的参数。它配置了以下参数

  • 屏幕/画布大小
  • CoverFlow 大小和位置
  • 要渲染的封面数量和当前选中封面索引
  • 相机设置,如视场角或剪切距离、俯仰、倾斜等
  • 特定封面流类型的设置
  • 当前视图类型和与类型过渡相关的参数

形态比是一个用于在不同封面流类型之间进行动画过渡的属性。

    property real morphRatio: 1
    property int currentViewType: CoverFlowType.Carousel
    property int previousViewType: CoverFlowType.Carousel

    NumberAnimation on morphRatio {
        id: morphAnimation
        from: 0.0
        to: 1.0
        duration: 200
    }

使用 switchViewType 在封面流类型之间切换。

    function switchViewType(newType : int){
         previousViewType = currentViewType
         currentViewType = newType
         morphAnimation.start()
    }
CoverFlow

CoverFlow.qml 文件实现了封面流组件。它负责创建多个 Cover 组件的实例,其中其实际的渲染逻辑得到实现。

该组件有一个属性,用来持有其当前状态。

Item {
    id: root
    property CoverFlowState currentState
    ...

一个 Repeater 负责根据封面流状态中定义的值动态创建一定数量的 Covers

    Repeater {
        model: root.currentState.numberOfCovers
        delegate: Cover {
            required property int index

            texture: "cover" + index + ".jpg"
            coverIndex: index
            state: root.currentState
            postMatrix: root.globalPostMatrix
            preMatrix: root.globalPreMatrix
            reflectionTransform: root.reflectionTransform
        }
    }
Cover

Cover.qml 实现了渲染封面(及其反射)所需的所有矩阵计算,使用正确的变换。Cover 控件是一个 Rectangle,其中带有 Image 项目(应用了适当的变换)作为其子项。一个图像代表实际的封面图像,而第二个用于渲染第一个图像的反射。两个图像项都使用 Matrix4x4 变换,其中 matrix 属性绑定到返回 matrix4x4 QML 基本类型的适当函数(请区分 Matrix4x4 变换对象和 matrix4x4 基本类型)。

Rectangle {
    id: cover

    property string texture
    property int coverIndex
    property CoverFlowState state

    property matrix4x4 coverTransform: calcCoverTransform()
    property matrix4x4 postMatrix
    property matrix4x4 preMatrix
    property matrix4x4 reflectionTransform
    z: calcFinalZ()

    Image {
        id: coverImageBase
        source: cover.texture
        transform: Matrix4x4 { matrix: coverTransform }
        opacity: 1.0
    }

    Image {
        id: mirrorImageBase
        visible: cover.state.showReflection
        source: cover.texture
        transform: [
            Matrix4x4 { matrix: reflectionTransform },
            Matrix4x4 { matrix: coverTransform }
        ]

        opacity: 0.1
    }
    ...

可以使用 Qt::matrix4x4() 工厂方法创建一个新的 matrix4x4,提供所有矩阵组件。以下是一个举翻译矩阵的例子

    function mtxTranslate(x : real, y : real, z : real) : matrix4x4 {
        return Qt.matrix4x4(1, 0, 0, x,
                            0, 1, 0, y,
                            0, 0, 1, z,
                            0, 0, 0, 1)
    }

matrix4x4 类型有以下算术运算可供使用

所有这些都在 calcCoverTransform() 函数中使用

    function calcCoverTransform() : matrix4x4 {
        var previousViewType = state.previousViewType
        var currentViewType = state.currentViewType
        if (state.morphRatio == 0) {
            currentViewType = previousViewType
        } else if (state.morphRatio == 1) {

矩阵4x4类型有十六个值,每个值可以通过属性m11m44(行/列顺序)访问。这在计算封面整体z位置时使用。

    function calcFinalZ() : real {
        var coverTransform = cover.coverTransform

        var x = state.coverSize/2
        var y = state.coverSize/2

        var inv_d = 1.0 / (coverTransform.m41 * x + coverTransform.m42 * y + coverTransform.m44)
        var fX = (coverTransform.m11 * x + coverTransform.m12 * y + coverTransform.m14) * inv_d
        var fZ = (coverTransform.m31 * x + coverTransform.m32 * y + coverTransform.m34) * inv_d

        var littleX = Math.abs(fX - state.coverFlowX - state.coverFlowW * 0.5)
        return -fZ * 100000 - littleX
    }

Cover.qml实现了在矩阵上操作的多项功能,可以用作参考。这些功能可以分为以下几类:

  • 基本矩阵生成函数(平移、缩放、旋转、透视)
  • 计算前转换和后转换矩阵(相机矩阵和初始封面转换)
  • 计算针对单个封面流类型的最终转换

所有这些基本构建块足以实现复杂的2.5D效果。

文件

图像

另请参阅Matrix4x4matrix4x4Qt::matrix4x4

在某些Qt许可证下提供。
了解更多信息。