Шейдер Cook-Torrance с более чем одной точечной подсветкой
Я пытаюсь реализовать режим освещения Кука-Торранса с четырьмя точечными источниками света. Несмотря на то, что я получаю хорошие результаты, используя только один точечный источник света, я не могу понять, как правильно подытожить зеркальное выражение в моей петле света.
Я определяю материалы следующим образом:
struct material {
vec3 ambient; /* ambient color */
vec3 diffuse; /* diffuse color */
vec3 specular; /* speculr color */
float metallic;
float roughness;
};
... благодаря чему мои огни имеют только одно свойство цвета / интенсивности,
struct light {
vec3 position;
vec3 color;
bool enabled;
};
Вот функция внутри моего фрагментного шейдера с вычислением цвета фрагмента:
vec3 lighting() {
vec3 color = vec3(0.0,0.0,0.0);
float r0 = pow(material.metallic - 1.0,2.0)/pow(material.metallic + 1.0,2.0);
vec3 V = normalize(-v_viewpos);
vec3 N = normalize(v_normal);
for (int i = 0; i < 4; i++) {
if (light[i].enabled) {
vec3 L = normalize(light[i].position - v_viewpos);
// Half-way vector
vec3 halfVector = normalize(L + V);
float NdotL = max(dot(N, L),0.0);
float NdotV = max(dot(N, V),0.0);
if (NdotL > 0.001 && NdotV > 0.001) {
float NdotH = max(0.0, dot(N, halfVector));
float HdotV = max(0.0, dot(halfVector, V));
// Beckmann
float tanAlpha = sqrt(1.0-NdotH*NdotH)/NdotH;
float D = exp(-pow(tanAlpha/material.roughness,2.0))/(4.0*pow(material.roughness,2.0)*pow(NdotH,4.0));
// Shadowing-masking term
float G1 = (2.0 * NdotH * NdotV) / HdotV;
float G2 = (2.0 * NdotH * NdotL) / HdotV;
float G = min(1.0, min(G1, G2));
// Fresnel reflection, Schlick approximation
float F = r0 + (1.0 - r0) * pow(1.0 - NdotL, 5.0);
float R = (F*G*D) / (3.14159 * NdotL * NdotV);
color += light[i].color * R * NdotL;
}
color += material.diffuse * light[i].color;
}
}
return color;
}
Я считаю, что ключевой момент здесь - мои неправильные вычисления в цикле освещения:
color += light[i].color * R * NdotL;
Вот пример того, что я имею в виду, результирующий цвет фрагмента либо слишком темный, либо слишком яркий. Я не могу суммировать каждый вклад света, чтобы получить хороший плавный градиент цвета между зеркальным термином и цветами материала.
Я читаю здесь о гамма-коррекции, но не могу понять, относится ли это к моему вопросу или нет.
Как я должен суммировать каждый light.color с рассеянным, рассеянным и зеркальным цветами материала, чтобы вычислить окончательный цвет фрагмента, правильно включив общее количество зеркального вклада в каждый свет?
2 ответа
Большинство ваших расчетов выглядят хорошо (в том числе направления V
а также L
и термин Френеля). Единственное, что вы могли бы перепутать, как комбинировать отдельные компоненты освещения. Для зеркального и рассеянного у вас есть
color += light[i].color * R * NdotL;
R
соответствует зеркальной части и NdotL
к диффузной части. Оба являются аддитивными, однако. Следовательно, уравнение должно быть (плюс с учетом материальных параметров):
color += light[i].color * (material.specular * R + material.diffuse * NdotL);
На окружающий срок у вас есть
color += material.diffuse * light[i].color;
замещать material.diffuse
с material.ambient
и это должно быть правильно.
И будьте уверены, что ваши огни не слишком яркие. На экране не может отображаться ничего ярче белого (или полностью насыщенного красного).
vec3 V
должен быть нормализованным вектором, начиная с позиции фрагмента до положения камеры.vec3 L
должен быть нормализованным вектором, начиная с позиции фрагмента до позиции света.
Один из этих векторов неверен в вашем шейдере, в зависимости от фактического значения v_viewpos
,
Френель должен быть основан на HoV, а не на NoL:pow(1.0 - HoV, 5.0)
Для рассеянной части вы рассматриваете свой свет как рассеянный, а не точечный.
color += material.diffuse * light[i].color;
должно быть (для простого Lambertian)
color += material.diffuse * light[i].color * NoL;