Données programmables relatives aux matériaux, aux effets, à la géométrie et aux textures
Bien que les matériaux intégrés d’ Qt Quick 3D, d’ DefaultMaterial et d’ PrincipledMaterial, offrent un large degré de personnalisation via leurs propriétés, ils ne permettent pas de programmation au niveau des shaders de sommets et de fragments. Pour y remédier, le type « CustomMaterial » est fourni.
| Un modèle avec PrincipledMaterial | Avec un « CustomMaterial » transformant les sommets |
|---|---|
|
|
Les effets de post-traitement, où un ou plusieurs passages de traitement sur le tampon de couleur sont effectués, en tenant compte éventuellement du tampon de profondeur, avant que la sortie de l’ View3D ne soit transmise à Qt Quick, existent également sous deux formes :
- des étapes de post-traitement intégrées pouvant être configurées via l’ ExtendedSceneEnvironment, telles que la lueur/le bloom, la profondeur de champ, le vignettage, les reflets de lentille,
customdes effets implémentés par l’application sous forme de code de shader de fragment et d’une spécification des passes de traitement dans un objet ` Effect `.
En pratique, il existe une troisième catégorie d’effets de post-traitement : les effets 2D implémentés via Qt Quick, agissant sur la sortie de l’élément View3D sans aucune intervention du moteur de rendu 3D. Par exemple, pour appliquer un flou à un élément « View3D », l’approche la plus simple consiste à utiliser les fonctionnalités existantes d’ Qt Quick, telles que « MultiEffect ». Le système de post-traitement 3D s’avère avantageux pour les effets complexes qui impliquent des concepts de scène 3D tels que le tampon de profondeur ou la texture d’écran, ou qui nécessitent de gérer le mappage de tons HDR ou plusieurs passes avec des tampons intermédiaires, etc. Les effets 2D simples qui ne nécessitent aucune connaissance de la scène 3D ni du moteur de rendu peuvent toujours être mis en œuvre à l’aide de ShaderEffect ou MultiEffect.
| Scène sans effet | La même scène avec un effet de post-traitement personnalisé appliqué |
|---|---|
|
|
Outre les matériaux programmables et le post-traitement, il existe deux types de données qui sont généralement fournies sous forme de fichiers (fichiers.mesh ou d’images telles que .png) :
- les données de sommets, comprenant la géométrie du maillage à rendre, les coordonnées de texture, les normales, les couleurs et d’autres données,
- le contenu des textures qui sont ensuite utilisées comme cartes de texture pour les objets rendus, ou avec un skybox ou un éclairage basé sur des images.
Si elles le souhaitent, les applications peuvent fournir ces données depuis C++ sous la forme d’un fichier « QByteArray ». Ces données peuvent également être modifiées au fil du temps, ce qui permet de générer de manière procédurale puis de modifier ultérieurement les données pour un « Model » ou un « Texture ».
| Une grille, rendue en spécifiant dynamiquement les données de sommets depuis le C++ | Un cube texturé avec des données d’image générées à partir de C++ |
|---|---|
|
|
Ces quatre approches permettant de personnaliser et de rendre dynamiques les matériaux, les effets, la géométrie et les textures offrent une programmabilité de l’ombrage et une génération procédurale des données fournies en entrée aux shaders. Les sections suivantes présentent un aperçu de ces fonctionnalités. La référence complète est disponible dans les pages de documentation consacrées aux types respectifs :
Remarque : pour un contrôle total sur l’ensemble du pipeline de rendu, y compris les passes de rendu multiples et les cibles de rendu personnalisées, consultez la section « Passes de rendu définies par l’utilisateur » dans « Qt Quick 3D ». Les passes de rendu définies par l’utilisateur complètent les fonctionnalités décrites sur cette page en vous permettant de définir l’architecture complète du pipeline de rendu, tandis que les options « CustomMaterial » et «Effect» se concentrent sur la personnalisation des matériaux individuels et des étapes de post-traitement au sein du pipeline.
| Fonctionnalité | Documentation de référence | Exemples pertinents |
|---|---|---|
| Matériaux personnalisés | CustomMaterial | Qt Quick 3D - Exemple de shaders personnalisés, Qt Quick 3D - Exemple de matériaux personnalisés |
| Effets de post-traitement personnalisés | Effect | Qt Quick 3D - Exemple d'effet personnalisé |
| Géométrie personnalisée | QQuick3DGeometry, Model::geometry | Qt Quick 3D - Exemple de géométrie personnalisée |
| Données de texture personnalisées | QQuick3DTextureData, Texture::textureData | Qt Quick 3D - Exemple de texture procédurale |
Programmabilité des matériaux
Prenons une scène avec un cube, et commençons par une PrincipledMaterial et une CustomMaterial par défaut :
| PrincipledMaterial | CustomMaterial |
|---|---|
| |
Ces deux configurations donnent exactement le même résultat, car une « CustomMaterial » est en réalité une « PrincipledMaterial » lorsqu’aucun code de shader de sommet ou de fragment n’y est ajouté.

