Problem
The Point Light Source defined in the previous tutorial emits light in all directions from a point. You want to define a spotlight, which is very similar to a point light source, but the light only illuminates a cone area, 6-10.
Figure 6-10 define a spotlight variable
Solution
In pixel shader, determine whether the current pixel is in the Light cone. This can be done by performing dot multiplication in the light direction and the cone direction.
Working Principle
The initial code is the same as that in the previous tutorial. Because the spotlight has more things to set than the point light source, you need to add the following XNA-to-HLSL variables to the. fx file:
float xLightStrength; float3 xConeDirection; float xConeAngle; float xConeDecay;
The first variable allows you to increase/decrease the illumination intensity. This variable is also useful for other types of light. It is also required when there are multiple light sources in the scenario (see tutorial 6-10 ). Then define the center line direction and width of the Light cone. Finally, it defines the attenuation of illumination.
In addition, you need to extend pixel shader. Basically, this is the same as the pixel-by-pixel light source (see tutorial 6-7). It only adds a step to check whether the pixel is in the cone:
SLPixelToFrame SLPixelShader(SLVertexToPixel PSIn) : COLOR0 { SLPixelToFrame Output = (SLPixelToFrame)0; float4 baseColor = float4(0,0,1,1); float3 normal = normalize(PSIn.Normal); float3 lightDirection = normalize(PSIn.LightDirection); float coneDot = dot(lightDirection, normalize(xConeDirection)); float shading = 0; if (coneDot > xConeAngle) { float coneAttenuation = pow(coneDot, xConeDecay); shading = dot(normal, -lightDirection); shading *= xLightStrength; shading *= coneAttenuation; } Output.Color = baseColor*(shading+xAmbient); return Output; }
After normalization of the normal and light direction, you need to check whether the current pixel is within the light cone. This can detect the angle between the two directions:
- Current pixel to Light Source Direction
- Direction of the center line of the optical cone
The first direction is lightDirection, and the second direction is defined by the xConeDirection variable. Pixels are illuminated only when the angle between the two directions is smaller than a critical value.
A quick method for detection is to calculate the point multiplication in these two directions. The result is close to 1, indicating that the angle between the two is very small. The smaller the result, the larger the angle.
To determine whether the angle is too large, you need to check whether the result of the dot multiplication is smaller than a critical value, which is stored in the ConeAngle variable. If the pixel is in the Light cone, the light factor is calculated. To reduce the illumination close to the edge of the cone, You need to calculate the xConeDecay power of the variable coneDot. As a result, for pixels that are far away from the center line of the optical cone, when the coneDot variable is less than or equal to 1, the power result will be smaller (see the right figure of Figure 6-11 ).
The pixel illumination value outside the light cone is 0, and the light has no effect on these pixels.
Code
The complete pixel shader code already exists.
In the Draw method of XNA code, enable effect, set parameters, and Draw the scenario:
effect.CurrentTechnique = effect.Techniques["SpotLight"]; effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix); effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix); effect.Parameters["xAmbient"].SetValue(0.2f); effect.Parameters["xLightPosition"].SetValue(new Vector3(5.0f, 2.0f, -15.0f+variation)); effect.Parameters["xConeDirection"].SetValue(new Vector3(0,-1,0)); effect.Parameters["xConeAngle"].SetValue(0.5f); effect.Parameters["xConeDecay"].SetValue(2.0f); effect.Parameters["xLightStrength"].SetValue(0.7f); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = myVertexDeclaration; device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip,vertices, 0, 6); pass.End(); }effect.End();