Skip to content

Kuesa Many Ducks C++ Example

manyducks-example.jpg

Setting up a scene

Creating a window

The easiest way to get started is to subclass the Qt3DExtras::Qt3DWindow ```cpp

class Window : public Qt3DExtras::Qt3DWindow

1
2
3
4
5
6
7
8
9
_Filename: manyducks/main.cpp_


## Creating a SceneEntity

The [SceneEntity ] will hold the assets we will load later. It will also act as our root entity. ```cpp

        m_scene = new Kuesa::Qt3D::SceneEntity();
        m_scene->addComponent(new DefaultEnvMap(m_scene));

Filename: manyducks/main.cpp

Creating a Camera and its controller

We can reuse the default camera provided by Qt3DExtras::Qt3DWindow ```cpp

1
2
3
4
5
6
7
    camera()->setPosition(QVector3D(5, 1.5, 5));
    camera()->setViewCenter(QVector3D(0, 0.5, 0));
    camera()->setUpVector(QVector3D(0, 1, 0));
    camera()->setAspectRatio(16.f / 9.f);

    auto camController = new Qt3DExtras::QOrbitCameraController(m_scene);
    camController->setCamera(camera());

``` Filename: manyducks/main.cpp

Setting up the FrameGraph

We can use the pre made [ForwardRenderer ] FrameGraph. cpp auto fg = new Kuesa::Qt3D::ForwardRenderer(); fg->setCamera(camera()); fg->setGamma(2.2f); fg->setExposure(1.f); fg->setClearColor("white"); fg->setSkinning(true); // We disable frustum culling if using instancing fg->setFrustumCulling(!usesInstancing); setActiveFrameGraph(fg);

Filename: manyducks/main.cpp

Importing a glTF2 File

In order to load a glTF2 file, Kuesa provides the [GLTF2Importer ] element. If the sceneEntity property is set to a valid [SceneEntity ] instance, Qt 3D assets generated while parsing the file will be automatically added to the various asset collections. ```cpp

1
2
3
4
5
6
    auto gltfImporter = new Kuesa::Qt3D::GLTF2Importer(m_scene);
    gltfImporter->setSceneEntity(m_scene);
    gltfImporter->setSource(QUrl{ "file://" ASSETS_DIR "/models/duck/Duck.glb" });

    connect(gltfImporter, &Kuesa::Qt3D::GLTF2Importer::statusChanged,
            this, &Window::on_sceneLoaded);

``` Filename: manyducks/main.cpp

Adding a Skybox

Kuesa provides [Skybox ]. It expects a patch and an extension. cpp // Skybox creation auto skybox = new Kuesa::Qt3D::Skybox(m_scene); skybox->setBaseName(envmap("radiance")); skybox->setExtension(".dds");

Filename: manyducks/main.cpp

Adding a PostProcessing Effect

We can make use of Kuesa's pre made post processing effects such as qmltype{DepthOfFieldEffect}.

1
2
3
4
5
6
        // Depth-of-field
        auto dof = new Kuesa::Qt3D::DepthOfFieldEffect();
        dof->setRadius(15.0f);
        dof->setFocusRange(2.0f);
        dof->setFocusDistance(6.5f);
        fg->addPostProcessingEffect(dof);

Filename: manyducks/main.cpp

Extracting Assets from Collections

Usually, you will want to interact with some elements of your scene.

You can use the Kuesa Studio gltfInspector to introspect a glTF2 scene files and find the names of the various assets it contains.

Upon successful loading of our glTF2 file, we will be able to proceed with asset retrieval. ```cpp

1
2
3
void on_sceneLoaded(Kuesa::Qt3D::GLTF2Importer::Status status)
{
    if (status == Kuesa::Qt3D::GLTF2Importer::Ready) {

``` Filename: manyducks/main.cpp

Manually Instancing Meshes

Retrieving Geometries and Material

Using the gltfInspector we know our scene files contains a Duck Entity name "KuesaEntity_0".. We can therefore retrieve it with: cpp // First let's take the components that we are going to use to create our clones // Gotten from gammaray auto parent = m_scene->entity("KuesaEntity_0");

Filename: manyducks/main.cpp

