Hi!
I compute spotlights with a formula I build based on the result of dot and cross products of vectors. I don't know if there is a faster way...

Goemetrically, the dot product of two vectors returns the scalar of the orthogonal projection of the second vector over the first. This is the fastest way to compute the distance from a plane to a point. A plane can be described with a point and a normal which is precisely what we get as a description of a spotlight: a position and a direction (normal).

The length of the resulting vector of the cross product of two vectors is the area of the parallelogram described by the two vectors. This is the fastest way to compute the distance from a line to a point.This is harder to explain, but it happens that when the first vector is a unit vector, the value of the area is the same as the minimum distance between the second vector and the line that contains the first vector.

We start with:
- world_pos -> the world position of the rendered pixel.
- light_pos -> the world position of the light.
- light_dir -> the direction of the light.

The first thing we need to do is to transform the world position to a position referenced to the light position, so it describes the world position in the same coordinate system as the light direction.

Code
float3 light_ray = world_pos - light_pos;


Since the light direction is a unit vector, the result of the dot product will return exactly the depth in the spotlight. In other words, the distance from the point to the plane described by the light data.

Code
float depth = dot(light_dir, light_ray);


This depth can be easily transformed to a 0<->1 factor

Code
float depth_factor = saturate(depth / light_range); // saturate clips the value between 0 and 1, any negative value will result in 0, and any value above 1 wiil result in 1.


Since the light direction is a unit vector, the length of the result of the cross product will be exactly the distance between the point and the light direction line.

Code
float radius = length(cross(light_dir, light_ray));


This radius can be easily transformed to a 0<->1 factor when dividing it by the maximum radius: the light range. The conic shape is given by the multiply of the maximum radius by the depth factor so we get a larger maximum radius while the depth gets larger. Notice that the result of the division is linearly proportional to the radius, so we need to inverse it so the maximum lighting is in the light direction line. You can add a custom multiplo to the operation in order to get certain control over the cone proportions.

Code
float spot_factor = 1.0 - saturate(radius / (light_range * depth_factor * custom_factor));


This spot factor has to be multiplied by the attenuation factor based on the length of light_ray, but that is another story.

Salud!