Préparation aux entretiens en Graphisme Informatique

Questions d'entretien Développeur en Graphisme Informatique

15 questions d'entretien en graphisme informatique sélectionnées, groupées par niveau d'ancienneté. Utilisez-les pour réviser les fondamentaux, les compromis pratiques et le raisonnement de production de niveau senior.

Démarrer un entretien IA en Graphisme InformatiqueAucune carte bancaire requise. 1 session gratuite disponible.
Préparation aux entretiens techniques en anglaisUn mode où les non-natifs peuvent s'entraîner à passer des entretiens techniques.

Questions Junior

1Décrivez le skinning par mélange linéaire et l'application des matrices d'os à un sommet soumis à plusieurs influences.

Le skinning par mélange linéaire (LBS) déforme un maillage 3D à partir d'une hiérarchie squelettique. Chaque os animé se déplace relativement à la pose de référence, ou bind pose. Pour un sommet influencé par plusieurs os : 1. Transformer sa position initiale dans l'espace du maillage vers l'espace local de chaque os avec la matrice de pose de liaison inverse B_i^{-1}. 2. Transformer ensuite vers la pose animée actuelle avec M_i. La transformation composée S_i = M_i · B_i^{-1} est la matrice de palette de skinning. 3. Calculer la position finale par somme linéaire pondérée : v' = Σ_{i=1}^{k} w_i · (M_i · B_i^{-1} · v), avec des poids normalisés, Σ w_i = 1,0. Sur le processeur graphique (GPU), le programme de traitement des sommets (vertex shader), ou une prépasse de skinning en calcul, récupère la palette précalculée dans un tampon uniforme/structuré à partir des indices d'os du sommet, puis mélange les positions. Conserver des espaces cohérents entre liaison, articulations animées et maillage, y compris l'éventuelle transformation inverse du maillage. Pour des os rigides, mélanger les normales transformées puis renormaliser est une approximation courante. En cas d'échelle non uniforme ou de cisaillement, utiliser une inverse transposée adaptée à l'approximation de déformation choisie ; transformer les tangentes comme directions et réorthogonaliser le repère tangent. La matrice mélangée n'est généralement pas une rotation pure.

#version 450

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in uvec4 inBoneIndices; // Up to 4 bone influences
layout(location = 3) in vec4 inBoneWeights;  // Normalized: sum to 1.0

layout(set = 0, binding = 0) uniform BonePalette {
    mat4 boneMatrices[128]; // Pre-multiplied: M_i * B_i^-1
};

layout(location = 0) out vec3 outNormal;

void main() {
    mat4 skinMatrix = inBoneWeights.x * boneMatrices[inBoneIndices.x] +
                      inBoneWeights.y * boneMatrices[inBoneIndices.y] +
                      inBoneWeights.z * boneMatrices[inBoneIndices.z] +
                      inBoneWeights.w * boneMatrices[inBoneIndices.w];

    vec4 skinnedPosition = skinMatrix * vec4(inPosition, 1.0);
    gl_Position = u_ViewProjection * skinnedPosition;
    
    // Transform normal with rotational part of skinMatrix and normalize
    outNormal = normalize(mat3(skinMatrix) * inNormal);
}
Essayer de répondre à cette question avec un coach IA

2Décrivez l'instanciation GPU (Graphics Processing Unit) et comment les données et les agencements par instance rendent le rendu de nombreux objets similaires efficace.

L'instanciation GPU est une technique de rendu qui dessine plusieurs copies de la même géométrie de base (partageant les tampons de sommets et d'indices) en un seul appel de tirage (draw call) (par exemple, `DrawIndexedInstanced` dans Direct3D ou `glDrawElementsInstanced` dans OpenGL), réduisant drastiquement la surcharge du pilote CPU (Central Processing Unit)-GPU et le nombre d'appels de tirage de l'API (Application Programming Interface).<br><br>Données par instance : Pour que les instances aient des apparences et des comportements distincts, des données par instance sont fournies, incluant couramment :<br>- **Données de transformation** : Matrice monde, ou position/rotation/échelle compactées.<br>- **Propriétés du matériau** : Teintes de couleur, décalages/échelles UV, ou indices d'ID de matériau.<br>- **Paramètres dynamiques** : Phase d'animation, décalages de lightmap, ou drapeaux de visibilité.<br><br>Agencements des données et méthodes d'accès :<br>1. **Tampons de sommets instanciés** : Un tampon de sommets dédié lié avec un taux d'avancement par instance (par exemple, `D3D11_INPUT_PER_INSTANCE_DATA`). Le GPU avance automatiquement le tampon par instance.<br>2. **`StructuredBuffer` / `Uniform Buffer` (SSBO / `Constant Buffer`)** : Les données d'instance sont téléchargées dans un tableau tampon, et le nuanceur de sommets (vertex shader) y accède en utilisant l'identifiant d'instance système intégré (`SV_InstanceID` en HLSL, `gl_InstanceID` en GLSL). Garder les données par instance compactes (par exemple, des matrices affines 3x4 ou position + quaternion au lieu de matrices 4x4 complètes, et des couleurs FP16/uint32 compactées) minimise la bande passante mémoire du GPU et optimise l'utilisation du cache.

