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