IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Qt Quick 3D Introduction with glTF Assets

Qt Quick 3D Reference Documentation.

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Qt Quick 3D Introduction with glTF Assets

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.

Our Skeleton Application

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.

 
Sélectionnez
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
        }
    }
}
Image non disponible

Importing an Asset

We are going to use two glTF 2.0 models from the Sample Models repository: Sponza and 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:

Image non disponible

How do we get all this into our Qt Quick 3D scene?

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.

  • Perform the same using balsamui, a GUI frontend for Balsam.

  • 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.

  • 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.

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.

In balsamui the command-line options are mapped to interactive elements:

Image non disponible

Importing via balsam

Let's get started! Assuming that the https://github.com/KhronosGroup/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:

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.

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.

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)

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.

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)

 
Sélectionnez
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.

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.

 
Sélectionnez
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:

Image non disponible

With the mouse and the WASDRF keys we can move around:

Image non disponible
Image non disponible

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).

Importing via balsamui

For our second model, let's use the graphical user interface of balsam instead.

Running balsamui opens the tool:

Image non disponible

Let's import the Suzanne model. This is a simpler model with two texture maps.

Image non disponible

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.

Image non disponible

From this point on, working with the generated assets is the same as in the previous section.

 
Sélectionnez
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.

Image non disponible

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:

 
Sélectionnez
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.

 
Sélectionnez
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:

Image non disponible

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.

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.

 
Sélectionnez
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:

Image non disponible

Skybox and image-based lighting

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.

 
Sélectionnez
environment: SceneEnvironment {
    backgroundMode: SceneEnvironment.SkyBox
    lightProbe: Texture {
        textureData: ProceduralSkyTextureData {
        }
    }
}

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.

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)

Image non disponible
Image non disponible

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.

 
Sélectionnez
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
        }
    }
}
Image non disponible

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:

Image non disponible

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.

Image non disponible

The Models page presents no surprises:

Image non disponible

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:

Image non disponible

This concludes our tour of the basics of building a Qt Quick 3D scene with imported assets.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+