Sur cette page

Qt 3D Représentation graphique du rendu

L'aspect Qt 3D Render permet à l'algorithme de rendu d'être entièrement piloté par les données. La structure de données de contrôle est connue sous le nom de framegraph. Tout comme le système ECS (entity component system) de Qt 3D vous permet de définir un "Scenegraph" en construisant une scène à partir d'une arborescence d'entités et de composants, le framegraph est également une structure arborescente, mais utilisée dans un but différent. Il s'agit de contrôler le rendu de la scène.

Au cours du rendu d'une seule image, un moteur de rendu 3D changera probablement d'état à de nombreuses reprises. Le nombre et la nature de ces changements d'état dépendent non seulement des matériaux (shaders, géométrie de maillage, textures et variables uniformes) présents dans la scène, mais aussi du schéma de rendu de haut niveau utilisé.

Par exemple, l'utilisation d'un schéma de rendu traditionnel simple est très différente de l'utilisation d'une approche de rendu différé. D'autres caractéristiques telles que les réflexions, les ombres, les angles de vue multiples et les passes de z-fill précoces modifient tous les états qu'un moteur de rendu doit définir au cours d'une image et le moment où ces changements d'état doivent se produire.

À titre de comparaison, le moteur de rendu du graphe de scèneQt Quick 2 chargé de dessiner les scènes Qt Quick 2 est câblé en C++ pour effectuer des opérations telles que la mise en lots des primitives et le rendu des éléments opaques suivi du rendu des éléments transparents. Dans le cas de Qt Quick 2, cela convient parfaitement, car cela couvre toutes les exigences. Comme vous pouvez le voir dans les exemples ci-dessus, un tel moteur de rendu câblé ne sera probablement pas assez flexible pour les scènes 3D génériques, étant donné la multitude de méthodes de rendu disponibles. Ou si un moteur de rendu pouvait être rendu suffisamment flexible pour couvrir tous ces cas, ses performances souffriraient probablement de sa trop grande généralité. Pour ne rien arranger, de nouvelles méthodes de rendu font l'objet de recherches en permanence. Nous avions donc besoin d'une approche à la fois flexible et extensible, tout en étant simple à utiliser et à maintenir. C'est ainsi qu'est né le graphe de cadre !

Chaque nœud du framegraph définit une partie de la configuration que le moteur de rendu utilisera pour rendre la scène. La position d'un nœud dans l'arbre du framegraph détermine quand et où le sous-arbre ancré à ce nœud sera la configuration active dans le pipeline de rendu. Comme nous le verrons plus tard, le moteur de rendu parcourt cet arbre afin de construire l'état nécessaire à votre algorithme de rendu à chaque point de l'image.

Évidemment, si vous souhaitez simplement rendre un simple cube à l'écran, vous pouvez penser que c'est exagéré. Cependant, dès que vous voulez commencer à réaliser des scènes un peu plus complexes, cela s'avère très utile. Pour les cas les plus courants, Qt 3D fournit des exemples de framegraphs prêts à l'emploi.

Nous allons démontrer la flexibilité du concept de graphe de cadre en présentant quelques exemples et les graphes de cadre qui en résultent.

Veuillez noter que, contrairement au Scenegraph qui est composé d'entités et de composants, le framegraph est uniquement composé de nœuds imbriqués qui sont tous des sous-classes de Qt3DRender::QFrameGraphNode. Ceci est dû au fait que les nœuds du framegraph ne sont pas des objets simulés dans notre monde virtuel, mais plutôt des informations de support.

Nous verrons bientôt comment construire notre premier graphe de cadre simple, mais avant cela, nous présenterons les nœuds de graphe de cadre disponibles. Comme pour l'arbre du Scenegraph, les API QML et C++ se complètent 1 à 1, vous pouvez donc privilégier celle qui vous convient le mieux. Pour des raisons de lisibilité et de concision, l'API QML a été choisie pour cet article.

La beauté du framegraph est qu'en combinant ces types de noeuds simples, il est possible de configurer le moteur de rendu pour répondre à vos besoins spécifiques sans toucher au code de rendu C/C++ de bas niveau.

