Why is my screen black? A digestible blog on common render issues and what could be their cause

Part 1

So, you’ve just poured your heart and soul into some real-time 3D rendering code and hit render. Wringing your hands in anticipation you wait for the screen to show your marvellous creation. Still… waiting. It says it’s done but, nothing. Well, maybe not nothing but simply darkness. You stare into the deep dark void as your own reflection stares back at you.

So, what went wrong?

Issue 1: The rendered object is actually, like some good old English Panto, “behind you!”

This is usually a common issue for the first render. It’s easy to overlook the Z position of the camera and even which way the camera was facing. There are then issues of various libraries and programs using different coordinate systems. OpenGL, for example, looks along the negative Z axis whilst Direct3D uses the positive.

Also if it’s a planar piece of geometry such as a quad to be used as a sprite, ensure that you are not rendering it exactly edge on. Yes we’ve done this too!

Try setting your window clear color to something other than black. That way even if your fragment shader (see later) is broken and outputting all fragments as black you will at least see the silhouette of the object.

Issue 2: You’re inside the object!

So, a few things can be at play here. First of all make sure the coordinates of the camera aren’t the same or too close to the item(s) you are rendering, like with Issue 1. However, you should also double check the ‘model units’. Is the model using mm instead of m, for example. This can be a common issue with shared or imported models.

Once you’ve checked the relative positions and orientation of your camera, also check that the object falls within the limits of the view frustum’s near and far planes.

Issue 3: Your triangle winding could be backwards.

If your winding order is opposite to what you expect and you have back or front face culling enabled then the rasterizer may not be generating any fragments for your object at all.

A fix for this would be to use a tool such as GammaRay or Apitrace to check geometry. In OpenGL you can also disable culling via glDisable(GL_CULL_FACE).

Issue 4: Shaders – are they compiling and linking? Are you feeding them the correct resources?

Make sure that #version is the very first token – no new lines, or anything of the sort before, as some drivers check that religiously. Have your application code check for compilation and linker failures and output any errors. Often it is simple syntactical errors or issues with the interface between shader stages. Also check that your shader stages are outputting exactly what you expect. For fragment shaders, output intermediate variables as a color to see what is going on.

Also use tools such as apitrace, renderdoc or nSight to introspect frames and check that you really have bound the correct set of buffers and textures.

Issue 5: Qt 3D specific: No techniques matched the actual renderer.

When building Qt 3D scenes that are designed to run on multiple platforms, materials need to provide multiple shaders targeting each specific version of OpenGL. Each version information is stored on QTechnique nodes attached to a QEffect node. Similarly, you can implement different algorithms (forward vs deferred rendering for example), so they get assigned filter keys which are key/value pairs. Finally, some algorithms require multiple passes, but may use different shaders in different passes. This pass information is stored in QRenderPass nodes (attached to the technique), also using filter keys.

When Qt 3D comes to do the render it needs to select the technique based on the available hardware. It will also need to select the technique appropriate to the rendering algorithm that is used. And when it processes each render pass, it will also need to select the appropriate shader based on the render pass. This can be controlled by building a frame graph which QTechniqueFilter nodes and QRenderPassFilter nodes.

You can find a more detailed explanation here.

A common source of “not seeing anything” (or missing some objects) is not providing valid shaders for a specific combination of active technique and current render pass.

In order to help debug this, the new debugging overlay introduced in Qt 3D 5.15 provides a way of dumping the filter state of the scene graph and frame graph which helps understand why some object may not be renderer. It will dump technique details only for the active graphics API (i.e. if you’re running on the desktop, it will not show details relative to OpenGL ES techniques).

For example, here’s a dump of the information for a very simple scene using the default forward renderer:

Active Graphics API: OpenGL 4.1 (Core Profile) (ATI Technologies Inc.) Render Views: 1 [ Qt3DExtras::QForwardRenderer <renderingStyle: forward> ] Scene Graph: Qt3DCore::Quick::Quick3DEntity{1} Qt3DRender::QCamera{13} Qt3DExtras::QOrbitCameraController{16} Qt3DCore::Quick::Quick3DEntity{75} [ T <renderingStyle: forward> ] Qt3DCore::Quick::Quick3DEntity{86} [ T <renderingStyle: forward> ]

This shows the active technique (desktop OpenGL on macOS); the technique filter used in the frame graph (QForwardRenderer is derived from QTechniqueFilter); the details of which matching techniques are assigned to materials.

So, once you’ve double checked the camera settings, shaders and your model settings, go again and you should be bright as rain!