Revision control
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\page qtquick3d-userpasses.html
\title User-Defined Render Passes in Qt Quick 3D
\brief Controlling the rendering pipeline with custom render passes
\since 6.11
Qt Quick 3D provides a high-level API for 3D rendering that handles most rendering
details automatically. However, for advanced use cases, applications may need complete
control over the rendering pipeline. User-defined render passes enable this by allowing
applications to disable the internal rendering pipeline and define their own custom passes.
User render passes enable advanced rendering techniques such as:
\list
\li Deferred shading and lighting
\li Multi-pass rendering effects
\li Custom post-processing pipelines
\li Selective rendering with layer-based filtering
\li Screen-space effects (ambient occlusion, reflections, etc.)
\li Custom shadow mapping techniques
\li Debug visualization passes
\endlist
\section1 Levels of Customization
Qt Quick 3D offers three complementary levels of rendering customization, each suited
to different use cases:
\table
\header
\li Level
\li Scope
\li Use Cases
\row
\li \l Effect
\li Post-processing
\li Applies effects after the scene is rendered (blur, color grading, etc.)
\row
\li \l CustomMaterial
\li Per-material shaders
\li Custom vertex and fragment shaders for individual materials
\row
\li \l RenderPass (User Render Passes)
\li Complete pipeline control
\li Deferred rendering, multiple passes, custom render targets
\endtable
User render passes (\l RenderPass) provide the most control, allowing you to either
supplement or completely replace the default rendering pipeline. This complements
\l CustomMaterial and \l Effect: \l CustomMaterial customizes how individual objects
are rendered, while user render passes control the overall rendering strategy and
architecture.
\section1 Using User Render Passes
User render passes can be used in two ways:
\section2 Supplementing Internal Passes
You can add custom \l RenderPass objects alongside the default rendering pipeline without
disabling internal passes. This is useful for rendering to textures that are then used by
\l Effect or \l CustomMaterial, or for creating auxiliary render targets.
\qml
View3D {
// Internal passes still run normally
RenderPassTexture { id: customTexture; format: RenderPassTexture.RGBA16F }
RenderPass {
// Custom pass renders to texture
commands: [
ColorAttachment { target: customTexture },
DepthStencilAttachment { }
]
}
// Use customTexture in an Effect or material
}
\endqml
\section2 Replacing Internal Passes
For complete control over the rendering pipeline, disable Qt Quick 3D's internal rendering
by setting the \l{View3D::renderOverrides}{renderOverrides} property:
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
// Your custom render passes go here
}
\endqml
When internal passes are disabled, Qt Quick 3D will not perform any default rendering.
This means you must:
\list
\li Define at least one \l RenderPass to render your scene
\li Provide the final output texture to display via \l SimpleQuadRenderer or similar mechanism
\li Handle all rendering aspects including depth buffers, transparency, etc.
\endlist
\note Disabling internal passes gives you complete control, but also complete responsibility.
Features like automatic shadow rendering, transparency sorting, and environment reflections
must be implemented in your custom passes if needed.
\section1 Core Concepts
User-defined render passes are built from several key components:
\section2 RenderPass
The \l RenderPass type is the main building block. It defines a single rendering operation
with a set of commands that control what gets rendered and how. Each pass can:
\list
\li Render to one or more color textures (up to 4 simultaneous render targets)
\li Write depth and stencil information
\li Filter which objects to render based on layers
\li Override graphics pipeline state (blending, culling, etc.)
\li Use original materials, augment them with custom shaders, or override them entirely
\endlist
+ \section2 Pass Ordering
+
+ The pass hierarchy determines the render order: nested passes render \e before
+ their parent, so a pass that produces a texture consumed by another pass is
+ declared as a child of its consumer, and results merge towards the top of the
+ hierarchy. A pass's nesting depth is the number of RenderPass ancestors it
+ has; other ancestors, such as \l Node items used for grouping, are transparent
+ and do not affect the order. A deeper-nested pass renders before all passes at
+ shallower depths, so its output is available to every consumer that frame, not
+ only its parent. The relative render order of passes at the same nesting
+ depth is not defined; use nesting to express an ordering requirement.
+
+ Nesting affects ordering only; a nested pass still renders into its own render
+ target, and its output is consumed through a \l RenderOutputProvider. To render
+ into the parent pass's render target instead, reference the pass from a
+ \l SubRenderPass command; such a pass is invoked by its parent and never
+ renders on its own.
+
\section2 RenderPassTexture
The \l RenderPassTexture type defines textures that serve as render targets. These can be
color textures in various formats (RGBA8, RGBA16F, RGBA32F, etc.) or depth/stencil
textures. Render pass textures are used as outputs from one pass and can be used as
texture inputs to subsequent passes.
\section2 RenderOutputProvider
The \l RenderOutputProvider type connects render passes by exposing the output textures
from one pass as texture inputs that can be used by materials or other passes. This is
essential for multi-pass rendering where later passes need to read the results of
earlier passes.
\section2 ContentLayer
The \l ContentLayer singleton provides layer constants (Layer0 through Layer23) used for
filtering which objects render in which pass. By assigning objects to specific layers and
using \l RenderablesFilter in your passes, you can control precisely what gets rendered
in each pass.
\section2 Render Commands
Each \l RenderPass contains a list of commands that configure its behavior:
\list
\li \l ColorAttachment: Specifies a color render target
\li \l DepthStencilAttachment: Specifies depth/stencil handling
\li \l DepthTextureAttachment: Uses a texture for depth output
\li \l RenderablesFilter: Filters objects by layer and type (opaque/transparent)
\li \l PipelineStateOverride: Controls graphics pipeline state
\li \c SubRenderPass: Executes another render pass within this pass
\li \l AddDefine: Adds shader preprocessor defines
\endlist
\section1 Material Modes
Each \l RenderPass has a \l{RenderPass::materialMode}{materialMode} property that controls
how materials are handled during rendering. The three modes offer different levels of
material control:
\section2 OriginalMaterial Mode
This mode renders objects with their assigned materials unchanged. It's useful when you
want to control the rendering pipeline structure (multiple passes, custom render targets)
but keep the material behavior standard.
\qml
RenderPass {
materialMode: RenderPass.OriginalMaterial
commands: [
ColorAttachment { target: myColorTexture },
DepthStencilAttachment { }
]
}
\endqml
\section2 AugmentMaterial Mode
This mode injects custom shader code into the existing material pipeline. It's particularly
useful for deferred rendering where you need to output additional data (like normals,
positions, etc.) to multiple render targets while preserving the material's base behavior.
\qml
RenderPass {
materialMode: RenderPass.AugmentMaterial
augmentShader: "my_augment.glsl"
commands: [
ColorAttachment { target: gbuffer0; name: "GBUFFER0" },
ColorAttachment { target: gbuffer1; name: "GBUFFER1" },
DepthStencilAttachment { }
]
}
\endqml
The augment shader file contains a \c{MAIN_FRAGMENT_AUGMENT()} function:
\badcode
void MAIN_FRAGMENT_AUGMENT()
{
// Access material properties
vec3 color = BASE_COLOR.rgb;
float metal = METALNESS;
float rough = ROUGHNESS;
vec3 normal = normalize(WORLD_NORMAL);
// Write to multiple render targets
GBUFFER0 = vec4(color, metal);
GBUFFER1 = vec4(normal * 0.5 + 0.5, rough);
}
\endcode
See \l{Augment Shaders for Multiple Render Targets} for more details.
\section2 OverrideMaterial Mode
This mode replaces all object materials with a single material. It's useful for specialized
passes like shadow mapping, depth prepass, or debug visualization.
\qml
RenderPass {
materialMode: RenderPass.OverrideMaterial
overrideMaterial: CustomMaterial {
fragmentShader: "depth_only.frag"
// All objects will use this material
}
commands: [
DepthTextureAttachment { target: depthTexture }
]
}
\endqml
\section1 Render Pass Commands
Commands are specified in the \l{RenderPass::commands}{commands} property and execute
in the order they are defined.
\section2 Color Attachment
The \l ColorAttachment command specifies a color render target. The \c name property
defines how the attachment is accessed in augment shaders.
\qml
ColorAttachment {
target: myTexture // RenderPassTexture to render to
name: "GBUFFER0" // Name for shader access (optional)
}
\endqml
You can have up to 4 color attachments per pass (for multiple render targets).
\section2 Depth Attachments
There are two ways to handle depth:
\b{DepthStencilAttachment} uses an implicit depth/stencil buffer:
\qml
DepthStencilAttachment { } // Creates depth/stencil buffer automatically
\endqml
\b{DepthTextureAttachment} uses an explicit texture for depth output:
\qml
RenderPassTexture {
id: depthTex
format: RenderPassTexture.Depth24Stencil8
}
DepthTextureAttachment {
target: depthTex // Explicit depth texture
}
\endqml
Use \l DepthTextureAttachment when you need to share the depth buffer between multiple
passes or use it as a texture input in shaders.
\section2 Renderables Filter
The \l RenderablesFilter command controls which objects are rendered based on their layer
assignment and renderable type.
\qml
RenderablesFilter {
layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
renderableTypes: RenderablesFilter.Opaque
}
\endqml
The \c layerMask property uses bitwise OR to combine layers. The \c renderableTypes
property can be:
\list
\li \c RenderablesFilter.Opaque: Render only opaque objects
\li \c RenderablesFilter.Transparent: Render only transparent objects
\li \c RenderablesFilter.Opaque | RenderablesFilter.Transparent: Render both
\endlist
\section2 Pipeline State Override
The \l PipelineStateOverride command provides fine-grained control over graphics pipeline
state. It can override depth testing, blending, culling, polygon mode, and more.
\qml
PipelineStateOverride {
depthTestEnabled: true
depthWriteEnabled: false
blendEnabled: true
cullMode: PipelineStateOverride.Back
polygonMode: PipelineStateOverride.Fill
}
\endqml
See \l{Pipeline State Control} for more examples.
\section2 Sub Render Pass
The \c SubRenderPass command allows hierarchical composition of render passes by executing
another render pass within the current pass.
\qml
RenderPass {
id: parentPass
commands: [
ColorAttachment { target: colorTex },
SubRenderPass { renderPass: childPass },
// More commands after child pass
]
}
RenderPass {
id: childPass
// This pass executes within parentPass
}
\endqml
\section2 Add Define
The \l AddDefine command adds shader preprocessor defines that affect shader compilation.
\qml
AddDefine {
name: "USE_SPECIAL_MODE"
value: 1
}
\endqml
\section1 Simple Example: Single Pass Rendering
Here's a minimal example showing user-defined render passes:
\qml
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
View3D {
anchors.fill: parent
renderOverrides: View3D.DisableInternalPasses
// Camera and lights
PerspectiveCamera { z: 300 }
DirectionalLight { }
// Define render target texture
RenderPassTexture {
id: colorTarget
format: RenderPassTexture.RGBA16F
}
// Define the render pass
RenderPass {
id: mainPass
materialMode: RenderPass.OriginalMaterial
clearColor: "skyblue"
commands: [
ColorAttachment { target: colorTarget },
DepthStencilAttachment { }
]
}
// Display the result
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: mainPass
attachmentSelector: RenderOutputProvider.Attachment0
}
}
}
// Scene content
Model {
source: "#Sphere"
materials: PrincipledMaterial {
baseColor: "red"
metalness: 0.0
roughness: 0.3
}
}
}
\endqml
This example:
\list 1
\li Disables internal passes
\li Creates a render target texture (colorTarget)
\li Defines a render pass that renders to that texture
\li Uses RenderOutputProvider to expose the texture
\li Displays the result with SimpleQuadRenderer
\li Renders a sphere with standard material
\endlist
\section1 Working with Layers
The \l ContentLayer singleton provides constants for organizing objects into layers,
allowing fine-grained control over what renders in each pass.
\section2 Layer Constants
Qt Quick 3D provides 24 user-assignable layers:
\list
\li \c ContentLayer.Layer0 through \c ContentLayer.Layer23: Individual layers
\li \c ContentLayer.LayerAll: All user layers combined
\li \c ContentLayer.LayerNone: No layers
\endlist
\note Layers 24-31 are reserved for internal use.
\section2 Assigning Objects to Layers
Assign objects to layers using the \l{Model::layers}{layers} property:
\qml
Model {
source: "#Cube"
layers: ContentLayer.Layer0
materials: PrincipledMaterial { baseColor: "red" }
}
Model {
source: "#Sphere"
layers: ContentLayer.Layer1 | ContentLayer.Layer2
materials: PrincipledMaterial { baseColor: "blue" }
}
\endqml
Objects can belong to multiple layers using bitwise OR.
\section2 Filtering by Layer
Use \l RenderablesFilter in your render passes to select which layers to render:
\qml
RenderPass {
id: pass1
commands: [
ColorAttachment { target: texture1 },
RenderablesFilter {
layerMask: ContentLayer.Layer0
}
]
// Only renders objects on Layer0 (red cube)
}
RenderPass {
id: pass2
commands: [
ColorAttachment { target: texture2 },
RenderablesFilter {
layerMask: ContentLayer.Layer1 | ContentLayer.Layer2
}
]
// Only renders objects on Layer1 or Layer2 (blue sphere)
}
\endqml
\section2 Use Case: Selective Rendering
A practical example is rendering certain objects as wireframe overlays:
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
RenderPassTexture { id: colorTex; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: depthTex; format: RenderPassTexture.Depth24Stencil8 }
// Pass 1: Render solid objects
RenderPass {
id: solidPass
commands: [
ColorAttachment { target: colorTex },
DepthTextureAttachment { target: depthTex },
RenderablesFilter { layerMask: ContentLayer.Layer0 }
]
}
// Pass 2: Render wireframe overlay
RenderPass {
id: wireframePass
commands: [
ColorAttachment { target: colorTex },
DepthTextureAttachment { target: depthTex },
PipelineStateOverride {
polygonMode: PipelineStateOverride.Line
depthTestEnabled: true
depthWriteEnabled: false
},
RenderablesFilter { layerMask: ContentLayer.Layer1 }
]
}
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: wireframePass
attachmentSelector: RenderOutputProvider.Attachment0
}
}
}
Model {
source: "#Sphere"
layers: ContentLayer.Layer0 // Rendered solid
materials: PrincipledMaterial { baseColor: "blue" }
}
Model {
source: "#Sphere"
layers: ContentLayer.Layer1 // Rendered as wireframe
materials: PrincipledMaterial { baseColor: "yellow" }
}
}
\endqml
\section1 Texture Management
User render passes rely heavily on textures both as render targets and as inputs to
subsequent passes or materials.
\section2 Render Pass Texture Formats
\l RenderPassTexture supports various formats for different use cases:
\b{Color Formats:}
\table
\header
\li Format
\li Description
\li Use Case
\row
\li RGBA8
\li 8-bit per channel
\li Standard color output, lower memory usage
\row
\li RGBA16F
\li 16-bit floating point
\li HDR rendering, intermediate buffers
\row
\li RGBA32F
\li 32-bit floating point
\li High precision calculations
\row
\li R8, R16, R16F, R32F
\li Single channel variants
\li Grayscale data, specialized buffers
\endtable
\b{Depth Formats:}
\table
\header
\li Format
\li Description
\row
\li Depth16
\li 16-bit depth
\row
\li Depth24
\li 24-bit depth
\row
\li Depth32
\li 32-bit depth
\row
\li Depth24Stencil8
\li 24-bit depth + 8-bit stencil
\endtable
Example texture definitions:
\qml
RenderPassTexture {
id: hdrColorBuffer
format: RenderPassTexture.RGBA16F // HDR color
}
RenderPassTexture {
id: depthBuffer
format: RenderPassTexture.Depth24Stencil8 // Depth + stencil
}
RenderPassTexture {
id: normalBuffer
format: RenderPassTexture.RGBA16F // Store normals
}
\endqml
\section2 Sharing Textures Between Passes
Multiple passes can share the same depth texture, allowing depth testing across passes:
\qml
RenderPassTexture {
id: sharedDepth
format: RenderPassTexture.Depth24Stencil8
}
RenderPass {
id: geometryPass
commands: [
ColorAttachment { target: colorTex1 },
DepthTextureAttachment { target: sharedDepth }
]
}
RenderPass {
id: transparentPass
renderTargetFlags: RenderPass.PreserveDepthStencilContents
commands: [
ColorAttachment { target: colorTex2 },
DepthTextureAttachment { target: sharedDepth }
// Uses depth from geometryPass for depth testing
]
}
\endqml
\section2 Render Output Provider
The \l RenderOutputProvider exposes render pass outputs as \l Texture inputs:
\qml
// Define a render pass with color output
RenderPass {
id: firstPass
commands: [
ColorAttachment { target: intermediateTexture }
]
}
// Expose its output
RenderOutputProvider {
id: intermediateProvider
textureSource: RenderOutputProvider.UserPassTexture
renderPass: firstPass
attachmentSelector: RenderOutputProvider.Attachment0
}
// Use in a material
CustomMaterial {
property TextureInput inputTex: TextureInput {
texture: Texture { textureProvider: intermediateProvider }
}
fragmentShader: "process.frag"
}
\endqml
The \c attachmentSelector property specifies which color attachment to use when a pass
has multiple render targets:
\list
\li \c RenderOutputProvider.Attachment0: First color attachment
\li \c RenderOutputProvider.Attachment1: Second color attachment
\li \c RenderOutputProvider.Attachment2: Third color attachment
\li \c RenderOutputProvider.Attachment3: Fourth color attachment
\endlist
\section2 Pass Chaining Example
- Multiple passes can be chained together where each pass processes the output of the
- previous pass:
+ Multiple passes can be chained together where each pass processes the output of
+ the previous pass. Each producer is nested inside its consumer, so it renders
+ first (see \l{Pass Ordering}):
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
// Intermediate textures
RenderPassTexture { id: tex0; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: tex1; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: tex2; format: RenderPassTexture.RGBA16F }
- // Pass 1: Render scene
- RenderPass {
- id: scenePass
- commands: [
- ColorAttachment { target: tex0 },
- DepthStencilAttachment { }
- ]
- }
-
- // Pass 2: Process tex0 -> tex1
- RenderPass {
- id: process1
- materialMode: RenderPass.OriginalMaterial
- commands: [
- ColorAttachment { target: tex1 }
- ]
- }
-
- Model {
- layers: ContentLayer.Layer10
- geometry: PlaneGeometry { }
- materials: CustomMaterial {
- property TextureInput input: TextureInput {
- texture: Texture {
- textureProvider: RenderOutputProvider {
- textureSource: RenderOutputProvider.UserPassTexture
- renderPass: scenePass
- }
- }
- }
- fragmentShader: "effect1.frag"
- }
- }
-
// Pass 3: Process tex1 -> tex2
RenderPass {
id: process2
commands: [
ColorAttachment { target: tex2 },
RenderablesFilter { layerMask: ContentLayer.Layer11 }
]
+
+ // Pass 2: Process tex0 -> tex1; renders before process2
+ RenderPass {
+ id: process1
+ materialMode: RenderPass.OriginalMaterial
+ commands: [
+ ColorAttachment { target: tex1 }
+ ]
+
+ // Pass 1: Render scene; renders before process1
+ RenderPass {
+ id: scenePass
+ commands: [
+ ColorAttachment { target: tex0 },
+ DepthStencilAttachment { }
+ ]
+ }
+ }
+ }
+
+ Model {
+ layers: ContentLayer.Layer10
+ geometry: PlaneGeometry { }
+ materials: CustomMaterial {
+ property TextureInput input: TextureInput {
+ texture: Texture {
+ textureProvider: RenderOutputProvider {
+ textureSource: RenderOutputProvider.UserPassTexture
+ renderPass: scenePass
+ }
+ }
+ }
+ fragmentShader: "effect1.frag"
+ }
}
Model {
layers: ContentLayer.Layer11
geometry: PlaneGeometry { }
materials: CustomMaterial {
property TextureInput input: TextureInput {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: process1
}
}
}
fragmentShader: "effect2.frag"
}
}
// Display final result
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: process2
}
}
}
}
\endqml
\section1 Pipeline State Control
The \l PipelineStateOverride command provides detailed control over graphics pipeline state,
allowing customization of depth testing, blending, culling, and more.
\section2 Depth Testing and Writing
Control how depth values are tested and written:
\qml
PipelineStateOverride {
depthTestEnabled: true
depthWriteEnabled: true
depthFunction: PipelineStateOverride.LessOrEqual
}
\endqml
The \c depthFunction property can be:
\list
\li \c Never, \c Less, \c Equal, \c LessOrEqual
\li \c Greater, \c NotEqual, \c GreaterOrEqual, \c Always
\endlist
\section2 Blending
Enable and configure blending for transparency:
\qml
PipelineStateOverride {
blendEnabled: true
// Uses default blend mode (source alpha blending)
}
\endqml
For multiple render targets, use per-target blend states. \l PipelineStateOverride
exposes the per-attachment value-type properties \c targetBlend0 through
\c targetBlend7 (of type \l renderTargetBlend) and the blend factor and
operation enums are in the \c RenderTargetBlend namespace:
\qml
PipelineStateOverride {
targetBlend0.enable: true
targetBlend0.srcColor: RenderTargetBlend.SrcAlpha
targetBlend0.dstColor: RenderTargetBlend.OneMinusSrcAlpha
targetBlend0.opColor: RenderTargetBlend.Add
targetBlend1.enable: false // No blending for attachment 1
}
\endqml
\section2 Culling
Control face culling:
\qml
PipelineStateOverride {
cullMode: PipelineStateOverride.Back // Override material's cull mode to Back
}
\endqml
Setting \c cullMode here overrides the value the material would otherwise specify
for this pass. Options: \c None (no culling), \c Front (cull front faces),
\c Back (cull back faces).
\section2 Wireframe Rendering
Render geometry as wireframes:
\qml
PipelineStateOverride {
polygonMode: PipelineStateOverride.Line
cullMode: PipelineStateOverride.None // Show both sides
}
\endqml
The \c polygonMode can be \c Fill (default) or \c Line (wireframe).
\section2 Scissor Rectangles
Limit rendering to a rectangular region:
\qml
PipelineStateOverride {
usesScissor: true
scissor: Qt.rect(100, 100, 400, 300) // x, y, width, height
}
\endqml
\section2 Viewport Control
Override the viewport:
\qml
PipelineStateOverride {
viewport: Qt.rect(0, 0, 800, 600)
}
\endqml
\section2 Complete Example: Wireframe Overlay
This example renders a scene normally, then overlays a wireframe version:
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
RenderPassTexture { id: color; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: depth; format: RenderPassTexture.Depth24Stencil8 }
// Solid pass
RenderPass {
id: solidPass
clearColor: "black"
commands: [
ColorAttachment { target: color },
DepthTextureAttachment { target: depth },
RenderablesFilter {
layerMask: ContentLayer.Layer0
renderableTypes: RenderablesFilter.Opaque
}
]
}
// Wireframe overlay
RenderPass {
id: wirePass
renderTargetFlags: RenderPass.PreserveColorContents |
RenderPass.PreserveDepthStencilContents
commands: [
ColorAttachment { target: color },
DepthTextureAttachment { target: depth },
PipelineStateOverride {
polygonMode: PipelineStateOverride.Line
depthTestEnabled: true
depthWriteEnabled: false
blendEnabled: true
},
RenderablesFilter { layerMask: ContentLayer.Layer0 }
]
}
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: wirePass
}
}
}
PerspectiveCamera { z: 300 }
DirectionalLight { }
Model {
source: "#Sphere"
layers: ContentLayer.Layer0
materials: PrincipledMaterial {
baseColor: "blue"
metalness: 0.5
roughness: 0.3
}
}
}
\endqml
\section1 Augment Shaders for Multiple Render Targets
When using \c{RenderPass.AugmentMaterial} mode, you provide an augment shader that injects
custom code into the material's fragment shader. This is particularly useful for deferred
rendering where you need to output material data to multiple render targets (MRT).
\section2 The MAIN_FRAGMENT_AUGMENT Function
Your augment shader file must define a \c{MAIN_FRAGMENT_AUGMENT()} function:
\badcode
void MAIN_FRAGMENT_AUGMENT()
{
// Your custom shader code here
}
\endcode
This function is called during the fragment shader after material calculations are complete,
giving you access to material properties and the ability to write to custom outputs.
\section2 Available Built-in Variables
Inside \c{MAIN_FRAGMENT_AUGMENT()}, the engine substitutes a set of macros
that expose material pipeline data. Only the macros listed below are part of
the augment shader API:
\table
\header
\li Macro
\li Type
\li Description
\row
\li \c BASE_COLOR
\li vec4
\li Material base color (linear color space, after material processing).
\row
\li \c METALNESS
\li float
\li Material metalness in the range 0.0 to 1.0.
\row
\li \c ROUGHNESS
\li float
\li Material roughness in the range 0.0 to 1.0.
\row
\li \c WORLD_NORMAL
\li vec3
\li World-space surface normal (post normal-mapping).
\row
\li \c WORLD_TANGENT
\li vec3
\li World-space tangent vector.
\row
\li \c WORLD_BINORMAL
\li vec3
\li World-space binormal vector.
\row
\li \c DIFFUSE_LIGHT
\li vec3
\li Accumulated diffuse light contribution.
\row
\li \c SPECULAR_LIGHT
\li vec3
\li Accumulated specular light contribution.
\row
\li \c EMISSIVE_LIGHT
\li vec3
\li Material emissive contribution.
\row
\li \c F0
\li vec3
\li Fresnel reflectance at normal incidence.
\row
\li \c F90
\li vec3
\li Fresnel reflectance at grazing incidence.
\endtable
\note Some examples (including the deferred-rendering one below) read the
world-space fragment position via \c qt_varWorldPos. This is the engine's
underlying varying name, not an augment-shader macro, and falls into the
same \e {semi-public} category as the \c .glsllib files described in
\l{Built-in Shader Facilities}: it works in practice but is not guaranteed
to remain stable across releases.
\section2 Writing to Named Outputs
Color attachments in your render pass can have names that correspond to output variables
in your shader:
\qml
RenderPass {
commands: [
ColorAttachment { target: tex0; name: "GBUFFER0" },
ColorAttachment { target: tex1; name: "GBUFFER1" },
ColorAttachment { target: tex2; name: "GBUFFER2" }
]
}
\endqml
In your augment shader:
\badcode
void MAIN_FRAGMENT_AUGMENT()
{
GBUFFER0 = vec4(...); // Writes to first attachment
GBUFFER1 = vec4(...); // Writes to second attachment
GBUFFER2 = vec4(...); // Writes to third attachment
}
\endcode
Qt Quick 3D supports up to 4 simultaneous color attachments (GBUFFER0 through GBUFFER3).
\section2 Complete Augment Shader Example
Here's a complete example for a deferred rendering G-buffer pass:
\badcode
// gbuffer_augment.glsl
void MAIN_FRAGMENT_AUGMENT()
{
// Get material properties
vec3 baseColor = BASE_COLOR.rgb;
float metalness = METALNESS;
float roughness = ROUGHNESS;
vec3 worldNormal = normalize(WORLD_NORMAL);
vec3 worldPos = qt_varWorldPos;
// GBuffer 0: Albedo (RGB) + Metalness (A)
GBUFFER0 = vec4(baseColor, metalness);
// GBuffer 1: World Normal (RGB) + Roughness (A)
// Encode normal from [-1,1] to [0,1] for storage
GBUFFER1 = vec4(worldNormal * 0.5 + 0.5, roughness);
// GBuffer 2: World Position
GBUFFER2 = vec4(worldPos, 1.0);
}
\endcode
Used with a render pass (\c RenderPassTexture instances are declared as
direct children of the enclosing \c View3D and referenced by id):
\qml
View3D {
RenderPassTexture { id: gbuffer0; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: gbuffer1; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: gbuffer2; format: RenderPassTexture.RGBA16F }
RenderPass {
id: gbufferPass
materialMode: RenderPass.AugmentMaterial
augmentShader: "gbuffer_augment.glsl"
commands: [
ColorAttachment { target: gbuffer0; name: "GBUFFER0" },
ColorAttachment { target: gbuffer1; name: "GBUFFER1" },
ColorAttachment { target: gbuffer2; name: "GBUFFER2" },
DepthStencilAttachment { }
]
}
}
\endqml
\section2 Preserving Material Behavior
The augment shader runs \e{in addition to} the normal material pipeline, not instead of it.
This means:
\list
\li The material's textures, properties, and calculations still occur
\li You're adding extra outputs, not replacing the primary color output
\li The original material's RGBA output is still written to the first color attachment unless overridden
\endlist
If you only need to output custom data and don't need the standard material calculations,
consider using \c{RenderPass.OverrideMaterial} instead.
\section2 When to Use Augment vs Override
Use \c AugmentMaterial when:
\list
\li You need material properties (metalness, roughness, normals) for G-buffers
\li You want to leverage existing material features like texture mapping
\li You need multiple render targets with material data
\endlist
Use \c OverrideMaterial when:
\list
\li You need identical behavior for all objects (depth prepass, shadow maps)
\li You don't need per-material properties
\li You want maximum performance by bypassing material calculations
\endlist
\section2 Built-in Shader Facilities
Augment shaders and custom materials used in render passes have access to Qt Quick 3D's
built-in shader infrastructure through an include system. These shader library files
(\c{.glsllib}) provide functionality for lighting, shadows, tonemapping, and more.
\b{Include Syntax:}
Use \c{\#include} directives in your shader code to import functionality:
\badcode
#include "tonemapping.glsllib"
#include "lightsData.glsllib"
#include "shadowMapping.glsllib"
#include "funcprocessPunctualLighting.glsllib"
void MAIN()
{
// Use included functions
vec3 color = qt_tonemap(hdrColor);
}
\endcode
\b{Available Shader Libraries:}
These shader libraries are located in the engine at \c{src/runtimerender/res/effectlib/}
and are considered \e{semi-public}: they are usable in user shaders but do not have the
same binary compatibility guarantees as the rest of Qt's public API.
\table
\header
\li Library
\li Description
\row
\li \c{tonemapping.glsllib}
\li Provides \c{qt_tonemap()} function for HDR to LDR conversion using the built-in
tonemapping algorithm
\row
\li \c{lightsData.glsllib}
\li Contains scene lighting information (light positions, colors, directions, etc.)
that can be accessed in custom lighting calculations
\row
\li \c{shadowMapping.glsllib}
\li Provides functions for sampling from the default shadow maps, enabling custom
materials to receive shadows
\row
\li \c{funcprocessPunctualLighting.glsllib}
\li Contains \c{qt_processPunctualLighting()} and other built-in lighting functions
for standard PBR lighting calculations
\row
\li \c{sampleProbe.glsllib}
\li Functions for sampling image-based lighting probes (\c{qt_sampleDiffuse()},
\c{qt_sampleGlossyPrincipled()})
\endtable
\b{Example: Custom Lighting with Built-in Functions}
Here's an example fragment shader using the built-in lighting facilities:
\badcode
#include "lightsData.glsllib"
#include "funcprocessPunctualLighting.glsllib"
#include "tonemapping.glsllib"
#include "sampleProbe.glsllib"
void MAIN()
{
vec3 worldPos = ...; // From G-buffer or varying
vec3 normal = ...;
vec3 viewDir = normalize(CAMERA_POSITION - worldPos);
vec3 baseColor = ...;
float roughness = ...;
float metalness = ...;
vec3 F0 = mix(vec3(0.04), baseColor, metalness);
vec3 diffuseAccum = vec3(0.0);
vec3 specAccum = vec3(0.0);
// Use built-in punctual lighting (directional, point, spot lights)
qt_processPunctualLighting(diffuseAccum,
specAccum,
baseColor,
worldPos,
normal,
viewDir,
vec3(1.0), // specularAmount
vec3(1.0), // specularTint
roughness,
metalness,
F0,
vec3(1.0)); // F90
// Add image-based lighting
vec4 probeDiffuse = vec4(baseColor, 1.0) * qt_sampleDiffuse(normal);
vec4 probeSpecular = qt_sampleGlossyPrincipled(normal, viewDir, F0, roughness);
diffuseAccum += probeDiffuse.rgb;
specAccum += probeSpecular.rgb;
vec3 color = diffuseAccum + specAccum;
// Apply tonemapping
FRAGCOLOR = vec4(qt_tonemap(color), 1.0);
}
\endcode
\note These shader libraries are semi-public API. While they are stable and intended for
use in custom shaders, Qt does not guarantee that their internal implementation or
available functions will remain unchanged between releases. However, commonly used functions
like \c{qt_tonemap()} and \c{qt_processPunctualLighting()} are unlikely to change
significantly.
\section1 Advanced Example: Deferred Rendering
Deferred rendering is a technique where geometry information is rendered to multiple textures
(called a G-buffer or geometry buffer) in a first pass, and lighting calculations are performed
in a second pass using the stored geometry data. This approach is particularly efficient when
you have many lights, as each pixel is shaded only once regardless of the number of lights.
\section2 Deferred Rendering Architecture
The deferred rendering pipeline consists of two main passes:
\list 1
\li \b{Geometry Pass (G-Buffer Pass)}: Renders scene geometry to multiple render targets,
storing material properties such as albedo, normals, roughness, metalness, and world position.
\li \b{Lighting Pass}: Renders a full-screen quad that samples the G-buffers and performs
lighting calculations for each pixel based on the stored geometry data.
\endlist
\section2 G-Buffer Pass Implementation
First, define a G-buffer pass that outputs material data to multiple render targets:
\b{GBufferPass.qml:} The G-buffer render targets are exposed as required
properties so the enclosing \c View3D supplies them and can read their
outputs:
\qml
import QtQuick
import QtQuick3D
RenderPass {
id: gbufferPass
clearColor: Qt.rgba(0.0, 0.0, 0.0, 0.0)
property alias layerMask: filter.layerMask
// Provided by the View3D that uses this pass
required property RenderPassTexture gbuffer0 // rgb: baseColor, a: metalness
required property RenderPassTexture gbuffer1 // rgb: normal, a: roughness
required property RenderPassTexture gbuffer2 // rgb: world pos, a: spare
required property RenderPassTexture depthTexture
materialMode: RenderPass.AugmentMaterial
augmentShader: "gbuffer_augment.glsl"
commands: [
ColorAttachment { target: gbufferPass.gbuffer0; name: "GBUFFER0" },
ColorAttachment { target: gbufferPass.gbuffer1; name: "GBUFFER1" },
ColorAttachment { target: gbufferPass.gbuffer2; name: "GBUFFER2" },
DepthTextureAttachment { target: gbufferPass.depthTexture },
RenderablesFilter {
id: filter
renderableTypes: RenderablesFilter.Opaque
}
]
}
\endqml
\b{gbuffer_augment.glsl:}
\badcode
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);
}
\endcode
The augment shader accesses material properties computed by the material pipeline and
writes them to the three G-buffer attachments. Normals are encoded from [-1,1] to [0,1]
range for storage.
\section2 Lighting Pass Implementation
The lighting pass renders a full-screen quad that samples the G-buffers and computes lighting:
\qml
// Lighting pass model (full-screen quad)
Model {
id: deferredLightingQuad
layers: ContentLayer.Layer13 // Dedicated layer for lighting quad
geometry: PlaneGeometry {
plane: PlaneGeometry.XY // Quad in screen space
}
materials: CustomMaterial {
// Texture inputs for G-buffers
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"
}
}
// Lighting pass renders the quad to main output
RenderPass {
id: deferredLightingPass
materialMode: RenderPass.OriginalMaterial
commands: [
ColorAttachment { target: mainColorTexture },
DepthStencilAttachment { },
RenderablesFilter { layerMask: ContentLayer.Layer13 }
]
}
\endqml
The lighting vertex shader (\c lighting.vert) creates a full-screen quad in normalized
device coordinates, and the fragment shader (\c lighting.frag) samples the G-buffers and
performs lighting calculations.
\section2 Complete Integration
Here's how to integrate both passes in a View3D:
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
// Main output texture
RenderPassTexture { id: mainColorTexture; format: RenderPassTexture.RGBA16F }
// Shared depth texture
RenderPassTexture { id: mainDepthStencilTexture; format: RenderPassTexture.Depth24Stencil8 }
// G-buffer render targets
RenderPassTexture { id: gbuffer0Tex; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: gbuffer1Tex; format: RenderPassTexture.RGBA16F }
RenderPassTexture { id: gbuffer2Tex; format: RenderPassTexture.RGBA16F }
- // G-buffer pass
- GBufferPass {
- id: gbufferPass
- layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
- gbuffer0: gbuffer0Tex
- gbuffer1: gbuffer1Tex
- gbuffer2: gbuffer2Tex
- depthTexture: mainDepthStencilTexture
+ // Lighting pass (commands as defined above). The G-buffer pass is nested
+ // inside its consumer, so it renders first and the lighting quad samples
+ // this frame's G-buffers.
+ RenderPass {
+ id: deferredLightingPass
+ materialMode: RenderPass.OriginalMaterial
+ commands: [
+ ColorAttachment { target: mainColorTexture },
+ DepthStencilAttachment { },
+ RenderablesFilter { layerMask: ContentLayer.Layer13 }
+ ]
+
+ // G-buffer pass
+ GBufferPass {
+ id: gbufferPass
+ layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
+ gbuffer0: gbuffer0Tex
+ gbuffer1: gbuffer1Tex
+ gbuffer2: gbuffer2Tex
+ depthTexture: mainDepthStencilTexture
+ }
}
// Expose G-buffer outputs
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
}
- // Lighting pass (defined above)
-
// Display final result
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: deferredLightingPass
attachmentSelector: RenderOutputProvider.Attachment0
}
}
}
// Scene objects
Model {
layers: ContentLayer.Layer0
source: "#Sphere"
materials: PrincipledMaterial {
baseColor: "red"
metalness: 0.5
roughness: 0.3
}
}
// Camera and lights
PerspectiveCamera { z: 300 }
DirectionalLight { eulerRotation.x: -45 }
}
\endqml
\section2 Rendering Flow
The rendering proceeds as follows:
\list 1
- \li \b{G-buffer Pass}: Scene objects on Layer0 and Layer1 are rendered. For each object,
+ \li \b{G-buffer Pass}: Renders first because it is nested inside the lighting
+ pass. Scene objects on Layer0 and Layer1 are rendered. For each object,
the augment shader writes albedo, normals, roughness, metalness, and position to three
color attachments. Depth is written to the shared depth texture.
\li \b{Lighting Pass}: The full-screen quad on Layer13 is rendered. Its custom material
samples the three G-buffer textures and the depth texture, reconstructs the scene
information, and performs lighting calculations (directional lights, image-based lighting,
etc.) to produce the final lit color.
\li \b{Display}: The result of the lighting pass is displayed via SimpleQuadRenderer.
\endlist
\section2 Advantages and Limitations
\b{Advantages:}
\list
\li Efficient with many lights (each pixel lit once)
\li Lighting complexity independent of scene complexity
\li Easy to implement screen-space effects
\li Decouples geometry and lighting passes
\endlist
\b{Limitations:}
\list
\li Higher memory bandwidth due to G-buffer reads/writes
\li No hardware MSAA (requires alternative anti-aliasing)
\li Transparency requires separate forward pass
\li More complex pipeline to set up and maintain
\endlist
For a complete working example, see \l{Qt Quick 3D - User Passes Example}.
\section1 Complete Rendering Pipeline Example
A realistic custom rendering pipeline often needs to combine multiple pass types: opaque
geometry, skybox background, 2D overlays, and transparent objects. Here's a complete example
showing how to organize these using \c SubRenderPass for hierarchical composition:
\qml
View3D {
renderOverrides: View3D.DisableInternalPasses
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData { }
}
}
RenderPassTexture {
id: mainColorTexture
format: RenderPassTexture.RGBA16F
}
RenderPassTexture {
id: mainDepthStencilTexture
format: RenderPassTexture.Depth24Stencil8
}
// Main pass orchestrates the complete render path:
// 1. Opaque geometry (deferred)
// 2. Skybox background
// 3. 2D UI overlays
// 4. Transparent objects (forward)
RenderPass {
id: mainColorPass
clearColor: "black"
renderTargetFlags: RenderPass.PreserveDepthStencilContents
commands: [
ColorAttachment { target: mainColorTexture },
DepthTextureAttachment { target: mainDepthStencilTexture },
RenderablesFilter {
renderableTypes: RenderablesFilter.None // Parent doesn't render
},
// Sub-pass 1: Deferred lighting
SubRenderPass {
renderPass: RenderPass {
id: deferredLightingPass
materialMode: RenderPass.OriginalMaterial
commands: [
PipelineStateOverride {
depthWriteEnabled: false
depthTestEnabled: false
},
RenderablesFilter { layerMask: ContentLayer.Layer13 }
]
}
},
// Sub-pass 2: Skybox (behind everything)
SubRenderPass {
renderPass: RenderPass {
passMode: RenderPass.SkyboxPass
commands: [
PipelineStateOverride {
depthTestEnabled: true
depthWriteEnabled: false
}
]
}
},
// Sub-pass 3: 2D Qt Quick content
SubRenderPass {
renderPass: RenderPass {
passMode: RenderPass.Item2DPass
}
},
// Sub-pass 4: Transparent objects (forward rendering)
SubRenderPass {
renderPass: RenderPass {
materialMode: RenderPass.OriginalMaterial
commands: [
RenderablesFilter {
renderableTypes: RenderablesFilter.Transparent
layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
},
PipelineStateOverride {
blendEnabled: true
depthTestEnabled: true
depthWriteEnabled: false
}
]
}
}
]
}
// G-buffer pass (referenced by deferred lighting)
GBufferPass {
id: gbufferPass
layerMask: ContentLayer.Layer0 | ContentLayer.Layer1
depthTexture: mainDepthStencilTexture
}
// Full-screen quad for deferred lighting
Model {
layers: ContentLayer.Layer13
geometry: PlaneGeometry { plane: PlaneGeometry.XY }
materials: CustomMaterial {
property TextureInput gbuffer0: TextureInput {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: gbufferPass
attachmentSelector: RenderOutputProvider.Attachment0
}
}
}
// ... other G-buffer inputs
shadingMode: CustomMaterial.Unshaded
fragmentShader: "lighting.frag"
vertexShader: "lighting.vert"
}
}
// Display final result
SimpleQuadRenderer {
texture: Texture {
textureProvider: RenderOutputProvider {
textureSource: RenderOutputProvider.UserPassTexture
renderPass: mainColorPass
}
}
}
// Opaque 3D content
Model {
layers: ContentLayer.Layer0
source: "#Sphere"
materials: PrincipledMaterial { baseColor: "red" }
}
// Transparent 3D content
Model {
layers: ContentLayer.Layer1
source: "#Cone"
materials: PrincipledMaterial {
baseColor: Qt.rgba(0.0, 1.0, 0.0, 0.5)
alphaMode: PrincipledMaterial.Blend
}
}
// 2D content in 3D space
Node {
x: -200
y: 100
Item {
Button { text: "Click Me!" }
Rectangle {
color: "blue"
width: 50; height: 50
}
}
}
PerspectiveCamera { z: 300 }
DirectionalLight { eulerRotation.x: -45 }
}
\endqml
\section2 Key Concepts
\b{SubRenderPass for Hierarchical Organization:}
The main pass uses \c SubRenderPass commands to execute child passes in sequence. The
parent pass itself doesn't render anything (\c{RenderablesFilter.None}), it just
orchestrates the child passes. This provides a clear structure and allows depth buffer
sharing across all sub-passes.
\b{Pass Modes:}
\list
\li \c{RenderPass.UserPass} (default): Custom rendering with your geometry and materials
\li \c{RenderPass.SkyboxPass}: Renders the environment skybox from \l SceneEnvironment
\li \c{RenderPass.Item2DPass}: Renders 2D Qt Quick content embedded in the 3D scene
\endlist
\b{Render Order:}
\list 1
\li Opaque geometry rendered to G-buffer
\li Deferred lighting applied to full-screen quad
\li Skybox rendered behind geometry (depth test enabled, depth write disabled)
\li 2D UI overlays rendered on top
\li Transparent objects rendered last with blending
\endlist
\b{Depth Buffer Sharing:}
The \c mainDepthStencilTexture is shared across passes using \c PreserveDepthStencilContents.
This ensures skybox renders behind geometry and transparent objects test against opaque
geometry correctly.
\b{Item2D Content:}
Qt Quick 2D items (Button, Rectangle, Text, etc.) placed in \l Node objects are rendered
by \c{RenderPass.Item2DPass}. These items are positioned in 3D space but rendered as 2D
overlays that can receive mouse/touch input.
\section1 Performance Considerations
User-defined render passes provide maximum control but require careful consideration of
performance implications.
\section2 Texture Format Choices
Choose texture formats based on your needs:
\table
\header
\li Format
\li Size per Pixel
\li Use Case
\row
\li RGBA8
\li 4 bytes
\li Final output, simple color buffers
\row
\li RGBA16F
\li 8 bytes
\li HDR content, intermediate buffers, normals
\row
\li RGBA32F
\li 16 bytes
\li High precision calculations, positions
\row
\li R16F
\li 2 bytes
\li Single-channel HDR (depth, AO, etc.)
\endtable
For a 1920x1080 framebuffer:
\list
\li RGBA8: ~8 MB
\li RGBA16F: ~16 MB
\li RGBA32F: ~32 MB
\li Three RGBA16F G-buffers: ~48 MB
\endlist
\b{Recommendation:} Use RGBA16F for intermediate buffers and RGBA8 or R8 where sufficient.
\section2 Deferred vs Forward Rendering
\table
\header
\li Aspect
\li Deferred Rendering
\li Forward Rendering
\row
\li Many lights
\li Efficient (O(lights + pixels))
\li Expensive (O(lights * objects))
\row
\li Memory bandwidth
\li High (G-buffer reads/writes)
\li Lower
\row
\li Transparency
\li Requires separate pass
\li Natural support
\row
\li MSAA
\li Not directly supported
\li Hardware MSAA works
\row
\li Setup complexity
\li More complex
\li Simpler
\endtable
\b{Use deferred rendering when:}
\list
\li You have many lights (10+)
\li Screen-space effects are important
\li Scene is mostly opaque
\endlist
\b{Use forward rendering when:}
\list
\li Few lights (<5)
\li Lots of transparency
\li Memory bandwidth is constrained
\li Simpler pipeline is preferable
\endlist
\section2 Pass Ordering Optimization
- Render passes execute in the order they are encountered. Optimize by:
+ Render passes execute in nesting order: deeper-nested passes render first;
+ the relative order of passes at equal depth is not defined (see
+ \l{Pass Ordering}). Optimize by:
\list
\li Rendering opaque geometry before transparent geometry
\li Using depth prepass when beneficial (reduces overdraw in complex scenes)
\li Minimizing render target switches
\li Reusing depth buffers across passes when possible
\endlist
\section2 Render Target Flags
The \l{RenderPass::renderTargetFlags}{renderTargetFlags} property controls memory behavior:
\qml
RenderPass {
// First pass: write new content
renderTargetFlags: 0 // Default: clear render targets
}
RenderPass {
// Second pass: add to existing content
renderTargetFlags: RenderPass.PreserveColorContents |
RenderPass.PreserveDepthStencilContents
}
RenderPass {
// Depth not needed after this pass
renderTargetFlags: RenderPass.DoNotStoreDepthStencilContents
}
\endqml
\b{PreserveColorContents/PreserveDepthStencilContents:} Keep existing data (e.g., for
multi-pass rendering to same target).
\b{DoNotStoreDepthStencilContents:} Hint that depth/stencil can be discarded (on some
hardware this can improve performance by avoiding memory writes).
\section2 When to Use User Passes
Consider using user render passes when:
\list
\li Implementing deferred rendering
\li Creating complex multi-pass effects
\li Needing explicit control over render order
\li Implementing custom rendering techniques
\li Building screen-space effects that need geometry data
\endlist
\b{Avoid user passes when:}
\list
\li Default rendering meets your needs
\li You only need post-processing (use \l Effect instead)
\li You only need custom material shaders (use \l CustomMaterial instead)
\li Performance is critical and extra passes would be wasteful
\endlist
\section1 Common Patterns and Use Cases
User render passes enable many advanced rendering techniques. Here are some common patterns:
\section2 Deferred Shading/Lighting
Store geometry attributes in G-buffers and perform lighting in screen space. Covered in
detail in \l{Advanced Example: Deferred Rendering}.
\section2 Edge Detection and Outlining
Render scene normally, then apply edge detection in a second pass:
\qml
// Pass 1: Render scene with normals/depth
RenderPass {
id: scenePass
commands: [
ColorAttachment { target: colorTex },
DepthTextureAttachment { target: depthTex }
]
}
// Pass 2: Edge detection using depth discontinuities
RenderPass {
id: edgePass
commands: [
ColorAttachment { target: outlineTex },
RenderablesFilter { layerMask: ContentLayer.Layer10 }
]
}
Model {
layers: ContentLayer.Layer10
geometry: PlaneGeometry { }
materials: CustomMaterial {
property TextureInput depthInput: TextureInput {
// depthProvider is the id of a RenderOutputProvider that exposes
// depthTex as a sampleable texture (see "Render Output Provider"
// earlier in this page).
texture: Texture { textureProvider: depthProvider }
}
fragmentShader: "edge_detect.frag"
// Shader samples depth, computes gradients, draws edges
}
}
\endqml
\section2 Custom Post-Processing Chains
Chain multiple post-processing effects together by feeding each pass's output
into the next via \l RenderOutputProvider. See
\l{Pass Chaining Example} for a worked example of a multi-stage chain
(\c scenePass → \c process1 → \c process2 → display).
\section2 Selective Wireframe Overlay
Render some objects solid and others as wireframe overlays. See the example in
\l{Working with Layers}.
\section2 Debug Visualization
Create debug passes that visualize normals, depth, or other data:
\qml
// Normal visualization pass
RenderPass {
materialMode: RenderPass.OverrideMaterial
overrideMaterial: CustomMaterial {
fragmentShader: "debug_normals.frag"
// FRAGCOLOR = vec4(normalize(NORMAL) * 0.5 + 0.5, 1.0);
}
commands: [
ColorAttachment { target: debugTex }
]
}
\endqml
\section2 Custom Shadow Mapping
Implement custom shadow mapping with explicit control:
\qml
// Shadow map pass (render from light's perspective)
RenderPass {
id: shadowPass
materialMode: RenderPass.OverrideMaterial
overrideMaterial: CustomMaterial {
fragmentShader: "depth_only.frag"
}
commands: [
DepthTextureAttachment { target: shadowMapTex }
]
}
// Main pass using shadow map
RenderPass {
id: mainPass
commands: [
ColorAttachment { target: colorTex },
DepthStencilAttachment { }
]
}
// Materials in main pass sample shadowMapTex for shadow testing
\endqml
\section1 Important Considerations
When working with user render passes, keep these important points in mind:
\section2 Shadow Handling
User render passes do \b{not} automatically include Qt Quick 3D's internal shadow rendering.
If you disable internal passes and need shadows, you must:
\list
\li Implement your own shadow mapping passes
\li Render the scene from light's perspective to a depth texture
\li Sample the shadow map in your lighting calculations
\endlist
Alternatively, for simpler cases, you may not need shadows or can use pre-baked shadow maps.
\section2 HDR and Tonemapping
When using floating-point render targets (RGBA16F, RGBA32F), your rendering is in linear
HDR space. You must apply tonemapping in your final display pass to convert to displayable
LDR:
\qml
CustomMaterial {
fragmentShader: "tonemap.frag"
}
\endqml
\badcode
// In tonemap.frag
vec3 tonemap(vec3 hdr) {
// Simple Reinhard tonemapping
return hdr / (hdr + vec3(1.0));
}
void MAIN() {
vec3 hdrColor = texture(hdrInput, UV0).rgb;
FRAGCOLOR = vec4(tonemap(hdrColor), 1.0);
}
\endcode
Qt Quick 3D provides \c{qt_tonemap()} function in shaders that can be used for this purpose.
\section2 Depth Texture Generation
When you need depth as a texture (for effects like depth-of-field), use
\l DepthTextureAttachment instead of \l DepthStencilAttachment:
\qml
RenderPassTexture {
id: depthTex
format: RenderPassTexture.Depth24Stencil8
}
RenderPass {
commands: [
ColorAttachment { target: colorTex },
DepthTextureAttachment { target: depthTex }
// depthTex can now be sampled in shaders
]
}
\endqml
\section2 Clear Values and Render Target Management
Each pass can specify clear values:
\qml
RenderPass {
clearColor: Qt.rgba(0.0, 0.0, 0.0, 0.0)
depthClearValue: 1.0
stencilClearValue: 0
}
\endqml
When a pass doesn't preserve contents (default), render targets are cleared to these values
before rendering. When \c PreserveColorContents or \c PreserveDepthStencilContents is set,
existing content is kept and clear values are ignored.
\section2 Layer Organization Best Practices
Organize your layers logically:
\list
\li \b{Layer0-2}: Main scene objects
\li \b{Layer3-4}: Transparent objects (separate pass)
\li \b{Layer10+}: Full-screen quads for post-processing
\li \b{Layer15+}: UI/debug overlays
\endlist
Document your layer usage in comments and be consistent across your application.
\section2 Shader Compatibility
Augment shaders and custom materials used in render passes must be compatible with Qt Quick
3D's shader infrastructure:
\list
\li Use GLSL-compatible syntax
\li Include appropriate \c{\#include} directives for Qt Quick 3D functions
\li Be aware of available built-in variables
\li Test on all target platforms (OpenGL, Vulkan, Metal, D3D)
\endlist
\section2 Multiple Render Targets Limitations
When using multiple render targets (MRT):
\list
\li Maximum of 4 color attachments
\li All attachments must have the same dimensions
\li Blend state can be specified per-attachment via \l PipelineStateOverride
\li Not all hardware supports MRT (check device capabilities)
\endlist
\section2 Transparency
Transparent objects are challenging with deferred rendering. Common approaches:
\list
\li \b{Forward pass for transparent}: Render opaque geometry deferred, transparent geometry
in a separate forward pass
\li \b{Separate transparent G-buffer}: Render transparent geometry to a separate set of
G-buffers with blending enabled
\li \b{Weighted blended order-independent transparency}: Use specialized OIT techniques
\endlist
For most cases, a separate forward pass for transparent objects is simplest.
\section1 API Reference
The following table provides a complete reference of all types related to user render passes:
\table
\header
\li Feature
\li QML Type
\li Description
\row
\li Main render pass definition
\li \l RenderPass
\li Defines a rendering pass with commands and material mode
\row
\li Render target textures
\li \l RenderPassTexture
\li Texture used as render target (color or depth/stencil)
\row
\li Texture output provider
\li \l RenderOutputProvider
\li Exposes render pass output as texture input
\row
\li Layer constants
\li \l ContentLayer
\li Singleton providing layer filtering constants
\row
\li Color output
\li \l ColorAttachment
\li Specifies a color render target
\row
\li Depth/stencil (default)
\li \l DepthStencilAttachment
\li Uses implicit depth/stencil buffer
\row
\li Depth/stencil (texture)
\li \l DepthTextureAttachment
\li Uses explicit depth texture
\row
\li Object filtering
\li \l RenderablesFilter
\li Filters objects by layer and type
\row
\li Pipeline state
\li \l PipelineStateOverride
\li Overrides graphics pipeline state
\row
\li Nested pass execution
\li \c SubRenderPass
\li Executes another render pass
\row
\li Shader defines
\li \l AddDefine
\li Adds shader preprocessor define
\row
\li Per-target blend state
\li \l renderTargetBlend
\li Blend configuration for MRT
\row
\li Display helper
\li \l SimpleQuadRenderer
\li Renders final output to View3D
\endtable
\section2 Related Types
\table
\header
\li Type
\li Description
\row
\li \l CustomMaterial
\li Custom material with vertex/fragment shaders
\row
\li \l Effect
\li Post-processing effects
\row
\li \l View3D
\li 3D view with \l{View3D::renderOverrides}{renderOverrides} property
\row
\li \l Model
\li 3D model with \l{Model::layers}{layers} property
\endtable
\section2 Examples
\list
\li \l{Qt Quick 3D - User Passes Example}: Complete deferred rendering example
\endlist
\section2 See Also
\list
\li \l{Programmable Materials, Effects, Geometry, and Texture data}: Overview of Qt Quick 3D
customization features
\li \l{Qt Quick 3D Architecture}: Understanding the rendering pipeline
\endlist
*/