Règles du FrameGraph

Afin de construire un arbre framegraph qui fonctionne correctement, vous devez connaître quelques règles sur la façon dont il est parcouru et sur la façon dont il est transmis au moteur de rendu Qt 3D.

Définition du Framegraph

L'arbre FrameGraph doit être assigné à la propriété activeFrameGraph d'un composant QRenderSettings, lui-même étant un composant de l'entité racine de la scène Qt 3D. C'est ce qui en fait le framegraph actif pour le moteur de rendu. Bien entendu, puisqu'il s'agit d'une propriété QML, le cadre graphique actif (ou des parties de celui-ci) peut être modifié à la volée au moment de l'exécution. Par exemple, si vous souhaitez utiliser des approches de rendu différentes pour les scènes intérieures et extérieures ou pour activer ou désactiver un effet spécial.

Entity {
    id: sceneRoot
    components: RenderSettings {
         activeFrameGraph: ... // FrameGraph tree
    }
}

Remarque : activeFrameGraph est la propriété par défaut du composant FrameGraph en QML.

Entity {
    id: sceneRoot
    components: RenderSettings {
         ... // FrameGraph tree
    }
}

Utilisation du FrameGraph

  • Le moteur de rendu Qt 3D effectue une première traversée en profondeur de l'arbre du FrameGraph. L'ordre dans lequel vous définissez les nœuds est important, car il s'agit d'un parcours en profondeur.
  • Lorsque le moteur de rendu atteint un nœud feuille du graphe, il rassemble tous les états spécifiés par le chemin du nœud feuille au nœud racine. Cela définit l'état utilisé pour effectuer le rendu d'une section du cadre. Si vous vous intéressez aux aspects internes de Qt 3D, cette collection d'états est appelée RenderView.
  • Étant donné la configuration contenue dans une RenderView, le moteur de rendu rassemble toutes les entités de la Scenegraph à rendre et, à partir de celles-ci, construit un ensemble de RenderCommands qu'il associe à la RenderView.
  • La combinaison du RenderView et de l'ensemble des RenderCommands est transmise à OpenGL.
  • Lorsque cette opération est répétée pour chaque nœud feuille dans le framegraph, le cadre est complet et le moteur de rendu appelle QOpenGLContext::swapBuffers() pour afficher le cadre.

Au fond, le framegraph est une méthode basée sur les données pour configurer le moteur de rendu Qt 3D. En raison de sa nature axée sur les données, nous pouvons modifier la configuration au moment de l'exécution, permettre aux développeurs ou concepteurs non-C++ de modifier la structure d'un cadre et tester de nouvelles approches de rendu sans avoir à écrire des milliers de lignes de code de type "boiler plate".

Exemples de graphes de cadre

Maintenant que vous connaissez les règles à respecter lors de l'écriture d'un arbre de graphe de cadre, nous allons passer en revue quelques exemples et les décomposer.

Un simple moteur de rendu en avant (Forward Renderer)

Le rendu avant est lorsque vous utilisez OpenGL de manière traditionnelle et que vous rendez directement dans le backbuffer un objet à la fois en ombrant chacun d'entre eux au fur et à mesure que vous avancez. Cela s'oppose au rendu différé où nous effectuons le rendu dans un tampon G intermédiaire. Voici un FrameGraph simple qui peut être utilisé pour le rendu différé :

Viewport {
     normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
     property alias camera: cameraSelector.camera

     ClearBuffers {
          buffers: ClearBuffers.ColorDepthBuffer

          CameraSelector {
               id: cameraSelector
          }
     }
}

Comme vous pouvez le voir, cet arbre a une seule feuille et est composé de 3 noeuds au total comme le montre le diagramme suivant.

Diagramme montrant comment les étapes de rendu fusionnent dans un RenderView avec Viewport, TechniqueFilter et CameraSelector

