具有多个点光源的 Cook-Torrance 着色器
Cook-Torrance shader with more than one point light
我正在尝试使用四个点光源实现 Cook-Torrance lighting mode。虽然我只使用一个点光源获得了不错的效果,但我无法理解哪种方法是总结我的光环内的镜面反射项的正确方法。
我将 material 定义如下:
struct material {
vec3 ambient; /* ambient color */
vec3 diffuse; /* diffuse color */
vec3 specular; /* speculr color */
float metallic;
float roughness;
};
...因此我的灯只有一个 color/intensity 属性,
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;
这是我的意思的一个例子,生成的片段颜色要么太暗,要么太亮。我无法总结每个光的贡献,以便在镜面反射项和 material 颜色之间获得漂亮的平滑颜色渐变。
我正在阅读 here 有关伽马校正的内容,但我不明白这是否适用于我的问题。
我应该如何将每个 light.color 与 material 的漫反射、环境光和镜面反射颜色相加,以通过正确包括镜面反射高光贡献的总量来计算最终片段颜色每盏灯?
vec3 V
应该是从片段位置到相机位置的归一化向量。
vec3 L
应该是从fragment位置到light位置的归一化向量。
其中一个向量在您的着色器中是错误的,具体取决于 v_viewpos
的实际值。
菲涅耳应该基于 HoV 而不是 NoL:
pow(1.0 - HoV, 5.0)
对于漫反射部分,您将灯光视为环境光而不是点光。
color += material.diffuse * light[i].color;
应该是(对于简单的 Lambertian)
color += material.diffuse * light[i].color * NoL;
你的大部分计算看起来都不错(包括 V
和 L
的方向以及菲涅耳项)。唯一的问题是您可能混淆了如何组合各个照明组件。对于镜面反射和漫反射,你有
color += light[i].color * R * NdotL;
R
对应高光部分,NdotL
对应漫反射部分。然而,两者都是附加的。因此,等式应该是(加上考虑 material 个参数):
color += light[i].color * (material.specular * R + material.diffuse * NdotL);
对于环境术语,您有
color += material.diffuse * light[i].color;
将material.diffuse
替换为material.ambient
,这应该是正确的。
并确保您的灯光不要太亮。屏幕无法显示比白色(或完全饱和的红色)更亮的任何东西。
我正在尝试使用四个点光源实现 Cook-Torrance lighting mode。虽然我只使用一个点光源获得了不错的效果,但我无法理解哪种方法是总结我的光环内的镜面反射项的正确方法。
我将 material 定义如下:
struct material {
vec3 ambient; /* ambient color */
vec3 diffuse; /* diffuse color */
vec3 specular; /* speculr color */
float metallic;
float roughness;
};
...因此我的灯只有一个 color/intensity 属性,
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;
这是我的意思的一个例子,生成的片段颜色要么太暗,要么太亮。我无法总结每个光的贡献,以便在镜面反射项和 material 颜色之间获得漂亮的平滑颜色渐变。
我正在阅读 here 有关伽马校正的内容,但我不明白这是否适用于我的问题。
我应该如何将每个 light.color 与 material 的漫反射、环境光和镜面反射颜色相加,以通过正确包括镜面反射高光贡献的总量来计算最终片段颜色每盏灯?
vec3 V
应该是从片段位置到相机位置的归一化向量。vec3 L
应该是从fragment位置到light位置的归一化向量。
其中一个向量在您的着色器中是错误的,具体取决于 v_viewpos
的实际值。
菲涅耳应该基于 HoV 而不是 NoL:
pow(1.0 - HoV, 5.0)
对于漫反射部分,您将灯光视为环境光而不是点光。
color += material.diffuse * light[i].color;
应该是(对于简单的 Lambertian)
color += material.diffuse * light[i].color * NoL;
你的大部分计算看起来都不错(包括 V
和 L
的方向以及菲涅耳项)。唯一的问题是您可能混淆了如何组合各个照明组件。对于镜面反射和漫反射,你有
color += light[i].color * R * NdotL;
R
对应高光部分,NdotL
对应漫反射部分。然而,两者都是附加的。因此,等式应该是(加上考虑 material 个参数):
color += light[i].color * (material.specular * R + material.diffuse * NdotL);
对于环境术语,您有
color += material.diffuse * light[i].color;
将material.diffuse
替换为material.ambient
,这应该是正确的。
并确保您的灯光不要太亮。屏幕无法显示比白色(或完全饱和的红色)更亮的任何东西。