Проблема с зеркальным светом при очень наклонных углах с помощью Blinn-Phong
У меня проблема с моим основным средством визуализации 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);
}
Ответы
TL; DR: умножьте свою зеркальность на точку (нормальный, светлый, нормальный). (И сделать зажим , что скалярное произведение к минимуму 0!)
Я, и я подозреваю (судя по всем учебным материалам) многие другие, все делали неправильно.
Я использовал функцию распределения двунаправленного отражения (AKA BRDF) непосредственно для расчета интенсивности зеркального отражения. Однако BRDF фактически определяет дифференциальную величину, которая предназначена для включения в радиометрический интеграл. Важно отметить , что обычно называют ДФО отсутствует термин COS (θ) , который является частью общего интеграла. Если вы неравнодушны к математике, об этом подробнее рассказывается здесь:http://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Surface_Reflection.html#TheBRDF
Когда мы имеем дело только с точечными источниками света, нам не нужно оценивать весь интеграл; предел интеграла, когда источник света становится точкой, переходит к подынтегральному выражению. Но член cos (θ) по-прежнему важен.
На практике это означает, что шейдер нужно немного подправить. Полученные результаты:

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

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