En utilisant les règles définies ci-dessus, cet arbre FrameGraph produit un seul RenderView avec la configuration suivante :

  • Nœud feuille -> RenderView
    • Fenêtre de visualisation qui remplit tout l'écran (utilise des coordonnées normalisées pour faciliter la prise en charge des fenêtres de visualisation imbriquées).
    • Les tampons de couleur et de profondeur sont réglés pour être effacés.
    • Caméra spécifiée dans la propriété exposée de la caméra

Plusieurs arbres FrameGraph différents peuvent produire le même résultat de rendu. Tant que l'état collecté de la feuille à la racine est le même, le résultat sera également le même. Il est préférable de placer l'état qui reste constant le plus longtemps près de la racine du FrameGraph, car cela réduira le nombre de nœuds feuilles et, par conséquent, le nombre de RenderViews.

Viewport {
     normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
     property alias camera: cameraSelector.camera

     CameraSelector {
          id: cameraSelector

          ClearBuffers {
               buffers: ClearBuffers.ColorDepthBuffer
          }
     }
}
CameraSelector {
      Viewport {
           normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)

           ClearBuffers {
                buffers: ClearBuffers.ColorDepthBuffer
           }
      }
}

Un graphe d'images à plusieurs fenêtres

Passons à un exemple un peu plus complexe qui rend un graphe de scènes du point de vue de 4 caméras virtuelles dans les 4 quadrants de la fenêtre. Il s'agit d'une configuration courante pour les outils de CAO ou de modélisation 3D, qui pourrait être adaptée pour faciliter le rendu d'un rétroviseur dans un jeu de course automobile ou l'affichage d'une caméra de vidéosurveillance.

Quatre vues de caméra d'une scène en 3D avec des jardinières, des tonneaux, une sculpture de nœuds rouges et un coffre au trésor.

Viewport {
     id: mainViewport
     normalizedRect: Qt.rect(0, 0, 1, 1)
     property alias Camera: cameraSelectorTopLeftViewport.camera
     property alias Camera: cameraSelectorTopRightViewport.camera
     property alias Camera: cameraSelectorBottomLeftViewport.camera
     property alias Camera: cameraSelectorBottomRightViewport.camera

     ClearBuffers {
          buffers: ClearBuffers.ColorDepthBuffer
     }

     Viewport {
          id: topLeftViewport
          normalizedRect: Qt.rect(0, 0, 0.5, 0.5)
          CameraSelector { id: cameraSelectorTopLeftViewport }
     }

     Viewport {
          id: topRightViewport
          normalizedRect: Qt.rect(0.5, 0, 0.5, 0.5)
          CameraSelector { id: cameraSelectorTopRightViewport }
     }

     Viewport {
          id: bottomLeftViewport
          normalizedRect: Qt.rect(0, 0.5, 0.5, 0.5)
          CameraSelector { id: cameraSelectorBottomLeftViewport }
     }

     Viewport {
          id: bottomRightViewport
          normalizedRect: Qt.rect(0.5, 0.5, 0.5, 0.5)
          CameraSelector { id: cameraSelectorBottomRightViewport }
     }
}

Cet arbre est un peu plus complexe avec 5 feuilles. En suivant les mêmes règles que précédemment, nous construisons 5 objets RenderView à partir du FrameGraph. Les diagrammes suivants montrent la construction des deux premiers RenderViews. Les autres RenderViews sont très similaires au deuxième diagramme, mais avec les autres sous-arbres.

Diagramme montrant quatre viewports de caméra fusionnés en un RenderView avec un viewport et un clear buffer

Diagramme montrant la fenêtre de visualisation en haut à gauche et le sélecteur de caméra combinés en un seul RenderView