Remarque : les propriétés telles que baseColor, metalness, baseColorMap et bien d’autres n’ont pas d’équivalent dans le type QML CustomMaterial. C’est voulu : la personnalisation du matériau s’effectue via le code du shader, et non en fournissant simplement quelques valeurs fixes.
Notre premier shader de sommets
Ajoutons un extrait de shader de sommets personnalisé. Pour ce faire, il suffit de référencer un fichier dans la propriété ` vertexShader `. L’approche sera la même pour les shaders de fragments. Ces références fonctionnent comme ` Image.source ` ou ` ShaderEffect.vertexShader` : il s’agit d’URL locales ou qrc, et un chemin relatif est interprété par rapport à l’emplacement du fichier ` .qml `. L’approche courante consiste donc à placer les fichiers .vert et .frag dans le système de ressources Qt (qt_add_resources lors de l’utilisation de CMake) et à les référencer à l’aide d’un chemin relatif.
Dans Qt 6.0, les chaînes de shaders en ligne ne sont plus prises en charge, ni dans « Qt Quick » ni dans « Qt Quick 3D ». (Notez bien que ces propriétés sont des URL, et non des chaînes de caractères.) Cependant, en raison de leur nature intrinsèquement dynamique, les matériaux personnalisés et les effets de post-traitement dans Qt Quick 3D fournissent toujours des extraits de shaders sous forme de code source dans les fichiers référencés. Cela diffère d’ ShaderEffect, où les shaders sont complets en eux-mêmes, sans modification supplémentaire par le moteur, et doivent donc être fournis sous forme de packs de shaders pré-conditionnés .qsb.
Remarque : dans Qt Quick 3D, les URL ne peuvent renvoyer qu’à des ressources locales. Les schémas pour le contenu distant ne sont pas pris en charge.
Remarque : le langage de shader utilisé est le GLSL compatible avec Vulkan. Les fichiers .vert et .frag ne constituent pas des shaders complets en eux-mêmes ; c’est pourquoi on les appelle souvent « snippets ». C’est la raison pour laquelle ces extraits de code ne fournissent pas directement de blocs uniform, de variables d’entrée et de sortie, ni d’uniforms d’échantillonnage. C’est plutôt le moteur 3D Qt Quick qui les modifiera comme il se doit.
| Modification dans main.qml, material.vert | Résultat |
|---|---|
|
|
Un extrait de shader de sommet ou de fragment personnalisé doit fournir une ou plusieurs fonctions portant des noms prédéfinis, tels que ` MAIN`, ` DIRECTIONAL_LIGHT`, ` POINT_LIGHT`, ` SPOT_LIGHT`, ` AMBIENT_LIGHT` ou ` SPECULAR_LIGHT`. Pour l’instant, concentrons-nous sur ` MAIN`.
Comme on peut le voir ici, le résultat final avec une fonction MAIN() vide est exactement le même qu'auparavant.
Avant de rendre les choses plus intéressantes, examinons un aperçu des mots-clés spéciaux les plus couramment utilisés dans les extraits de code de shaders de sommets personnalisés. Cette liste n’est pas exhaustive. Pour une référence complète, consultez la page CustomMaterial.
| Mot-clé | Type | Description |
|---|---|---|
| MAIN | void MAIN() est le point d'entrée. Cette fonction doit toujours être présente dans un extrait de shader de sommet personnalisé ; sinon, cela ne sert à rien d'en fournir un. | |
| VERTEX | vec3 | La position du sommet que le shader reçoit en entrée. Un cas d'utilisation courant des shaders de sommets dans les matériaux personnalisés consiste à modifier (déplacer) les valeurs x, y ou z de ce vecteur, en attribuant simplement une valeur à l'ensemble du vecteur ou à certaines de ses composantes. |
| NORMAL | vec3 | La normale du sommet issue des données du maillage d'entrée, ou une valeur composée uniquement de zéros si aucune normale n'a été fournie. Comme pour VERTEX, le shader est libre de modifier cette valeur comme il l'entend. La valeur modifiée est ensuite utilisée par le reste du pipeline, y compris pour les calculs d'éclairage à l'étape des fragments. |
| UV0 | vec2 | Le premier jeu de coordonnées de texture issu des données du maillage d'entrée, ou uniquement des zéros si aucune valeur UV n'a été fournie. Comme pour VERTEX et NORMAL, cette valeur peut être modifiée. |
| MODELVIEWPROJECTION_MATRIX | mat4 | La matrice modèle-vue-projection. Afin d’uniformiser le comportement quelle que soit l’API graphique utilisée pour le rendu, toutes les données de sommets et les matrices de transformation respectent les conventions OpenGL à ce niveau. (Axe Y orienté vers le haut, matrice de projection compatible OpenGL) En lecture seule. |
| MODEL_MATRIX | mat4 | La matrice du modèle (monde). En lecture seule. |
| NORMAL_MATRIX | mat3 | L'inverse transposé de la tranche 3x3 située en haut à gauche de la matrice du modèle. En lecture seule. |
| CAMERA_POSITION | vec3 | La position de la caméra dans l'espace mondial. Dans les exemples de cette page, elle est définie par ` (0, 0, 600)`. Lecture seule. |
| CAMERA_DIRECTION | vec3 | Le vecteur de direction de la caméra. Dans les exemples présentés sur cette page, il s'agit de (0, 0, -1). En lecture seule. |
| CAMERA_PROPERTIES | vec2 | Les valeurs de clip proche et lointain de la caméra. Dans les exemples de cette page, il s'agit de (10, 10000). En lecture seule. |
| POINT_SIZE | float | Pertinent uniquement lors du rendu avec une topologie de points, par exemple parce que l'custom geometry fournit une telle géométrie pour le maillage. Écrire dans cette valeur revient à définir pointSize on a PrincipledMaterial. |
| POSITION | vec4 | Comme gl_Position. En l’absence de cette valeur, une instruction d’affectation par défaut est générée automatiquement à partir de MODELVIEWPROJECTION_MATRIX et VERTEX. C’est pourquoi une fonction MAIN() vide est fonctionnelle, et dans la plupart des cas, il ne sera pas nécessaire de lui attribuer une valeur personnalisée. |
Créons un matériau personnalisé qui déplace les sommets selon un certain motif. Pour rendre le tout plus intéressant, ajoutons quelques propriétés QML animées, dont les valeurs seront finalement exposées sous forme d’uniformes dans le code du shader. (Pour être précis, la plupart des propriétés seront mappées à des membres d’un bloc d’uniformes, soutenus par un tampon d’uniformes lors de l’exécution, mais Qt Quick 3D rend commodément ces détails transparents pour l’auteur du matériau personnalisé)
| Modification dans main.qml, material.vert | Résultat |
|---|---|
|
Uniformes issus des propriétés QML
Les propriétés personnalisées de l’objet CustomMaterial sont mappées à des uniformes. Dans l’exemple ci-dessus, cela inclut uAmplitude et uTime. Chaque fois que les valeurs changent, la valeur mise à jour devient visible dans le shader. Ce concept vous est peut-être déjà familier grâce à ShaderEffect.
Le nom de la propriété QML et celui de la variable GLSL doivent correspondre. Il n’y a pas de déclaration distincte dans le code du shader pour chaque uniforme. Le nom de la propriété QML peut plutôt être utilisé tel quel. C’est pourquoi l’exemple ci-dessus peut simplement faire référence à uTime et uAmplitude dans l’extrait de code du shader de sommets sans aucune déclaration préalable pour celles-ci.
Le tableau suivant répertorie le mappage des types :
| Type QML | Type de shader | Remarques |
|---|---|---|
| real, int, bool | float, int, bool | |
| couleur | vec4 | La conversion sRGB vers linéaire est effectuée implicitement |
| vector2d | vec2 | |
| vecteur3d | vec3 | |
| vector4d | vec4 | |
| matrice4x4 | mat4 | |
| quaternion | vec4 | la valeur scalaire est w |
| rect | vec4 | |
| point, taille | vec2 | |
| TextureInput | sampler2D |
Amélioration de l'exemple
Avant d'aller plus loin, améliorons un peu l'aspect visuel de l'exemple. En ajoutant un maillage rectangulaire pivoté et en faisant en sorte que l'DirectionalLight e projette des ombres, nous pouvons vérifier que la modification apportée aux sommets du cube est correctement prise en compte dans toutes les passes de rendu, y compris les cartes d'ombres. Pour obtenir une ombre visible, la lumière est désormais placée un peu plus haut sur l’axe Y, et une rotation est appliquée pour qu’elle soit partiellement dirigée vers le bas. (Comme il s’agit d’une lumière de type « directional », la rotation a son importance)
| main.qml, material.vert | Résultat |
|---|---|
|
Ajout d'un shader de fragment
De nombreux matériaux personnalisés nécessiteront également un shader de fragment. En fait, beaucoup n’auront besoin que d’un shader de fragment. S’il n’y a pas de données supplémentaires à transmettre de l’étape des sommets à celle des fragments, et si la transformation par défaut des sommets est suffisante, la définition de la propriété ` vertexShader ` peut être omise dans l’ CustomMaterial.
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Notre premier shader de fragment contient une fonction MAIN() vide. Cela revient à ne pas spécifier du tout d’extrait de shader de fragment : le résultat obtenu est identique à celui obtenu avec un PrincipledMaterial par défaut.
Examinons quelques-uns des mots-clés couramment utilisés dans les shaders de fragments. Il ne s’agit pas d’une liste exhaustive ; consultez la documentation d’ CustomMaterial pour une référence complète. Bon nombre d’entre eux sont en lecture-écriture, ce qui signifie qu’ils ont une valeur par défaut, mais que le shader peut, et voudra souvent, leur attribuer une valeur différente.
Comme leur nom l’indique, bon nombre d’entre eux correspondent à des propriétés d’ PrincipledMaterial portant un nom similaire, avec la même signification et la même sémantique, conformément au modèle de matériau « metallic-roughness ». C’est à l’implémentation du matériau personnalisé de décider comment ces valeurs sont calculées : par exemple, une valeur pour « BASE_COLOR » peut être codée en dur dans le shader, être basée sur l’échantillonnage d’une texture, ou être calculée à partir de propriétés QML exposées sous forme d’uniformes ou de données interpolées transmises par le shader de sommets.
| Mot-clé | Type | Description |
|---|---|---|
| BASE_COLOR | vec4 | La couleur de base et la valeur alpha. Correspond à PrincipledMaterial::baseColor. La valeur alpha finale du fragment est l'opacité du modèle multipliée par l'alpha de la couleur de base. La valeur par défaut est (1.0, 1.0, 1.0, 1.0). |
| EMISSIVE_COLOR | vec3 | La couleur de l'auto-illumination. Correspond à PrincipledMaterial::emissiveFactor. La valeur par défaut est (0.0, 0.0, 0.0). |
| METALNESS | float | Metalness Valeur comprise entre 0 et 1. La valeur par défaut est 0, ce qui signifie que le matériau est diélectrique (non métallique). |
| ROUGHNESS | nombre flottant | Roughness valeur comprise entre 0 et 1. La valeur par défaut est 0. Des valeurs plus élevées adoucissent les reflets spéculaires et estompent les reflets. |
| SPECULAR_AMOUNT | nombre à virgule flottante | The strength of specularity comprise entre 0 et 1. La valeur par défaut est 0.5. Pour les objets métalliques dont la valeur « metalness » est définie sur 1, cette valeur n’aura aucun effet. Lorsque les valeurs « SPECULAR_AMOUNT » et « METALNESS » sont toutes deux supérieures à 0 mais inférieures à 1, le résultat correspond à un mélange des deux modèles de matériau. |
| NORMAL | vec3 | La normale interpolée dans l’espace mondial, ajustée pour tenir compte de la double face lorsque le culling des faces est désactivé. En lecture seule. |
| UV0 | vec2 | Les coordonnées de texture interpolées. Lecture seule. |
| VAR_WORLD_POSITION | vec3 | Position interpolée du sommet dans l'espace mondial. En lecture seule. |
Rendons la couleur de base du cube rouge :
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Renforçons maintenant légèrement le niveau d’auto-illumination :
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Au lieu d’avoir des valeurs codées en dur dans le shader, nous pourrions également utiliser des propriétés QML exposées en tant qu’uniformes, même animées :
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
Passons à quelque chose de moins trivial, qui ne peut pas être mis en œuvre avec un « PrincipledMaterial » et ses propriétés standard intégrées. Le matériau suivant visualise les coordonnées UV de la texture du maillage du cube. U varie de 0 à 1, allant ainsi du noir au rouge, tandis que V varie également de 0 à 1, du noir au vert.
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Tant que nous y sommes, pourquoi ne pas visualiser également les normales, cette fois-ci sur une sphère. Comme pour les coordonnées UV, si un extrait de shader de sommet personnalisé venait à modifier la valeur de NORMAL, la valeur interpolée par fragment dans le shader de fragment, également exposée sous le nom NORMAL, refléterait ces ajustements.
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Couleurs
Passons un instant à un modèle de théière, créons un matériau combinant des propriétés métalliques et diélectriques, et essayons de lui attribuer une couleur de base verte. La valeur « green » QColor correspond à (0, 128, 0); sur cette base, notre première tentative pourrait être la suivante :
| main.qml, material.frag |
|---|
|

