Porting Qt 3D to Qt Quick 3D: A Migration Guide

This guide is intended for developers currently using Qt 3D who wish to migrate to Qt Quick 3D.

Scope Differences

While Qt 3D and Qt Quick 3D may appear similar as both provide 3D APIs, they are designed with fundamentally different approaches.

Qt 3D is a flexible API for implementing custom 3D renderers, offering low-level abstractions for the various aspects of the rendering process. As a result, Qt 3D provides APIs for both C++ and QML.

In contrast, Qt Quick 3D is a high-level API focused on the rendering of 3D content, and designed as an extension of Qt Quick. Consequently, the majority of its APIs are QML-based. Therefore, if your current Qt 3D application relies heavily on C++, migrating to Qt Quick 3D will require transitioning to QML.

Architectural Differences

Qt 3D and Qt Quick 3D differ significantly in their architecture. Qt Quick 3D follows the same design patterns as Qt Quick, extending its capabilities to support 3D rendering.

The first major difference is in the use of design patterns. Qt 3D uses an Entity-Component System (ECS) pattern. In this system, a generic entity class is used, which can be specialized by adding various components to the entity, following a pattern of composition. In contrast, Qt Quick 3D uses an inheritance-based API that aligns with the broader Qt framework, where specialized nodes are subclasses of a common base class. Due to these fundamental differences, there is no direct 1:1 mapping between the two APIs.

Another significant difference is in the threading models. Qt 3D introduces the concept of Aspects, which provide different entry points for various types of components attached to entities. Each aspect can create tasks that are processed by a thread pool, following a tree of providers and dependencies to complete all work necessary for rendering a frame. On the other hand, Qt Quick 3D uses the same threading model as Qt Quick. It has a frontend API, accessible from the main GUI thread for managing scene state, and a backend API on the render thread. While the render thread may utilize additional threads for certain tasks, the conceptual model from the user's perspective involves only two threads. There is no distinct concept of Aspects in Qt Quick 3D, although much of the functionality provided by Qt 3D's aspects is still available.

From a rendering perspective, Qt 3D offers a highly customizable approach, allowing for the creation of bespoke rendering pipelines. However, this flexibility comes with complexity; by default, Qt 3D does not provide a pre-configured rendering pipeline. In contrast, Qt Quick 3D is designed to be more user-friendly, with an out-of-the-box rendering pipeline that automatically adapts to the needs of the scene. For instance, if a directional light in Qt Quick 3D is set to cast shadows, the rendering pipeline will automatically include a shadow map generation pass, which is then used by the materials in the scene. In Qt 3D, such functionality would require manual configuration of the frame graph and extension of the materials to support the shadow mapping pass. Additionally, for different light types, such as point or spotlights, further modifications to the frame graph and material definitions would be necessary. While Qt 3D offers powerful customization options for shadow mapping techniques, this can result in a significant overhead compared to Qt Quick 3D, where enabling shadows is as simple as setting a single property on a light.

Per Module Details

Qt 3D Core

The Qt 3D Core module provides the fundamental primitives upon which other Qt 3D modules are built. Most classes in Qt 3D Core have equivalent components in Qt Quick 3D, except for those classes that specifically support the Entity-Component-System (ECS) architecture. Qt Quick 3D offers pre-assembled components that correspond to common entity-component combinations used in Qt 3D, simplifying the development process.

As an example this is the setup for a renderable entity in Qt3D:

PhongMaterial {
    id: material
}

TorusMesh {
    id: torusMesh
    radius: 5
    minorRadius: 1
    rings: 100
    slices: 20
}

Transform {
    id: torusTransform
    scale3D: Qt.vector3d(1.5, 1, 0.5)
    rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), 45)
}

Entity {
    id: torusEntity
    components: [ torusMesh, material, torusTransform ]
}

In the above code you can see that the entity is composed of a mesh, a material and a transform. In Qt Quick 3D the equivalent code would look like this:

