On this page

Qt Quick 3D - User Passes Example

Demonstrates creating custom render passes in Qt Quick 3D.

A 3D scene rendered using custom user passes

}

The User Passes example demonstrates how to create custom render passes in Qt Quick 3D. It shows a simple 3D scene rendered using multiple user-defined render passes to achieve a simplined deferred lighting technique.

Disabling internal render passes

By default, Qt Quick 3D uses a set of internal render passes to render the 3D scene. Sometimes you may want to disable these internal passes and implement your own rendering pipeline using user-defined render passes.

To disable the internal render passes, set the renderOverrides property of the View3D to View3D.DisableInternalPasses.

View3D {
    id: view3D
    anchors.fill: parent
    renderOverrides: View3D.DisableInternalPasses
    environment: SceneEnvironment {
        lightProbe: Texture {
            textureData: ProceduralSkyTextureData {
            }
        }
        backgroundMode: SceneEnvironment.SkyBox
    }

If you disable the internal render passes, you will need to provide the result of the main color pass for the View3D to be able to display anything on the screen.

Geometry Buffer Pass

In this example, the first custom render pass is a geometry buffer (G-buffer) pass that renders the scene geometry into multiple render targets, storing different material properties in each target. The provided example is a subset of the full material properties provided by Qt Quick 3D materials, focusing on the properties needed for a basic deferred lighting implementation.

Our RenderPass is defined in GBufferPass.qml:

RenderPass {
    id: gbufferPass
    clearColor: Qt.rgba(0.0, 0.0, 0.0, 0.0)

    property alias layerMask: filter.layerMask
    required property RenderPassTexture depthTexture

    RenderPassTexture {
        id: gbuffer0
        format: RenderPassTexture.RGBA16F
        // rgb: baseColor (linear), a: metalness
    }

    RenderPassTexture {
        id: gbuffer1
        format: RenderPassTexture.RGBA16F
        // rgb: normal, a: roughness
    }

    RenderPassTexture {
        id: gbuffer2
        format: RenderPassTexture.RGBA16F
        // rgb: emissive, a: ao/spare
    }

    commands: [
        ColorAttachment { target: gbuffer0; name: "GBUFFER0" },
        ColorAttachment { target: gbuffer1; name: "GBUFFER1" },
        ColorAttachment { target: gbuffer2; name: "GBUFFER2" },
        DepthTextureAttachment { target: gbufferPass.depthTexture },
        RenderablesFilter {
            id: filter
            renderableTypes: RenderablesFilter.Opaque
        }
    ]

    materialMode: RenderPass.AugmentMaterial
    augmentShader: "gbuffer_augment.glsl"
}

It defines 3 color attachments and 1 depth attachment. The pass requires 3 textures which are defined as RenderPassTexture objects inside the RenderPass. These 3 RenderPassTextures are used as the targets for the color attachments of the pass, and the depth attachment uses a depth texture provided from outside the pass.

The RenderPass itself is set to AugmentMaterial mode, which means that it will augment the materials of the rendered objects with additional shader code. The augment shader is provided in the gbuffer_augment.glsl file, which outputs the required material properties to the multiple render targets.

void MAIN_FRAGMENT_AUGMENT()
{
    vec3 baseColor   = BASE_COLOR.rgb;
    float metalness  = METALNESS;
    float roughness  = ROUGHNESS;
    vec3 worldNormal = normalize(WORLD_NORMAL);

    // GBuffer 0: albedo + metalness
    GBUFFER0 = vec4(baseColor, metalness);

    // GBuffer 1: normal (encoded to 0..1) + roughness
    GBUFFER1 = vec4(worldNormal * 0.5 + 0.5, roughness);

    // GBuffer 2: world position
    GBUFFER2 = vec4(qt_varWorldPos, 1.0);
}

Here you see how the base color, metalness, worldNormal, roughness and world position are stored into the 3 color attachments of the G-buffer.

To actually use the G-buffer pass in the rendering pipeline, we need to create an instance of it in Main.qml, and provide the required depth texture:

RenderPassTexture {
    id: mainDepthStencilTexture
    format: RenderPassTexture.Depth24Stencil8
}
GBufferPass {
    id: gbufferPass
    layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
    depthTexture: mainDepthStencilTexture
}

RenderOutputProvider {
    id: gbuffer0Provider
    textureSource: RenderOutputProvider.UserPassTexture
    renderPass: gbufferPass
    attachmentSelector: RenderOutputProvider.Attachment0
}

RenderOutputProvider {
    id: gbuffer1Provider
    textureSource: RenderOutputProvider.UserPassTexture
    renderPass: gbufferPass
    attachmentSelector: RenderOutputProvider.Attachment1
}

RenderOutputProvider {
    id: gbuffer2Provider
    textureSource: RenderOutputProvider.UserPassTexture
    renderPass: gbufferPass
    attachmentSelector: RenderOutputProvider.Attachment2
}

Three instances of RenderPassTexture are created to provide references to the rendered G-buffer textures, which will be used in the subsequent lighting pass.

The layerMask property of the G-buffer pass is set to only render objects that are on ContentLayer.Layer0 and ContentLayer.Layer1. This allows us to control which objects are rendered in the G-buffer pass by setting their layers property accordingly.

Deferred Lighting Pass

The second custom render pass is a deferred lighting pass that uses the data stored in the G-buffer to compute lighting for the scene. This pass renders a full-screen quad that samples the G-buffer textures and applies lighting calculations in the fragment shader.

The deferred lighting pass is defined in Main.qml as follows:

Model {
    id: deferredLightingQuad
    layers: ContentLayer.Layer13
    geometry: PlaneGeometry {
        // geometry doesn't matter, just need 4 verts
        plane: PlaneGeometry.XY
    }
    materials: [
        CustomMaterial {
            id: lightingPassMaterial
            property TextureInput gbuffer0: TextureInput {
                enabled: true
                texture: Texture {
                    textureProvider: gbuffer0Provider
                }
            }
            property TextureInput gbuffer1: TextureInput {
                enabled: true
                texture: Texture {
                    textureProvider: gbuffer1Provider
                }
            }
            property TextureInput gbuffer2: TextureInput {
                enabled: true
                texture: Texture {
                    textureProvider: gbuffer2Provider
                }
            }
            shadingMode: CustomMaterial.Unshaded
            fragmentShader: "lighting.frag"
            vertexShader: "lighting.vert"
        }
    ]
}

RenderPass {
    id: deferredLightingPass

    readonly property Texture gbuffer0: Texture { textureProvider: gbuffer0Provider }
    readonly property Texture gbuffer1: Texture { textureProvider: gbuffer1Provider }
    readonly property Texture gbuffer2: Texture { textureProvider: gbuffer2Provider }

    materialMode: RenderPass.OriginalMaterial

    commands: [
        ColorAttachment { target: mainColorTexture },
        DepthStencilAttachment {},
        RenderablesFilter { layerMask: ContentLayer.Layer13 }
    ]

}

The deferredLightingQuad Model uses a CustomMaterial with vertex and fragment shaders defined in lighting.vert and lighting.frag files. The CustomMaterial has three TextureInput properties that are used to pass the G-buffer textures to the shaders.

The deferredLightingPass RenderPass renders the full-screen quad to the main color texture of the View3D. It uses a RenderablesFilter to only render objects on ContentLayer.Layer13, which is where the deferredLightingQuad Model is placed.

Rendering to the screen

Finally, to display the result of our custom render passes on the screen, we need to ensure that the View3D's main color texture is updated with the result of the deferred lighting pass.

SimpleQuadRenderer {
    texture: mainColorTexture
}

RenderPassTexture {
    id: mainColorTexture
    format: RenderPassTexture.RGBA16F
}

RenderOutputProvider {
    id: mainColorTextureProvider
    textureSource: RenderOutputProvider.UserPassTexture
    renderPass: deferredLightingPass
    attachmentSelector: RenderOutputProvider.Attachment0
}

Here the SimpleQuadRenderer is used to render the main color texture of the View3D using the texture provided by the deferredLightingPass.

This is a highly simplified example of using custom user passes in Qt Quick 3D.

Example project @ code.qt.io

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