关于opengl:具有多个点光源的Cook-Torrance着色器

Cook-Torrance shader with more than one point light

我正在尝试用四个点光源实现Cook-Torrance照明模式。虽然仅使用一个点光源即可获得不错的结果,但我不明白哪种方法是总结光回路内镜面反射项的正确方法。

我将材料定义如下:

1
2
3
4
5
6
7
struct material {
    vec3 ambient; /* ambient color */
    vec3 diffuse; /* diffuse color */
    vec3 specular; /* speculr color */
    float metallic;
    float roughness;
};

...我的灯只有一种颜色/强度属性,

1
2
3
4
5
struct light {
    vec3 position;
    vec3 color;
    bool enabled;
};

这是片段着色器中片段着色器内部的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
}

我相信这里的关键是我在光循环中的错误计算:

1
color += light[i].color * R * NdotL;

这是我的意思的示例,生成的片段颜色要么太暗,要么太亮。我无法对每种光贡献进行汇总,以在镜面反射项和材质颜色之间获得良好的平滑颜色渐变。

enter image description here

我在这里阅读有关伽玛校正的信息,但是我不知道这是否适用于我的问题。

我如何将每种light.color与材质的漫反射,环境和镜面反射颜色相加,以通过正确地包括每种光源的镜面反射高光贡献的总量来计算最终的片段颜色?


您的大多数计算看起来不错(包括VL的方向以及菲涅耳项)。唯一的事情是您可能已经混淆了如何组合各个照明组件。对于镜面反射和漫反射,您有

1
color += light[i].color * R * NdotL;

R对应于镜面反射部分,NdotL对应于漫反射部分。但是,两者都是可加的。因此,公式应为(加上材料参数):

1
color += light[i].color * (material.specular * R + material.diffuse * NdotL);

对于环境而言

1
color += material.diffuse * light[i].color;

material.ambient替换material.diffuse,这应该是正确的。

并确保您的灯光不是太亮。屏幕无法显示比白色(或完全饱和的红色)更亮的任何东西。


  • 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;