Qt3D SceneLoader entity is not rendered

1k views Asked by At

I'm trying to create a Qt3D prototype which is able to render an obj which contains transparent objects. Therefor I need to somehow incorporate QSortPolicy with a custom frame graph. I put together a few examples which I found on the internet (unfortunately there aren't many Qt3D C++ examples). The source of the protoype is shown below. The problem is that I add a QTorusMesh and the content of the QSceneLoader to the root entity, but when rendered only the torus will be shown, the obj (in this case the monkey) is not rendered and I don't know why and also not how to debug it. I dumped all the trees (scene graph and frame graph) and couldn't find any inconsistency. Only thing I can think of that the loaded object somehow needs a different renderer (QGeometryRenderer) which needs to be added to the frame graph? Does someone know what I'm doing wrong?

Examples: Custom frame graph, Scene walker

#include <QGuiApplication>

#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DCore/QAspectEngine>

#include <Qt3DInput/QInputAspect>

#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DExtras/QOrbitCameraController>
#include <Qt3DExtras/QTorusMesh>

#include <Qt3DRender/QCamera>
#include <Qt3DRender/QRenderAspect>
#include <Qt3DRender/QSceneLoader>
#include <Qt3DRender/QRenderSurfaceSelector>
#include <Qt3DRender/QClearBuffers>
#include <Qt3DRender/QLayerFilter>
#include <Qt3DRender/QViewport>
#include <Qt3DRender/QCameraSelector>
#include <Qt3DRender/QLayer>
#include <Qt3DRender/QRenderSettings>

class SceneWalker : public QObject
{
public:
    SceneWalker(Qt3DRender::QSceneLoader* loader):
        m_loader(loader)
    {}

    void onStatusChanged();

private:
    void walkEntity(Qt3DCore::QEntity* e, int depth = 0);

    Qt3DRender::QSceneLoader* m_loader;
};

void SceneWalker::onStatusChanged()
{
    qDebug() << "Status changed:" << m_loader->status();
    if (m_loader->status() != Qt3DRender::QSceneLoader::Ready)
        return;

    // The QSceneLoader instance is a component of an entity. The loaded scene
    // tree is added under this entity.
    QVector<Qt3DCore::QEntity*> entities = m_loader->entities();

    // Technically there could be multiple entities referencing the scene loader
    // but sharing is discouraged, and in our case there will be one anyhow.
    if (entities.isEmpty())
        return;

    Qt3DCore::QEntity* root = entities[0];
    // Print the tree.
    walkEntity(root);

    // To access a given node (like a named mesh in the scene), use QObject::findChild().
    // The scene structure and names always depend on the asset.
    Qt3DCore::QEntity* e = root->findChild<Qt3DCore::QEntity*>(QStringLiteral("PlanePropeller_mesh")); // toyplane.obj
    if (e)
        qDebug() << "Found propeller node" << e << "with components" << e->components();
}

void SceneWalker::walkEntity(Qt3DCore::QEntity* e, int depth)
{
    Qt3DCore::QNodeVector nodes = e->childNodes();
    for (int i = 0; i < nodes.count(); ++i)
    {
        Qt3DCore::QNode* node = nodes[i];
        Qt3DCore::QEntity* entity = qobject_cast<Qt3DCore::QEntity*>(node);
        if (entity)
        {
            QString indent;
            indent.fill(' ', depth * 2);
            qDebug().noquote() << indent << "Entity:" << entity << "Components:" << entity->components();
            walkEntity(entity, depth + 1);
        }
    }
}

