# DX11 Why won't my falloff work for this dot product based spotlight?

## Recommended Posts

I made a spotlight that

1. Projects 3d models onto a render target from each light POV to simulate shadows

2. Cuts a circle out of the square of light that has been projected onto the render target

as a result of the light frustum, then only lights up the pixels inside that circle

(except the shadowed parts of course), so you dont see the square edges of the projected frustum.

After doing an if check to see if the dot product of light direction and light to vertex vector is greater than .95

to get my initial cutoff, I then multiply the light intensity value inside the resulting circle by the same dot product value,

which should range between .95 and 1.0.

This should give the light inside that circle a falloff from 100% lit to 0% lit toward the edge of the circle. However,

there is no falloff. It's just all equally lit inside the circle. Why on earth, I have no idea. If someone could take a gander

and let me know, please help, thank you so much.

float CalculateSpotLightIntensity(
float3 LightPos_VertexSpace,
float3 LightDirection_WS,
float3 SurfaceNormal_WS)
{
//float3 lightToVertex = normalize(SurfacePosition - LightPos_VertexSpace);
float3 lightToVertex_WS = -LightPos_VertexSpace;

float dotProduct = saturate(dot(normalize(lightToVertex_WS), normalize(LightDirection_WS)));

// METALLIC EFFECT (deactivate for now)
float metalEffect = saturate(dot(SurfaceNormal_WS, normalize(LightPos_VertexSpace)));
if(dotProduct > .95 /*&& metalEffect > .55*/)
{
return saturate(dot(SurfaceNormal_WS, normalize(LightPos_VertexSpace)));
//return saturate(dot(SurfaceNormal_WS, normalize(LightPos_VertexSpace))) * dotProduct;
//return dotProduct;
}
else
{
return 0;
}
}

float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
float2 projectTexCoord;
float depthValue;
float lightDepthValue;
float4 textureColor;

// Set the bias value for fixing the floating point precision issues.
float bias = 0.001f;

// Set the default output color to the ambient light value for all pixels.
float4 lightColor = cb_ambientColor;

/////////////////// NORMAL MAPPING //////////////////
float4 bumpMap = shaderTextures[4].Sample(SampleType, input.tex);

// Expand the range of the normal value from (0, +1) to (-1, +1).
bumpMap = (bumpMap * 2.0f) - 1.0f;

// Change the COORDINATE BASIS of the normal into the space represented by basis vectors tangent, binormal, and normal!
float3 bumpNormal = normalize((bumpMap.x * input.tangent) + (bumpMap.y * input.binormal) + (bumpMap.z * input.normal));

//////////////// LIGHT LOOP ////////////////
for(int i = 0; i < NUM_LIGHTS; ++i)
{
// Calculate the projected texture coordinates.
projectTexCoord.x =  input.vertex_ProjLightSpace[i].x / input.vertex_ProjLightSpace[i].w / 2.0f + 0.5f;
projectTexCoord.y = -input.vertex_ProjLightSpace[i].y / input.vertex_ProjLightSpace[i].w / 2.0f + 0.5f;

if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
{
// Sample the shadow map depth value from the depth texture using the sampler at the projected texture coordinate location.
depthValue = shaderTextures[6 + i].Sample(SampleTypeClamp, projectTexCoord).r;

// Calculate the depth of the light.
lightDepthValue = input.vertex_ProjLightSpace[i].z / input.vertex_ProjLightSpace[i].w;

// Subtract the bias from the lightDepthValue.
lightDepthValue = lightDepthValue - bias;

float lightVisibility = shaderTextures[6 + i].SampleCmp(SampleTypeComp, projectTexCoord, lightDepthValue );

// Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
// If the light is in front of the object then light the pixel, if not then shadow this pixel since an object (occluder) is casting a shadow on it.
if(lightDepthValue < depthValue)
{
// Calculate the amount of light on this pixel.
float lightIntensity = saturate(dot(bumpNormal, normalize(input.lightPos_LS[i])));

if(lightIntensity > 0.0f)
{
// Determine the final diffuse color based on the diffuse color and the amount of light intensity.
float spotLightIntensity = CalculateSpotLightIntensity(
input.lightPos_LS[i], // NOTE - this is NOT NORMALIZED!!!
cb_lights[i].lightDirection,
bumpNormal/*input.normal*/);

lightColor += cb_lights[i].diffuseColor*spotLightIntensity* .18f; // spotlight
//lightColor += cb_lights[i].diffuseColor*lightIntensity* .2f; // square light
}
}
}
}

