This commit is contained in:
Pokechu22 2025-03-11 13:06:11 +00:00 committed by GitHub
commit 5832a1d4c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 170 additions and 126 deletions

View file

@ -218,22 +218,24 @@ static float CalculateLightAttn(const LightPointer* light, Vec3* _ldir, const Ve
switch (chan.attnfunc)
{
case AttenuationFunc::None:
{
PanicAlertFmt("None lighting in use!");
ldir = ldir.Normalized();
break;
}
case AttenuationFunc::Dir:
{
ldir = ldir.Normalized();
if (ldir == Vec3(0.0f, 0.0f, 0.0f))
ldir = normal;
break;
}
case AttenuationFunc::Spec:
{
PanicAlertFmt("Specular lighting in use!");
ldir = ldir.Normalized();
attn = (ldir * normal) >= 0.0 ? std::max(0.0f, light->dir * normal) : 0;
Vec3 attLen = Vec3(1.0, attn, attn * attn);
Vec3 cosAttn = light->cosatt;
Vec3 distAttn = light->distatt;
if (chan.diffusefunc != DiffuseFunc::None)
distAttn = distAttn.Normalized();
attn = SafeDivide(std::max(0.0f, attLen * cosAttn), attLen * distAttn);
break;

View file

@ -11,69 +11,97 @@
#include "VideoCommon/XFMemory.h"
static void GenerateLightShader(ShaderCode& object, const LightingUidData& uid_data, int index,
int litchan_index, bool alpha)
AttenuationFunc attn_func, DiffuseFunc diffuse_func, bool alpha)
{
object.Write(" {{ // {} light {}\n", alpha ? "Alpha" : "Color", index);
const char* swizzle = alpha ? "a" : "rgb";
const char* swizzle_components = (alpha) ? "" : "3";
const auto attnfunc =
static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3);
const auto diffusefunc =
static_cast<DiffuseFunc>((uid_data.diffusefunc >> (2 * litchan_index)) & 0x3);
// Create a normalized vector pointing from the light to the current position. We manualy
// normalize instead of using normalize() because the raw distance is needed for spot lights.
object.Write(" float3 ldir = " LIGHT_POS ".xyz - pos.xyz;\n", LIGHT_POS_PARAMS(index));
object.Write(" float dist2 = dot(ldir, ldir);\n"
" float dist = sqrt(dist2);\n"
" ldir = ldir / dist;\n");
switch (attnfunc)
switch (attn_func)
{
case AttenuationFunc::None:
// This logic correctly reproduces the behavior (if diffuse > 0, then lacc is 255, if it's < 0
// lacc is 0, and if it's equal to 0 lacc is unchanged, but with DiffuseFunc::None lacc instead
// has the light's color added to it), but may be an overly jank implementation (and might give
// incorrect results for a light value of 1/256, for instance; testing is needed)
if (diffuse_func == DiffuseFunc::None)
object.Write(" float attn = 1.0;\n");
else
object.Write(" float attn = 1024.0;\n");
break;
case AttenuationFunc::Dir:
object.Write("ldir = normalize(" LIGHT_POS ".xyz - pos.xyz);\n", LIGHT_POS_PARAMS(index));
object.Write("attn = 1.0;\n");
object.Write("if (length(ldir) == 0.0)\n\t ldir = _normal;\n");
object.Write(" float attn = 1.0;\n");
break;
case AttenuationFunc::Spec:
object.Write("ldir = normalize(" LIGHT_POS ".xyz - pos.xyz);\n", LIGHT_POS_PARAMS(index));
object.Write("attn = (dot(_normal, ldir) >= 0.0) ? max(0.0, dot(_normal, " LIGHT_DIR
".xyz)) : 0.0;\n",
object.Write(" float cosine = 0.0;\n"
" // Ensure that the object is facing the light\n"
" if (dot(_normal, ldir) >= 0.0) {{\n"
" // Compute the cosine of the angle between the object normal\n"
" // and the half-angle direction for the viewer\n"
" // (assuming the half-angle direction is a unit vector)\n"
" cosine = max(0.0, dot(_normal, " LIGHT_DIR ".xyz));\n",
LIGHT_DIR_PARAMS(index));
object.Write("cosAttn = " LIGHT_COSATT ".xyz;\n", LIGHT_COSATT_PARAMS(index));
object.Write("distAttn = {}(" LIGHT_DISTATT ".xyz);\n",
(diffusefunc == DiffuseFunc::None) ? "" : "normalize",
LIGHT_DISTATT_PARAMS(index));
object.Write("attn = max(0.0f, dot(cosAttn, float3(1.0, attn, attn*attn))) / dot(distAttn, "
"float3(1.0, attn, attn*attn));\n");
object.Write(" }}\n"
" // Specular lights use the angle for the denominator as well\n"
" dist = cosine;\n"
" dist2 = dist * dist;\n");
break;
case AttenuationFunc::Spot:
object.Write("ldir = " LIGHT_POS ".xyz - pos.xyz;\n", LIGHT_POS_PARAMS(index));
object.Write("dist2 = dot(ldir, ldir);\n"
"dist = sqrt(dist2);\n"
"ldir = ldir / dist;\n"
"attn = max(0.0, dot(ldir, " LIGHT_DIR ".xyz));\n",
object.Write(" // Compute the cosine of the angle between the vector to the object\n"
" // and the light's direction (assuming the direction is a unit vector)\n"
" float cosine = max(0.0, dot(ldir, " LIGHT_DIR ".xyz));\n",
LIGHT_DIR_PARAMS(index));
// attn*attn may overflow
object.Write("attn = max(0.0, " LIGHT_COSATT ".x + " LIGHT_COSATT ".y*attn + " LIGHT_COSATT
".z*attn*attn) / dot(" LIGHT_DISTATT ".xyz, float3(1.0,dist,dist2));\n",
LIGHT_COSATT_PARAMS(index), LIGHT_COSATT_PARAMS(index), LIGHT_COSATT_PARAMS(index),
LIGHT_DISTATT_PARAMS(index));
break;
}
switch (diffusefunc)
if (attn_func == AttenuationFunc::Spot || attn_func == AttenuationFunc::Spec)
{
object.Write(" float3 cosAttn = " LIGHT_COSATT ".xyz;\n", LIGHT_COSATT_PARAMS(index));
object.Write(" float3 distAttn = " LIGHT_DISTATT ".xyz;\n", LIGHT_DISTATT_PARAMS(index));
object.Write(" float cosine2 = cosine * cosine;\n"
""
" // This is equivalent to dot(cosAttn, float3(1.0, attn, attn*attn)),\n"
" // except with spot lights games often don't set the direction value,\n"
" // as they configure cosAttn to (1, 0, 0). GX light objects are often\n"
" // stack-allocated, so those values are uninitialized and may be\n"
" // arbitrary garbage, including NaN or Inf, or become Inf when squared.\n"
" float numerator = cosAttn.x; // constant term\n"
" if (cosAttn.y != 0.0) numerator += cosAttn.y * cosine; // linear term\n"
" if (cosAttn.z != 0.0) numerator += cosAttn.z * cosine2; // quadratic term\n"
" // Same with the denominator, though this generally is not garbage\n"
" // Note that VertexShaderManager ensures that distAttn is not zero, which\n"
" // should prevent division by zero (TODO: what does real hardware do?)\n"
" float denominator = distAttn.x; // constant term\n"
" if (distAttn.y != 0.0) denominator += distAttn.y * dist; // linear term\n"
" if (distAttn.z != 0.0) denominator += distAttn.z * dist2; // quadratic term\n"
""
" float attn = max(0.0f, numerator / denominator);\n");
}
switch (diffuse_func)
{
case DiffuseFunc::None:
object.Write("lacc.{} += int{}(round(attn * float{}(" LIGHT_COL ")));\n", swizzle,
swizzle_components, swizzle_components, LIGHT_COL_PARAMS(index, swizzle));
object.Write(" float diffuse = 1.0;\n");
break;
case DiffuseFunc::Sign:
case DiffuseFunc::Clamp:
object.Write("lacc.{} += int{}(round(attn * {}dot(ldir, _normal)) * float{}(" LIGHT_COL
")));\n",
swizzle, swizzle_components, diffusefunc != DiffuseFunc::Sign ? "max(0.0," : "(",
swizzle_components, LIGHT_COL_PARAMS(index, swizzle));
default: // Confirmed by hardware testing that invalid values use this
object.Write(" float diffuse = dot(ldir, _normal);\n");
break;
case DiffuseFunc::Clamp:
object.Write(" float diffuse = max(0.0, dot(ldir, _normal));\n");
break;
default:
ASSERT(false);
}
object.Write("\n");
object.Write(" lacc.{} += int{}(round(attn * diffuse * float{}(" LIGHT_COL ")));\n", swizzle,
swizzle_components, swizzle_components, LIGHT_COL_PARAMS(index, swizzle));
object.Write(" }}\n");
}
// vertex shader
@ -84,94 +112,133 @@ static void GenerateLightShader(ShaderCode& object, const LightingUidData& uid_d
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data,
std::string_view in_color_name, std::string_view dest)
{
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
for (u32 chan = 0; chan < NUM_XF_COLOR_CHANNELS; chan++)
{
object.Write("{{\n");
// Data for alpha is stored after all colors
const u32 chan_a = chan + NUM_XF_COLOR_CHANNELS;
const bool colormatsource = !!(uid_data.matsource & (1 << j));
if (colormatsource) // from vertex
object.Write("int4 mat = int4(round({}{} * 255.0));\n", in_color_name, j);
else // from color
object.Write("int4 mat = {}[{}];\n", I_MATERIALS, j + 2);
const auto color_matsource = static_cast<MatSource>((uid_data.matsource >> chan) & 1);
const auto color_ambsource = static_cast<AmbSource>((uid_data.ambsource >> chan) & 1);
const bool color_enable = ((uid_data.enablelighting >> chan) & 1) != 0;
const auto alpha_matsource = static_cast<MatSource>((uid_data.matsource >> chan_a) & 1);
const auto alpha_ambsource = static_cast<AmbSource>((uid_data.ambsource >> chan_a) & 1);
const bool alpha_enable = ((uid_data.enablelighting >> chan_a) & 1) != 0;
if ((uid_data.enablelighting & (1 << j)) != 0)
object.Write("{{\n"
" // Lighting for channel {}:\n"
" // Color material source: {}\n"
" // Color ambient source: {}\n"
" // Color lighting enabled: {}\n"
" // Alpha material source: {}\n"
" // Alpha ambient source: {}\n"
" // Alpha lighting enabled: {}\n",
chan, color_matsource, color_ambsource, color_enable, alpha_matsource,
alpha_ambsource, alpha_enable);
if (color_matsource == MatSource::Vertex)
object.Write(" int4 mat = int4(round({}{} * 255.0));\n", in_color_name, chan);
else // from material color register
object.Write(" int4 mat = {}[{}];\n", I_MATERIALS, chan + 2);
if (color_enable)
{
if ((uid_data.ambsource & (1 << j)) != 0) // from vertex
object.Write("lacc = int4(round({}{} * 255.0));\n", in_color_name, j);
else // from color
object.Write("lacc = {}[{}];\n", I_MATERIALS, j);
if (color_ambsource == AmbSource::Vertex)
object.Write(" int4 lacc = int4(round({}{} * 255.0));\n", in_color_name, chan);
else // from ambient color register
object.Write(" int4 lacc = {}[{}];\n", I_MATERIALS, chan);
}
else
{
object.Write("lacc = int4(255, 255, 255, 255);\n");
object.Write(" int4 lacc = int4(255, 255, 255, 255);\n");
}
// check if alpha is different
const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2)));
if (alphamatsource != colormatsource)
if (color_matsource != alpha_matsource)
{
if (alphamatsource) // from vertex
object.Write("mat.w = int(round({}{}.w * 255.0));\n", in_color_name, j);
else // from color
object.Write("mat.w = {}[{}].w;\n", I_MATERIALS, j + 2);
if (alpha_matsource == MatSource::Vertex)
object.Write(" mat.w = int(round({}{}.w * 255.0));\n", in_color_name, chan);
else // from material color register
object.Write(" mat.w = {}[{}].w;\n", I_MATERIALS, chan + 2);
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0)
if (alpha_enable)
{
if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex
object.Write("lacc.w = int(round({}{}.w * 255.0));\n", in_color_name, j);
else // from color
object.Write("lacc.w = {}[{}].w;\n", I_MATERIALS, j);
if (alpha_ambsource == AmbSource::Vertex) // from vertex
object.Write(" lacc.w = int(round({}{}.w * 255.0));\n", in_color_name, chan);
else // from ambient color register
object.Write(" lacc.w = {}[{}].w;\n", I_MATERIALS, chan);
}
else
{
object.Write("lacc.w = 255;\n");
object.Write(" lacc.w = 255;\n");
}
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
if (color_enable)
{
for (int i = 0; i < 8; ++i)
const auto attnfunc = static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * chan)) & 3);
const auto diffusefunc = static_cast<DiffuseFunc>((uid_data.diffusefunc >> (2 * chan)) & 3);
const u32 light_mask =
(uid_data.light_mask >> (NUM_XF_LIGHTS * chan)) & ((1 << NUM_XF_LIGHTS) - 1);
object.Write(" // Color attenuation function: {}\n", attnfunc);
object.Write(" // Color diffuse function: {}\n", diffusefunc);
object.Write(" // Color light mask: {:08b}\n", light_mask);
for (u32 light = 0; light < NUM_XF_LIGHTS; light++)
{
if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0)
GenerateLightShader(object, uid_data, i, j, false);
if ((light_mask & (1 << light)) != 0)
{
GenerateLightShader(object, uid_data, light, attnfunc, diffusefunc, false);
}
}
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
if (alpha_enable)
{
for (int i = 0; i < 8; ++i)
const auto attnfunc = static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * chan_a)) & 3);
const auto diffusefunc = static_cast<DiffuseFunc>((uid_data.diffusefunc >> (2 * chan_a)) & 3);
const u32 light_mask =
(uid_data.light_mask >> (NUM_XF_LIGHTS * chan_a)) & ((1 << NUM_XF_LIGHTS) - 1);
object.Write(" // Alpha attenuation function: {}\n", attnfunc);
object.Write(" // Alpha diffuse function: {}\n", diffusefunc);
object.Write(" // Alpha light mask function: {:08b}\n", light_mask);
for (u32 light = 0; light < NUM_XF_LIGHTS; light++)
{
if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0)
GenerateLightShader(object, uid_data, i, j + 2, true);
if ((light_mask & (1 << light)) != 0)
{
GenerateLightShader(object, uid_data, light, attnfunc, diffusefunc, true);
}
}
}
object.Write("lacc = clamp(lacc, 0, 255);\n");
object.Write("{}{} = float4((mat * (lacc + (lacc >> 7))) >> 8) / 255.0;\n", dest, j);
object.Write(" lacc = clamp(lacc, 0, 255);\n");
object.Write(" {}{} = float4((mat * (lacc + (lacc >> 7))) >> 8) / 255.0;\n", dest, chan);
object.Write("}}\n");
}
}
void GetLightingShaderUid(LightingUidData& uid_data)
{
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
for (u32 chan = 0; chan < NUM_XF_COLOR_CHANNELS; chan++)
{
uid_data.matsource |= static_cast<u32>(xfmem.color[j].matsource.Value()) << j;
uid_data.matsource |= static_cast<u32>(xfmem.alpha[j].matsource.Value()) << (j + 2);
uid_data.enablelighting |= xfmem.color[j].enablelighting << j;
uid_data.enablelighting |= xfmem.alpha[j].enablelighting << (j + 2);
// Data for alpha is stored after all colors
const u32 chan_a = chan + NUM_XF_COLOR_CHANNELS;
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
uid_data.matsource |= static_cast<u32>(xfmem.color[chan].matsource.Value()) << chan;
uid_data.matsource |= static_cast<u32>(xfmem.alpha[chan].matsource.Value()) << chan_a;
uid_data.enablelighting |= xfmem.color[chan].enablelighting << chan;
uid_data.enablelighting |= xfmem.alpha[chan].enablelighting << chan_a;
if ((uid_data.enablelighting & (1 << chan)) != 0) // Color lights
{
uid_data.ambsource |= static_cast<u32>(xfmem.color[j].ambsource.Value()) << j;
uid_data.attnfunc |= static_cast<u32>(xfmem.color[j].attnfunc.Value()) << (2 * j);
uid_data.diffusefunc |= static_cast<u32>(xfmem.color[j].diffusefunc.Value()) << (2 * j);
uid_data.light_mask |= xfmem.color[j].GetFullLightMask() << (8 * j);
uid_data.ambsource |= static_cast<u32>(xfmem.color[chan].ambsource.Value()) << chan;
uid_data.attnfunc |= static_cast<u32>(xfmem.color[chan].attnfunc.Value()) << (2 * chan);
uid_data.diffusefunc |= static_cast<u32>(xfmem.color[chan].diffusefunc.Value()) << (2 * chan);
uid_data.light_mask |= xfmem.color[chan].GetFullLightMask() << (NUM_XF_LIGHTS * chan);
}
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
if ((uid_data.enablelighting & (1 << chan_a)) != 0) // Alpha lights
{
uid_data.ambsource |= static_cast<u32>(xfmem.alpha[j].ambsource.Value()) << (j + 2);
uid_data.attnfunc |= static_cast<u32>(xfmem.alpha[j].attnfunc.Value()) << (2 * (j + 2));
uid_data.diffusefunc |= static_cast<u32>(xfmem.alpha[j].diffusefunc.Value()) << (2 * (j + 2));
uid_data.light_mask |= xfmem.alpha[j].GetFullLightMask() << (8 * (j + 2));
uid_data.ambsource |= static_cast<u32>(xfmem.alpha[chan].ambsource.Value()) << chan_a;
uid_data.attnfunc |= static_cast<u32>(xfmem.alpha[chan].attnfunc.Value()) << (2 * chan_a);
uid_data.diffusefunc |= static_cast<u32>(xfmem.alpha[chan].diffusefunc.Value())
<< (2 * chan_a);
uid_data.light_mask |= xfmem.alpha[chan].GetFullLightMask() << (NUM_XF_LIGHTS * chan_a);
}
}
}

