diff --git a/hw/xbox/nv2a/shaders.c b/hw/xbox/nv2a/shaders.c index fd0b9820f2..a2a113f13a 100644 --- a/hw/xbox/nv2a/shaders.c +++ b/hw/xbox/nv2a/shaders.c @@ -566,8 +566,31 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz /* Everything done already */ break; case LIGHT_SPOT: - assert(false); - /*FIXME: calculate falloff */ + /* From: https://docs.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor#spotlight-factor */ + /* Note: normalizing the direction does not produce the same result as on hardware. + * Instead, dividing the direction by 1-w produces the same result as on hardware. + * Rationale: the w component is always close (but not equal to) the difference + * between 1 and the the direction vector length (e.g. 1.0-2.6=-1.6, with w=-1.5). + * So, dividing by 1-w is the same as normalizing the vector and scaling it a bit. + * In the example: 1-(-1.5) = 2.5, 2.6/2.5 = 1.04. This also scales the subsequent + * dot product by the same amount, effectively increasing the spotlight radius. + */ + qstring_append_fmt(body, + " vec3 dir = lightSpotDirection(%d).xyz / (1.0 - lightSpotDirection(%d).w);\n" + " float rho = dot(dir, VP);\n" + " float theta = lightSpotFalloff(%d).x;\n" + " float phi = lightSpotFalloff(%d).y;\n" + " float falloff = lightSpotFalloff(%d).z;\n" + " float cosHalfPhi = cos(phi * 0.5);\n" + " float cosHalfTheta = cos(theta * 0.5);\n" + " if (rho > cosHalfTheta) {\n" /* do nothing => spotlightFactor = 1 */ + " } else if (rho <= cosHalfPhi) {\n" + " attenuation = 0.0;\n" /* spotlightFactor = 0 */ + " } else {\n" + " float spotlightFactor = pow((rho - cosHalfPhi) / (cosHalfTheta - cosHalfPhi), falloff);\n" + " attenuation = attenuation * spotlightFactor;\n" + " }\n", + i, i, i, i, i); break; default: assert(false);