Problème avec la lumière spéculaire à des angles très obliques avec Blinn-Phong

Aug 17 2020

J'ai un problème avec mon moteur de rendu Blinn-Phong de base, lorsque je regarde des objets à des angles très obliques:

Je ne pense pas que ce soit un problème avec mon code, bien que je posterai le GLSL de mon fragment ci-dessous. Cela semble plutôt être une conséquence nécessaire de la réduction de la composante spéculaire de l'éclairage à zéro lorsque dot (Normal, Light) <= 0. (Ce que tout le monde vous dit de faire.) Mais cela signifie qu'il y aura cette discontinuité au niveau du terminateur. La suppression du serrage entraîne d'autres problèmes; il n'y a plus de couture visible, mais maintenant le reflet spéculaire continue vers le côté sombre de la sphère.

Y a-t-il un moyen simple de contourner cela, ou est-ce juste un inconvénient inévitable du modèle Blinn-Phong?

ÉDITER

TL; DR: Il semble que ce ne soit pas seulement un inconvénient du modèle Blinn-Phong.

J'ai fait quelques recherches supplémentaires sur les BRDF et j'ai trouvé cet article: Un nouveau modèle de BRDF de Ward avec albédo borné et données de réflectance d'ajustement pour RADIANCE , qui traite des lacunes du modèle de Ward, en particulier autour des angles de pâturage élevés (exactement mon problème!) ils ont modifié le modèle pour le réparer. Ward est un modèle anisotrope, mais il peut être simplifié pour être isotrope, et lorsque vous faites cela à leur formulaire prêt à l'implémentation à partir de la page 22, vous obtenez:

Je l'ai branché dans mon code (mis à jour ci-dessous), aaaannnnnd ... pas de dés. Cela a l'air joli dans les cas normaux, mais présente les mêmes modes de défaillance au bord, et certains encore plus spectaculaires au-delà du bord (encore pire que Blinn-Phong):

Remarque: les deux modèles utilisent le paramètre "brillant", mais ils signifient des choses différentes pour chaque modèle. Les captures d'écran originales étaient avec shiny = .8, pour Ward, j'ai dû le baisser à .1.

#version 150

#extension GL_ARB_conservative_depth : enable

in Frag {
    vec3 color;
    vec3 coord;
    vec3 center;
    float R;
};

out vec4 color_out;
layout (depth_greater) out float gl_FragDepth;

uniform mat4 VIEW;
uniform mat4 PROJ;

const vec3 gamma = vec3(1.0 / 2.2);
const float ambientPower = .15;
const float diffusePower = .75;

const bool PHONG = false;
const float specHardness = 60.0;
const float shiny = .1;

const bool WARD = true;

void main() {
    // Find intersection of ray (given by coord) with sphere
    vec3 eyeNormal = normalize(coord);
    float b = dot(center, eyeNormal);
    float c = b * b - (dot(center, center) - R * R);
    if (c < 0.0) {
        discard;  // Doesn't intersect sphere
    }
    vec3 point = (b - sqrt(c)) * eyeNormal;
    // Redo depth part of the projection matrix
    gl_FragDepth = (PROJ[2].z * point.z + PROJ[3].z) / -point.z;

    // Lighting begins here

    // The light dir is in world-space, unlike the others, so we have to project it to view space.
    // The direction (0, 1, 0) corresponds to the 2nd column. By the properties of the view matrix
    // (the 3x3 part is an orthogonal matrix), this is already normalized.
    vec3 lightNormal = VIEW[1].xyz;
    vec3 normal = normalize(point - center);
    float diffuse = dot(lightNormal, normal);

    float specular = 0.0;
    if (PHONG) {
        // Have to reverse sign for eyeNormal so it points out
        vec3 halfway = normalize(lightNormal - eyeNormal);
        specular = diffuse <= 0.0 ? 0.0 : pow(max(0.0, dot(halfway, normal)), specHardness);
    } else if (WARD) {
        const float PI = 3.14159265359;
        const float alpha = .15;
        const float invAlpha2 = 1 / (alpha * alpha);
        // Would move this computation to CPU and pass invAlpha2 as uniform if alpha were a parameter
        const float cFactor = invAlpha2 / PI;

        // Have to reverse sign for eyeNormal so it points out, note this is *unnormalized*
        vec3 halfway = lightNormal - eyeNormal;
        float dotP = dot(halfway, normal);
        float invDot2 = 1 / (dotP * dotP);
        float semiNormalizedInvDot = dot(halfway, halfway) * invDot2;
        // Note: You can't factor the exp(invAlpha2) part out as a constant term,
        // you'll blow out the floating-point range if you try.
        specular = cFactor * exp(invAlpha2-invAlpha2*semiNormalizedInvDot) * semiNormalizedInvDot * invDot2;
    }
    diffuse = max(0.0, diffuse);
    vec3 colorPre = (ambientPower + diffusePower * diffuse) * color
        + specular * shiny * vec3(1);

    color_out = vec4(pow(colorPre, gamma), 0);
}

Réponses

4 D0SBoots Aug 20 2020 at 05:35

TL; DR: multipliez votre valeur spéculaire par un point (normal, lightNormal). (Et faire pince qui parsèment produit à un minimum de 0!)

Moi, et je soupçonne (en voyant tous les tutoriels là-bas) beaucoup d'autres, avons fait tout cela mal.

J'utilisais directement la fonction de distribution de réflectance bidirectionnelle (AKA BRDF) pour calculer l'intensité spéculaire. Cependant, la BRDF définit en fait une grandeur différentielle qui est censée être branchée dans une intégrale radiométrique. Fondamentalement, ce que l'on appelle habituellement le BRDF manque d'un terme cos (θ) qui fait partie de l'intégrale globale. Si vous avez le ventre pour les maths, cela est discuté plus en détail ici:http://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Surface_Reflection.html#TheBRDF

Lorsqu'il s'agit uniquement de sources lumineuses ponctuelles, nous n'avons pas besoin d'évaluer l'intégrale entière; la limite de l'intégrale en tant que source lumineuse devient un point va à l'intégrale. Mais le terme cos (θ) est toujours important.

En pratique, cela signifie que le shader a besoin d'un léger ajustement. Résultats:

Ci-dessus, Blinn-Phong, fixe. C'est avec la même brillance qu'avant; comme Blinn-Phong n'a pas de terme de Fresnel, la correction de cos (θ) fait chuter l'intensité de façon spectaculaire aux angles rasants. Mais la discontinuité a au moins disparu.

Ceci est fixe Ward, avec la même brillance que la 2ème image avant. La réflexion de Fresnel (en gros, une réflexion plus élevée à des angles plus obliques) est modélisée dans Ward, donc cela semble bon. Et pas de discontinuité!