Qt Quick 3D: Building Real-World 3D Applications
7 of 126 When nothing renders
Chapter 1: Qt Quick 3D foundations · Lesson 7 of 126

When nothing renders

Qt Quick 3D will not tell you why your scene is empty. There is no warning, no error, and no logging category that reports a missing camera. You get a rectangle of flat colour and no explanation, and that is the same picture for five completely different mistakes. This lesson produces all five on purpose, so that when one of them happens to you for real you recognise it by sight instead of rereading your file for an hour.

Sandbox as lesson 1.1 leaves itDownload

First, make the viewport announce itself

Your window is #1a1a2e and your clearColor is also #1a1a2e. That was a reasonable choice in lesson 1.1 and it is a terrible one while debugging, because it hides the single most useful distinction available to you: whether the View3D is drawing at all.

Change the clear colour to something that could not possibly be a mistake:

        environment: SceneEnvironment {
            clearColor: "#00aa88"
            backgroundMode: SceneEnvironment.Color

Run it. The window is teal. Now delete the viewport’s anchors.fill: parent line and run again: the window is #1a1a2e navy, and the teal never appears anywhere.

That is the whole technique. A View3D with no size is still a valid item, it just occupies no pixels, so what you see is the Window behind it. Teal means the viewport is there and its scene is empty. Navy means there is no viewport covering that area at all, and no amount of staring at your camera will help, because the problem is two-dimensional.

Put anchors.fill: parent back. Leave the clear colour teal until the end of the lesson.

Nothing to see with

Delete the PerspectiveCamera, or comment it out, and run:

        // PerspectiveCamera {
        //     z: 300
        // }

A flat teal rectangle. No warning on the console, with or without QT_LOGGING_RULES="qt.quick3d.*=true". Qt Quick 3D ships five logging categories and they cover effects, input events, picking, physics and XR. None of them reports that the renderer had no point of view to render from.

Put the camera back, and now break it a subtler way instead. Try each of these in turn:

        PerspectiveCamera {
            z: -300      // behind the cube, facing away
        }
        PerspectiveCamera {
            z: 30        // inside the cube
        }
        PerspectiveCamera {
            z: 300
            clipFar: 200 // the cube is further away than this
        }

All three give you the same flat teal rectangle as having no camera at all. The first is the easiest to reason about: a camera looks down its own negative Z, so at z: -300 it is behind the cube with its back to it. The second is the one that surprises people. At z: 30 you are inside the cube, and the only face between you and the outside world is a back face, which is culled, so you see straight through the geometry you are standing in. The third is a frustum you drew too small: anything nearer than clipNear or further than clipFar does not exist as far as the renderer is concerned.

Restore z: 300 and delete the extra line.

Nothing to see by

Delete the DirectionalLight and run it.

        // DirectionalLight {
        //     color: "#fff5e6"
        //     eulerRotation.x: -45
        // }

This is the one failure that looks different, and it is worth the detour to see it. The cube is still there. It is a black cube, a perfect silhouette against the teal, because a lit material with no light to return gives back nothing. Present and black is not the same as absent, and the difference is visible whenever your background is not also black. It is the strongest argument for not using a black clearColor.

You get the identical picture by aiming the light away instead of deleting it. Set eulerRotation.x: 135 and the cube goes just as black, because a DirectionalLight is a direction and nothing else. Its position is meaningless; only where it points matters.

Put the light back.

Nothing to draw

Two mistakes live here and they are both silent.

The first is a Model with no material at all:

        Model {
            source: "#Cube"
            eulerRotation.y: 30
        }

Flat teal. There is no default material and no complaint: geometry with nothing to shade it is simply not submitted. If you have ever deleted a materials: line while refactoring and watched an object vanish, this is what happened.

The second is a Model that is not in the viewport’s scene. Move the cube out of the View3D so that it becomes a child of the Window instead:

    View3D {
        id: view
        anchors.fill: parent
        // ...
    }

    Model {
        source: "#Cube"
        eulerRotation.y: 30
        materials: PrincipledMaterial {
            baseColor: "#e74c3c"
        }
    }

This compiles, runs, warns about nothing, and draws nothing. A Model is not an Item, so QML keeps it as an ordinary child object of the Window and no renderer ever walks it. Indentation is the only thing distinguishing a scene from a paperweight, which is why the next lesson gives the scene a name of its own.

Move the cube back inside the View3D, and set clearColor back to "#1a1a2e".

Two failures that survive the fix

Exercises

1. With the light deleted, add lighting: PrincipledMaterial.NoLighting to the material. Explain why the cube comes back.

Show the solution

NoLighting tells the material to skip the lighting calculation entirely and return baseColor flat. So the cube renders red, with no shading anywhere on it, in a scene that contains no lights. It proves the geometry, the camera and the viewport were all fine and only the lighting term was producing zero. Flat-shaded materials are genuinely useful for indicators, overlays and wireframe-like guides, and they are the quickest way to answer “is my geometry even there?”.

2. Set clipNear: 400 with the camera at z: 300, and predict what you will see before you run it. Then set clipNear: 0.001 and clipFar: 100000 and say why that is a bad idea even though the cube comes back.

Show the solution

At clipNear: 400 the near plane is past the cube, so the cube is clipped away entirely and you get flat teal. The second pair brings it back, but the depth buffer has a fixed number of bits spread across the range between the two planes, so a huge range spends almost all of its precision on the first few units and leaves neighbouring surfaces far away sharing the same depth value. That is z-fighting: two coplanar surfaces flickering as the camera moves. Keep the range as tight as the scene allows.

3. Give the View3D width: 400; height: 300 instead of anchors.fill: parent, with the teal clear colour still in place. What do you see, and what does it tell you about where 3D sits in the QML item tree?

Show the solution

A 400 by 300 teal rectangle in the top left corner, with the cube centred inside it and navy around it. The cube is smaller in pixels, and it still occupies the same fraction of the viewport’s height as it did before, because fieldOfView is vertical by default and the vertical angle did not change. Its fraction of the width did change, because the aspect ratio did. Nothing in the scene moved. A View3D is an ordinary item that renders a 3D scene into its own rectangle, and it obeys every layout rule any other item does.

Summary

  • Five different mistakes produce the same flat rectangle: no camera, a camera that cannot see the subject, no material, geometry outside the viewport’s scene, and a viewport with no size.
  • Qt Quick 3D reports none of them. Only the missing import produces a message.
  • A clearColor that differs from the window’s colour separates “the viewport is empty” from “there is no viewport here”, in one edit.
  • A black object is present and unlit. A missing object is absent. Never use a black background while you are working out which one you have.

Next we give the scene an identity separate from the viewport looking at it, which is what makes two views of one scene possible, and which removes the “is it inside the View3D?” question for good.

This is a free preview lesson.

Get the full course to unlock the rest of the curriculum.