Le résultat ne semble pas tout à fait correct. Comparons avec la deuxième approche :
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
En passant à une valeur « PrincipledMaterial », nous pouvons confirmer qu’en définissant l’ PrincipledMaterial::baseColor sur « green » et en respectant les propriétés de métallicité et autres, le résultat est identique à celui de notre deuxième approche :
| Modification dans main.qml | Résultat |
|---|---|
|
|
Si le type de la propriété « uColor » était remplacé par « vector4d », ou par tout autre type que « color », les résultats changeraient soudainement et deviendraient identiques à ceux de notre première approche.
Pourquoi cela ?
La réponse réside dans la conversion sRGB vers linéaire qui est effectuée implicitement pour les propriétés de couleur de DefaultMaterial, PrincipledMaterial, ainsi que pour les propriétés personnalisées de type color dans un CustomMaterial. Cette conversion n’est effectuée pour aucune autre valeur ; ainsi, si le shader code en dur une valeur de couleur ou la fait dépendre d’une propriété QML dont le type est différent de ` color`, il incombera au shader d’effectuer la linéarisation au cas où la valeur source se trouverait dans l’espace colorimétrique sRGB. La conversion en mode linéaire est importante car Qt Quick 3D effectue une « tonemapping » sur les résultats du shading de fragments, et ce processus suppose que les valeurs en entrée sont dans l’espace sRGB.
Les constantes intégrées d’ QColor, telles que "green", sont toutes exprimées dans l’espace sRGB. Par conséquent, se contenter d’affecter vec4(0.0, 0.5, 0.0, 1.0) à BASE_COLOR lors de la première tentative est insuffisant si l’on souhaite obtenir un résultat correspondant à une valeur RVB (0, 128, 0) dans l’espace sRGB. Consultez la documentation sur la « BASE_COLOR » (Conversion de couleurs) à l'adresse CustomMaterial pour obtenir une formule permettant de linéariser ces valeurs de couleur. Il en va de même pour les valeurs de couleur extraites par échantillonnage de textures : si les données de l'image source ne sont pas dans l'espace colorimétrique sRGB, une conversion est nécessaire (à moins que l'option « tonemapping » (Conversion automatique des couleurs) ne soit désactivée).
Mélange
Il ne suffit pas d’écrire une valeur inférieure à 1.0 dans BASE_COLOR.a si l’on souhaite obtenir un mélange alpha. Ces matériaux modifient très souvent les valeurs des propriétés sourceBlend et destinationBlend pour obtenir les résultats souhaités.
Gardez également à l’esprit que la valeur alpha combinée correspond à Node opacity multipliée par l’alpha du matériau.
Pour illustrer cela, utilisons un shader qui attribue la couleur rouge avec un alpha de 0.5 à BASE_COLOR:
| main.qml, material.frag | Résultat |
|---|---|
|
|
Le premier cube attribue une valeur de 0,5 à l’alpha de la couleur, mais cela ne produit aucun résultat visible car le mélange alpha n’est pas activé. Le deuxième cube active un mélange alpha simple via les propriétés d’ CustomMaterial. Le troisième attribue également une opacité de 0,5 au modèle, ce qui signifie que l’opacité effective est de 0,25.
Transmission de données entre le shader de sommet et le shader de fragment
On calcule une valeur par sommet (par exemple, en supposant un seul triangle, pour les 3 sommets du triangle), puis on la transmet à l'étape des fragments, où, pour chaque fragment (par exemple, chaque fragment recouvert par le triangle rastérisé), une valeur interpolée est mise à disposition. Dans les extraits de shader de matériau personnalisés, cela est rendu possible par le mot-clé ` VARYING `. Celui-ci fournit une syntaxe similaire à GLSL 120 et GLSL ES 100, mais fonctionnera quelle que soit l’API graphique utilisée lors de l’exécution. Le moteur se chargera de réécrire la déclaration de variable de manière appropriée.
Voyons à quoi ressemblerait l’échantillonnage classique d’une texture à l’aide de coordonnées UV. Les textures seront abordées dans une section ultérieure ; pour l’instant, concentrons-nous sur la manière d’obtenir les coordonnées UV pouvant être transmises à la fonction ` texture() ` dans le shader.
| main.qml, material.vert, material.frag |
|---|
|
| qt_logo_rect.png | Résultat |
|---|---|
|
|
Notez les déclarations ` VARYING `. Le nom et le type doivent correspondre ; ` uv ` dans le shader de fragment exposera la coordonnée UV interpolée pour le fragment actuel.
Tout autre type de données peut être transmis à l’étape de fragmentation de la même manière. Il convient de noter que, dans de nombreux cas, il n’est pas nécessaire de configurer les variables propres au matériau, car des fonctions intégrées sont fournies pour répondre à la plupart des besoins courants. Cela inclut la génération des normales (interpolées), des coordonnées UV, de la position dans l’espace mondial (VAR_WORLD_POSITION) ou du vecteur pointant vers la caméra (VIEW_VECTOR).
L’exemple ci-dessus peut en effet être simplifié comme suit, car ` UV0 ` est automatiquement disponible à l’étape du fragment également :
| Modification dans main.qml, material.frag | Résultat |
|---|---|
|
|
Pour désactiver l'interpolation d'une variable, utilisez le mot-clé flat dans les extraits de code des shaders de sommets et de fragments. Par exemple :
VARYING flat vec2 v;Textures
Un « CustomMaterial » ne dispose pas de cartes de texture intégrées, ce qui signifie qu’il n’existe pas d’équivalent, par exemple, à ` PrincipledMaterial::baseColorMap`. En effet, la mise en œuvre de cette fonctionnalité est souvent triviale, tout en offrant beaucoup plus de flexibilité que ce qui est intégré dans `DefaultMaterial` et ` PrincipledMaterial `. Au-delà du simple échantillonnage d’une texture, les extraits de shader de fragment personnalisés sont libres de combiner et de fusionner des données provenant de diverses sources lors du calcul des valeurs qu’ils attribuent à BASE_COLOR, EMISSIVE_COLOR, ROUGHNESS, etc. Ils peuvent baser ces calculs sur des données fournies via des propriétés QML, des données interpolées transmises depuis l’étape des sommets, des valeurs récupérées à partir de l’échantillonnage de textures, ainsi que sur des valeurs codées en dur.
Comme le montre l’exemple précédent, l’exposition d’une texture aux shaders de sommets, de fragments, ou aux deux, est très similaire aux valeurs uniformes scalaires et vectorielles : une propriété QML de type TextureInput sera automatiquement associée à un sampler2D dans le code du shader. Comme toujours, il n’est pas nécessaire de déclarer cet échantillonneur dans le code du shader.
Un « TextureInput » fait référence à un « Texture », avec une propriété supplémentaire « enabled ». Un « Texture » peut s’alimenter en données de trois façons : « from an image file », from a texture with live Qt Quick contentou can be provided from C++ via QQuick3DTextureData.
Remarque : en ce qui concerne les propriétés d’ Texture, seules celles liées à la source, au mosaïquage et au filtrage sont prises en compte implicitement avec les matériaux personnalisés, les autres (telles que les transformations UV) devant être implémentées par les shaders personnalisés comme bon leur semble.
Voyons un exemple où un modèle, une sphère dans le cas présent, est texturé à l’aide d’un contenu d’ Qt Quick en temps réel :
| main.qml, material.frag |
|---|
|
Ici, le sous-arbre 2D (un rectangle avec deux éléments enfants : un autre rectangle et le texte) est rendu dans une texture 2D de 512x512 à chaque fois que cette mini-scène change. La texture est ensuite exposée au matériau personnalisé sous le nom « someTextureMap ».
Notez l’inversion de la coordonnée V dans le shader. Comme indiqué ci-dessus, les matériaux personnalisés, qui offrent une programmabilité totale au niveau du shader, ne proposent pas les fonctionnalités « fixes » de Texture et PrincipledMaterial. Cela signifie que toute transformation des coordonnées UV devra être appliquée par le shader. Ici, nous savons que la texture est générée via Texture::sourceItem; il faut donc inverser la coordonnée V pour obtenir un résultat correspondant au jeu de coordonnées UV du maillage que nous utilisons.
Cet exemple montre que cela est également possible avec un « PrincipledMaterial ». Rendons-le plus intéressant en y ajoutant un simple effet de relief :
| material.frag | Résultat |
|---|---|
|
Grâce aux fonctionnalités abordées jusqu’à présent, un large éventail de possibilités s’offre à vous pour créer des matériaux qui ombrent les maillages de manière visuellement impressionnante. Pour conclure cette présentation de base, examinons un exemple qui applique des cartes de hauteur et de normales à un maillage plan. (un fichier d’ .mesh dédié est utilisé ici car l’ #Rectangle e intégré ne dispose pas d’un nombre suffisant de subdivisions) Pour obtenir de meilleurs résultats d’éclairage, nous utiliserons un éclairage basé sur une image avec une image HDR à 360 degrés. L’image est également définie comme skybox afin de mieux comprendre ce qui se passe.
Commençons par un fichier ` CustomMaterial` vide :
| main.qml | Résultat |
|---|---|
|
|
Créons maintenant des shaders qui appliquent une carte de hauteur et une carte de normales au maillage :
| Carte de hauteur | Carte de normales |
|---|---|
|
|
| material.vert, material.frag |
|---|
|
| Modification dans main.qml | Résultat |
|---|---|
|
|
Remarque : l’objet ` WasdController ` peut s’avérer extrêmement utile pendant le développement et le dépannage, car il permet de naviguer et d’explorer la scène à l’aide du clavier et de la souris, de manière intuitive. Pour contrôler une caméra via l’objet ` WasdController `, il suffit de :
import QtQuick3D.Helpers
View3D {
PerspectiveCamera {
id: camera
}
// ...
}
WasdController {
controlledObject: camera
}Textures de profondeur et d’écran
Lorsqu’un extrait de shader personnalisé utilise les mots-clés ` DEPTH_TEXTURE ` ou ` SCREEN_TEXTURE `, il choisit de générer les textures correspondantes lors d’un passage de rendu distinct, ce qui n’est pas nécessairement une opération peu coûteuse en ressources, mais permet de mettre en œuvre diverses techniques, telles que la réfraction pour les matériaux de type verre.
DEPTH_TEXTURE sampler2D permet d’échantillonner une texture avec le contenu du tampon de profondeur, tous les objets de la scène ayant été rendus. De même, est une qui permet d’échantillonner une texture contenant le contenu de la scène, à l’exclusion de tout matériau transparent ou de tout matériau utilisant également SCREEN_TEXTURE. Cette texture peut être utilisée pour les matériaux qui nécessitent le contenu du tampon de trame sur lequel ils sont rendus. La texture SCREEN_TEXTURE utilise le même mode d’effacement que l’ . La taille de ces textures correspond à celle de l’ en pixels. opaque SCREEN_TEXTURE sampler2D View3D View3D
Faisons une démonstration simple en visualisant le contenu du tampon de profondeur via ` DEPTH_TEXTURE`. L’ far clip value de la caméra est ici réduite de la valeur par défaut de 10 000 à 2 000, afin d’avoir une plage plus restreinte et de rendre ainsi plus évidentes les différences de valeurs de profondeur visualisées. Le résultat est un rectangle qui permet de visualiser le tampon de profondeur de la scène sur sa surface.
| main.qml, material.frag | Résultat |
|---|---|
|
Notez que le cylindre n’apparaît pas dans « DEPTH_TEXTURE » (plage de profondeur) car il repose sur la semi-transparence, ce qui le place dans une catégorie différente de celle des autres objets, qui sont tous opaques. Ces objets n’écrivent pas dans le tampon de profondeur, bien qu’ils effectuent des tests par rapport aux valeurs de profondeur écrites par les objets opaques, et reposent sur un rendu de l’arrière vers l’avant. Ils ne figurent donc pas non plus dans DEPTH_TEXTURE.
Que se passe-t-il si nous configurons le shader pour qu’il échantillonne plutôt l’ SCREEN_TEXTURE?
| material.frag | Résultat |
|---|---|
|
|
Ici, le rectangle est texturé avec l’ SCREEN_TEXTURE, tandis que les pixels transparents sont remplacés par du violet.
Fonctions du processeur d’éclairage
Une fonctionnalité avancée d’ CustomMaterial est la possibilité de définir, dans le shader de fragment, des fonctions qui réimplémentent les équations d’éclairage utilisées pour calculer la couleur du fragment. Une fonction de traitement de la lumière, lorsqu’elle est présente, est appelée une fois par source lumineuse de la scène, pour chaque fragment. Il existe une fonction dédiée pour chaque type de lumière, ainsi que pour les contributions ambiante et spéculaire. En l’absence de fonction de traitement de la lumière correspondante, les calculs standard sont utilisés, comme le ferait un « PrincipledMaterial ». Lorsqu’un processeur de lumière est présent, mais que le corps de la fonction est vide, cela signifie qu’il n’y aura aucune contribution d’un type de lumière donné dans la scène.
Reportez-vous à la documentation « CustomMaterial » pour plus de détails sur des fonctions telles que DIRECTIONAL_LIGHT, POINT_LIGHT, SPOT_LIGHT, AMBIENT_LIGHT et SPECULAR_LIGHT.
Matériaux personnalisés non ombrés
Il existe un autre type de « CustomMaterial » : les matériaux personnalisés de type « unshaded ». Tous les exemples présentés jusqu’à présent utilisaient des matériaux personnalisés de type « shaded », avec la propriété « shadingMode » conservée à sa valeur par défaut « CustomMaterial.Shaded».
Que se passe-t-il si nous modifions cette propriété pour la définir sur CustomMaterial.Unshaded ?
Tout d’abord, les mots-clés tels que BASE_COLOR, EMISSIVE_COLOR, METALNESS, etc. n’ont plus l’effet escompté. En effet, un matériau non ombré, comme son nom l’indique, n’est pas automatiquement modifié par la plupart des codes d’ombrage standard, ignorant ainsi les lumières, l’éclairage basé sur des images, les ombres et l’occlusion ambiante de la scène. Au contraire, un matériau non ombré confère un contrôle total au shader via le mot-clé « FRAGCOLOR ». Cela s’apparente à gl_FragColor : la couleur attribuée à « FRAGCOLOR » correspond au résultat et à la couleur finale du fragment, sans aucun ajustement supplémentaire de la part d’ Qt Quick 3D.
| main.qml, material.frag, material2.frag | Résultat |
|---|---|
|
|
Remarquez comment le cylindre de droite ignore l’ DirectionalLight ion de la scène. Son ombrage ne tient pas compte de l’éclairage de la scène ; la couleur finale du fragment est entièrement blanche.
Le shader de sommets d’un matériau non ombré dispose toujours des entrées habituelles : VERTEX, NORMAL, MODELVIEWPROJECTION_MATRIX, etc. et peut écrire dans POSITION. Le shader de fragments ne dispose toutefois plus de ces facilités : NORMAL, UV0 ou VAR_WORLD_POSITION ne sont pas disponibles dans le shader de fragments d’un matériau non ombré. C'est désormais au code du shader de calculer et de transmettre, à l'aide de ` VARYING `, tout ce dont il a besoin pour déterminer la couleur finale du fragment.
Prenons un exemple comportant à la fois un shader de sommets et un shader de fragments. La position modifiée du sommet est transmise au shader de fragments, avec une valeur interpolée mise à disposition pour chaque fragment.
| main.qml, material.vert, material.frag |
|---|
|
Les matériaux non ombrés sont utiles lorsque l’interaction avec l’éclairage de la scène n’est ni nécessaire ni souhaitée, et que le matériau doit disposer d’un contrôle total sur la couleur finale du fragment. Remarquez que dans l’exemple ci-dessus, il n’y a ni « DirectionalLight » ni aucune autre source de lumière, mais que la sphère dotée du matériau personnalisé s’affiche comme prévu.
Remarque : un matériau non ombré qui ne comporte qu’un extrait de shader de sommet, mais qui ne spécifie pas la propriété `fragmentShader`, restera fonctionnel, mais les résultats seront les mêmes que si le `shadingMode` était défini sur `Shaded`. Il n’est donc pas très utile de modifier le `shadingMode` pour les matériaux qui ne possèdent qu’un shader de sommet.
Programmabilité des effets
Les effets de post-traitement appliquent un ou plusieurs shaders de fragment au résultat d’un « View3D ». La sortie de ces shaders de fragment s’affiche alors à la place des résultats de rendu d’origine. Ce concept est très similaire à celui des « ShaderEffect » et « ShaderEffectSource » de Qt Quick.
Remarque : les effetsde post-traitement ne sont disponibles que lorsque le paramètre « renderMode » de l’« View3D » est défini sur « View3D.Offscreen ».
Des extraits de shader de sommets personnalisés peuvent également être spécifiés pour un effet, mais leur utilité est limitée et on s'attend donc à ce qu'ils soient relativement peu utilisés. L'entrée de sommets pour un effet de post-traitement est un quadrilatère (soit deux triangles, soit une bande de triangles) ; la transformation ou le déplacement des sommets de celui-ci n'est souvent pas utile. Il peut toutefois être judicieux d’utiliser un shader de sommets afin de calculer et de transmettre des données au shader de fragments à l’aide du mot-clé ` VARYING `. Comme d’habitude, le shader de fragments recevra alors une valeur interpolée basée sur la coordonnée actuelle du fragment.
La syntaxe des extraits de shader associés à un « Effect » est identique à celle des shaders pour une « CustomMaterial » sans shader. En ce qui concerne les mots-clés spéciaux intégrés, « VARYING », « MAIN », « FRAGCOLOR » (shader de fragment uniquement), « POSITION » (shader de sommet uniquement), « VERTEX » (shader de sommet uniquement) et « MODELVIEWPROJECTION_MATRIX » fonctionnent de la même manière que « CustomMaterial ».
Les mots-clés spéciaux les plus importants pour les shaders de fragment « Effect » sont les suivants :
| Nom | Type | Description |
|---|---|---|
| INPUT | sampler2D ou sampler2DArray | L'échantillonneur pour la texture d'entrée. Un effet échantillonnera généralement celle-ci à l'aide de la fonction ` INPUT_UV`. |
| INPUT_UV | vec2 | Coordonnées UV pour l'échantillonnage de INPUT. |
| INPUT_SIZE | vec2 | Taille de la texture d'INPUT, en pixels. Il s'agit d'une alternative pratique à l'appel de la fonction textureSize(). |
| OUTPUT_SIZE | vec2 | La taille de la texture de sortie, en pixels. Elle est souvent égale à celle de la texture d'INPUT_SIZE, mais un effet à passes multiples peut comporter des passes dont la sortie est dirigée vers des textures intermédiaires de tailles différentes. |
| DEPTH_TEXTURE | sampler2D | Texture de profondeur contenant le contenu du tampon de profondeur avec les objets opaques de la scène. Comme pour ` CustomMaterial`, la présence de ce mot-clé dans le shader déclenche la génération automatique de la texture de profondeur. |
Remarque : lorsque le rendu multivue est activé, la texture d’entrée est un tableau de textures 2D. Les fonctions GLSL telles que `texture()` et `textureSize()` prennent/renvoient alors respectivement un `vec3` et un `ivec3`. Utilisez ` VIEW_INDEX ` pour la couche. Dans les applications VR/AR devant fonctionner aussi bien avec qu’sans rendu multivue, l’approche la plus portable consiste à écrire le code du shader comme suit :
#if QSHADER_VIEW_COUNT >= 2
vec4 c = texture(INPUT, vec3(INPUT_UV, VIEW_INDEX));
#else
vec4 c = texture(INPUT, INPUT_UV);
#endifUn effet de post-traitement
Commençons par une scène simple, en utilisant cette fois-ci quelques objets supplémentaires, notamment un rectangle texturé qui utilise une texture en damier comme carte de couleurs de base.
| main.qml | Résultat |
|---|---|
|
|
Appliquons maintenant un effet à l’ensemble de la scène. Plus précisément, à l’ View3D. Lorsqu’il y a plusieurs éléments « View3D » dans la scène, chacun dispose de sa propre « SceneEnvironment » et donc de sa propre chaîne d’effets de post-traitement. Dans l’exemple, il n’y a qu’un seul « View3D » couvrant toute la fenêtre.
| Modification dans main.qml | effect.frag |
|---|---|
| |
Cet effet simple modifie la valeur du canal de couleur rouge. L'exposition des propriétés QML en tant qu’uniformes fonctionne de la même manière avec les effets qu’avec les matériaux personnalisés. Le shader commence par une ligne qui sera très courante lors de l’écriture de shaders de fragments pour les effets : échantillonnage de INPUT aux coordonnées UV INPUT_UV. Il effectue ensuite les calculs souhaités et attribue la couleur finale du fragment à FRAGCOLOR.
De nombreuses propriétés définies dans l’exemple sont au pluriel (effets, passes, shaders). Bien que la syntaxe de la liste [ ] puisse être omise lorsqu’il n’y a qu’un seul élément, toutes ces propriétés sont des listes et peuvent contenir plusieurs éléments. Pourquoi ?
- effects est une liste, car View3D permet d’enchaîner plusieurs effets. Les effets sont appliqués dans l’ordre dans lequel ils sont ajoutés à la liste. Cela permet d’appliquer facilement deux effets ou plus à la fois à l’ View3D, et s’apparente à ce que l’on peut obtenir dans Qt Quick en imbriquant des éléments ShaderEffect. La texture «
INPUT» de l’effet suivant est toujours une texture contenant la sortie de l’effet précédent. La sortie du dernier effet de la chaîne est utilisée comme sortie finale de l’« View3D ». - passes est une liste, car contrairement à ShaderEffect, Effect prend en charge nativement les passes multiples. Un effet à passes multiples est plus puissant que l’enchaînement de plusieurs effets indépendants dans effects: une passe peut générer une texture intermédiaire temporaire, qui peut ensuite être utilisée comme entrée pour les passes suivantes, en plus de la texture d’entrée d’origine de l’effet. Cela permet de créer des effets complexes qui calculent, rendent et fusionnent plusieurs textures afin d’obtenir la couleur finale du fragment. Ce cas d’utilisation avancé ne sera pas abordé ici. Reportez-vous à la page de documentation Effect pour plus de détails.
- shaders Il s'agit d'une liste, car un effet peut être associé à la fois à un shader de sommet et à un shader de fragment.
Enchaînement de plusieurs effets
Examinons un exemple dans lequel l’effet de l’exemple précédent est complété par un autre effet similaire à l’effet intégré « DistortionSpiral ».
| Modification dans main.qml | effect2.frag |
|---|---|
| |
Voici maintenant la question qui pourrait surprendre : pourquoi s'agit-il d'un mauvais exemple ?
Plus précisément, ce n’est pas un mauvais exemple, mais il illustre plutôt un schéma qu’il vaut souvent mieux éviter.
Enchaîner les effets de cette manière peut s’avérer utile, mais il est important de garder à l’esprit les implications en termes de performances : effectuer deux passes de rendu (l’une pour générer une texture avec le canal de couleur rouge ajusté, puis une autre pour calculer la distorsion) est assez coûteux alors qu’une seule suffirait. Si les extraits de shader de fragment avaient été combinés, le même résultat aurait pu être obtenu avec un seul effet.
Définition des données de maillage et de texture en C++
La génération procédurale de données de maillage et d’images de texture suit des étapes similaires :
- Sous-classer ` QQuick3DGeometry ` ou QQuick3DTextureData
- Définir les données de sommets ou d’images souhaitées lors de la construction en appelant les fonctions membres protégées de la classe de base
- Si des modifications dynamiques s’avèrent nécessaires ultérieurement, définissez les nouvelles données et appelez la fonction update()
- Une fois l’implémentation terminée, la classe doit être enregistrée pour qu’elle soit visible dans QML
- Model et les objets ` Texture ` dans QML peuvent désormais utiliser le fournisseur de données de sommets ou d’images personnalisé en définissant la propriété ` Model::geometry ` ou ` Texture::textureData `
Données de sommets personnalisées
Les données de sommets désignent la séquence de valeurs (généralement de type ` float`) qui composent un maillage. Au lieu de charger des fichiers ` .mesh `, un fournisseur de géométrie personnalisé est chargé de fournir ces mêmes données. Les données de sommets se composent d’ attributes, tels que la position, les coordonnées de texture (UV) ou les normales. La spécification des attributs décrit le type d’attributs présents, le type de composante (par exemple, un vecteur flottant à 3 composantes pour la position d’un sommet, composé des valeurs x, y et z), à quel décalage ils commencent dans les données fournies, et quelle est la pas (l’incrément à ajouter au décalage pour pointer vers l’élément suivant du même attribut).
Cela peut sembler familier à ceux qui ont déjà travaillé directement avec des API graphiques telles qu’OpenGL ou Vulkan, car la manière dont les données de sommets sont spécifiées avec ces API correspond globalement à ce que définissent un fichier « .mesh » ou une instance « QQuick3DGeometry ».
De plus, la topologie du maillage (type de primitive) doit également être spécifiée. Pour le dessin indexé, les données d’un tampon d’index doivent également être fournies.
Il existe une implémentation intégrée de géométrie personnalisée : le module ` QtQuick3D.Helpers` inclut un type ` GridGeometry `. Cela permet de rendre une grille dans la scène à l’aide de primitives de ligne, sans avoir à implémenter une sous-classe personnalisée de ` QQuick3DGeometry `.
Un autre cas d’utilisation courant est le rendu de points. Cela est assez simple à réaliser puisque la spécification des attributs est minimale : nous fournissons trois flottants (x, y, z) pour chaque sommet, rien d’autre. Une sous-classe de ` QQuick3DGeometry ` pourrait implémenter une géométrie composée de 2 000 points de la manière suivante :
clear();
const int N = 2000;
const int stride = 3 * sizeof(float);
QByteArray v;
v.resize(N * stride);
float *p = reinterpret_cast<float *>(v.data());
QRandomGenerator *rg = QRandomGenerator::global();
for (int i = 0; i < N; ++i) {
const float x = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
const float y = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
*p++ = x;
*p++ = y;
*p++ = 0.0f;
}
setVertexData(v);
setStride(stride);
setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points);
addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);Associée à un matériau de
DefaultMaterial {
lighting: DefaultMaterial.NoLighting
cullMode: DefaultMaterial.NoCulling
diffuseColor: "yellow"
pointSize: 4
}le résultat final est similaire à ceci (ici vu sous un angle de caméra modifié, à l’aide d’ WasdController) :