int main(int argc, char* argv[])
{
    QGuiApplication app(argc, argv);

    Qt3DExtras::Qt3DWindow* window = new Qt3DExtras::Qt3DWindow();

    // Root
    Qt3DCore::QEntity* rootEntity = new Qt3DCore::QEntity();
    window->setRootEntity(rootEntity);

    Qt3DRender::QRenderSurfaceSelector *renderSurfaceSelector = new Qt3DRender::QRenderSurfaceSelector();
    renderSurfaceSelector->setSurface(window);

    // clearing the buffers
    Qt3DRender::QClearBuffers* clearBuffers = new Qt3DRender::QClearBuffers(renderSurfaceSelector);
    clearBuffers->setBuffers(Qt3DRender::QClearBuffers::ColorDepthBuffer);

    // Framegraph for objects
    Qt3DRender::QLayerFilter* objectsLayerFilter = new Qt3DRender::QLayerFilter(renderSurfaceSelector);
    Qt3DRender::QLayer* objectsLayer = new Qt3DRender::QLayer(objectsLayerFilter);
    objectsLayerFilter->addLayer(objectsLayer);
    Qt3DRender::QViewport* viewport = new Qt3DRender::QViewport(objectsLayer);
    Qt3DRender::QCameraSelector* objectsCameraSelector = new Qt3DRender::QCameraSelector(viewport);
    Qt3DRender::QCamera* objectsCamera = new Qt3DRender::QCamera(objectsCameraSelector);
    objectsCamera->lens()->setPerspectiveProjection(45.f, 16.0f/9.0f, 0.01f, 1000.f);
    objectsCamera->setPosition(QVector3D(0, 0, -10));
    objectsCamera->setViewCenter(QVector3D(0, 0, 0));
    objectsCamera->setUpVector(QVector3D(0, 1, 0));
    objectsCameraSelector->setCamera(objectsCamera);

    // Set the new framegraph
    window->setActiveFrameGraph(renderSurfaceSelector);
    window->renderSettings()->setRenderPolicy(Qt3DRender::QRenderSettings::Always);

    // camera controls
    Qt3DExtras::QOrbitCameraController* camController = new Qt3DExtras::QOrbitCameraController(rootEntity);
    camController->setLinearSpeed(50.0f);
    camController->setLookSpeed(180.0f);
    camController->setCamera(objectsCamera);

    // Torus
    Qt3DCore::QEntity* torusEntity = new Qt3DCore::QEntity(rootEntity);
    Qt3DExtras::QTorusMesh* torusMesh = new Qt3DExtras::QTorusMesh(torusEntity);
    torusMesh->setSlices(50.0f);
    torusMesh->setRings(50.0f);
    torusMesh->setRadius(2.0f);
    Qt3DExtras::QPhongMaterial* torusMaterial = new Qt3DExtras::QPhongMaterial(torusEntity);
    torusMaterial->setAmbient(Qt::gray);
    Qt3DCore::QTransform* torusTransform = new Qt3DCore::QTransform(torusEntity);
    torusTransform->setTranslation(QVector3D(0.0f, 0.0f, 10.0f));
    torusTransform->setRotationY(50.0f);
    torusTransform->setScale(2.0f);
    torusEntity->addComponent(torusTransform);
    torusEntity->addComponent(torusMesh);
    torusEntity->addComponent(torusMaterial);
    torusEntity->addComponent(objectsLayer);

    // Scene loader
    Qt3DCore::QEntity* sceneLoaderEntity = new Qt3DCore::QEntity(rootEntity);
    Qt3DRender::QSceneLoader* sceneLoader = new Qt3DRender::QSceneLoader(sceneLoaderEntity);
    sceneLoader->setSource(QUrl::fromLocalFile("monkey.obj"));

    // Transform
    Qt3DCore::QTransform* sceneLoaderTransform = new Qt3DCore::QTransform(sceneLoaderEntity);
    sceneLoaderTransform->setScale(2.0f);
    sceneLoaderTransform->setTranslation(QVector3D(0.0f, 0.0f, 10.0f));

    SceneWalker sceneWalker(sceneLoader);
    QObject::connect(sceneLoader, &Qt3DRender::QSceneLoader::statusChanged, &sceneWalker, &SceneWalker::onStatusChanged);

    sceneLoaderEntity->addComponent(sceneLoader);
    sceneLoaderEntity->addComponent(sceneLoaderTransform);
    sceneLoaderEntity->addComponent(objectsLayer);

    window->show();

    return app.exec();
}
1

There are 1 answers

0
Urbano Lugrís On

The problem is that you are not actually adding the monkey to the objects layer. QSceneLoader creates its own scene subtree, even when the OBJ file contains a single object. So you are just adding the root entity of the subtree to the objects layer.

The SceneWalker object traverses the QSceneLoader subtree and provides information about its structure. You can just look at the console output, find the names of the entities you want to render, and attach them to the desired layer.

This has to be done asynchronously, using signals and slots. Do this right after creating the QSceneLoader object (assuming your mesh is named "monkey"):

    QObject::connect(sceneLoader, &Qt3DRender::QSceneLoader::statusChanged, &app,
        [sceneLoader, objectsLayer](Qt3DRender::QSceneLoader::Status s) {
            if (s == Qt3DRender::QSceneLoader::Status::Ready)
                sceneLoader->entity("monkey")->addComponent(objectsLayer);
    });

Since you are using an OBJ file, you can even ditch the SceneWalker entirely. OBJ files are text-based, so you can find the name of the mesh by just opening it with a text editor.

If you succeed with layer filters, you don't need to use QSortPolicy (check out this answer).