Model {
    id: torusModel
    scale: Qt.vector3d(1.5, 1, 0.5)
    eulerRotation.x: 45
    geometry: TorusGeometry {
        radius: 5
        tubeRadius: 1
        rings: 100
        segments: 20
    }
    materials: [
        PrincipledMaterial {
            id: material
    ]
}

In this example, the entity is replaced by a Model, which is a subclass of the Node component which inherits the equivalent of the Transform component from Qt 3D. In addition any Node subclass will be part of a scene graph, with parent-child relationships. The Model component is a renderable entity that can have a geometry and material(s). In this case we use the geometry property to match the procedural API used by the Qt 3D code, but it's also possible to specify geometry from a static file using the source property. The materials property of the Model component is a list of materials to be used to shade the geometry. This is a list because it's possible for geometries to contain multiple subsets, each of which may use a separate material. The PrincipledMaterial component is a PBR (Physically Based Rendering) material that uses the Metallic-Roughness workflow, and in the case of this example replaces the PhongMaterial used by the Qt 3D snippet.

Qt 3D Input

Qt 3D provides a range of input classes through its own custom input system. In contrast, Qt Quick 3D primarily reuses input classes from Qt Quick. For interacting with a 3D scene, Qt Quick 3D provides additional APIs to perform ray casting and object picking.

View3D is the main class for handling picking operations in Qt Quick 3D, offering both explicit and implicit picking methods. The explicit picking API allows you to pick directly from 2D space by specifying the x and y coordinates in the View3D. This operation returns a pickResult value that contains details about the object hit and the hit location. The API is synchronous, meaning the result is available immediately after the ray is cast. Additionally, it is possible to cast rays from any point in the scene. Both explicit and implicit picking methods can return a list of all hits, not just the first one.

import QtQuick
import QtQuick.Controls
import QtQuick3D

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    View3D {
        id: view
        anchors.fill: parent

        PerspectiveCamera {
            z: 200
        }

        DirectionalLight {

        }

        PrincipledMaterial {
            id: material
            baseColor: "red"
        }
        PrincipledMaterial {
            id: selectedMaterial
            baseColor: "blue"
        }

        Model {
            id: sphereModel
            source: "#Sphere"
            pickable: true
            materials: [
                material
            ]
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: (mouse) => {
            let result = view.pick(mouse.x, mouse.y)
            if (result.hitType == PickResult.Model) {
                if (result.objectHit == sphereModel) {
                    // toggle selection
                    if (sphereModel.materials[0] == material)
                        sphereModel.materials[0] = selectedMaterial
                    else
                        sphereModel.materials[0] = material
                }
            } else {
                // deselect
                sphereModel.materials[0] = material
            }
        }
    }
}

In this example code snippet, a simple scene is created with a sphere model. The sphere model is set to be pickable, which means that when a ray is cast into the scene, this model will be considered for picking. On top of the View3D is a MouseArea that listens for clicks. When a click is detected, the pick method is called with the x and y coordinates of the click. The result of the pick operation is then used to determine if the sphere model was hit. If it was, the material of the sphere model is toggled between the red and blue materials.

import QtQuick
import QtQuick.Controls
import QtQuick3D

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    View3D {
        id: view
        anchors.fill: parent

        Node {
            eulerRotation.y: 45
            PerspectiveCamera {
                z: 200

            }
        }

        DirectionalLight {

        }

        Node {
            id: anchorItem
            Item {
                anchors.centerIn: parent
                width: 250
                height: 250
                Button {
                    anchors.centerIn: parent
                    text: "Click Me"
                }

            }
        }
    }
}

In this example code snippet, a simple scene is created with a Button in the center of the screen. Button is a 2D item, but since it's parent is a Node, it will be projected into 3D space. Such components can be interacted with the same way as in 2D space. View3D will automatically handle casting rays into the scene and forwarding input events to any 2D items that are part of the scene.

import QtQuick
import QtQuick.Controls
import QtQuick3D

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    View3D {
        id: view
        anchors.fill: parent

        Node {
            eulerRotation.y: 25
            PerspectiveCamera {
                z: 200

            }
            DirectionalLight {

            }
        }

        Model {
            source: "#Cube"
            pickable: true
            materials: [
                PrincipledMaterial {
                    baseColorMap: Texture {
                        sourceItem: Pane {
                            width: 250
                            height: 250
                            Button {
                                anchors.centerIn: parent
                                text: "Click Me"
                            }
                        }
                    }
                }
            ]
        }
    }
}

Similarly in the above code snippet, a cube Model is created with a texture that is a Pane containing a Button. In this case a 250x250 pixel Texture is created by rendering a Pane component containing a Button. Normally this texture would be non-interactive, but by setting pickable to true on the Model, the same implicit picking mechanism that forwarded input events to the Button in the previous example will be used to forward input events to the Button in this example. In the case of the cube Model, each face of the cube will now be interactive with the Button.

