I am working on a 3D project in DirectX11, and am currently implementing different lights using the Frank Luna 3D Game Programming with DirectX11 book with my existing code.
Currently, I am developing a spotlight, which should follow the camera's position and look in the same direction, however, the position that is being lit is moving oddly. When the position is being changes, the direction vector of the light seems to be tracking in the (+x, +y, 0) direction. Best explained with a picture.
It look here like they are lit properly, and if the camera stays where it is, the spotlight can be moved around as you'd expect, and it tracks the camera direction.
In this image, I've moved the camera closer to the boxes, along the z axis, and the light spot should just get smaller on the nearest box, but it's instead tracking upwards.
This is the code where the spotlight struct is being set up to be passed into the constant buffer, that is all of the values in the struct, aside from a float being used as a pad at the end:
cb.spotLight = SpotLight();
cb.spotLight.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
cb.spotLight.specular = XMFLOAT4(0.5, 0.5, 0.5, 10.0);
cb.spotLight.diffuse = XMFLOAT4(0.5, 0.5, 0.5, 1.0);
cb.spotLight.attenuation = XMFLOAT3(1, 1, 1);
cb.spotLight.range = 15;
XMVECTOR cameraP = XMLoadFloat3(&cameraPos);
XMVECTOR s = XMVectorReplicate(cb.spotLight.range);
XMVECTOR l = XMLoadFloat3(&camera.getForwards());
XMVECTOR lookat = XMVectorMultiplyAdd(s, l, cameraP);
XMStoreFloat3(&cb.spotLight.direction, XMVector3Normalize(lookat - XMVectorSet(cameraPos.x, cameraPos.y, cameraPos.z, 1.0f)));
cb.spotLight.position = cameraPos;
cb.spotLight.spot = 96;
Here is the function being used to calculate the ambient, diffuse and specular values of the spotlight in the shader:
void calculateSpotLight(Material mat, SpotLight light, float3 position, float3 normal, float3 toEye,
out float4 ambient, out float4 diffuse, out float4 specular)
{
ambient = float4(0, 0, 0, 0);
specular = float4(0, 0, 0, 0);
diffuse = float4(0, 0, 0, 0);
float3 lightV = light.position - position;
float distance = length(lightV);
if (distance > light.range)
{
return;
}
lightV /= distance;
ambient = mat.ambient * light.ambient;
float diffuseFact = dot(lightV, normal);
[flatten]
if (diffuseFact > 0.0f)
{
float3 vect = reflect(-lightV, normal);
float specularFact = pow(max(dot(vect, toEye), 0.0f), mat.specular.w);
diffuse = diffuseFact * mat.diffuse * light.diffuse;
specular = specularFact * mat.specular * light.specular;
}
float spot = pow(max(dot(-lightV, float3(-light.direction.x, -light.direction.y, light.direction.z)), 0.0f), light.spot);
float attenuation = spot / dot(light.attenuation, float3(1.0f, distance, distance*distance));
ambient *= spot;
diffuse *= attenuation;
specular *= attenuation;
}
And for completenesses sake, the vertex and the relevant section of the pixel shader.
VS_OUTPUT VS( float4 Pos : POSITION, float3 NormalL : NORMAL, float2 TexC : TEXCOORD )
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul( Pos, World );
//Get normalised vector to camera position in world coordinates
output.PosW = normalize(eyePos - output.Pos.xyz);
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
//Getting normalised surface normal
float3 normalW = mul(float4(NormalL, 0.0f), World).xyz;
normalW = normalize(normalW);
output.Norm = normalW;
output.TexC = TexC;
return output;
}
float4 PS( VS_OUTPUT input ) : SV_Target
{
input.Norm = normalize(input.Norm);
Material newMat;
newMat.ambient = material.ambient;
newMat.diffuse = texCol;
newMat.specular = specCol;
float4 ambient = (0.0f, 0.0f, 0.0f, 0.0f);
float4 specular = (0.0f, 0.0f, 0.0f, 0.0f);
float4 diffuse = (0.0f, 0.0f, 0.0f, 0.0f);
float4 amb, spec, diff;
calculateSpotLight(newMat, spotLight, input.PosW, input.Norm, input.PosW, amb, diff, spec);
ambient += amb;
specular += spec;
diffuse += diff;
//Other light types
float4 colour;
colour = ambient + specular + diffuse;
colour.a = material.diffuse.a;
return colour;
}
Where did I go wrong?
Third argument input.PosW
is incorrect here. You must use position in world space. input.PosW
is a normalized vector. It doesn't make any sense to subtract normalized vector from light position.
You have
calculateSpotLight(newMat, spotLight, input.PosW, input.Norm, input.PosW, amb, diff, spec);
You need (input.Pos in WS, not projection space)
calculateSpotLight(newMat, spotLight, input.Pos, input.Norm, input.PosW, amb, diff, spec);