Comment résoudre les défauts courants des ombres PCF et VSM en une seule étape ? Les secrets d'un ingénieur graphique senior

Les ombres jouent un rôle crucial en graphisme 3D pour améliorer le réalisme spatial. Elles révèlent les relations entre objets et la position des sources lumineuses. La technique standard repose sur la cartographie d'ombre (shadow mapping), où l'on compare la profondeur vue de la source lumineuse avec celle de la caméra principale.

Exemple de code pour une vérification simple de l'ombre dans un fragment shader :


// Vérification basique de l'ombre
float facteurOmbre = (profondeurFragment > profondeurLumiere) ? 1.0 : 0.0;
vec3 couleurFinale = mix(couleurBase, couleurBase * 0.4, facteurOmbre);

Cette approche applique un assombrissement là où l'objet est occulté.

Comparaison des techniques d'ombre

  • Shadow Mapping : Efficace pour le temps réel, qualité moyenne.
  • Ray Tracing : Haute précision, coûteux en calcul.
  • Shadow Volumes : Idéal pour les lumières ponctuelles, bords nets.
Technique Précision Coût calcul Cas d'usage
Shadow Mapping Moyenne Faible Jeux vidéo, applications interactives
Ray Tracing Haute Élevé Rendu cinématique

Chapitre 2 : Approfondissement des techniques PCF et VSM

2.1 PCF : Mécanisme de filtrage pour adoucir les bords

PCF (Percentage-Closer Filtering) lisse les ombres en comparant plusieurs échantillons de profondeur autour du pixel, avec des poids variables pour simuler une occlusion partielle.

Exemple de code avec un noyau de 3x3 :


float calculerOmbrePCF(sampler2D carteOmbre, vec2 coordonnees, float reference) {
    float accumulation = 0.0;
    vec2 taillePixel = 1.0 / textureSize(carteOmbre, 0);
    for(int i = -1; i <= 1; ++i) {
        for(int j = -1; j <= 1; ++j) {
            float profondeurEchantillon = texture(carteOmbre, coordonnees + vec2(i, j) * taillePixel).r;
            accumulation += (profondeurEchantillon < reference) ? 1.0 : 0.0;
        }
    }
    return accumulation / 9.0;
}

Cette fonction échantillonne neuf points, où reference est la profondeur du fragment vue de la lumière.

2.2 VSM : Utilisation des moments pour une ombre douce probabiliste

VSM (Variance Shadow Maps) stocke la profondeur moyanne et sa variance dans la carte d'ombre. L'inégalité de Tchebychev permet d'estimer la probabilité qu'un fragment soit dans l'ombre, réduisant les artefacts.

Structure pour stocker les moments :


struct DonneesMoment {
    float moyenne;     // Premier moment
    float variance;    // Second moment
};

Dans le shader, on utilise ces moments pour calculer une ombre adoucie sans échantillonnage multiple.

2.3 Défauts courants : leurs causes et solutions

Aliasing (crénelage) : Dû à un sous-échantillonnage, visible comme des bords dentelés. L'antialiasing (MSAA) atténue ce problème.

Bleeding (fuite de lumière) : Se produit quand l'ombre déborde sur des surfaces non occultées, souvent à cause d'une précision insuffisante de la carte d'ombre.

Perte de précision en profondeur : Le tampon de profondeur a une précision non linéaire, meilleure près de la caméra. Une profondeur logarithmique peut équilibrer cela :


float profondeurLogarithmique(float z, float planProche, float planLoin) {
    return log(z / planProche) / log(planLoin / planProche);
}

Cela améliore la stabilité des ombres à grande distance.

2.4 Exemple de reproduction d'artefacts dans OpenGL

Les artefacts typiques comme l'acné d'ombre (shadow acne) surviennent lorsque la comparaison de profondeur est trop sensible. Un biais insuffisant cause des motifs striés.


// Comparaison avec biais dynamique
float seuil = max(0.005 * (1.0 - dot(normale, directionLumiere)), 0.0005);
float ombre = (profondeurActuelle - seuil > profondeurCarte) ? 0.0 : 1.0;

Ici, le biais varie avec l'angle de la surface, réduisant l'acné.

Chapitre 3 : Solution unifiée pour améliorer la stabilité

3.1 Refonte de la distribution de profondeur

En remappant la profondeur dans un espace logarithmique, on atténue les erreurs d'interpolation qui causent des bords instables dans les ombres.

3.2 Filtrage adaptatif basé sur la géométrie

On ajuste dynamiquement le noyau de filtrage PCF selon l'angle entre la normale de surface et la direction de vue, élargissant le noyau pour les surfaces rasantes.


float echelleNoyau(vec3 normale, vec3 directionVue) {
    float alignement = abs(dot(normale, directionVue));
    return mix(1.0, 5.0, 1.0 - alignement);
}

Cette approche réduit le crénelage sur les surfaces inclinées.

3.3 Intégration dans le pipeline de rendu

Pour une intégration transparente, on utilise une abstraction de données avec double tampon pour éviter les conflits GPU/CPU.


struct ParametresRendu {
    mat4 vueProjection;
    vec4 plansVolume[6];
};
// Mise à jour alternée
ParametresRendu* donneesCourantes = &tampons[indiceFrame % 2];
donneesCourantes->vueProjection = camera.ObtenirVueProjection();

Chapitre 4 : Techniques d'optimisation avancées

4.1 Stabilisation de VSM avec normalisation

On applique une normalisation en deux canaux : l'un pour comprimer la norme du vecteur, l'autre pour corriger les biais statistiques via une moyenne mobile exponentielle.

4.2 Échantillonnage PCF intelligent

En combinant la profondeur, la normale et la distance à la caméra, on alloue des échantillons supplémentaires aux zones critiques, comme les bords d'ombre rapides.

4.3 Filtrage hybride par distance

Près de la caméra, on utilise PCF pour des détails fins ; au loin, on passe à VSM pour réduire le coût tout en maintenant une qualité acceptable.

Cette approche équilibrée permet de résoudre efficacement les défauts courants des ombres PCF et VSM en une seule étape intégrée au pipeline de rendu.

Étiquettes: PCF VSM Shadow Mapping OpenGL Fragment Shader

Publié le 14 juin à 22h10