Most of the input classes in Qt 3D are not directly translatable to Qt Quick 3D, so some porting effort is required, but the implicit picking mechanism in Qt Quick 3D makes it easy to interact with 2D items in a 3D scene.

Qt 3D Logic

The Qt 3D Logic module contains a single component that provides a callback for each frame rendered. In Qt Quick 3D, the equivalent approach is to use the FrameAnimation component, a general Qt Quick component that provides timing information and a callback mechanism for each rendered frame.

FrameAnimation {
    running: true
    onTriggered: {
        console.log(`currentFrame: ${currentFrame}, frameTime: ${frameTime}`)
    }
}

This component can be used anywhere in your scene, including as a child of an object where you want to perform some action each frame.

It is worth noting though that this pattern is only needed when you want to perform some action each frame. While FrameAnimation makes it possible to recreate the polling behavior in some game engines it is also possible to use the same event driving patterns in Qt Quick 3D as in Qt Quick.

Qt 3D Render

The Qt 3D Render module contains most of the critical classes needed for rendering. In Qt Quick 3D, most of the equivalent functionality is provided as internal implementation details that are not directly exposed to the user. There are however some classes that are exposed to the user as API's that can be used to extend and customize the functionality of Qt Quick 3D.

Geometry

Geometry or mesh data in Qt 3D is represented by the QGeometryRenderer class or the GeometryRenderer component. In Qt Quick 3D, the equivalents are the QQuick3DGeometry class and the ProceduralMesh component. In Qt 3D, the QGeometryRenderer class is lower level and expects the user to provide the vertex attribute layouts as part of the geometry data. In Qt Quick 3D, you get control over which built-in vertex attributes to use, but the layout of the buffers is handled internally as implementation details.

For more details on how this is done, check out the Qt Quick 3D Custom Geometry Example.

Textures

Textures in Qt 3D are represented by the subclasses of QAbstractTexture. In Qt Quick 3D, the equivalent is the Texture component, which is the frontend representation of a texture and sampler. The data used by the texture component can come from various sources though. The simplest way is to just set the source property to an image file, and that will get uploaded to the GPU as a textures data. It is also possible to procedurally generate texture data at runtime by either using 2D qml content as the source, or by subclassing QQuick3DTextureData or using the ProceduralTextureData component. These types let you specify texture data by providing size, format, and raw image data. There is also the possibility to create textures on the GPU through the use of render extensions.

For more details on how this is done, check out the Qt Quick 3D Procedural Texture Example.

Materials

While it was possible to use some built-in materials in Qt 3D, when doing so you were limited to the built in framegraphs they could work with. Qt Quick 3D is a similar situation in that it provides a selection of builtin materials that work with the internal render strategy. However for both Qt 3D and Qt Quick 3D it is possible to create custom materials.

In Qt 3D, this was a fairly involved process involving a tree of QMaterial, QEffect, QTechnique, QRenderPass, and QShaderProgram. In Qt Quick 3D, for materials this process is simplified to a single CustomMaterial component. This component allows you to specify custom shader code for the material, and the rest of the of the setup is done by the renderer. There are two modes for CustomMaterial, shaded and unshaded. In shaded mode the custom shader code API allows you to customize the built in PrincipledMaterial shader, and in unshaded mode you can write your own shader code from scratch. In both cases the shader language is GLSL, with some Qt specific keyword extensions to facilitate the integration with the rest of the Qt Quick 3D API.

For more details on how this is done, check out the Shaded Mode and Unshaded Mode examples, as well as this overview on using Qt Quick 3D's custom GLSL shader language.

Effects

From the Qt3D perspective, there is no difference between rendering a 3D scene and rendering post processing effects, and from a low level perspective this is correct. However there is a distinction made by Qt Quick 3D. Models part of a 3D scene will contain a list of materials, and these get rendered during the main passes. The result of these main passes are the 3D content rendered either directly to a window surface or a texture. Effects are different in that in Qt Quick 3D they refer to post processing effects in which each pass ends up being a single quad that covers the entire render target. The resulting texture of the main color pass is then passed to the first effect pass as a texture, and the result of that pass is then passed to the next Effect pass as a texture, and so on. The final pass is then rendered to the output render target, which is usually the window surface. Some Effects require buffers created during the main passes, like the depth texture, and other will require intermediate steps, all of which can be defined using the Effects APIs in Qt Quick 3D.

Qt Quick 3D specializes this post processing effect chain with API's that allow you to define as many render passes as you need to achieve the desired effect.