In turn, using Qt3D Component API, we can retrieve the QGeometry, Qt3DRender::QMaterial components of the previously retrieved entity. ```cpp

1
2
3
            auto *orig_entity = qobject_cast<Qt3DCore::QEntity *>(m_scene->entity("KuesaEntity_2")->childNodes()[1]);
            auto *orig_geometry = componentFromEntity<Qt3DRender::QGeometryRenderer>(orig_entity);
            auto *orig_material = componentFromEntity<Qt3DRender::QMaterial>(orig_entity);

_Filename: manyducks/main.cpp_ Then, we can create several entities referencing the same material and geometry.cpp // Then create clones by giving them a custom transform, and the same components than before QRandomGenerator *rand = QRandomGenerator::global(); for (int i = 0; i < Ducks; i++) { auto new_entity = new Qt3DCore::QEntity{ parent }; auto new_transform = new Qt3DCore::QTransform; new_transform->setScale(0.2f); new_transform->setTranslation(QVector3D(rand->generate() % r - r / 2, rand->generate() % r - r / 2, rand->generate() % r - r / 2)); new_transform->setRotationX(rand->generate() % 360); new_transform->setRotationY(rand->generate() % 360); new_transform->setRotationZ(rand->generate() % 360); new_entity->addComponent(new_transform); new_entity->addComponent(orig_geometry); new_entity->addComponent(orig_material); m_transforms[i] = new_transform; } ```

Filename: manyducks/main.cpp

Relying on GPU based instancing

A more efficient approach to drawing multiple instances of the same geometry is to rely on GPU based instancing. In Kuesa, assuming the GPU supports that feature, this can easily be leveraged by using the qmltype{Kuesa::Qt3D::MeshInstantiator} class.

We need to generate a transformation matrix for each of the instances. ```cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
            // Build base transformation matrices for each instance

            QRandomGenerator *rand = QRandomGenerator::global();
            m_matrices.reserve(Ducks + 1);
            for (int i = 0; i < Ducks; i++) {
                QMatrix4x4 m;

                const int extent = r / 20;
                m.translate(QVector3D(rand->generate() % extent - extent * 0.5, rand->generate() % extent - extent * 0.5, rand->generate() % extent - extent * 0.5));
                m.rotate(rand->generate() % 360, QVector3D(1.0f, 0.0f, 0.0f));
                m.rotate(rand->generate() % 360, QVector3D(0.0f, 1.0f, 0.0f));
                m.rotate(rand->generate() % 360, QVector3D(0.0f, 0.0f, 1.0f));

                m_matrices.push_back(m);
            }
            // Add an extra matrices to have one instance placed at the origin
            m_matrices.push_back({});

_Filename: manyducks/main.cpp_ Then we can create our MeshInstantiator, specify the name of the Entity to instantiate and set the transformation matrices on it.cpp // Create MeshInstantiator m_meshInstantiator = new Kuesa::Qt3D::MeshInstantiator(m_scene); // Specify which Entity with a Mesh needs to be instanced m_meshInstantiator->setEntityName(QStringLiteral("KuesaEntity_2")); // Set transformation matrices m_meshInstantiator->setTransformationMatrices(m_matrices); ```

Filename: manyducks/main.cpp

This approach greatly reduces CPU usage compared to the manual approach.

Animating our Scene

Subclassing the timerEvent function on Qt3DExtras::Qt3DWindow allows us to add some logic to be executed at every frame. ```cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void timerEvent(QTimerEvent *event) override
{
    Q_UNUSED(event)
    if (!m_usesInstancing) {
        for (auto transform : m_transforms) {
            transform->setRotationX(transform->rotationX() + 0.1f);
            transform->setRotationY(transform->rotationY() + 0.1f);
            transform->setRotationZ(transform->rotationZ() + 0.1f);
        }
    } else {
        static bool wasInitialized = false;
        static QMatrix4x4 rotationIncrementMatrix;

        if (!wasInitialized) {
            rotationIncrementMatrix.rotate(0.1f, QVector3D(1.0f, 0.0f, 0.0f));
            rotationIncrementMatrix.rotate(0.1f, QVector3D(0.0f, 1.0f, 0.0f));
            rotationIncrementMatrix.rotate(0.1f, QVector3D(0.0f, 0.0f, 1.0f));
            wasInitialized = true;
        }

        // Apply rotation transform to all matrices
        // This accumulates over time
        for (QMatrix4x4 &m : m_matrices)
            m *= rotationIncrementMatrix;

        m_meshInstantiator->setTransformationMatrices(m_matrices);
    }
}

```

Filename: manyducks/main.cpp

Please note that glTF2 offers way to embedded animations in the glTF files directly. This should be preferred unless you want to manually do animations like illustrated above.


Updated on 2022-10-18 at 11:12:52 +0200