Search code examples
openglglsl

Sphere lighting wrong results


I have a simple per-pixel-lighting shader, but the sphere I want to draw looks a bit like per-vertex shading. I investigated this since yesterday, but didn't find any issues. The normals of a sphere are the same as the normalized vertex positions, or am I wrong?

Vertex shader:


#version 330 core

uniform mat4 ModelViewProjectionMatrix;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;

in vec3 Position;
in vec3 Normal;

out vec3 VertexEyePosition;
out vec3 NormalEyePosition;

void main()
{
    gl_Position = ModelViewProjectionMatrix * vec4(Position, 1.0f);
    VertexEyePosition = vec3(ModelViewMatrix * vec4(Position, 1.0f));
    NormalEyePosition = NormalMatrix * normalize(Normal);
}

Fragment shader:


#version 330 core

uniform vec4 EyeLightPosition;

in vec3 VertexEyePosition;
in vec3 NormalEyePosition;

out vec4 FinalColor;

void main()
{
    vec3 L = normalize(vec3(EyeLightPosition) - VertexEyePosition);
    vec3 N = NormalEyePosition;
    vec3 R = reflect(-L, N);
    vec3 V = normalize(-VertexEyePosition);

    vec3 Ambient = 0.1 * vec3(1.0, 0.0, 0.0);
    vec3 Diffuse = 0.8 * vec3(1.0, 0.0, 0.0) * max(0.0, dot(N, L));
    vec3 Specular = vec3(1.0) * pow(max(0.0, dot(R, V)), 8.0);

    FinalColor = vec4(Ambient + Diffuse + Specular, 1.0);
}

For a cube or plane the shader works fine.

Sphere generation:

void SphereMesh::GenerateGeometry()
{
    vertices.clear();
    uv_coords.clear();
    normals.clear();
    faces.clear();

    vertices.reserve((segments + 1) * (rings + 1));
    uv_coords.reserve((segments + 1) * (rings + 1));

    // Generate vertices and UV coordinates;
    for (unsigned int ring = 0; ring <= rings; ring++)
    {
        const float ring_sin = glm::sin(static_cast<float>(ring) / rings * glm::pi<float>() - 0.5f * glm::pi<float>());
        const float ring_cos = glm::cos(static_cast<float>(ring) / rings * glm::pi<float>() - 0.5f * glm::pi<float>());

        for (unsigned int segment = 0; segment <= segments; segment++)
        {
            if (((ring == 0) || (ring == rings)) && (segment == segments)) continue;

            const float segment_sin = glm::sin(static_cast<float>(segment) / segments * 2.0f * glm::pi<float>());
            const float segment_cos = glm::cos(static_cast<float>(segment) / segments * 2.0f * glm::pi<float>());

            const float x = radius * ring_cos * segment_sin;
            const float y = radius * ring_sin;
            const float z = radius * ring_cos * segment_cos;

            const float u = ((ring == 0) || (ring == rings)) && (segment == segments) ?
                    static_cast<float>(segment) / (segments - 1) + 0.5f * static_cast<float>(segment) / (segments - 1):
                    static_cast<float>(segment) / segments;
            const float v = static_cast<float>(ring) / rings;

            vertices.emplace_back(x, y, z);
            uv_coords.emplace_back(u, v);
            normals.emplace_back(glm::normalize(glm::vec3(x, y, z)));
        }
    }

    // Generate faces
    for (unsigned int ring = 0; ring < rings; ring++)
    {
        for (unsigned int segment = 0; segment < segments; segment++)
        {
            if (ring == 0)  // Lower cap
            {
                Face face;
                face.a = segment;
                face.b = segments + segment + 1;
                face.c = segments + segment;
                faces.push_back(face);
            }
            else if (ring < rings - 1) // Body
            {
                const unsigned int v1 = segments + ((ring - 1) * (segments + 1)) + segment;
                const unsigned int v2 = segments + ((ring - 1) * (segments + 1)) + segment + 1;
                const unsigned int v3 = segments + ((ring - 1 + 1) * (segments + 1)) + segment;
                const unsigned int v4 = segments + ((ring - 1 + 1) * (segments + 1)) + segment + 1;
                Face face1, face2;
                face1.a = v1;
                face1.b = v2;
                face1.c = v3;
                face2.a = v3;
                face2.b = v2;
                face2.c = v4;
                faces.push_back(face1);
                faces.push_back(face2);
            }
            else // Upper cap
            {
                Face face;
                face.a = segments + ((ring - 1) * (segments + 1)) + segment;
                face.b = segments + ((ring - 1) * (segments + 1)) + segment + 1;
                face.c = segments + ((ring - 1 + 1) * (segments + 1)) + segment;
                faces.push_back(face);
            }
        }
    }
}

Result:

enter image description here


Solution

  • NormalEyePosition must also be normalized in the fragment shader When interpolating vectors, the length of the vector is not maintained.

    vec3 N = NormalEyePosition;

    vec3 N = normalize(NormalEyePosition);