Qt Quick 3D Introduction with glTF Assets
带glTF资产的Qt Quick 3D简介
The Qt Quick 3D - Introduction example provides a quick introduction to creating QML-based applications with Qt Quick 3D, but it does so using only built-in primitives, such as spheres and cylinders. This page provides an introduction using glTF 2.0 assets, using some of the models from the Khronos glTF Sample Models repository.
Qt Quick 3D-Introduction示例快速介绍了如何使用Qt Quick 3D创建基于QML的应用程序,但它只使用内置的基本体(如球体和圆柱体)。本页提供了使用glTF 2.0资产的介绍,使用了Khronos glTF示例模型存储库中的一些模型。
Our Skeleton Application
我们的Skeleton应用程序
Let's start with the following application. This code snippet is runnable as-is with the qml
command-line tool. The result is a very green 3D view with nothing else in it.
让我们从下面的应用程序开始。这个代码片段可以使用qml命令行工具按原样运行。结果是一个非常绿色的三维视图,其中没有其他内容。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
WasdController {
controlledObject: camera
}
}
}
Importing an Asset
导入资产
We are going to use two glTF 2.0 models from the Sample Models repository: Sponza and Suzanne.
我们将使用示例模型库中的两个glTF 2.0模型:Sponza和Suzanne。
These models typically come with a number of texture maps and the mesh (geometry) data stored in a separate binary file, in addition to the .gltf file:
除了.gltf文件外,这些模型通常还附带许多纹理贴图和存储在单独二进制文件中的网格(几何体)数据:
How do we get all this into our Qt Quick 3D scene?
我们如何将这一切融入我们的Qt Quick 3D场景?
There are a number of options:
有许多选项:
- Generate QML components that can be instantiated in the scene. The command-line tool to perform this conversion is the Balsam tool. Besides generating a .qml file, that is effectively a subscene, this also repacks the mesh (geometry) data into an optimized, fast-to-load format, and copies the texture map image files as well.
- 生成可以在场景中实例化的QML组件。执行此转换的命令行工具是Balsam工具。除了生成一个.qml文件(实际上是一个子集)外,这还将网格(几何体)数据重新打包为优化的快速加载格式,并复制纹理贴图图像文件。
- Perform the same using
balsamui
, a GUI frontend for Balsam. - 使用Balsam的GUI前端Balsamui执行相同的操作。
- If using Qt Design Studio, the asset import process is integrated into the visual design tools. Importing can be triggered, for example, by dragging and dropping the .gltf file onto the appropriate panel.
- 如果使用Qt Design Studio,则资产导入过程将集成到可视化设计工具中。例如,可以通过将.gltf文件拖放到相应的面板上来触发导入。
- For glTF 2.0 assets in particular, there is a runtime option as well: the RuntimeLoader type. This allows loading a .gltf file (and the associated binary and texture data files) at runtime, without doing any pre-processing via tools such as Balsam. This is very handy in applications that wish to open and load user-provided assets. On the other hand, this approach is significantly less efficient when it comes to performance. Therefore, we will not be focusing on this approach in this introduction. Check the Qt Quick 3D - RuntimeLoader Example for an example of this approach.
- 特别是对于glTF 2.0资产,还有一个运行时选项:RuntimeLoader类型。这允许在运行时加载.gltf文件(以及相关的二进制和纹理数据文件),而无需通过Balsam等工具进行任何预处理。这在希望打开和加载用户提供的资产的应用程序中非常方便。另一方面,在性能方面,这种方法的效率明显较低。因此,我们将不会在本介绍中重点介绍这种方法。查看Qt Quick 3D-RuntimeLoader示例以了解此方法的示例。
Both the balsam
and balsamui
applications are shipped with Qt, and should be present in the directory with other similar executable tools, assuming Qt Quick 3D is installed or built. In many cases, running balsam from the command-line on the .gltf file is sufficient, without having to specify any additional arguments. It is worth being aware however of the many command-line, or interactive if using balsamui
or Qt Design Studio, options. For example, when working with baked lightmaps to provide static global illumination, it is likely that one will want to pass --generateLightmapUV
to get the additional lightmap UV channel generated at asset import time, instead of performing this potentially consuming process at run-time. Similarly, --generateMeshLevelsOfDetail
is essential when it is desirable to have simplified versions of the meshes generated in order to have automatic LOD enabled in the scene. Other options allow generating missing data (e.g. --generateNormals
) and performing various optimizations.
balsam和balsamui应用程序都随Qt一起提供,并且应该与其他类似的可执行工具一起出现在目录中,假设安装或构建了Qt Quick 3D。在许多情况下,在.gltf文件上从命令行运行balsam就足够了,无需指定任何其他参数。然而,值得注意的是,有许多命令行选项,如果使用balsamui或Qt Design Studio,则为交互式选项。例如,当使用烘焙的光照贴图来提供静态全局照明时,可能会希望通过--generateLightmapUV来获得在资源导入时生成的附加光照贴图UV通道,而不是在运行时执行这一潜在的消耗过程。类似地,当希望生成简化版本的网格以便在场景中启用自动LOD时,--generateMeshLevelsOfDetail是必不可少的。其他选项允许生成丢失的数据(例如--generateNormals)并执行各种优化。
In balsamui
the command-line options are mapped to interactive elements:
在balsamui中,命令行选项被映射到交互式元素:
Importing via balsam
通过balsam导入
Let's get started! Assuming that the GitHub - KhronosGroup/glTF-Sample-Models: glTF Sample Models git
repository is checked out somewhere, we can simply run balsam from our example application directory, by specifying an absolute path to the .gltf files:
让我们开始吧!假设GitHub-KhronosGroup/glTF Sample Models:glTF Sample Models git存储库已在某处检出,我们可以通过指定.glTF文件的绝对路径,简单地从示例应用程序目录中运行ball:
balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf
This gives us a Sponza.qml
, a .mesh
file under the meshes
subdirectory, and the texture maps copied under maps
.
这为我们提供了一个Sponza.qml,一个位于mesh子目录下的.mesh文件,以及在贴图下复制的纹理贴图。
Note: This qml file is not runnable on its own. It is a component, that should be instantiated within a 3D scene associated with a View3D.
注意:此qml文件本身不可运行。它是一个组件,应该在与View3D相关联的3D场景中实例化。
Our project structure is very simple here, as the asset qml files live right next to our main .qml scene. This allows us to simply instantiate the Sponza type using the standard QML component system. (at run-time this will then look for Sponza.qml in the filesystem)
我们的项目结构在这里非常简单,因为资产qml文件就位于我们的main.qml场景旁边。这允许我们使用标准QML组件系统简单地实例化Sponza类型。(在运行时,它将在文件系统中查找Sponza.qml)
Just adding the model (subscene) is pointless however, since by default the materials feature the full PBR lighting calculations, so nothing is shown from our scene without a light such as DirectionalLight, PointLight, or SpotLight, or having image-based lighting enabled via the environment.
然而,仅仅添加模型(子场景)是没有意义的,因为默认情况下,材质具有完整的PBR照明计算功能,因此在没有灯光(如DirectionalLight、PointLight或SpotLight)或通过环境启用基于图像的照明的情况下,场景中不会显示任何内容。
For now, we choose to add a DirectionalLight with the default settings. (meaning the color is white
, and the light emits in the direction of the Z axis)
目前,我们选择使用默认设置添加DirectionalLight。(表示颜色为白色,光沿Z轴方向发射)
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
DirectionalLight {
}
Sponza {
}
WasdController {
controlledObject: camera
}
}
}
Running this with the qml
tool will load and run, but the scene is all empty by default since the Sponza model is behind the camera. The scale is also not ideal, e.g. moving around with WASD keys and the mouse (enabled by the WasdController) does not feel right.
使用qml工具运行此操作将加载并运行,但由于Sponza模型位于摄影机后面,因此默认情况下场景全部为空。比例也不理想,例如使用WASD键四处移动,鼠标(由WasdController启用)感觉不对劲。
To remedy this, we scale the Sponza model (subscene) by 100
along the X, Y, and Z axis. In addition, the camera's starting Y position is bumped to 100.
为了解决这一问题,我们将Sponza模型(子场景)沿X、Y和Z轴缩放100。此外,摄影机的起始Y位置会被碰撞到100。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
WasdController {
controlledObject: camera
}
}
}
Running this gives us:
运行此程序可使我们:
With the mouse and the WASDRF keys we can move around:
使用鼠标和WASDRF键,我们可以四处移动:
Note: We mentioned
subscene
a number of times above as an alternative to "model". Why is this? While not obvious with the Sponza asset, which in its glTF form is a single model with 103 submeshes, mapping to a single Model object with 103 elements in its materials list, an asset can contain any number of models, each with multiple submeshes and associated materials. These Models can form parent-child relationships and can be combined with additional nodes to perform transforms such as translate, rotate, or scale. It is therefore more appropriate to look at the imported asset as a complete subscene, an arbitrary tree of nodes, even if the rendered result is visually perceived as a single model. Open the generated Sponza.qml, or any other QML file generated from such assets, in a plain text editor to get an impression of the structure (which naturally always depends on how the source asset, in this case the glTF file, was designed).
注:作为“模型”的替代方案,我们在上面多次提到了subcene。为什么会这样?Sponza资产在其glTF形式中是一个具有103个子网格的单个模型,映射到其材料列表中具有103个元素的单个模型对象,虽然这一点并不明显,但资产可以包含任何数量的模型,每个模型都具有多个子网格和相关材料。这些模型可以形成父子关系,并可以与其他节点组合以执行转换,如平移、旋转或缩放。因此,将导入的资产视为一个完整的子集(一个任意的节点树)更为合适,即使渲染结果在视觉上被视为单个模型。在纯文本编辑器中打开生成的Sponza.qml或从此类资产生成的任何其他qml文件,以获得结构的印象(这自然总是取决于源资产(在本例中为glTF文件)的设计方式)。
Importing via balsamui
通过balsamui导入
For our second model, let's use the graphical user interface of balsam
instead.
对于我们的第二个模型,让我们使用balance的图形用户界面。
Running balsamui
opens the tool:
运行balsamui打开工具:
Let's import the Suzanne model. This is a simpler model with two texture maps.
让我们导入苏珊娜模型。这是一个更简单的模型,有两个纹理贴图。
As there is no need for any additional configuration options, we can just Convert. The result is the same as running balsam
: a Suzanne.qml and some additional files generated in the specific output directory.
由于不需要任何额外的配置选项,我们可以直接转换。结果与运行balsam相同:一个Suzanne.qml和一些在特定输出目录中生成的附加文件。
From this point on, working with the generated assets is the same as in the previous section.
从这一点开始,使用生成的资产与上一节中的操作相同。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
eulerRotation.y: -90
}
WasdController {
controlledObject: camera
}
}
}
Again, a scale is applied to the instantiated Suzanne node, and the Y position is altered a bit so that the model does not end up in the floor of the Sponza building.
再次,将比例应用于实例化的Suzanne节点,并稍微更改Y位置,以使模型不会最终出现在Sponza建筑的楼层中。
All properties can be changed, bound to, and animated, just like with Qt Quick. For example, let's apply a continuous rotation to our Suzanne model:
所有属性都可以更改、绑定到和设置动画,就像使用Qt Quick一样。例如,让我们将连续旋转应用于我们的Suzanne模型:
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
Making it Look Better
让它看起来更好
More light
更多光线
Now, our scene is a bit dark. Let's add another light. This time a PointLight, and one that casts a shadow.
现在,我们的场景有点暗。让我们再加一盏灯。这一次是一个PointLight,也是一个投射阴影的PointLight。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
}
Launching this scene and moving the camera around a bit reveals that this is indeed starting to look better than before:
启动这个场景并将相机移动一点,可以发现这确实开始比以前看起来更好了:
Light debugging
灯光调试
The PointLight is placed slightly above the Suzanne model. When designing the scene using visual tools, such as Qt Design Studio, this is obvious, but when developing without any design tools it may become handy to be able to quickly visualize the location of lights and other nodes.
PointLight位于Suzanne模型的略上方。当使用视觉工具(如Qt Design Studio)设计场景时,这是显而易见的,但当在没有任何设计工具的情况下进行开发时,能够快速可视化灯光和其他节点的位置可能会变得很方便。
This we can do by adding a child node, a Model to the PointLight. The position of the child node is relative to the parent, so the default (0, 0, 0)
is effectively the position of the PointLight in this case. Enclosing the light within some geometry (the built-in cube in this case) is not a problem for the standard real-time lighting calculations since this system has no concept of occlusion, meaning the light has no problems with traveling through "walls". If we used pre-baked lightmaps, where lighting is calculated using raytracing, that would be a different story. In that case we would need to make sure the cube is not blocking the light, perhaps by moving our debug cube a bit above the light.
我们可以通过向PointLight添加一个子节点Model来实现这一点。子节点的位置相对于父节点,因此在这种情况下,默认值(0,0,0)实际上是PointLight的位置。将光封装在某些几何体(在这种情况下为内置立方体)内对于标准实时照明计算来说不是问题,因为该系统没有遮挡的概念,这意味着光在穿过“墙”时没有问题。如果我们使用预烘焙的光照贴图,其中使用光线追踪来计算照明,那就另当别论了。在这种情况下,我们需要确保立方体没有遮挡光线,也许可以将调试立方体移动到光线上方一点。
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}
Another trick we use here is turning off lighting for the material used with the cube. It will just appear using the default base color (white), without being affected by lighting. This is handy for objects used for debugging and visualizing purposes.
我们在这里使用的另一个技巧是关闭立方体所用材质的照明。它将仅使用默认的基本颜色(白色)显示,不受照明的影响。这对于用于调试和可视化目的的对象非常方便。
The result, note the small, white cube appearing, visualizing the position of the PointLight:
结果,请注意出现的白色小立方体,可视化PointLight的位置:
Skybox and image-based lighting
Skybox和基于图像的照明
Another obvious improvement is doing something about the background. That green clear color is not quite ideal. How about some environment that also contributes to lighting?
另一个明显的改进是对背景做些什么。那种清澈的绿色不太理想。一些同样有助于照明的环境怎么样?
As we do not necessarily have suitable HDRI panorama image available, let's use a procedurally generated high dynamic range sky image. This is easy to do with the help of ProceduralSkyTextureData and Texture's support for non-file based, dynamically generated image data. Instead of specifying source, we rather use the textureData property.
由于我们不一定有合适的HDRI全景图像可用,让我们使用程序生成的高动态范围天空图像。借助ProceduralSkyTextureData和Texture对非基于文件的动态生成图像数据的支持,这很容易做到。我们不指定源,而是使用textureData属性。
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}
Note: The example code prefers defining objects inline. This is not mandatory, the SceneEnvironment or ProceduralSkyTextureData objects could have also been defined elsewhere in the object tree, and then referenced by
id
.
注意:示例代码更喜欢内联定义对象。这不是强制性的,SceneEnvironment或ProceduralSkyTextureData对象也可以在对象树的其他位置定义,然后由id引用。
As a result, we have both a skybox and improved lighting. (the former due to the backgroundMode being set to SkyBox and light probe being set to a valid Texture; the latter due to light probe being set to a valid Texture)
因此,我们既有一个skybox,又改善了照明。(前者是因为backgroundMode被设置为SkyBox,而光探测器被设置为有效纹理;后者是因为光探测器被设定为有效纹理)
Basic Performance Investigations
基本性能调查
To get some basic insights into the resource and performance aspects of the scene, it is a good idea to add a way to show an interactive DebugView item early on in the development process. Here we choose to add a Button that toggles the DebugView, both anchored in the top-right corner.
为了获得对场景的资源和性能方面的一些基本见解,最好在开发过程的早期添加一种显示交互式DebugView项目的方式。在这里,我们选择添加一个按钮来切换DebugView,两者都锚定在右上角。
import QtQuick
import QtQuick.Controls
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
id: view3D
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
Button {
anchors.right: parent.right
text: "Toggle DebugView"
onClicked: debugView.visible = !debugView.visible
DebugView {
id: debugView
source: view3D
visible: false
anchors.top: parent.bottom
anchors.right: parent.right
}
}
}
This panel shows live timings, allows examining the live list of texture maps and meshes, and gives an insight into the render passes that need to be performed before the final color buffer can be rendered.
此面板显示实时计时,允许检查纹理贴图和网格的实时列表,并深入了解在渲染最终颜色缓冲区之前需要执行的渲染过程。
Due to making the PointLight a shadow casting light, there are multiple render passes involved:
由于使PointLight成为阴影投射光,因此涉及多个渲染过程:
In the Textures
section we see the texture maps from the Suzanne and Sponza assets (the latter has a lot of them), as well as the procedurally generated sky texture.
在“Textures
”部分中,我们可以看到Suzanne和Sponza资产的纹理贴图(后者有很多),以及按程序生成的天空纹理。
The Models
page presents no surprises:
“模型”页面显示的内容并不意外:
On the Tools
page there are some interactive controls to toggle wireframe mode and various material overrides.
在“工具”页面上,有一些交互式控件用于切换线框模式和各种材质替代。
Here with wireframe mode enabled and forcing rendering to only use the base color component of the materials:
在启用线框模式并强制渲染仅使用材质的基本颜色组件的情况下:
This concludes our tour of the basics of building a Qt Quick 3D scene with imported assets.
这就结束了我们使用导入的资产构建Qt Quick 3D场景的基础知识之旅。
© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.