Проблема с зеркальным светом при очень наклонных углах с помощью Blinn-Phong

Aug 17 2020

У меня проблема с моим основным средством визуализации Blinn-Phong, когда я смотрю на объекты под очень наклонными углами:

Я не думаю, что это проблема моего кода, хотя я опубликую GLSL моего фрагмента ниже. Скорее, это кажется необходимым следствием обрезания зеркальной составляющей освещения до нуля, когда точка (Нормальный, Свет) <= 0. (Что вам все говорят). Но это означает, что в точке будет этот разрыв. терминатор. Удаление зажима приводит к другим проблемам; видимого шва больше нет, но теперь зеркальный свет продолжается вокруг темной стороны сферы.

Есть ли простой способ обойти это, или это просто неизбежный недостаток модели Блинн-Фонга?

РЕДАКТИРОВАТЬ

TL; DR: Похоже, это не просто обратная сторона модели Блинн-Фонга.

Я провел еще несколько исследований BRDF и нашел эту статью: Новая модель BRDF Варда с ограниченным альбедо и соответствующими данными отражения для RADIANCE , в которой обсуждаются недостатки модели Уорда, особенно вокруг высоких углов скольжения (именно моя проблема!), И как они подправили модель, чтобы исправить это. Ward - это анизотропная модель, но ее можно упростить до изотропной, и когда вы сделаете это для их готовой к реализации формы со страницы 22, вы получите:

Я вставил это в свой код (обновленный ниже), aaaannnnnd ... без кубиков. В обычных случаях это выглядит красиво, но демонстрирует те же самые режимы отказа на краю, а некоторые даже более впечатляющие за краем (даже хуже, чем у Блинн-Фонга):

Примечание: обе модели используют параметр «блестящий», но для каждой модели они означают разные вещи. Исходные скриншоты были с shiny = .8, для Ward мне пришлось уменьшить его до .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);
}

Ответы

4 D0SBoots Aug 20 2020 at 05:35

TL; DR: умножьте свою зеркальность на точку (нормальный, светлый, нормальный). (И сделать зажим , что скалярное произведение к минимуму 0!)

Я, и я подозреваю (судя по всем учебным материалам) многие другие, все делали неправильно.

Я использовал функцию распределения двунаправленного отражения (AKA BRDF) непосредственно для расчета интенсивности зеркального отражения. Однако BRDF фактически определяет дифференциальную величину, которая предназначена для включения в радиометрический интеграл. Важно отметить , что обычно называют ДФО отсутствует термин COS (θ) , который является частью общего интеграла. Если вы неравнодушны к математике, об этом подробнее рассказывается здесь:http://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Surface_Reflection.html#TheBRDF

Когда мы имеем дело только с точечными источниками света, нам не нужно оценивать весь интеграл; предел интеграла, когда источник света становится точкой, переходит к подынтегральному выражению. Но член cos (θ) по-прежнему важен.

На практике это означает, что шейдер нужно немного подправить. Полученные результаты:

Вверху - фиксированный Блинн-Фонг. Это с таким же блеском, что и раньше; поскольку у Блинн-Фонга нет члена Френеля, фиксация cos (θ) приводит к резкому падению интенсивности при скользящих углах. Но по крайней мере, разрыв исчез.

Это фиксированный Уорд с таким же блеском, что и на втором изображении ранее. Отражение Френеля (грубо говоря, более высокое отражение при более наклонных углах) моделируется в Уорде, так что это выглядит хорошо. И никаких разрывов!