I'm trying to do a spot (Blinn) light, with falloff and attenuation. It seems to be working OK except I have a bit of a space problem. That is, whenever I move the camera the light moves to maintain the same relative position, rather than changing with the camera. This results in the light moving around, i.e. not always falling on the same surfaces. It's as if there's a flashlight attached to the camera.
I'm transforming the lights beforehand into view space, so Light_Position and Light_Direction are already in eye space (I hope!). I made a little movie of what it looks like here:
My camera rotating around a point inside a box.
The light is fixed in the centre up and its "look at" point in a fixed position in front of it. As you can see, as the camera rotates around the origin (always looking at the centre), so don't think the box is rotating (!). The lighting follows it around. To start, some code. This is how I'm transforming the light into view space (it gets passed into the shader already in view space):
// Compute eye-space light position.
Math::Vector3d eyeSpacePosition = MyCamera->ViewMatrix() * MyLightPosition;
MyShaderVariables->Set(MyLightPositionIndex, eyeSpacePosition);
// Compute eye-space light direction vector.
Math::Vector3d eyeSpaceDirection = Math::Unit(MyLightLookAt - MyLightPosition);
MyCamera->ViewMatrixInverseTranspose().TransformNormal(eyeSpaceDirection);
MyShaderVariables->Set(MyLightDirectionIndex, eyeSpaceDirection);
Can anyone give me a clue as to what I'm doing wrong here? I think the light should remain looking at a fixed point on the box, regardless of the camera orientation.
Here are the vertex and pixel shaders:
///////////////////////////////////////////////////
// Vertex Shader
///////////////////////////////////////////////////
#version 420
///////////////////////////////////////////////////
// Uniform Buffer Structures
///////////////////////////////////////////////////
// Camera.
layout (std140) uniform Camera
{
mat4 Camera_View;
mat4 Camera_ViewInverseTranspose;
mat4 Camera_Projection;
};
// Matrices per model.
layout (std140) uniform Model
{
mat4 Model_World;
mat4 Model_WorldView;
mat4 Model_WorldViewInverseTranspose;
mat4 Model_WorldViewProjection;
};
// Spotlight.
layout (std140) uniform OmniLight
{
float Light_Intensity;
vec3 Light_Position;
vec3 Light_Direction;
vec4 Light_Ambient_Colour;
vec4 Light_Diffuse_Colour;
vec4 Light_Specular_Colour;
float Light_Attenuation_Min;
float Light_Attenuation_Max;
float Light_Cone_Min;
float Light_Cone_Max;
};
///////////////////////////////////////////////////
// Streams (per vertex)
///////////////////////////////////////////////////
layout(location = 0) in vec3 attrib_Position;
layout(location = 1) in vec3 attrib_Normal;
layout(location = 2) in vec3 attrib_Tangent;
layout(location = 3) in vec3 attrib_BiNormal;
layout(location = 4) in vec2 attrib_Texture;
///////////////////////////////////////////////////
// Output streams (per vertex)
///////////////////////////////////////////////////
out vec3 attrib_Fragment_Normal;
out vec4 attrib_Fragment_Position;
out vec2 attrib_Fragment_Texture;
out vec3 attrib_Fragment_Light;
out vec3 attrib_Fragment_Eye;
///////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////
void main()
{
// Transform normal into eye space
attrib_Fragment_Normal = (Model_WorldViewInverseTranspose * vec4(attrib_Normal, 0.0)).xyz;
// Transform vertex into eye space (world * view * vertex = eye)
vec4 position = Model_WorldView * vec4(attrib_Position, 1.0);
// Compute vector from eye space vertex to light (light is in eye space already)
attrib_Fragment_Light = Light_Position - position.xyz;
// Compute vector from the vertex to the eye (which is now at the origin).
attrib_Fragment_Eye = -position.xyz;
// Output texture coord.
attrib_Fragment_Texture = attrib_Texture;
// Compute vertex position by applying camera projection.
gl_Position = Camera_Projection * position;
}
and the pixel shader:
///////////////////////////////////////////////////
// Pixel Shader
///////////////////////////////////////////////////
#version 420
///////////////////////////////////////////////////
// Samplers
///////////////////////////////////////////////////
uniform sampler2D Map_Diffuse;
///////////////////////////////////////////////////
// Global Uniforms
///////////////////////////////////////////////////
// Material.
layout (std140) uniform Material
{
vec4 Material_Ambient_Colour;
vec4 Material_Diffuse_Colour;
vec4 Material_Specular_Colour;
vec4 Material_Emissive_Colour;
float Material_Shininess;
float Material_Strength;
};
// Spotlight.
layout (std140) uniform OmniLight
{
float Light_Intensity;
vec3 Light_Position;
vec3 Light_Direction;
vec4 Light_Ambient_Colour;
vec4 Light_Diffuse_Colour;
vec4 Light_Specular_Colour;
float Light_Attenuation_Min;
float Light_Attenuation_Max;
float Light_Cone_Min;
float Light_Cone_Max;
};
///////////////////////////////////////////////////
// Input streams (per vertex)
///////////////////////////////////////////////////
in vec3 attrib_Fragment_Normal;
in vec3 attrib_Fragment_Position;
in vec2 attrib_Fragment_Texture;
in vec3 attrib_Fragment_Light;
in vec3 attrib_Fragment_Eye;
///////////////////////////////////////////////////
// Result
///////////////////////////////////////////////////
out vec4 Out_Colour;
///////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////
void main(void)
{
// Compute N dot L.
vec3 N = normalize(attrib_Fragment_Normal);
vec3 L = normalize(attrib_Fragment_Light);
vec3 E = normalize(attrib_Fragment_Eye);
vec3 H = normalize(L + E);
float NdotL = clamp(dot(L,N), 0.0, 1.0);
float NdotH = clamp(dot(N,H), 0.0, 1.0);
// Compute ambient term.
vec4 ambient = Material_Ambient_Colour * Light_Ambient_Colour;
// Diffuse.
vec4 diffuse = texture2D(Map_Diffuse, attrib_Fragment_Texture) * Light_Diffuse_Colour * Material_Diffuse_Colour * NdotL;
// Specular.
float specularIntensity = pow(NdotH, Material_Shininess) * Material_Strength;
vec4 specular = Light_Specular_Colour * Material_Specular_Colour * specularIntensity;
// Light attenuation (so we don't have to use 1 - x, we step between Max and Min).
float d = length(-attrib_Fragment_Light);
float attenuation = smoothstep(Light_Attenuation_Max, Light_Attenuation_Min, d);
// Adjust attenuation based on light cone.
float LdotS = dot(-L, Light_Direction), CosI = Light_Cone_Min - Light_Cone_Max;
attenuation *= clamp((LdotS - Light_Cone_Max) / CosI, 0.0, 1.0);
// Final colour.
Out_Colour = (ambient + diffuse + specular) * Light_Intensity * attenuation;
}