Remarque : sachez que les tailles de points et les largeurs de lignes différentes de 1 peuvent ne pas être prises en charge lors de l’exécution, selon l’API graphique sous-jacente. Qt n’a aucun contrôle sur ce point. Il peut donc s’avérer nécessaire de mettre en œuvre des techniques alternatives plutôt que de s’appuyer sur le tracé de points et de lignes.
Données de texture personnalisées
Avec les textures, les données à fournir sont beaucoup plus simples sur le plan structurel : il s’agit des données brutes de pixels, avec un nombre variable d’octets par pixel, selon le format de la texture. Par exemple, une texture de type RGBA nécessite quatre octets par pixel, tandis qu’une texture de type RGBA16F utilise quatre demi-flottants par pixel. Cela s’apparente à ce que stocke en interne un QImage. Cependant, les textures 3D de Qt Quick peuvent avoir des formats dont les données ne peuvent pas être représentées par un QImage. C’est le cas, par exemple, des textures HDR à virgule flottante ou des textures compressées. Par conséquent, les données de QQuick3DTextureData sont toujours fournies sous la forme d’une séquence brute d’octets. Cela peut sembler familier à ceux qui ont déjà travaillé directement avec des API graphiques telles qu’OpenGL ou Vulkan.
Pour plus de détails, consultez les pages de documentation QQuick3DGeometry et QQuick3DTextureData.
Voir également CustomMaterial, Effect, QQuick3DGeometry, QQuick3DTextureData, Qt Quick 3D - Exemple d’effet personnalisé, Qt Quick 3D - Exemple de shaders personnalisés, Qt Quick 3D - Exemple de matériaux personnalisés, Qt Quick 3D - Exemple de géométrie personnalisée et Qt Quick 3D - Exemple de texture procédurale.
© 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.























