Per-pixel spotlight problems

I’m having a lot of trouble getting my per-pixel spotlight shader to work right. I’ve attached the code as well as a video demonstrating the problem. The little floating guy in the video represents the position and orientation of the light. As you can see, the problem is that the circle on the ground from the light is the wrong shape, and actually changes shape when I rotate the camera even though the light is never moved. This only happens when I rotate the camera, not when I simply translate it. I’ve verified that the normals of the ground’s vertices are all [0,1,0] as they should be. The relevant values for the light are:

cone angle : 15,
rate of decay : 0,
constant attenuation : 1,
linear attenuation" : 0,
quadratic attenuation : 0

video link:
http://www.youtube.com/watch?v=45AMXpeTSw8

vertex shader:


#version 130

uniform mat4x4 projectionMatrix;
uniform mat4x4 modelViewMatrix;
uniform mat2x2 textureMatrix;
uniform vec4 globalAmbient;
uniform vec3 lightPosition; // in view space
uniform vec4 lightAmbient;
uniform vec4 lightDiffuse;
uniform vec4 materialAmbient;
uniform vec4 materialDiffuse;

in vec4 position;
in vec2 textureCoordinate;
in vec3 normal;

out vec2 vertexTextureCoordinate;
out vec3 vertexNormal;
out vec4 vertexGlobalAmbient;
out vec3 vertexLightDirection;
out vec3 vertexHalfVector;
out float vertexLightDistance;
out vec4 vertexAmbientColor;
out vec4 vertexDiffuseColor;

void main()
{
    vertexNormal = mat3x3(modelViewMatrix) * normal; // We assume no non-uniform scaling here
    
    // Compute the light direction
    gl_Position = modelViewMatrix * position;
    vec3 lightVector = lightPosition - gl_Position.xyz;
    vertexLightDirection = normalize( lightVector );
    vertexLightDistance = length( lightVector );
    vertexHalfVector = normalize( vertexLightDirection + gl_Position.xyz );
    
    // Compute ambient and diffuse colors
    vertexGlobalAmbient = globalAmbient * materialAmbient;
    vertexAmbientColor = materialAmbient * lightAmbient;
    vertexDiffuseColor = materialDiffuse * lightDiffuse;

    gl_Position = projectionMatrix * gl_Position;
    vertexTextureCoordinate = textureMatrix * textureCoordinate;
}

fragment shader:


#version 130

uniform sampler2D texture;
uniform vec4 lightSpecular;
uniform float lightConstantAttenuation;
uniform float lightLinearAttenuation;
uniform float lightQuadraticAttenuation;
uniform vec3 lightDirection;
uniform float lightConeAngleCosine;
uniform uint lightRateOfDecay;
uniform vec4 materialSpecular;
uniform int materialShininess;

in vec2 vertexTextureCoordinate;
in vec3 vertexNormal;
in vec4 vertexGlobalAmbient;
in vec3 vertexLightDirection;
in vec3 vertexHalfVector;
in float vertexLightDistance;
in vec4 vertexAmbientColor;
in vec4 vertexDiffuseColor;

out vec4 fragmentColor;

void main()
{
    fragmentColor = vertexGlobalAmbient;
    vec3 normal = normalize( vertexNormal );
    
    float normalDotLightDirection = max( dot( normal, normalize(vertexLightDirection) ), 0 );    
    if( normalDotLightDirection > 0 )
    {
        float spotEffect = dot( normalize(lightDirection), normalize(-vertexLightDirection) );
        if( spotEffect > lightConeAngleCosine )
        {
            spotEffect = pow( spotEffect, lightRateOfDecay );
        
            float attenuation = spotEffect / ( lightConstantAttenuation +
                                               lightLinearAttenuation * vertexLightDistance +
                                               lightQuadraticAttenuation * vertexLightDistance * vertexLightDistance );
                                    
            fragmentColor += attenuation * ( vertexDiffuseColor * normalDotLightDirection + vertexAmbientColor );
        
            vec3 halfVector = normalize( vertexHalfVector );
            float normalDotHalfVector = max( dot(normal, halfVector), 0 );
            fragmentColor += attenuation * materialSpecular * lightSpecular * pow( normalDotHalfVector, materialShininess );
        }
    }
    
    vec4 texel = texture2D( texture, vertexTextureCoordinate );
    fragmentColor *= texel;
    fragmentColor.a = texel.a;
}