// Saturate the final light color.
lightColor = saturate(lightColor);
// lightColor = saturate( CalculateNormalMapIntensity(input, lightColor, cb_lights[0].lightDirection));

// TEXTURE ANIMATION -  Sample pixel color from texture at this texture coordinate location.
input.tex.x += textureTranslation;

// BLENDING
float4 color1 = shaderTextures[0].Sample(SampleTypeWrap, input.tex);
float4 color2 = shaderTextures[1].Sample(SampleTypeWrap, input.tex);
float4 alphaValue = shaderTextures[3].Sample(SampleTypeWrap, input.tex);
textureColor = saturate((alphaValue * color1) + ((1.0f - alphaValue) * color2));

// Combine the light and texture color.
float4 finalColor = lightColor * textureColor;

/////// TRANSPARENCY /////////
//finalColor.a = 0.2f;

return finalColor;
}

Light_vs.hlsl

Light_ps.hlsl

##### Share on other sites

Hi,

Those calculations seem correct at first glance. But something is missing. With only a dot(normal, lightDirection) you won't get a smooth spotlight attenuation for the cutoff. You also want to calculate a spotlight attenuation factor. My code:

float3 L = light.worldPosition - surfaceWorldPosition; // light-vector
float dist = length(L); // distance from surface to light

float SpotFactor = dot(L, light.directionWorldSpace); // spotlight lookAt vector
float spotCutOff = light.coneAngleCos; // cos(spotLightFieldOfView)

// ...

// pointlight-like attenuation factor
float att = (light.energy * (light.range / (light.range + 1 + dist)));
float attenuation = (att * (light.range - dist) / light.range);

// spotlight-specific attenuation factor:
// this is resposible for smooth gradient of spotlight cutoff circle
attenuation *= saturate((1.0 - (1.0 - SpotFactor) * 1.0 / (1.0 - spotCutOff)));

diffuse *= attenuation;
specular *= attenuation;

In the above code, spotCutoff would be your hardcoded 0.95 value, SpotFactor is your "dotProduct" valriable. Good luck!

##### Share on other sites
18 hours ago, turanszkij said:

Hi,

Those calculations seem correct at first glance. But something is missing. With only a dot(normal, lightDirection) you won't get a smooth spotlight attenuation for the cutoff. You also want to calculate a spotlight attenuation factor. My code:


float3 L = light.worldPosition - surfaceWorldPosition; // light-vector
float dist = length(L); // distance from surface to light

float SpotFactor = dot(L, light.directionWorldSpace); // spotlight lookAt vector
float spotCutOff = light.coneAngleCos; // cos(spotLightFieldOfView)

// ...

// pointlight-like attenuation factor
float att = (light.energy * (light.range / (light.range + 1 + dist)));
float attenuation = (att * (light.range - dist) / light.range);

// spotlight-specific attenuation factor:
// this is resposible for smooth gradient of spotlight cutoff circle
attenuation *= saturate((1.0 - (1.0 - SpotFactor) * 1.0 / (1.0 - spotCutOff)));

diffuse *= attenuation;
specular *= attenuation;

In the above code, spotCutoff would be your hardcoded 0.95 value, SpotFactor is your "dotProduct" valriable. Good luck!

Thanks, will try it out!

## Create an account or sign in to comment

You need to be a member in order to leave a comment

## Create an account

Sign up for a new account in our community. It's easy!

Register a new account

• 10
• 11
• 9
• 16
• 18
• ### Similar Content