struct InstanceData {
    float4x4 worldMatrix;
    float4   colorTint;
};

StructuredBuffer<InstanceData> gInstanceData : register(t0);

struct VSInput {
    float3 position : POSITION;
    float3 normal   : NORMAL;
};

struct VSOutput {
    float4 position : SV_POSITION;
    float4 color    : COLOR;
};

VSOutput main(VSInput input, uint instanceID : SV_InstanceID)
{
    VSOutput output;
    InstanceData inst = gInstanceData[instanceID];
    
    float4 worldPos = mul(inst.worldMatrix, float4(input.position, 1.0));
    output.position = mul(gViewProjMatrix, worldPos);
    output.color    = inst.colorTint;
    return output;
}
Essayer de répondre à cette question avec un coach IA

3Expliquez le culling de frustum, le culling d'occlusion et le culling de faces arrière, et où chacun se produit généralement dans un moteur de rendu.

Le culling de frustum, le culling d'occlusion et le culling de faces arrière sont trois techniques de visibilité complémentaires qui permettent de rejeter les primitives non visibles à différentes étapes et granularités dans un pipeline de rendu :<br><br>1. **Culling de frustum** : Rejette la géométrie se trouvant complètement en dehors du frustum de vue de la caméra. Il est généralement effectué sur des volumes englobants grossiers (comme les AABBs (Axis-Aligned Bounding Boxes) ou les sphères englobantes) sur le CPU (Central Processing Unit) avant la soumission du tirage (draw submission), ou sur le GPU (Graphics Processing Unit) via des nuanceurs de calcul (compute shaders) dans les pipelines de rendu pilotés par le GPU.<br>2. **Culling d'occlusion** : Rejette les objets ou les primitives qui sont à l'intérieur du frustum mais cachés derrière d'autres géométries opaques. Il peut se produire sur le CPU (en utilisant la rastérisation logicielle ou la visibilité précalculée) ou sur le GPU (en utilisant des requêtes d'occlusion matérielles, des tests de tampon de profondeur Hi-Z de calcul GPU ou le culling de meshlet) avant la rastérisation complète.<br>3. **Culling de faces arrière** : Rejette les polygones individuels dont les normales de surface sont orientées loin de la caméra. Cela est traditionnellement effectué automatiquement par le matériel à fonction fixe pendant la configuration/rastérisation des triangles sur le GPU, basé sur l'ordre des sommets dans l'espace écran (screen-space winding order), bien que cela puisse également être évalué grossièrement sur les cônes normaux de cluster (par exemple, dans les nuanceurs de maillage (mesh shaders)).

struct Plane { glm::vec3 normal; float distance; };
struct Sphere { glm::vec3 center; float radius; };

bool isSphereInsideFrustum(const Sphere& sphere, const Plane frustumPlanes[6]) {
    for (int i = 0; i < 6; ++i) {
        // Signed distance from plane to sphere center
        float dist = glm::dot(frustumPlanes[i].normal, sphere.center) + frustumPlanes[i].distance;
        if (dist < -sphere.radius) {
            return false; // Completely outside
        }
    }
    return true; // Inside or intersecting
}
Essayer de répondre à cette question avec un coach IA

4Comment les splines Bézier, B-spline et Catmull-Rom produisent-elles des trajectoires lisses ou des bandes géométriques ?

Les splines définissent des fonctions paramétriques P(t) pour les trajectoires 3D, caméras et géométries extrudées comme rubans, routes ou tubes. 1. Types : les Bézier utilisent les polynômes de Bernstein et n'interpolent que les extrémités ; les points de contrôle intermédiaires définissent les poignées tangentes. Des poignées colinéaires bien orientées assurent la continuité géométrique des tangentes (G1) ; la continuité paramétrique de la dérivée première (C1) nécessite en plus l'égalité des amplitudes des dérivées, compte tenu du degré et des intervalles paramétriques. Les B-splines utilisent des fonctions de base sur un vecteur de nœuds, offrent contrôle local et continuité élevée, continuité des dérivées jusqu'à l'ordre deux (C2) pour une cubique aux nœuds intérieurs simples, réduite aux nœuds répétés, mais ne passent généralement pas par les points de contrôle intérieurs. Les Catmull-Rom sont interpolantes et passent par tous les points de contrôle intérieurs avec C1 pour une paramétrisation cohérente non dégénérée, adaptées aux trajectoires dessinées par l'utilisateur. 2. Génération : évaluer t donne la position P(t) et la tangente T(t) = P'(t). Extruder rubans ou tubes nécessite un repère orthogonal le long de la courbe, avec normale N(t) et binormale B(t). Les repères de Frenet-Serret échouent ou se retournent aux inflexions où la courbure κ vaut zéro. Les repères à transport parallèle, ou de Bishop, propagent doucement une orientation de référence en minimisant la torsion rotationnelle, évitant les torsions artificielles des rubans.

struct Vector3 { float x, y, z; };

Vector3 EvaluateCatmullRom(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t) {
    float t2 = t * t;
    float t3 = t2 * t;
    return 0.5f * ( (2.0f * p1) +
                    (-p0 + p2) * t +
                    (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
                    (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3 );
}
Essayer de répondre à cette question avec un coach IA

5Qu'est-ce qu'un tampon de commandes (command buffer), et pourquoi l'enregistrement de commandes multithreadé est-il important dans les moteurs de jeux haut de gamme ?

Un tampon de commandes (ou liste de commandes dans Direct3D 12) est une structure de données en mémoire où les commandes graphiques, de calcul et de transfert — telles que la définition de l'état du pipeline, la liaison des descripteurs, l'émission des appels de dessin (draw calls) et l'enregistrement des barrières de pipeline — sont enregistrées sur le CPU pour une soumission ultérieure et une exécution asynchrone sur une file d'attente du GPU. L'enregistrement de commandes multithreadé est crucial dans les moteurs de jeux haut de gamme car la préparation des appels de dessin côté CPU, la liaison d'état et le culling ont traditionnellement été les principaux goulots d'étranglement. En supprimant les contraintes de contexte à thread unique, les API (Application Programming Interface) explicites permettent à un moteur de diviser une trame en tâches de rendu indépendantes réparties sur plusieurs threads de travail CPU. Par exemple, les passes d'ombres, les blocs de G-buffer et le post-traitement peuvent être enregistrés simultanément. Les API modernes facilitent cela via des tampons de commandes primaires et secondaires (Vulkan) ou des listes de commandes et des bundles (D3D12). Les tampons de commandes secondaires et les bundles permettent aux threads de travail d'enregistrer des sous-ensembles de commandes de dessin qui peuvent être exécutés à l'intérieur d'un tampon de commandes primaire sur le thread de soumission, maximisant l'utilisation des CPU multi-cœurs et minimisant les blocages de la file d'attente GPU.

// Worker Thread Job:
void RecordShadowPassChunk(VkCommandBuffer secondaryCmdBuf, const RenderJob& job) {
    VkCommandBufferInheritanceInfo inheritInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO };
    inheritInfo.renderPass = job.shadowRenderPass;
    
    VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
    beginInfo.pInheritanceInfo = &inheritInfo;
    
    vkBeginCommandBuffer(secondaryCmdBuf, &beginInfo);
    vkCmdBindPipeline(secondaryCmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, job.pipeline);
    vkCmdDrawIndexed(secondaryCmdBuf, job.indexCount, 1, 0, 0, 0);
    vkEndCommandBuffer(secondaryCmdBuf);
}

// Main Thread Submission:
// vkCmdExecuteCommands(primaryCmdBuf, secondaryCount, secondaryCmdBuffers.data());
// vkQueueSubmit(queue, 1, &submitInfo, fence);
Essayer de répondre à cette question avec un coach IA

6En quoi les constantes push ou les constantes racine diffèrent-elles des tampons uniformes/constantes, et quand faut-il les utiliser ?

Les constantes push (dans Vulkan) et les constantes racine (dans DirectX 12) offrent un mécanisme pour passer de petites quantités de données uniformes directement en ligne dans le tampon de commandes ou la signature racine, contournant ainsi la surcharge liée à l'allocation, la mise à jour et la liaison des ressources de tampons GPU (Graphics Processing Unit) basées sur des descripteurs. En revanche, les tampons uniformes (UBO) ou les tampons de constantes (CBO) sont pris en charge par des allocations de mémoire GPU dédiées qui sont liées au pipeline via des descripteurs, des tables de descripteurs ou des ensembles de descripteurs. Étant donné que les constantes push/racine sont intégrées directement dans le flux de commandes, elles sont idéales pour les données par tirage de haute fréquence qui changent fréquemment (telles que les matrices de transformation d'objets, les indices de matériaux/maillages, les valeurs de temps ou les décalages dynamiques). Cependant, elles ont des limites de taille strictes (par exemple, Vulkan garantit une limite minimale de seulement 128 octets, et l'espace de signature racine D3D12 est plafonné à 64 DWORD (Double Word) partagés avec les descripteurs racine et les tables). Les tampons uniformes/constantes doivent être utilisés lorsque la charge utile de données dépasse les limites de taille des constantes push, lorsque les données sont partagées entre plusieurs tirages (tels que les matrices de caméra/vue par image, l'éclairage de scène global ou les paramètres d'environnement), ou lorsqu'un stockage persistant entre les passes est nécessaire.

// Push Constant setup
struct PushData {
    glm::mat4 modelMatrix;
    uint32_t materialIndex;
};

// Command buffer recording: inline write directly into command stream
PushData data = { object.transform, object.matID };
vkCmdPushConstants(cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushData), &data);
vkCmdDrawIndexed(cmdBuffer, indexCount, 1, 0, 0, 0);

// Compared to UBO: requires updating mapped GPU buffer, managing offsets/ring buffers, and binding descriptor sets
vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &perObjectDescriptorSet, 0, nullptr);
Essayer de répondre à cette question avec un coach IA