I notice something in the vertex shader that doesn’t look good. Here’s an excerpt:

    // Compute the light direction
    vec3 lightVector = lightPosition - gl_Position.xyz;
    vertexLightDirection = normalize( lightVector );
    vertexHalfVector = normalize( vertexLightDirection + gl_Position.xyz );

I think your calculation of vertexHalfVector is incorrect.

Ya, I was pretty sure the half vector calculation was wrong, but that just affects the specular highlight and the floor material doesn’t have a specular component anyways, so it doesn’t visually affect the problem I’m having at all. Still, what is the correct way to calculate the half vector?

I think the usual way to calculate the half vector is to normalize the sum of the light direction and the view direction. Both the light direction and view direction should be normalized vectors. Neither depends on the object being rendered, so the half vector can be computed once per frame per light.

When I have a problem I can’t figure out, I try to simplify it as much as possible, until the problem is isolated and becomes apparent. Your code is not trivial; you could simplify it in a way intended to isolate where the problem is occurring. For example, you could replace all the code within the nested “if” statement in the fragment shader, with something really simple such as “fragmentColor *= 1.5;”. By doing that, and observing whether the problem continues or goes away, will let you know where you need to focus your attention. By conducting a number of such tests, you should be able to pretty quickly isolate exactly where the problem occurs.

I did some tests in the fragment shader like you suggested (replacing nested if contents, etc) and the problem still persists. It looks like the problem must be with the vertex shader or with something I’m feeding the vertex shader…

How are you calculating lightDirection? I’m having a similar problem and I’ve narrowed it down to the calculation of the light’s direction.

I’m glad you mentioned that because I was passing lightDirection to the fragment shader in world space instead of view space which I have since corrected. My spotlight is still moving around when I move the camera though :frowning:

Have you verified your VIEWING transform for the purposes of transforming the light isn’t the identity transform?

Are you transforming not only the spot light position, but it’s cone axis direction vector to EYE space as well?

Have you verified your VIEWING transform for the purposes of transforming the light isn’t the identity transform?

Are you transforming not only the spot light position, but it’s cone axis direction vector to EYE space as well?
[/QUOTE]

I figured it out. When transforming the light’s direction vector to view space, I was transforming it using the entire 4x4 view matrix instead of just the rotation component (upper 3x3).

The only issue I’m having now is that the lit area isn’t perfectly round when the spotlight is pointed directly at a flat plane. It’s slightly oval shaped…any suggestions on where to look for the cause of that problem?

So I can describe the problem a little better now. The scene I’m using for testing is a large plane centered at the origin consisting of two triangles. When I position the spotlight directly above the center of the plane, the lit area is a perfect circle as expected. If I move the light away from the center of the plane, but still pointing directly down at it, the lit area correctly moves with it, but distorts into an oval. The oval appears to be what the lit area would look like if I had left the light over the origin and tilted it instead of translating it. Does that description make sense?

Yes, that description makes sense. My guess is that it is now an entirely different problem: perspective projection distortion. If you change the field of view to a narrower angle and the distortion goes down, then that is your problem. So, can you reduce your field of view (and compensate by moving the camera position further away, to maintain the same relative sizes of your scene)?

Thanks for helping, but that doesn’t appear to be the problem. The shape of the lit area isn’t affected by changing the camera’s FOV. Here are a couple screenshots. The plane is 1000x1000 centered at the origin. The light is positioned at (0,700,-800) and is oriented to point straight down at the plane (I have verified both of these things in the debugger). As you can see, not only is the lit area elongated, but it’s rotated slightly as well even though the only rotation applied to the light is a single 90 degree rotation to point it straight down.


I’ve run in the exact same problem and found this solution :
You shouldn’t normalize the lightDir vector (the one going from the light to the vertex) in the vertex shader.

I think the explanation is that the lightDir is supposed to be vertPos-lightPos. Now, the fragment will interpolate this direction from the vertices around the fragment. By normalizing before this interpolation, a vertex very far away will have the same “weight” as a close vertex and I guess that is the cause of the problem.

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.