• By reenigne
For those that don't know me. I am the individual who's two videos are listed here under setup for https://wiki.libsdl.org/Tutorials
I also run grhmedia.com where I host the projects and code for the tutorials I have online.
Recently, I received a notice from youtube they will be implementing their new policy in protecting video content as of which I won't be monetized till I meat there required number of viewers and views each month.

Frankly, I'm pretty sick of youtube. I put up a video and someone else learns from it and puts up another video and because of the way youtube does their placement they end up with more views.
Even guys that clearly post false information such as one individual who said GLEW 2.0 was broken because he didn't know how to compile it. He in short didn't know how to modify the script he used because he didn't understand make files and how the requirements of the compiler and library changes needed some different flags.

At the end of the month when they implement this I will take down the content and host on my own server purely and it will be a paid system and or patreon.

I get my videos may be a bit dry, I generally figure people are there to learn how to do something and I rather not waste their time.
I used to also help people for free even those coming from the other videos. That won't be the case any more. I used to just take anyone emails and work with them my email is posted on the site.

I don't expect to get the required number of subscribers in that time or increased views. Even if I did well it wouldn't take care of each reoccurring month.
I figure this is simpler and I don't plan on putting some sort of exorbitant fee for a monthly subscription or the like.
I was thinking on the lines of a few dollars 1,2, and 3 and the larger subscription gets you assistance with the content in the tutorials if needed that month.
Maybe another fee if it is related but not directly in the content.
The fees would serve to cut down on the number of people who ask for help and maybe encourage some of the people to actually pay attention to what is said rather than do their own thing. That actually turns out to be 90% of the issues. I spent 6 hours helping one individual last week I must have asked him 20 times did you do exactly like I said in the video even pointed directly to the section. When he finally sent me a copy of the what he entered I knew then and there he had not. I circled it and I pointed out that wasn't what I said to do in the video. I didn't tell him what was wrong and how I knew that way he would go back and actually follow what it said to do. He then reported it worked. Yea, no kidding following directions works. But hey isn't alone and well its part of the learning process.

So the point of this isn't to be a gripe session. I'm just looking for a bit of feed back. Do you think the fees are unreasonable?
Should I keep the youtube channel and do just the fees with patreon or do you think locking the content to my site and require a subscription is an idea.

I'm just looking at the fact it is unrealistic to think youtube/google will actually get stuff right or that youtube viewers will actually bother to start looking for more accurate videos.
• By KarimIO
Hey guys,
Are lightmaps still the best way to handle static diffuse irradiance, or is SH used for both diffuse and specular irradiance now?
Also, do any modern games use direct light in lightmaps, or are all direct lighting handled by shadow maps now?
Finally, how is SH usually baked?
Thanks!
• By KarimIO
Hey guys
So I was wondering how modern terrain and water geometry works both with and without tesselation. Essentially:
1) Is Geoclipmapping still the best CPU tesselation technique?
2) Is Geoclipmapping still used with tesselation?
3) Is non-tesselated water just flat? Is there any other (reasonable) ways to simulate it? Do people use Geoclipmapping for that too?
Thanks!

•
Hey guys,

Anthony here from Atwo Studios bringing you some new updates for the new year!
In this video I go over our game ROY, the new games and some general updates to the company!

If you have not checked out ROY feel free to give it a try! Many people have said they enjoyed the game thus far!
ROY: https://goo.gl/o6JJ5P

• I wanted to see how others are currently handling descriptor heap updates and management.
I've read a few articles and there tends to be three major strategies :
1 ) You split up descriptor heaps per shader stage ( i.e one for vertex shader , pixel , hull, etc)
2) You have one descriptor heap for an entire pipeline
3) You split up descriptor heaps for update each update frequency (i.e EResourceSet_PerInstance , EResourceSet_PerPass , EResourceSet_PerMaterial, etc)
The benefits of the first two approaches is that it makes it easier to port current code, and descriptor / resource descriptor management and updating tends to be easier to manage, but it seems to be not as efficient.
The benefits of the third approach seems to be that it's the most efficient because you only manage and update objects when they change.