After much head / wall bashing I managed to put something together myself. All done in the fragment shader by setting some shader array and constant values. I'm sure there are better and more realistic implementations but this is a good starting point for me.
The SpotCutoff[] array contains the spotlight angles. You must populate it with Sin(lightAngle). SpotDirection is a normal and SpotPosition is the world position of the light. Don't forget to pass the number of spotlights in gNumSpotLights.
const int MAX_POINT_LIGHTS = 4;
uniform sampler2D texture0;
uniform mediump vec3 SpotPosition[MAX_POINT_LIGHTS];
uniform mediump vec3 SpotDirection[MAX_POINT_LIGHTS];
uniform mediump vec3 SpotColor[MAX_POINT_LIGHTS];
uniform mediump float SpotCutoff[MAX_POINT_LIGHTS];
uniform float gNumSpotLights;
varying highp vec3 posVarying;
varying mediump vec3 normalVarying;
varying mediump vec2 uvVarying;
varying mediump vec3 lightVarying;
mediump vec3 GetPSLighting( mediump vec3 normal, highp vec3 pos );
vec4 CalcSpotLight(int i, vec3 pos, vec3 color, vec3 norm)
{
vec3 lightDir = pos - SpotPosition[i];
vec3 LightToPixel = normalize(lightDir);
float distance = length(lightDir);
float SpotFactor = dot(LightToPixel, SpotDirection[i]);
if (SpotFactor > SpotCutoff[i]) {
float SpotLightIntensity = (1.0 - (1.0 - SpotFactor)/(1.0 - SpotCutoff[i]));
return vec4((color * SpotColor[i] * SpotLightIntensity) / (distance * distance),1);
}
else {
return vec4(0,0,0,0);
}
}
void main()
{
vec3 norm = normalize(normalVarying);
vec3 light = lightVarying + GetPSLighting( norm, posVarying );
vec3 diffuse = texture2D(texture0, uvVarying).rgb;
vec3 color = diffuse * light;
vec4 totalLight = vec4(color,1.0);
for (int i = 0 ;i < int(gNumSpotLights) ;i++) {
totalLight += CalcSpotLight(i, posVarying, diffuse, norm) * 0.5;
}
gl_FragColor = totalLight;
}