Les RenderViews créés sont les suivants :

  • RenderView (1)
    • Port de visualisation plein écran défini
    • Les tampons de couleur et de profondeur sont prêts à être effacés.
  • RenderView (2)
    • Fenêtre plein écran définie
    • Sous-écran défini (l'écran de rendu sera mis à l'échelle par rapport à son parent)
    • CameraSelector spécifié
  • RenderView (3)
    • Fenêtre plein écran définie
    • Sous-écran défini (l'écran de rendu sera mis à l'échelle par rapport à son parent)
    • CameraSelector spécifié
  • RenderView (4)
    • Fenêtre plein écran définie
    • Sous-écran défini (l'écran de rendu sera mis à l'échelle par rapport à son parent)
    • CameraSelector spécifié
  • RenderView (5)
    • Fenêtre plein écran définie
    • Sous-écran défini (l'écran de rendu sera mis à l'échelle par rapport à son parent)
    • CameraSelector spécifié

Toutefois, dans ce cas, l'ordre est important. Si le nœud ClearBuffers était le dernier au lieu du premier, il en résulterait un écran noir pour la simple raison que tout serait effacé juste après avoir été rendu avec tant de soin. Pour la même raison, il ne peut pas être utilisé comme racine du FrameGraph, car cela entraînerait un appel à l'effacement de l'ensemble de l'écran pour chacune de nos fenêtres.

Bien que l'ordre de déclaration du FrameGraph soit important, Qt 3D est en mesure de traiter chaque RenderView en parallèle car chaque RenderView est indépendant des autres dans le but de générer un ensemble de RenderCommands à soumettre pendant que l'état du RenderView est en vigueur.

Qt 3D utilise une approche du parallélisme basée sur les tâches qui augmente naturellement avec le nombre de cœurs disponibles. C'est ce que montre le diagramme suivant pour l'exemple précédent.

Diagramme montrant comment le gestionnaire de jon est divisé en cinq vues de rendu

Les RenderCommands pour les RenderViews peuvent être générés en parallèle sur plusieurs cœurs, et tant que nous prenons soin de soumettre les RenderViews dans le bon ordre sur le thread de soumission OpenGL dédié, la scène résultante sera rendue correctement.

Renderer différé

En ce qui concerne le rendu, le rendu différé est une bête différente en termes de configuration du moteur de rendu par rapport au rendu direct. Au lieu de dessiner chaque maillage et d'appliquer un effet de shader pour l'ombrer, le rendu différé adopte une méthode à deux passes de rendu.

Tout d'abord, toutes les mailles de la scène sont dessinées à l'aide du même nuanceur qui produira, généralement pour chaque fragment, au moins quatre valeurs :

  • vecteur de normalité du monde
  • Couleur (ou autres propriétés matérielles)
  • Profondeur
  • vecteur de position du monde

Chacune de ces valeurs sera stockée dans une texture. Les textures de normalité, de couleur, de profondeur et de position forment ce que l'on appelle le G-Buffer. Rien n'est dessiné à l'écran lors de la première passe, mais plutôt dans le G-Buffer, prêt à être utilisé ultérieurement.

Une fois que toutes les mailles ont été dessinées, le G-Buffer est rempli avec toutes les mailles qui peuvent actuellement être vues par la caméra. La deuxième passe de rendu est alors utilisée pour rendre la scène dans le tampon arrière avec l'ombrage final des couleurs en lisant les valeurs de normalité, de couleur et de position des textures du tampon G et en produisant une couleur sur un quadrant plein écran.

L'avantage de cette technique est que la puissance de calcul importante requise pour les effets complexes n'est utilisée qu'au cours de la deuxième passe, uniquement sur les éléments qui sont réellement vus par la caméra. La première passe ne coûte pas beaucoup de puissance de calcul car chaque maille est dessinée avec un simple shader. Le rendu différé découple donc l'ombrage et l'éclairage du nombre d'objets dans une scène et les associe plutôt à la résolution de l'écran (et du G-Buffer). Cette technique a été utilisée dans de nombreux jeux car elle permet d'utiliser un grand nombre de lumières dynamiques au prix d'une utilisation supplémentaire de la mémoire du GPU.

