2
votes

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.

Boxes lit "properly"

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.

Boxes misbehaving

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?

1
@NicoSchertler Oops, knew I'd forget something. It's right at the bottom of the section, just before float4 colour is set.Yann
There seems to be a discrepancy with your variable naming. output.PosW looks like a position, but it is the direction to the camera. You pass this to calculateSpotlight() as the position parameter, where it is used as a position. One of both parts must be changed. Furthermore, when you calculate spot, you are missing the minus before light.direction.z. Anyway, there seem too many minuses in there (reflect(-lightV, normal), dot(-lightV, ..., -light.direction).Nico Schertler
You might want to take a look at the FixedFuncEMUFX11 sample which has HLSLshaders that implement all the standard "fixed-function" rendering including spot lights.Chuck Walbourn

1 Answers

3
votes

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);