现代Qt开发教程(新手篇)5.15——QtQuick3D QML 3D 场景基础
1. 前言:在 QML 里写 3D,真的就这么简单
上一篇我们用纯 C++ 的 Qt 3D 搭建了一个 3D 场景——创建 Entity、挂载 Component、配置 Mesh/Transform/Material、手动设置相机和光源,代码量不算少。如果你的项目本来就是 QML 前端,用 C++ API 写 3D 就显得有些割裂了——2D 界面用 QML 声明式写,3D 部分突然切到 C++ 命令式写法,来回跳转很痛苦。
QtQuick3D 就是解决这个问题的。它是 Qt 官方主推的 3D 方案,在 QML 中直接声明 3D 场景——View3D 创建 3D 视口,Model 定义几何体,PerspectiveCamera 配置相机,DirectionalLight 添加光源,PrincipledMaterial 配置 PBR 材质,全部都是 QML 声明式语法。对比上一篇 Qt 3D 的 C++ 代码,同样的场景用 QtQuick3D 写,代码量能减少一半以上,而且和 Qt Quick 2D 元素无缝混合——一个 QML 界面里同时放 2D 按钮和 3D 模型,位置、大小、动画都用同一套 QML 绑定系统管理。
这篇我们要做的是用 QML 搭建一个包含基础几何体的 3D 场景,配置相机和光照,使用 PrincipledMaterial 实现 PBR 物理材质效果,最后在 3D 场景上叠加 2D 控制面板,演示 QtQuick3D 和 Qt Quick 2D 的混合布局能力。
2. 环境说明
本篇基于 Qt 6.9.1,需要引入 QtQuick3D 和 Quick 模块。CMake 配置如下:
find_package(Qt6 REQUIRED COMPONENTS Quick3D Quick)QtQuick3D 是 Qt 6 中 3D 功能的主力模块,在 Qt Installer 中默认随标准安装包提供。它的底层使用 Qt Rendering Hardware Interface (RHI) 进行渲染——这意味着同一个 QML 3D 代码可以在 OpenGL、Vulkan、Direct3D、Metal 后端上运行,不需要针对不同图形 API 写不同的代码。
QtQuick3D 支持加载外部 3D 模型文件(.mesh、.gltf、.fbx 等),也内置了几种基础几何体(球体、立方体、圆柱、圆锥、平面)可以直接在 QML 中声明使用。本篇只用内置几何体,加载外部模型是进阶话题。
工具链和前面一致:MSVC 2019+、GCC 11+、Clang 14+,C++17 标准,CMake 3.26+ 构建系统。QtQuick3D 对 GPU 有一定要求——集成显卡通常够用,但老旧显卡可能不支持某些 PBR 材质特性。
3. 核心概念讲解
3.1 View3D——QML 中的 3D 视口
View3D 是 QtQuick3D 的核心容器组件,在 QML 界面中创建一个 3D 渲染区域。它本质上是一个 Qt Quick Item,可以像 Rectangle、Image 一样放在任何 QML 布局中:
import QtQuick
import QtQuick3D
Window {
width: 800
height: 600
visible: true
View3D {
anchors.fill: parent
// 场景内容(相机、光源、模型)都放在 View3D 内部
PerspectiveCamera {
position: Qt.vector3d(0, 5, 15)
eulerRotation: Qt.vector3d(-15, 0, 0)
}
DirectionalLight {
eulerRotation: Qt.vector3d(-45, 45, 0)
}
Model {
source: "#Sphere"
materials: PrincipledMaterial {
baseColor: "#4080ff"
}
}
}
}View3D 内部的坐标系是标准的 3D 右手坐标系:X 轴朝右,Y 轴朝上,Z 轴朝屏幕外(朝观察者)。所有 3D 对象的 position、rotation、scale 属性都在这个坐标系中定义。
View3D 作为 Qt Quick Item,可以设置 anchors、width、height 等属性控制其在 2D 界面中的位置和大小。这意味着你可以轻松实现"左边 2D 控制面板、右边 3D 视图"或者"3D 视图上方叠加 2D 按钮"这样的布局——不需要任何窗口容器包装,直接用 QML 的布局系统就行。
环境贴图方面,View3D 默认提供一个深灰色背景。如果想要更丰富的环境效果(天空盒、环境光照贴图),可以使用 SceneEnvironment 组件配置:
environment: SceneEnvironment {
clearColor: "#1a1a2e"
backgroundMode: SceneEnvironment.Color
}3.2 PerspectiveCamera——透视相机
PerspectiveCamera 定义了观察 3D 场景的视角。它的核心属性是 position(相机位置)和 eulerRotation(欧拉角旋转,控制朝向),此外还有 clipNear、clipFar(裁剪范围)和 fieldOfView(视场角):
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 8, 20) // 相机位于高处偏后
eulerRotation: Qt.vector3d(-20, 0, 0) // 稍微俯视
fieldOfView: 45 // 视场角 45 度
clipNear: 0.1 // 近裁剪面
clipFar: 1000 // 远裁剪面
}eulerRotation 使用欧拉角表示旋转,三个分量分别是绕 X、Y、Z 轴的旋转角度(度数)。把 X 旋转设为负值会让相机朝下看(俯视),设为正值朝上看(仰视)。这种表示方式比 lookAt 直觉上不够直观,但更灵活——你可以独立控制俯仰角和偏航角。
如果你想要和上一篇 QOrbitCameraController 类似的鼠标旋转/缩放体验,QtQuick3D 提供了 WasdController 组件(键盘 WASD + 鼠标控制):
WasdController {
controlledObject: camera
speed: 0.1
}不过 WasdController 的行为更像第一人称游戏控制(WASD 移动 + 鼠标转向),不是轨道旋转。如果你需要轨道控制,需要自己用 MouseArea + 属性绑定实现,或者等 Qt 后续提供更丰富的控制器。
3.3 Model 节点——几何体和模型加载
Model 是 QtQuick3D 中表示 3D 物体的组件。source 属性指定几何体来源——加载外部模型文件用文件路径,使用内置几何体用 "#" 前缀的预定义名称。
QtQuick3D 内置了几种基础几何体,用 source 属性的魔法字符串引用:
// 球体
Model {
source: "#Sphere"
scale: Qt.vector3d(1.5, 1.5, 1.5)
position: Qt.vector3d(-4, 1.5, 0)
}
// 立方体
Model {
source: "#Cube"
scale: Qt.vector3d(2, 2, 2)
position: Qt.vector3d(3, 1, 0)
}
// 平面(XZ 平面,适合做地面)
Model {
source: "#Rectangle"
scale: Qt.vector3d(20, 20, 1)
eulerRotation: Qt.vector3d(-90, 0, 0)
position: Qt.vector3d(0, 0, 0)
}内置几何体都是单位尺寸(半径/边长为 1),通过 scale 属性缩放到需要的大小。position 控制世界坐标位置。eulerRotation 控制旋转角度。
加载外部 3D 模型文件时,source 直接设为文件路径:
Model {
source: "models/character.mesh"
position: Qt.vector3d(0, 0, 0)
}QtQuick3D 支持的模型格式包括 Qt 自有的 .mesh 格式(需要用 Qt 的 balsam 工具从 .fbx/.gltf 等格式转换)、以及直接加载 .gltf/.glb 文件。不过模型加载涉及到材质映射、骨骼动画、纹理路径等问题,是进阶话题,本篇只用内置几何体。
3.4 PrincipledMaterial——PBR 物理材质
QtQuick3D 推荐使用 PrincipledMaterial——一种基于物理的渲染(PBR)材质。它比 Phong 模型更真实,通过少量参数就能模拟金属、塑料、玻璃、布料等各种材质的外观。
PrincipledMaterial 的核心属性是 baseColor(基础颜色)和 metalness(金属度)、roughness(粗糙度):
// 蓝色塑料球
materials: PrincipledMaterial {
baseColor: "#4080ff"
metalness: 0.0 // 非金属
roughness: 0.5 // 半粗糙
}
// 金属质感的红色立方体
materials: PrincipledMaterial {
baseColor: "#cc3333"
metalness: 0.9 // 高金属度
roughness: 0.2 // 光滑金属表面
}baseColor 是物体在白光下的颜色。metalness 范围 0 到 1——0 表示非金属(电介质),1 表示纯金属。金属表面会反射环境色,而非金属表面主要显示自身的 baseColor。roughness 范围 0 到 1——0 表示完全光滑(镜面反射),1 表示完全粗糙(漫反射)。
对比上一篇的 QPhongMaterial(ambient + diffuse + specular + shininess),PrincipledMaterial 用 metalness + roughness 两个参数替代了 specular + shininess 的组合,参数更少但效果更真实。PBR 的物理基础确保了同一组参数在不同光照条件下看起来都自然——Phong 模型在某些角度和光照组合下会出现不真实的高光或暗部,PBR 基本不会。
如果你想添加环境反射效果(让金属物体反射周围环境),可以配合 SceneEnvironment 的 lightProbe 属性加载 HDR 环境贴图。不过没有环境贴图的情况下 PrincipledMaterial 也能正常工作,只是反射效果会弱一些。
3.5 与 Qt Quick 2D 混合——3D 场景上的 2D 叠加
QtQuick3D 最大的优势之一是和 Qt Quick 2D 元素无缝混合。View3D 本身就是一个标准的 Qt Quick Item,你可以用 Z 轴叠加、anchors 锚点、Row/Column 布局等常规 QML 布局手段把 2D 元素放在 3D 视图上面、下面、旁边:
Window {
View3D {
id: view3d
anchors.fill: parent
// 3D 场景内容...
// 在 3D 场景内部叠加 2D 元素
Text {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20
text: "QtQuick3D 场景"
font.pixelSize: 24
color: "white"
style: Text.Outline
styleColor: "black"
}
}
// 2D 控制面板浮在 3D 视图上
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
width: 200
height: 150
color: "#80000000" // 半透明黑色背景
radius: 10
Column {
anchors.fill: parent
anchors.margins: 10
spacing: 8
Button {
text: "切换颜色"
onClicked: {
// 修改 3D 模型的材质属性
sphereMaterial.baseColor = "#ff4040"
}
}
}
}
}这种混合方式在实现 3D 产品展示页面、游戏 HUD、数据可视化面板等场景时非常实用——不需要额外的窗口管理,2D 和 3D 用同一套 QML 语法,属性绑定和信号/槽机制完全互通。
2D 元素叠在 View3D 上方时,鼠标事件会被 2D 元素拦截——如果你需要 3D 场景接收鼠标事件,确保叠加的 2D 元素设置了 enabled: false 或者只占屏幕的部分区域。
4. 综合示例:QtQuick3D 基础场景 + 2D 控制面板
把前面的知识串起来,我们用 QML 搭建一个包含球体和立方体的 3D 场景,右侧叠加一个 2D 控制面板,通过按钮切换球体和立方体的材质颜色。
因为 QtQuick3D 项目主要是 QML 代码,C++ 的 main.cpp 只需要很少的启动代码——创建 QQmlApplicationEngine 加载 QML 文件即可。CMake 用 qt_add_qml_module 注册 QML 模块。
CMake 配置:
find_package(Qt6 REQUIRED COMPONENTS Quick Quick3D)
qt_add_executable(${PROJECT_NAME} main.cpp)
qt_add_qml_module(${PROJECT_NAME}
URI Quick3DScene
VERSION 1.0
QML_FILES Main.qml
)
target_link_libraries(${PROJECT_NAME}
PRIVATE Qt6::Quick Qt6::Quick3D)QML 场景文件 Main.qml 的核心结构。场景包含 PerspectiveCamera、两盏 DirectionalLight(主光 + 补光)、一个蓝色球体、一个橙色立方体、一个灰色地面平面,以及右上角浮动的 2D 控制面板:
import QtQuick
import QtQuick.Controls
import QtQuick3D
Window {
width: 1000
height: 700
visible: true
title: "QtQuick3D 基础场景"
View3D {
id: view3d
anchors.fill: parent
environment: SceneEnvironment {
clearColor: "#1a1a2e"
backgroundMode: SceneEnvironment.Color
}
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 10, 25)
eulerRotation: Qt.vector3d(-20, 0, 0)
fieldOfView: 45
}
DirectionalLight {
eulerRotation: Qt.vector3d(-60, 45, 0)
brightness: 1.0
}
DirectionalLight {
eulerRotation: Qt.vector3d(-30, -45, 0)
brightness: 0.3
}
// 球体
Model {
id: sphereModel
source: "#Sphere"
position: Qt.vector3d(-4, 1.5, 0)
scale: Qt.vector3d(1.5, 1.5, 1.5)
materials: PrincipledMaterial {
id: sphereMaterial
baseColor: "#4080ff"
metalness: 0.1
roughness: 0.4
}
}
// 立方体
Model {
id: cubeModel
source: "#Cube"
position: Qt.vector3d(4, 1, 0)
scale: Qt.vector3d(2, 2, 2)
materials: PrincipledMaterial {
id: cubeMaterial
baseColor: "#e68a30"
metalness: 0.5
roughness: 0.3
}
}
// 地面
Model {
source: "#Rectangle"
position: Qt.vector3d(0, 0, 0)
scale: Qt.vector3d(30, 30, 1)
eulerRotation: Qt.vector3d(-90, 0, 0)
materials: PrincipledMaterial {
baseColor: "#808080"
roughness: 0.8
}
}
}
// 2D 控制面板
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 15
width: 220
height: 200
color: "#c01a1a2e"
radius: 12
border.color: "#ffffff40"
border.width: 1
Column {
anchors.fill: parent
anchors.margins: 15
spacing: 12
Label {
text: "控制面板"
font.bold: true
font.pixelSize: 16
color: "white"
}
Button {
text: "球体变红"
onClicked: sphereMaterial.baseColor = "#ff4444"
}
Button {
text: "立方体变绿"
onClicked: cubeMaterial.baseColor = "#44cc44"
}
Button {
text: "重置颜色"
onClicked: {
sphereMaterial.baseColor = "#4080ff"
cubeMaterial.baseColor = "#e68a30"
}
}
}
}
}运行程序后你会看到一个深色背景的 3D 场景:左侧蓝色球体、右侧橙色立方体、底部灰色地面,右上角浮动着半透明的 2D 控制面板。点击面板上的按钮可以实时切换球体和立方体的颜色——QML 的属性绑定机制让材质颜色的切换即时生效,不需要重新创建对象或手动刷新。
对比上一篇用纯 C++ 写的 Qt 3D 场景,你会发现 QML 版本的代码可读性强得多——3D 对象的属性(位置、缩放、材质参数)全部用声明式的 QML 语法写出来,一目了然。而且 2D 控制面板和 3D 场景的混合如此自然,完全不需要额外的桥接代码。
5. 练习项目
练习项目:带旋转动画和颜色选择器的 3D 展示台。
在基础场景上增加旋转动画和交互式颜色控制。球体和立方体各自绕 Y 轴持续旋转(速度不同),2D 面板上增加两个 ColorDialog 颜色选择器分别控制球体和立方体的 baseColor,再增加一个 Slider 滑块控制立方体的 metalness 从 0 到 1 变化。
完成标准是这样的:球体旋转一圈用时 4 秒,立方体旋转一圈用时 6 秒,使用 NumberAnimation 对 Model 的 eulerRotation.y 属性做动画(from: 0, to: 360, duration: 毫秒数, loops: Animation.Infinite);颜色选择器点击后弹出系统颜色对话框,选中颜色后更新对应材质的 baseColor;metalness 滑块实时显示当前值(保留一位小数),拖动时实时更新立方体材质。
几个实现提示:QML 的 NumberAnimation 可以直接嵌入到 Model 的属性声明中,语法是 Behavior on eulerRotation.y { NumberAnimation { ... } },或者用独立的 NumberAnimation 组件设置 target 和 property;颜色选择器可以用 Qt Labs Platform 的 ColorDialog(需要 import Qt.labs.platform),或者简单地用几个预设颜色的按钮代替;Slider 的 value 属性直接绑定到 cubeMaterial.metalness 即可,QML 的属性绑定是双向的——Slider 拖动会自动更新材质。
6. 官方文档参考
Qt 文档 · QtQuick3D 模块 -- QtQuick3D 模块总览
Qt 文档 · View3D -- 3D 视口组件
Qt 文档 · PerspectiveCamera -- 透视相机
Qt 文档 · Model -- 模型节点
Qt 文档 · PrincipledMaterial -- PBR 材质
Qt 文档 · DirectionalLight -- 方向光
Qt 文档 · SceneEnvironment -- 场景环境配置
(链接已验证,2026-04-23 可访问)
到这里就大功告成了。QtQuick3D 用 QML 声明式语法把 3D 场景搭建简化到了极致——View3D 开视口,Model 塞几何体,PrincipledMaterial 配材质,PerspectiveCamera 定视角,DirectionalLight 打光,和 2D Qt Quick 元素混合时用同一套布局系统。对比上一篇 Qt 3D 的 C++ ECS 架构,QtQuick3D 在代码量、可读性、2D/3D 混合能力上都更有优势,是 Qt 6 中 3D 开发的首选方案。下一篇我们会在这个基础上继续深入,看 QtQuick3D Physics 物理模拟——让 3D 物体在重力下掉落、碰撞、弹跳。