Viewport {
    id: root
    normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)

    property GBuffer gBuffer
    property alias camera: sceneCameraSelector.camera
    property alias sceneLayer: sceneLayerFilter.layers
    property alias screenQuadLayer: screenQuadLayerFilter.layers

    RenderSurfaceSelector {

        CameraSelector {
            id: sceneCameraSelector

            // Fill G-Buffer
            LayerFilter {
                id: sceneLayerFilter
                RenderTargetSelector {
                    id: gBufferTargetSelector
                    target: gBuffer

                    ClearBuffers {
                        buffers: ClearBuffers.ColorDepthBuffer

                        RenderPassFilter {
                            id: geometryPass
                            matchAny: FilterKey {
                                name: "pass"
                                value: "geometry"
                            }
                        }
                    }
                }
            }

            TechniqueFilter {
                parameters: [
                    Parameter { name: "color"; value: gBuffer.color },
                    Parameter { name: "position"; value: gBuffer.position },
                    Parameter { name: "normal"; value: gBuffer.normal },
                    Parameter { name: "depth"; value: gBuffer.depth }
                ]

                RenderStateSet {
                    // Render FullScreen Quad
                    renderStates: [
                        BlendEquation { blendFunction: BlendEquation.Add },
                        BlendEquationArguments {
                            sourceRgb: BlendEquationArguments.SourceAlpha
                            destinationRgb: BlendEquationArguments.DestinationColor
                        }
                    ]

                    LayerFilter {
                        id: screenQuadLayerFilter
                        ClearBuffers {
                            buffers: ClearBuffers.ColorDepthBuffer
                            RenderPassFilter {
                                matchAny: FilterKey {
                                    name: "pass"
                                    value: "final"
                                }
                                parameters: Parameter {
                                    name: "winSize"
                                    value: Qt.size(1024, 768)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

(Le code ci-dessus est adapté de qt3d/tests/manual/deferred-renderer-qml.)

Graphiquement, le framegraph résultant ressemble à :

Organigramme d'un diagramme de rendu différé avec des branches de quadrillage de scène et d'écran

Et les RenderViews résultants sont :

  • RenderView (1)
    • Spécifier la caméra à utiliser
    • Définir une fenêtre de visualisation qui remplit tout l'écran
    • Sélectionner toutes les entités pour le composant de calque sceneLayer
    • Définir le site gBuffer comme cible de rendu active
    • Effacer la couleur et la profondeur de la cible de rendu actuellement liée ( gBuffer)
    • Sélectionner uniquement les entités de la scène qui ont un matériau et une technique correspondant aux annotations du composant sceneLayer. RenderPassFilter
  • RenderView (2)
    • Définir une fenêtre de visualisation qui remplit tout l'écran
    • Sélectionner toutes les entités pour le composant de couche screenQuadLayer
    • Effacer les tampons de couleur et de profondeur sur le framebuffer actuellement lié (l'écran)
    • Sélectionner uniquement les entités de la scène qui ont un matériau et une technique correspondant aux annotations de la fenêtre de rendu. RenderPassFilter

Autres avantages du FrameGraph

Étant donné que l'arbre du FrameGraph est entièrement piloté par les données et peut être modifié dynamiquement au moment de l'exécution, vous pouvez :

  • Disposer de différents arbres graphiques pour différentes plates-formes et différents matériels et sélectionner le plus approprié au moment de l'exécution.
  • ajouter et activer facilement le débogage visuel dans une scène
  • utiliser différents arbres FrameGraph en fonction de la nature de ce que vous devez rendre dans une région particulière de la scène
  • Implémenter une nouvelle technique de rendu sans avoir à modifier les composants internes de Qt 3D.

Conclusion

Nous avons présenté le FrameGraph et les types de nœuds qui le composent. Nous avons ensuite discuté de quelques exemples pour illustrer les règles de construction du FrameGraph et la façon dont le moteur Qt 3D utilise le FrameGraph en coulisses. Vous devriez maintenant avoir une bonne vue d'ensemble du FrameGraph et de la manière dont il peut être utilisé (par exemple pour ajouter une passe de remplissage en z à un moteur de rendu). Vous devez également garder à l'esprit que le FrameGraph est un outil que vous pouvez utiliser et que vous n'êtes donc pas lié au moteur de rendu et aux matériaux fournis par Qt 3D.

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