7Décrivez les espaces de coordonnées qu'un sommet traverse, de l'espace modèle à l'espace écran, dans un moteur de rendu en temps réel.

Dans un pipeline de rendu en temps réel, un sommet traverse généralement plusieurs espaces de coordonnées : l'espace modèle (local), l'espace monde, l'espace vue (caméra), l'espace de découpage (clip space), les coordonnées normalisées de l'appareil (NDC - Normalized Device Coordinates) et l'espace écran (viewport/fenêtre). Le sommet commence dans l'espace modèle par rapport à l'origine locale de l'objet. La multiplication par la matrice Modèle/Monde le positionne et l'oriente dans l'espace monde partagé. La multiplication par la matrice de Vue le transforme en espace de vue, où la caméra est à l'origine et regarde dans une direction de vision standard. Ensuite, la multiplication par la matrice de Projection transforme les coordonnées en espace de découpage 4D $(x_c, y_c, z_c, w_c)$, où la géométrie est découpée par rapport au volume de vue. Après le découpage, le matériel à fonction fixe effectue la division perspective (en divisant $x_c, y_c, z_c$ par $w_c$) pour produire des coordonnées normalisées de l'appareil (NDC) 3D. Enfin, la transformation du viewport mappe les coordonnées NDC aux coordonnées de pixels 2D de l'espace écran et aux valeurs du tampon de profondeur.

