diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h
index ae9e192ef2..9dd9b18a03 100644
--- a/hw/xbox/nv2a/nv2a_regs.h
+++ b/hw/xbox/nv2a/nv2a_regs.h
@@ -1056,6 +1056,7 @@
# define NV097_SET_TEXGEN_VIEW_MODEL_LOCAL_VIEWER 0
# define NV097_SET_TEXGEN_VIEW_MODEL_INFINITE_VIEWER 1
# define NV097_SET_FOG_PLANE 0x000009D0
+# define NV097_SET_SPECULAR_PARAMS 0x000009E0
# define NV097_SET_SCENE_AMBIENT_COLOR 0x00000A10
# define NV097_SET_VIEWPORT_OFFSET 0x00000A20
# define NV097_SET_POINT_PARAMS 0x00000A30
@@ -1259,6 +1260,7 @@
# define NV097_SET_CLEAR_RECT_HORIZONTAL 0x00001D98
# define NV097_SET_CLEAR_RECT_VERTICAL 0x00001D9C
# define NV097_SET_SPECULAR_FOG_FACTOR 0x00001E20
+# define NV097_SET_SPECULAR_PARAMS_BACK 0x00001E28
# define NV097_SET_COMBINER_COLOR_OCW 0x00001E40
# define NV097_SET_COMBINER_CONTROL 0x00001E60
# define NV097_SET_SHADOW_ZSLOPE_THRESHOLD 0x00001E68
diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c
index b3c72bb2c2..73044bffc3 100644
--- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c
+++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c
@@ -230,8 +230,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
}
/* Lighting */
- if (state->lighting) {
-
+ if (!state->lighting) {
+ mstring_append(body, " oD0 = diffuse;\n");
+ mstring_append(body, " oD1 = specular;\n");
+ mstring_append(body, " oB0 = backDiffuse;\n");
+ mstring_append(body, " oB1 = backSpecular;\n");
+ } else {
//FIXME: Do 2 passes if we want 2 sided-lighting?
static char alpha_source_diffuse[] = "diffuse.a";
@@ -264,17 +268,17 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
mstring_append(body, "oD1 = vec4(0.0, 0.0, 0.0, specular.a);\n");
+ if (state->local_eye) {
+ mstring_append(body,
+ "vec3 VPeye = normalize(eyePosition.xyz / eyePosition.w - tPosition.xyz / tPosition.w);\n"
+ );
+ }
+
for (i = 0; i < NV2A_MAX_LIGHTS; i++) {
if (state->light[i] == LIGHT_OFF) {
continue;
}
- /* FIXME: It seems that we only have to handle the surface colors if
- * they are not part of the material [= vertex colors].
- * If they are material the cpu will premultiply light
- * colors
- */
-
mstring_append_fmt(body, "/* Light %d */ {\n", i);
if (state->light[i] == LIGHT_LOCAL
@@ -285,18 +289,20 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
"%svec3 lightLocalAttenuation%d;\n",
u, i, u, i);
mstring_append_fmt(body,
- " vec3 VP = lightLocalPosition%d - tPosition.xyz/tPosition.w;\n"
+ " vec3 tPos = tPosition.xyz/tPosition.w;\n"
+ " vec3 VP = lightLocalPosition%d - tPos;\n"
" float d = length(VP);\n"
-//FIXME: if (d > lightLocalRange) { .. don't process this light .. } /* inclusive?! */ - what about directional lights?
- " VP = normalize(VP);\n"
- " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n"
- " + lightLocalAttenuation%d.y * d\n"
- " + lightLocalAttenuation%d.z * d * d);\n"
- " vec3 halfVector = normalize(VP + eyePosition.xyz / eyePosition.w);\n" /* FIXME: Not sure if eyePosition is correct */
- " float nDotVP = max(0.0, dot(tNormal, VP));\n"
- " float nDotHV = max(0.0, dot(tNormal, halfVector));\n",
- i, i, i, i);
-
+ " if (d <= lightLocalRange(%d)) {\n" /* FIXME: Double check that range is inclusive */
+ " VP = normalize(VP);\n"
+ " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n"
+ " + lightLocalAttenuation%d.y * d\n"
+ " + lightLocalAttenuation%d.z * d * d);\n"
+ " vec3 halfVector = normalize(VP + %s);\n"
+ " float nDotVP = max(0.0, dot(tNormal, VP));\n"
+ " float nDotHV = max(0.0, dot(tNormal, halfVector));\n",
+ i, i, i, i, i,
+ state->local_eye ? "VPeye" : "vec3(0.0, 0.0, -1.0)"
+ );
}
switch(state->light[i]) {
@@ -309,15 +315,21 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
"%svec3 lightInfiniteDirection%d;\n",
u, i, u, i);
mstring_append_fmt(body,
- " float attenuation = 1.0;\n"
- " float nDotVP = max(0.0, dot(tNormal, normalize(vec3(lightInfiniteDirection%d))));\n"
- " float nDotHV = max(0.0, dot(tNormal, vec3(lightInfiniteHalfVector%d)));\n",
- i, i);
-
- /* FIXME: Do specular */
-
- /* FIXME: tBackDiffuse */
-
+ " {\n"
+ " float attenuation = 1.0;\n"
+ " vec3 lightDirection = normalize(lightInfiniteDirection%d);\n"
+ " float nDotVP = max(0.0, dot(tNormal, lightDirection));\n",
+ i);
+ if (state->local_eye) {
+ mstring_append(body,
+ " float nDotHV = max(0.0, dot(tNormal, normalize(lightDirection + VPeye)));\n"
+ );
+ } else {
+ mstring_append_fmt(body,
+ " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n",
+ i
+ );
+ }
break;
case LIGHT_LOCAL:
/* Everything done already */
@@ -325,18 +337,18 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
case LIGHT_SPOT:
/* https://docs.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor#spotlight-factor */
mstring_append_fmt(body,
- " vec4 spotDir = lightSpotDirection(%d);\n"
- " float invScale = 1/length(spotDir.xyz);\n"
- " float cosHalfPhi = -invScale*spotDir.w;\n"
- " float cosHalfTheta = invScale + cosHalfPhi;\n"
- " float spotDirDotVP = dot(spotDir.xyz, VP);\n"
- " float rho = invScale*spotDirDotVP;\n"
- " if (rho > cosHalfTheta) {\n"
- " } else if (rho <= cosHalfPhi) {\n"
- " attenuation = 0.0;\n"
- " } else {\n"
- " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */
- " }\n",
+ " vec4 spotDir = lightSpotDirection(%d);\n"
+ " float invScale = 1/length(spotDir.xyz);\n"
+ " float cosHalfPhi = -invScale*spotDir.w;\n"
+ " float cosHalfTheta = invScale + cosHalfPhi;\n"
+ " float spotDirDotVP = dot(spotDir.xyz, VP);\n"
+ " float rho = invScale*spotDirDotVP;\n"
+ " if (rho > cosHalfTheta) {\n"
+ " } else if (rho <= cosHalfPhi) {\n"
+ " attenuation = 0.0;\n"
+ " } else {\n"
+ " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */
+ " }\n",
i);
break;
default:
@@ -345,59 +357,75 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz
}
mstring_append_fmt(body,
- " float pf;\n"
- " if (nDotVP == 0.0) {\n"
- " pf = 0.0;\n"
- " } else {\n"
- " pf = pow(nDotHV, /* specular(l, m, n, l1, m1, n1) */ 0.001);\n"
- " }\n"
- " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n"
- " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n"
- " vec3 lightSpecular = lightSpecularColor(%d) * pf;\n",
- i, i, i);
+ " float pf;\n"
+ " if (nDotVP == 0.0) {\n"
+ " pf = 0.0;\n"
+ " } else {\n"
+ " pf = pow(nDotHV, %f);\n"
+ " }\n"
+ " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n"
+ " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n"
+ " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n"
+ " lightSpecular *= vec3(lessThanEqual(lightSpecular.rgb, vec3(1.0)));\n",
+ state->specular_power, i, i, i);
mstring_append(body,
- " oD0.xyz += lightAmbient;\n");
+ " oD0.xyz += lightAmbient;\n");
switch (state->diffuse_src) {
case MATERIAL_COLOR_SRC_MATERIAL:
mstring_append(body,
- " oD0.xyz += lightDiffuse;\n");
+ " oD0.xyz += lightDiffuse;\n");
break;
case MATERIAL_COLOR_SRC_DIFFUSE:
mstring_append(body,
- " oD0.xyz += diffuse.xyz * lightDiffuse;\n");
+ " oD0.xyz += diffuse.xyz * lightDiffuse;\n");
break;
case MATERIAL_COLOR_SRC_SPECULAR:
mstring_append(body,
- " oD0.xyz += specular.xyz * lightDiffuse;\n");
+ " oD0.xyz += specular.xyz * lightDiffuse;\n");
break;
}
- mstring_append(body,
- " oD1.xyz += specular.xyz * lightSpecular;\n");
+ switch (state->specular_src) {
+ case MATERIAL_COLOR_SRC_MATERIAL:
+ mstring_append(body,
+ " oD1.xyz += lightSpecular;\n");
+ break;
+ case MATERIAL_COLOR_SRC_DIFFUSE:
+ mstring_append(body,
+ " oD1.xyz += diffuse.xyz * lightSpecular;\n");
+ break;
+ case MATERIAL_COLOR_SRC_SPECULAR:
+ mstring_append(body,
+ " oD1.xyz += specular.xyz * lightSpecular;\n");
+ break;
+ }
- mstring_append(body, "}\n");
+ mstring_append(body, " }\n"
+ "}\n");
}
- } else {
- mstring_append(body, " oD0 = diffuse;\n");
- mstring_append(body, " oD1 = specular;\n");
- }
- mstring_append(body, " oB0 = backDiffuse;\n");
- mstring_append(body, " oB1 = backSpecular;\n");
+ /* TODO: Implement two-sided lighting */
+ mstring_append(body, " oB0 = backDiffuse;\n");
+ mstring_append(body, " oB1 = backSpecular;\n");
+ }
if (!state->specular_enable) {
mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n");
mstring_append(body, " oB1 = vec4(0.0, 0.0, 0.0, 1.0);\n");
} else {
if (!state->separate_specular) {
- mstring_append(body, " oD1 = specular;\n");
- mstring_append(body, " oB1 = backSpecular;\n");
+ mstring_append(body,
+ " oD0.xyz += oD1.xyz;\n"
+ " oD1 = specular;\n"
+ " oB0.xyz += oB1.xyz;\n"
+ " oB1 = backSpecular;\n"
+ );
}
if (state->ignore_specular_alpha) {
mstring_append(body,
- " oD1.w = 1.0;\n"
+ " oD1.a = 1.0;\n"
" oB1.a = 1.0;\n"
);
}
diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc
index 894dff7709..322c72be31 100644
--- a/hw/xbox/nv2a/pgraph/methods.h.inc
+++ b/hw/xbox/nv2a/pgraph/methods.h.inc
@@ -96,6 +96,7 @@ DEF_METHOD_RANGE(NV097, SET_FOG_PARAMS, 3)
DEF_METHOD_RANGE(NV097, SET_TEXGEN_PLANE_S, 4*4*4)
DEF_METHOD(NV097, SET_TEXGEN_VIEW_MODEL)
DEF_METHOD_RANGE(NV097, SET_FOG_PLANE, 4)
+DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS, 6)
DEF_METHOD_RANGE(NV097, SET_SCENE_AMBIENT_COLOR, 3)
DEF_METHOD_RANGE(NV097, SET_VIEWPORT_OFFSET, 4)
DEF_METHOD_RANGE(NV097, SET_POINT_PARAMS, 8)
@@ -178,6 +179,7 @@ DEF_METHOD(NV097, CLEAR_SURFACE)
DEF_METHOD(NV097, SET_CLEAR_RECT_HORIZONTAL)
DEF_METHOD(NV097, SET_CLEAR_RECT_VERTICAL)
DEF_METHOD_RANGE(NV097, SET_SPECULAR_FOG_FACTOR, 2)
+DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS_BACK, 6)
DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE)
DEF_METHOD_RANGE(NV097, SET_COMBINER_COLOR_OCW, 8)
DEF_METHOD(NV097, SET_COMBINER_CONTROL)
diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c
index aaee7bf783..cc6a3013c3 100644
--- a/hw/xbox/nv2a/pgraph/pgraph.c
+++ b/hw/xbox/nv2a/pgraph/pgraph.c
@@ -19,6 +19,8 @@
* License along with this library; if not, see .
*/
+#include
+
#include "hw/xbox/nv2a/nv2a_int.h"
#include "ui/xemu-notifications.h"
#include "ui/xemu-settings.h"
@@ -1812,6 +1814,42 @@ DEF_METHOD_INC(NV097, SET_FOG_PLANE)
pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_FOG] = true;
}
+// Based on curve fitting to observed values from DirectX uses.
+// x = -log2(30.80722523) / power
+// c3_param = -1.01441946 * pow(exp2( x ), 2) + 0.01451685
+#define SPECULAR_POWER_NUMERATOR_CONSTANT 30.80722523f
+#define SPECULAR_POWER_COEFFICIENT_A -1.0141946f
+#define SPECULAR_POWER_CONSTANT_COEFFICIENT 0.01451685f
+//#define SPECULAR_POWER_LOG_CONSTANT (-2.f * log2(SPECULAR_POWER_NUMERATOR_CONSTANT))
+#define SPECULAR_POWER_LOG_CONSTANT -9.8903942108154297f
+static float reconstruct_specular_power_from_c3(uint32_t c3_parameter)
+{
+ float c3 = *(float*)&c3_parameter;
+
+ // FIXME: This handling is not correct, but is visually distinct without causing a crash.
+ // It does not appear possible for a DirectX-generated value to be positive, so while this differs from hardware
+ // behavior, it may be irrelevant in practice.
+ float invert = 1.f;
+ if (c3 > 0.0f) {
+ invert = -1.f;
+ c3 *= invert;
+ }
+
+ c3 -= SPECULAR_POWER_CONSTANT_COEFFICIENT;
+ float ret = SPECULAR_POWER_LOG_CONSTANT / log2(c3 / SPECULAR_POWER_COEFFICIENT_A);
+ assert(!isnan(ret) && "Failed to reconstruct specular power factor");
+ return ret * invert;
+}
+
+DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS)
+{
+ int slot = (method - NV097_SET_SPECULAR_PARAMS) / 4;
+ NV2A_DPRINTF("NV097_SET_SPECULAR_PARAMS[%d] 0x%X\n", slot, parameter);
+ if (slot == 3) {
+ pg->specular_power = reconstruct_specular_power_from_c3(parameter);
+ }
+}
+
DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR)
{
int slot = (method - NV097_SET_SCENE_AMBIENT_COLOR) / 4;
@@ -2790,6 +2828,15 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_FOG_FACTOR)
pgraph_reg_w(pg, NV_PGRAPH_SPECFOGFACTOR0 + slot*4, parameter);
}
+DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS_BACK)
+{
+ int slot = (method - NV097_SET_SPECULAR_PARAMS_BACK) / 4;
+ NV2A_DPRINTF("SET_SPECULAR_PARAMS_BACK[%d] 0x%X\n", slot, parameter);
+ if (slot == 3) {
+ pg->specular_power_back = reconstruct_specular_power_from_c3(parameter);
+ }
+}
+
DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE)
{
pgraph_reg_w(pg, NV_PGRAPH_SHADERCLIPMODE, parameter);
diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h
index 64b671e71d..156b3010b6 100644
--- a/hw/xbox/nv2a/pgraph/pgraph.h
+++ b/hw/xbox/nv2a/pgraph/pgraph.h
@@ -197,6 +197,9 @@ typedef struct PGRAPHState {
float light_local_position[NV2A_MAX_LIGHTS][3];
float light_local_attenuation[NV2A_MAX_LIGHTS][3];
+ float specular_power;
+ float specular_power_back;
+
float point_params[8];
VertexAttribute vertex_attributes[NV2A_VERTEXSHADER_ATTRIBUTES];
diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c
index ce80dc127a..cddbf450f0 100644
--- a/hw/xbox/nv2a/pgraph/shaders.c
+++ b/hw/xbox/nv2a/pgraph/shaders.c
@@ -96,6 +96,11 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg)
pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR);
state.ignore_specular_alpha = !GET_MASK(
pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR);
+ state.local_eye = GET_MASK(
+ pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_LOCALEYE);
+
+ state.specular_power = pg->specular_power;
+ state.specular_power_back = pg->specular_power_back;
/* vertex program stuff */
state.vertex_program = vertex_program,
diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h
index 9496cbbea2..4cc07e3a9d 100644
--- a/hw/xbox/nv2a/pgraph/shaders.h
+++ b/hw/xbox/nv2a/pgraph/shaders.h
@@ -81,6 +81,9 @@ typedef struct ShaderState {
bool separate_specular;
bool ignore_specular_alpha;
+ bool local_eye;
+ float specular_power;
+ float specular_power_back;
bool lighting;
enum VshLight light[NV2A_MAX_LIGHTS];