View file

@ -1124,10 +1124,6 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
out.Write("\tfloat3 _normal = normalize(Normal.xyz);\n\n"
"\tfloat3 pos = WorldPos;\n");
out.Write("\tint4 lacc;\n"
"\tfloat3 ldir, h, cosAttn, distAttn;\n"
"\tfloat dist, dist2, attn;\n");
// TODO: Our current constant usage code isn't able to handle more than one buffer.
// So we can't mark the VS constant as used here. But keep them here as reference.
// out.SetConstantsUsed(C_PLIGHT_COLORS, C_PLIGHT_COLORS+7); // TODO: Can be optimized further

View file

@ -26,18 +26,13 @@ void WriteLightingFunction(ShaderCode& out)
out.Write(" case {:s}:\n", AttenuationFunc::Dir);
out.Write(" ldir = normalize(" I_LIGHTS "[index].pos.xyz - pos.xyz);\n"
" attn = 1.0;\n"
" if (length(ldir) == 0.0)\n"
" ldir = normal;\n"
" break;\n\n");
out.Write(" case {:s}:\n", AttenuationFunc::Spec);
out.Write(" ldir = normalize(" I_LIGHTS "[index].pos.xyz - pos.xyz);\n"
" attn = (dot(normal, ldir) >= 0.0) ? max(0.0, dot(normal, " I_LIGHTS
"[index].dir.xyz)) : 0.0;\n"
" cosAttn = " I_LIGHTS "[index].cosatt.xyz;\n");
out.Write(" if (diffusefunc == {:s})\n", DiffuseFunc::None);
out.Write(" distAttn = " I_LIGHTS "[index].distatt.xyz;\n"
" else\n"
" distAttn = normalize(" I_LIGHTS "[index].distatt.xyz);\n"
" cosAttn = " I_LIGHTS "[index].cosatt.xyz;\n"
" distAttn = " I_LIGHTS "[index].distatt.xyz;\n"
" attn = max(0.0, dot(cosAttn, float3(1.0, attn, attn*attn))) / dot(distAttn, "
"float3(1.0, attn, attn*attn));\n"
" break;\n\n");

