- Qt 6.11
- Qt Quick 3D
- Qt Quick 3D Examples and Tutorials
- Qt Quick 3D - User Passes Example
- Qt Quick 3D | Commercial or GPLv3

# Qt Quick 3D - User Passes Example

Demonstrates creating custom render passes in Qt Quick 3D.

}

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
         castsShadows: false
         receivesShadows: false
         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: Texture {
             textureProvider: mainColorTextureProvider
         }
     }

     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