For more details on how this is done, check out the Qt Quick 3D Post Processing Example.

Instance Buffers

In Qt 3D, the usage of instancing and instance buffers is quite low level, and so how it is used will depend on the specific use case. In Qt Quick 3D, the built-in materials have some fixed instancing properties that can be set, but to do so you need to provide the instance data as a buffer. There are several ways to provide this data and once it is provided, its just a matter of setting the instancing property on the Model component to associate the instance buffer data with what you want to instance.

For more details, check out this article specific to instancing in Qt Quick 3D.

Renderer Extensions

The purpose of Qt 3D is to provide a powerful way to define a custom rendering solution for your application. Qt Quick 3D does not provide this level of customization, since it aims to be easy to use instead of easy to customize, but it does provide some ways to extend the rendering pipeline in addition to all of the ways listed above. This is done by implementing render extensions, which allow you to add custom render passes to the renderer using the same data as the renderer. This is a low level API that requires a good understanding of the Qt Quick 3D rendering pipeline, as well as the Qt Rendering Hardware Interface (RHI) API.

For an example of how this is done, check out the Stencil Outline Extension Example

Qt 3D Extras

The Qt 3D Extras module provides various utilities to facilitate an “out-of-the-box” experience, such as materials, geometry generators, and camera controllers. It also includes a forward renderer frame graph. In Qt Quick 3D, you do not need to explicitly define a frame graph; instead, a frame graph is generated automatically based on the scene's requirements.

Builtin Materials

In Qt Quick 3D, materials are provided either by using PrincipledMaterial or SpecularGlossyMaterial, or by defining a CustomMaterial with custom shader code. The PrincipledMaterial is a PBR (Physically Based Rendering) shader that uses the Metallic-Roughness workflow. The complexity of the generated shader increases based on the properties set by the user and the contents of the scene. For example, if a light casts shadows, the generated shader will include code for shadow reception; if a light probe or reflection probe is present, the shader will incorporate that lighting information. This dynamic adaptation also applies to CustomMaterial in shaded mode. However, unshaded CustomMaterial shaders do not automatically consider scene information, such as lighting.

Geometry Helpers

Similar to Qt 3D, Qt Quick 3D also provides many built-in geometry primitives, as well as many procedural geometry generators.

The following table provides a mapping of geometry classes from Qt 3D to their equivalents in Qt Quick 3D:

Camera Controllers

For camera control, Qt Quick 3D provides comparable options with OrbitCameraController and WasdController. The WasdController is similar to the FirstPersonCameraController in Qt3D but it can control any item in the scene, not just cameras.

Qt 3D Animation

The Qt 3D Animation module defines how different types of animations are handled in 3D. In Qt Quick 3D, equivalent functionality is spread across several modules, often leveraging existing classes from Qt Quick when possible. For instance, you can use the various animation classes from the QtQuick import to animate properties of 3D nodes.

When importing animations from 3D content creation tools, they are typically defined using timelines. The Qt Quick Timeline module provides the necessary classes for defining such animations, and any imported content with animations will require this module to function.

In addition to basic transformations (such as translation, rotation, and scaling) for items in a scene, Qt Quick 3D supports armature-based animations, where you animate bones in a skeleton, affecting the vertices associated with each joint or bone. This is achieved through the Skin component, which connects Node objects representing bones in the scene with corresponding bone weights in a Model's geometry. In this case, animating the skeleton is done by animating the bones using transformations.

Qt Quick 3D also supports morph target animations, where you can animate changes in the geometry of a model directly. Morph targets are predefined snapshots of a model's geometry, and you can animate between them using the MorphTarget component.

For combining multiple animations or blending them together, the components in the Qt Quick Timeline Blend Trees module are used. This module allows you to define complex trees for managing how different animations interact. Most of these generic animation classes are not specific to Qt Quick 3D but are part of the broader Qt Quick framework, leveraging existing functionality instead of introducing new methods.

Qt 3D Scene2D

In Qt 3D, the Scene2D component is used to render 2D Qt Quick scenes into textures that can be used within a 3D scene. In Qt Quick 3D, this is achieved using the sourceItem property of the Texture component. The sourceItem property can reference an existing Item or define an inline item. The top-level item determines the texture size, and the Qt Quick scene is rendered to a layer that maps onto the Texture component.

Additionally, it is possible to use Item-based components directly within a 3D scene, where they are projected in 3D space without needing a texture.

© 2025 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.