// Vertex Shader Stage
float4 worldPos = mul(modelMatrix, float4(inPosition, 1.0));
float4 viewPos  = mul(viewMatrix, worldPos);
float4 clipPos  = mul(projMatrix, viewPos); // Output to rasterizer

// Hardware Fixed-Function Stages:
// 1. Clipping against [-w, w]
// 2. Perspective Divide: ndcPos = clipPos.xyz / clipPos.w;
// 3. Viewport Transform -> Screen pixel coordinates (x_px, y_px)
Essayer de répondre à cette question avec un coach IA

Questions Intermédiaires

8Comparez le skinning par mélange linéaire avec le skinning par quaternions duaux en termes de qualité de déformation, d'artefacts et de complexité d'ingénierie.

Le Skinning par Mélange Linéaire (LBS) et le Skinning par Quaternions Duaux (DQS) représentent deux approches distinctes de la déformation de maillage squelettique : 1. **Qualité de déformation et artefacts :** * Le LBS (Linear Blend Skinning) calcule les sommets transformés par interpolation linéaire de matrices de transformation d'os. Bien que rapide, le LBS souffre d'une perte de volume lors de rotations et de torsions sévères, notamment l'artefact de l'« emballage de bonbon » où la géométrie cylindrique s'effondre le long de l'axe de torsion. * Le DQS (Dual-Quaternion Skinning) représente les transformations rigides des os sous forme de quaternions duaux unitaires (combinant rotation et translation). Lorsqu'il est mélangé (par exemple, en utilisant le mélange linéaire dual), le DQS préserve naturellement le volume et élimine les artefacts de torsion en « emballage de bonbon ». Cependant, le DQS introduit ses propres artefacts, tels que le gonflement ou le pincement lors de flexions articulaires extrêmes. 2. **Complexité d'ingénierie et d'implémentation :** * Le LBS prend nativement en charge les transformations affines complètes (translation, rotation et mise à l'échelle non uniforme ou cisaillement) à l'aide de pipelines de matrices 4x4 standard. * Le DQS ne gère nativement que les transformations rigides. La gestion de la mise à l'échelle (en particulier la mise à l'échelle non uniforme) nécessite une déformation multipasses, une décomposition polaire ou une séparation échelle-cisaillement. De plus, le DQS exige une gestion de l'antipodalité lors du mélange (vérification des produits scalaires des quaternions duaux pour prendre le chemin de rotation le plus court et éviter le retournement/l'effondrement du maillage), ce qui rend les calculs de shader et le pipeline d'actifs plus complexes.

struct DualQuat {
    float4 rot;
    float4 trans;
};

DualQuat BlendDualQuaternions(uint4 indices, float4 weights, StructuredBuffer<DualQuat> boneDQs)
{
    DualQuat dq0 = boneDQs[indices.x];
    DualQuat blended = dq0;
    blended.rot *= weights.x;
    blended.trans *= weights.x;

    [unroll]
    for (int i = 1; i < 4; ++i)
    {
        DualQuat dqi = boneDQs[indices[i]];
        // Antipodality check: ensure shortest path
        float signVal = dot(dq0.rot, dqi.rot) < 0.0 ? -1.0 : 1.0;
        blended.rot += dqi.rot * (weights[i] * signVal);
        blended.trans += dqi.trans * (weights[i] * signVal);
    }
    float len = length(blended.rot);
    blended.rot /= len;
    blended.trans /= len;
    return blended;
}
Essayer de répondre à cette question avec un coach IA

9Les personnages animés se déforment incorrectement uniquement sur certains maillages. Quelles données d'actif (asset) et de shader inspecteriez-vous ?

Lorsque les personnages animés se déforment incorrectement uniquement sur un sous-ensemble de maillages, le problème provient généralement d'inadéquations de données à travers la pipeline d'actifs (assets), l'agencement des sommets (vertex layout) ou les constantes de shader. Une inspection systématique devrait couvrir : 1. **Agencement des sommets et limites d'index des os :** Assurez-vous que les indices d'os des sommets (vertex bone indices) ne dépassent pas le nombre d'os du squelette ou ne débordent pas de leur type de données compacté (par exemple, en utilisant `uint8`/`ubyte4` lorsque le squelette a plus de 256 os, ce qui provoque un bouclage d'index). 2. **Normalisation des poids des os :** Vérifiez que la somme des poids des os par sommet est égale à 1.0. Des poids non normalisés entraînent le rétrécissement des sommets vers le squelette ou leur éloignement. 3. **Matrices de pose de liaison inverse (IBMs) :** Confirmez que les matrices de liaison inverse du maillage correspondent à la pose de repos du squelette et à son espace de coordonnées. Des poses de liaison non concordantes peuvent faire exploser le maillage ou le décaler incorrectement. 4. **Influences maximales par sommet :** Vérifiez si l'exportateur DCC (Digital Content Creation) a exporté plus d'influences d'os par sommet (par exemple, 8 influences) que ce que l'agencement du tampon de sommets (vertex buffer layout) ou le shader supporte (par exemple, 4 influences), entraînant la suppression de poids sans re-normalisation. 5. **Hiérarchie du squelette et indexation de la palette :** Validez que les mappages d'indices d'os dans le maillage correspondent à la palette de matrices d'os téléchargée vers les tampons constants/structurés.

struct SkinVertex {
    float position[3];
    uint8_t boneIndices[4];
    uint8_t boneWeights[4]; // UNORM8
};

void ValidateMeshSkinData(const std::vector<SkinVertex>& vertices, uint32_t maxBoneCount)
{
    for (size_t i = 0; i < vertices.size(); ++i)
    {
        const auto& v = vertices[i];
        int weightSum = 0;
        for (int b = 0; b < 4; ++b)
        {
            assert(v.boneIndices[b] < maxBoneCount && "Bone index exceeds palette size!");
            weightSum += v.boneWeights[b];
        }
        assert(std::abs(weightSum - 255) <= 1 && "Bone weights do not normalize to 1.0!");
    }
}
Essayer de répondre à cette question avec un coach IA

10Que sont les cibles de morphing (morph targets) ou les formes de mélange (blend shapes), et comment sont-elles combinées avec le skinning squelettique pour l'animation faciale ?

Les cibles de morphing (morph targets ou blend shapes) représentent des déformations géométriques stockées sous forme de décalages delta par sommet (positions delta, normales delta et éventuellement tangentes delta) par rapport à un maillage de pose de repos de base. Chaque cible de morphing est contrôlée par un poids scalaire (généralement de 0,0 à 1,0), et les attributs de sommet déformés sont calculés comme suit : `Morphed_Attribute = Base_Attribute + Sum(Weight_i * Delta_i)`. Lors de la combinaison des cibles de morphing avec le skinning squelettique (par exemple, pour l'animation faciale) : 1. **Ordre d'évaluation :** Les deltas des cibles de morphing doivent être évalués dans l'espace modèle de la pose neutre/liée avant l'application du skinning squelettique. 2. **Passe de skinning :** Les positions et normales morphées sont ensuite transformées par les matrices osseuses du skinning squelettique. L'application du morphing avant le skinning garantit que les expressions faciales se déforment naturellement avec les rotations de la tête et des articulations de la mâchoire. Du point de vue des performances et de la bande passante, le stockage et la lecture naïfs de copies complètes du maillage pour des dizaines de formes de mélange entraînent une forte pression sur la bande passante mémoire. Les implémentations pratiques stockent des deltas épars (uniquement les sommets non nuls), compressent les formats de delta (par exemple, FP16 ou entiers quantifiés), ou utilisent des pré-passes de compute shader GPU pour calculer les sommets morphés une seule fois avant plusieurs passes de rendu.

struct VertexInput {
    float3 position : POSITION;
    float3 normal   : NORMAL;
    uint4  boneIndices : BLENDINDICES;
    float4 boneWeights : BLENDWEIGHT;
};

// 1. Accumulate morph deltas in local rest space
float3 morphedPos = input.position;
float3 morphedNorm = input.normal;

for (int i = 0; i < activeMorphCount; ++i) {
    morphedPos  += morphDeltasPos[i]  * morphWeights[i];
    morphedNorm += morphDeltasNorm[i] * morphWeights[i];
}
morphedNorm = normalize(morphedNorm);

// 2. Skin morphed geometry to world space
float4 skinnedPos = 0;
float3 skinnedNorm = 0;
for (int b = 0; b < 4; ++b) {
    float4x4 boneMat = BoneMatrices[input.boneIndices[b]];
    skinnedPos  += mul(boneMat, float4(morphedPos, 1.0)) * input.boneWeights[b];
    skinnedNorm += mul((float3x3)boneMat, morphedNorm)   * input.boneWeights[b];
}
Essayer de répondre à cette question avec un coach IA

11Expliquez la sélection du niveau de détail géométrique (LOD), la simplification des maillages et les transitions conciliant stabilité visuelle, préservation des attributs et performance.

Le niveau de détail géométrique, LOD, réduit la complexité des maillages lorsque les objets s'éloignent, en équilibrant fidélité et fréquence d'images. 1. Sélection : utiliser des métriques écran, comme diamètre projeté de la sphère englobante, pourcentage de hauteur ou erreur projetée en pixels, plutôt qu'une distance fixe dans le monde, pour tenir compte du champ de vision et de la résolution. Une hystérésis avec seuils distincts de montée et de descente évite les oscillations rapides aux frontières. 2. Simplification : la génération hors ligne utilise souvent les métriques d'erreur quadratique, QEM, avec contractions successives d'arêtes. Préserver les silhouettes des limites et pénaliser la distorsion, ainsi que les attributs, coutures des coordonnées de texture (UV), séparations de normales, couleurs et poids de skinning, par des termes d'erreur intégrés à la métrique quadratique. 3. Transitions : le fondu croisé tramé, ou screen-door stippling, élimine des pixels selon un motif entrelacé, comme une matrice de Bayer, pour fondre entre LOD sans mélange alpha ordinaire. Mais les changements de couverture et le rejet de fragments peuvent limiter les écritures de profondeur anticipées ou autres optimisations de profondeur anticipées (early-Z) selon l'interface de programmation applicative (API) et le matériel ; mesurer aussi le coût du double rendu pendant la transition. Le geomorphing interpole les positions entre LOD voisins sur le processeur graphique (GPU) pendant un court intervalle, avec une correspondance ou une carte de contractions explicites ; on ne peut pas simplement interpoler les indices de sommets de maillages arbitraires aux topologies différentes.

float CalculateLODDither(float2 screenPos, float lodBlendFactor)
{
    const float bayer4x4[16] = {
         0.0/16.0,  8.0/16.0,  2.0/16.0, 10.0/16.0,
        12.0/16.0,  4.0/16.0, 14.0/16.0,  6.0/16.0,
         3.0/16.0, 11.0/16.0,  1.0/16.0,  9.0/16.0,
        15.0/16.0,  7.0/16.0, 13.0/16.0,  5.0/16.0
    };
    uint2 pixelCoord = (uint2)screenPos.xy % 4;
    float threshold = bayer4x4[pixelCoord.y * 4 + pixelCoord.x];
    return (lodBlendFactor - threshold);
}

// In pixel shader: if (CalculateLODDither(input.position.xy, lodTransitionAlpha) < 0.0) discard;
Essayer de répondre à cette question avec un coach IA

12Qu'est-ce que l'optimisation des tampons d'index ou des caches de sommets, et pourquoi l'ordre des triangles affecte-t-il l'efficacité du cache post-transformation ?

L'optimisation du cache de sommets (ou tampon d'index) réorganise les indices de triangles et les données de sommets dans un maillage pour maximiser les taux d'accès dans les caches de sommets matériels du GPU (Graphics Processing Unit). Les GPU ont deux caches de sommets principaux : 1. **Cache post-transformation** : Un petit cache FIFO (First-In, First-Out)/LRU (Least Recently Used) stockant les sorties transformées du nuanceur de sommets (positions, attributs). Lorsque des triangles adjacents partagent des sommets, référencer ces sommets de près dans le flux d'indices permet au GPU de réutiliser les sorties de nuanceur mises en cache au lieu d'exécuter le nuanceur de sommets plusieurs fois pour le même sommet. 2. **Cache pré-transformation** : Le cache mémoire L1/L2 du GPU pour les données brutes du tampon de sommets. Réorganiser les données du tampon de sommets pour correspondre à l'ordre de premier accès des indices optimisés maximise la localité spatiale et l'efficacité de la bande passante mémoire. L'ordre des triangles détermine directement la séquence d'accès dans le cache post-transformation. Les algorithmes d'optimisation (tels que l'algorithme de Tom Forsyth ou Tipsify) attribuent des scores de réutilisation dynamiques aux sommets basés sur la valence et la position dans le cache, priorisant les triangles qui complètent les références restantes aux sommets récemment mis en cache pour minimiser le Taux Moyen d'Échecs de Cache (ACMR - Average Cache Miss Ratio).

float calculateVertexScore(int cachePosition, int remainingValence) {
    if (remainingValence == 0) return -1.0f;
    float score = 0.0f;
    if (cachePosition >= 0) {
        if (cachePosition < 3) {
            score = 0.75f; // Recent vertex in cache (bonus for immediate reuse)
        } else {
            score = std::pow(1.0f - (cachePosition - 3) / 29.0f, 1.5f); // Gradual falloff
        }
    }
    // Bonus for vertices with few remaining triangles (clearing valence faster)
    score += 2.0f * std::pow(remainingValence, -0.5f);
    return score;
}
Essayer de répondre à cette question avec un coach IA

Questions Senior

13Quelles approches d'élimination des objets cachés (occlusion culling) évitent les blocages CPU-GPU et les apparitions incorrectes (popping) ?

Les requêtes d'occlusion matérielles traditionnelles provoquent des blocages de lecture synchrone CPU-GPU si le CPU attend les résultats de visibilité dans la même trame. Retarder les lectures d'une trame évite les blocages mais introduit une latence temporelle, causant des apparitions subites (popping) visibles lorsque les objets nouvellement visibles ne sont pas rendus immédiatement. Pour éviter à la fois les blocages CPU-GPU et le popping visuel, les architectures de production modernes utilisent : 1. **Élimination des objets cachés (Occlusion Culling) GPU en deux phases basée sur Hi-Z** : Le GPU teste les boîtes englobantes par rapport à une pyramide de profondeur Hi-Z (Hierarchical-Z) générée à partir de la trame précédente. Les objets connus pour être visibles sont dessinés dans la Phase 1 (générant la profondeur initiale de la trame actuelle). Les objets précédemment occlus sont retestés par rapport au tampon Hi-Z mis à jour de la trame actuelle dans la Phase 2 ; tous les objets nouvellement révélés sont rendus immédiatement avant l'éclairage et le post-traitement, éliminant le popping avec zéro lecture CPU. 2. **Rastérisation logicielle CPU** : Un tampon de profondeur à basse résolution est rastérisé purement sur des threads de travail CPU (utilisant SIMD) à partir de maillages d'occlusion simplifiés. Le CPU teste les boîtes englobantes de manière synchrone sans avoir besoin de requêtes GPU ni d'encourir de latence de transfert GPU-vers-CPU. 3. **Délimitation conservative et hystérésis temporel** : L'expansion des volumes englobants ou le retardement des rétrogradations de l'état de visibilité évitent l'élimination prématurée lors de mouvements rapides de la caméra.

// Phase 1: Render instances visible in the previous frame
[numthreads(64, 1, 1)]
void Phase1_CullCS(uint id : SV_DispatchThreadID) {
    if (id >= totalInstances) return;
    Instance inst = instances[id];
    if (wasVisibleLastFrame[id] && TestHiZ(inst.bounds, prevFrameHiZ)) {
        AppendDraw(phase1DrawBuffer, inst);
        currentVisibility[id] = true;
    }
}
// [Phase 1 draws -> depth buffer written -> Hi-Z updated for current frame]

// Phase 2: Test previously occluded objects against updated Hi-Z to avoid popping
[numthreads(64, 1, 1)]
void Phase2_CullCS(uint id : SV_DispatchThreadID) {
    if (id >= totalInstances) return;
    if (!currentVisibility[id] && TestHiZ(instances[id].bounds, currentFrameHiZ)) {
        AppendDraw(phase2DrawBuffer, instances[id]);
        currentVisibility[id] = true;
    }
}
Essayer de répondre à cette question avec un coach IA

14Décrivez un pipeline typique de traitement d'actifs, du maillage créé aux tampons GPU d'exécution, incluant la génération de tangentes, la quantification, la validation et l'optimisation.

Un pipeline standard de traitement d'actifs transforme les maillages DCC (Digital Content Creation) bruts créés (FBX, glTF, USD) en formats binaires haute performance, prêts pour le GPU (Graphics Processing Unit), en passant par cinq étapes principales : 1. **Ingestion et Validation** : Le maillage source est nettoyé en supprimant les sommets dupliqués ou inutilisés, en écartant les triangles dégénérés/de surface nulle, en vérifiant la géométrie manifold, en gérant les NaN (Not a Number) et en divisant les maillages multi-matériaux en sous-maillages distincts. 2. **Génération de l'espace tangent** : Les tangentes et bitangentes sont calculées en utilisant des algorithmes standardisés (principalement MikkTSpace) pour garantir une parité visuelle avec les outils de 'normal baking'. Cela prend correctement en compte les coutures UV et les cartes UV miroir (stockage de l'orientation (handedness) dans tangent.w). 3. **Optimisation** : Les indices sont réorganisés pour l'efficacité du cache de sommets post-transformation (par exemple, Forsyth/Tipsify), les tampons de sommets sont réordonnés pour la localité de récupération des sommets pré-transformation, et des niveaux de détail (LOD) ou des meshlets sont générés. 4. **Quantification et regroupement d'attributs** : Les attributs de sommet sont quantifiés pour réduire l'empreinte mémoire et la bande passante mémoire : les positions en demi-flottants/unorm 16 bits ou entiers normalisés, les normales et tangentes en SNORM 8 bits ou encodages octaédriques (Oct16/Oct32), et les UV en flottants/unorm 16 bits. Les attributs peuvent être entrelacés (AoS - Array of Structures) ou divisés en plusieurs flux (SoA - Structure of Arrays, par exemple, position seulement pour les pré-passes de profondeur). 5. **Cuisson (Cooking) et sérialisation** : Les tampons, les volumes englobants (AABBs/sphères) et les tables LOD sont sérialisés dans des fichiers binaires plats ne nécessitant aucune correction de pointeur à l'exécution, permettant un téléchargement DMA (Direct Memory Access) rapide vers les tampons GPU via une mémoire de staging.

// Compress a float3 normal into 2D octahedral coordinates (8-bit SNORM each)
vec2 OctEncode(vec3 n) {
    n /= (abs(n.x) + abs(n.y) + abs(n.z));
    vec2 oct = (n.z >= 0.0) ? n.xy : (1.0 - abs(n.yx)) * sign(n.xy);
    return oct * 0.5 + 0.5;
}
// Stored as 2x 8-bit unorm/snorm (2 bytes vs 12 bytes float3)
Essayer de répondre à cette question avec un coach IA

15Expliquez les meshlets, le filtrage de clusters, les shaders de maillage et les pipelines de micro-géométrie dense pour les grandes scènes statiques.

Les pipelines de meshlets et les architectures de micro-géométrie dense (telles que Nanite d'Unreal) remplacent les grands appels de dessin (draw calls) à mémoire tampon d'indices par de petits clusters géométriques délimités appelés 'meshlets'. 1. **Meshlets** : Un meshlet est un cluster de géométrie généralement limité à 32-128 sommets et jusqu'à 128-256 triangles. Chaque meshlet contient des indices de sommets locaux, des flux d'attributs et des données de délimitation précalculées (une sphère englobante et un cône de normales). 2. **Shaders de Maillage et d'Amplification** : Ils remplacent le pipeline à fonction fixe de sommets, d'assemblage de primitives et de shaders géométriques. Les shaders d'amplification (ou de tâche) évaluent le filtrage par frustum, l'occlusion et le cône de normales pour les faces arrière au niveau du cluster sur des groupes de meshlets. Les meshlets survivants dispatchent les shaders de maillage, où un groupe de threads (threadgroup) transforme coopérativement les sommets dans la mémoire partagée embarquée (LDS - Local Data Share) et produit directement les indices de primitives vers le rastériseur. 3. **Pipelines de Micro-Géométrie Dense** : La géométrie haute densité produit des triangles sous-pixel qui souffrent d'un quad-overdraw sévère (où le matériel standard rastérise des quads d'aide de 2x2 pixels, exécutant des shaders de pixels complets pour seulement 1 pixel couvert). Les systèmes modernes de micro-géométrie dense utilisent des structures hiérarchiques de Niveau de Détail (LOD) de clusters (DAGs) pour sélectionner dynamiquement les LODs de clusters, assurant des longueurs d'arête d'environ 1 pixel, et combinent souvent la rastérisation matérielle pour les grands polygones avec des rastériseurs logiciels personnalisés basés sur le calcul pour les micro-polygones sous-pixel.

#define MAX_VERTS 64
#define MAX_PRIMS 128

struct MeshletPayload { uint meshletIndices[32]; };

[outputtopology("triangle")]
[numthreads(32, 1, 1)]
void MainMS(
    in uint gtid : SV_GroupThreadID,
    in uint gid : SV_GroupID,
    in payload MeshletPayload payloadData,
    out vertices VertexOutput outVerts[MAX_VERTS],
    out indices uint3 outIndices[MAX_PRIMS]
) {
    uint meshletId = payloadData.meshletIndices[gid];
    Meshlet m = meshlets[meshletId];
    SetMeshOutputCounts(m.vertexCount, m.primitiveCount);
    
    // Cooperatively transform vertices
    for (uint v = gtid; v < m.vertexCount; v += 32) {
        outVerts[v] = TransformVertex(m.vertexOffset + v);
    }
    // Output local triangle indices
    for (uint p = gtid; p < m.primitiveCount; p += 32) {
        outIndices[p] = GetMeshletTriangle(m.triangleOffset + p);
    }
}
Essayer de répondre à cette question avec un coach IA