View file

@ -430,10 +430,6 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
out.Write("o.pos = float4(dot(" I_PROJECTION "[0], pos), dot(" I_PROJECTION
"[1], pos), dot(" I_PROJECTION "[2], pos), dot(" I_PROJECTION "[3], pos));\n");
out.Write("int4 lacc;\n"
"float3 ldir, h, cosAttn, distAttn;\n"
"float dist, dist2, attn;\n");
GenerateLightingShaderCode(out, uid_data->lighting, "vertex_color_", "o.colors_");
// transform texcoords
@ -497,7 +493,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
case TexGenType::EmbossMap: // calculate tex coords into bump map
// transform the light dir into tangent space
out.Write("ldir = normalize(" LIGHT_POS ".xyz - pos.xyz);\n",
out.Write("float3 ldir = normalize(" LIGHT_POS ".xyz - pos.xyz);\n",
LIGHT_POS_PARAMS(texinfo.embosslightshift));
out.Write(
"o.tex{}.xyz = o.tex{}.xyz + float3(dot(ldir, _tangent), dot(ldir, _binormal), 0.0);\n",

View file

@ -256,22 +256,9 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures,
dstlight.pos[1] = light.dpos[1];
dstlight.pos[2] = light.dpos[2];
// TODO: Hardware testing is needed to confirm that this normalization is correct
auto sanitize = [](float f) {
if (std::isnan(f))
return 0.0f;
else if (std::isinf(f))
return f > 0.0f ? 1.0f : -1.0f;
else
return f;
};
double norm = double(light.ddir[0]) * double(light.ddir[0]) +
double(light.ddir[1]) * double(light.ddir[1]) +
double(light.ddir[2]) * double(light.ddir[2]);
norm = 1.0 / sqrt(norm);
dstlight.dir[0] = sanitize(static_cast<float>(light.ddir[0] * norm));
dstlight.dir[1] = sanitize(static_cast<float>(light.ddir[1] * norm));
dstlight.dir[2] = sanitize(static_cast<float>(light.ddir[2] * norm));
dstlight.dir[0] = light.ddir[0];
dstlight.dir[1] = light.ddir[1];
dstlight.dir[2] = light.ddir[2];
}
dirty = true;

View file

@ -14,6 +14,7 @@
#include "VideoCommon/CPMemory.h"
constexpr size_t NUM_XF_COLOR_CHANNELS = 2;
constexpr size_t NUM_XF_LIGHTS = 8;
// Lighting
@ -430,7 +431,7 @@ struct alignas(16) XFMemory
float normalMatrices[96]; // 0x0400 - 0x045f
u32 unk1[160]; // 0x0460 - 0x04ff
float postMatrices[256]; // 0x0500 - 0x05ff
Light lights[8]; // 0x0600 - 0x067f
Light lights[NUM_XF_LIGHTS]; // 0x0600 - 0x067f
u32 unk2[2432]; // 0x0680 - 0x0fff
u32 error; // 0x1000
u32 diag; // 0x1001