OpenGL Phong 照明:镜面高光错误

OpenGL Phong lighting: specular highlight is wrong

我在 OpenGL 中的 Phong 光照着色器似乎有一个奇怪的问题。镜面高光出现在物体的错误一侧。

手头的问题:

如你所见,镜面高光出现在立方体的另一侧(从光的角度来看,也出现在立方体垂直边的角上。它应该只出现在最接近的一侧光。

立方体的顶点:

float cubeVertices[] = {
    // positions          // normals           // texture coords
    -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
    -1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
     1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
     1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
     1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
    -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
    -1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
     1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   1.0f, 0.0f,
     1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
     1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
    -1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   0.0f, 1.0f,
    -1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
    -1.0f,  1.0f,  1.0f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
    -1.0f,  1.0f, -1.0f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
    -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
    -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,  1.0f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
    -1.0f,  1.0f,  1.0f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
     1.0f,  1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
     1.0f, -1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
     1.0f, -1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
     1.0f, -1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
     1.0f,  1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
     1.0f,  1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
    -1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
     1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,
     1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
     1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
    -1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,
    -1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
    -1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
    -1.0f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
     1.0f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
     1.0f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
     1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
    -1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
};

顶点着色器:

#version 330 core

layout (location = 0) in vec3 vPos;
layout (location = 1) in vec3 vNormal;
layout (location = 2) in vec2 vTexCoords;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    FragPos = vec3(model * vec4(vPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * vNormal;  
    TexCoords = vTexCoords;

    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片段着色器:

#version 330 core

out vec4 FragColor; 

in vec3 FragPos;  
in vec3 Normal;  
in vec2 TexCoords;

uniform vec3 viewPos;
uniform sampler2D diffuseMap;

struct Light {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light lights[8];
uniform int numLights;

vec3 calculateAmbient(vec3 fragPos, vec3 materialAmbient)
{
    vec3 ambient = vec3(0.0);

    for (int i = 0; i < numLights && i < 8; i++)
    {
        ambient += lights[i].ambient * materialAmbient;
    }

    return ambient * 0.5;
}

vec3 calculateDiffuse(vec3 fragPos, vec3 normal, vec3 materialDiffuse)
{
    vec3 diffuse = vec3(0.0);

    for (int i = 0; i < numLights && i < 8; i++)
    {
        vec3 lightDir = normalize(lights[i].position - fragPos);
        float diff = max(dot(normal, lightDir), 0.0);
        diffuse += lights[i].diffuse * (diff * materialDiffuse);
    }

    return diffuse;
}

vec3 calculateSpecular(vec3 fragPos, vec3 normal, vec3 viewDir, vec3 materialSpecular, float shininess)
{
    vec3 specular = vec3(0.0);

    for (int i = 0; i < numLights && i < 8; i++)
    {
        vec3 lightDir = normalize(lights[i].position - fragPos);
        vec3 reflectDir = reflect(-lightDir, normal);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0f * shininess);
        specular += lights[i].specular * (spec * materialSpecular);
    }

    return specular;
}

void main()
{
    vec4 tex = texture(diffuseMap, TexCoords);

    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);

    vec3 ambient = calculateAmbient(FragPos, material.ambient);
    vec3 diffuse = calculateDiffuse(FragPos, norm, material.diffuse);
    vec3 specular = calculateSpecular(FragPos, norm, viewDir, material.specular, material.shininess);

    vec3 result = (ambient + diffuse + specular) * tex.rgb;
    FragColor = vec4(result, 1.0);
} 

镜面高光也出现在背面朝向光源的面上,因为relfect实际计算:

refelct(I, N) = I - 2.0 * dot(N, I) * N

在背面法向量指向远离光源,但是refelct(I, N) == refelct(I, -N),因为:

I - 2.0 * dot(N, I) * N == I - 2.0 * dot(-N, I) * -N

Phong reflection model中,如果漫反射光>0,则只添加镜面高光。例如:

for (int i = 0; i < numLights && i < 8; i++)
{
    vec3 lightDir = normalize(lights[i].position - fragPos);
    float NdotL = dot(normal, lightDir);
    if (NdotL > 0.0)
    {
        vec3 reflectDir = reflect(-lightDir, normal);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0f * shininess);
        specular += lights[i].specular * (spec * materialSpecular);
    }
}