mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Lighting: Use the GE's approximate pow for specular, diffuse and spot
The GE computes these powers as exp2(e * log2(x)), with log2 and exp2 each a straight line between powers of two (Mitchell's approximation), and only uses the top 4 bits of the specular coefficient's mantissa. Through a highlight's falloff a true pow is 10-30 steps of 255 brighter at the exponents games use. Measured in gpu/lighting/specular. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
a7ec706a4c
commit
d7a96875fb
6 files changed
+53
-14
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Common/FramebufferManagerCommon.h"
|
||||
#include "GPU/Common/GPUStateUtils.h"
|
||||
#include "GPU/Common/TransformCommon.h"
|
||||
#include "GPU/Math3D.h"
|
||||
|
||||
using namespace Lin;
|
||||
@@ -222,7 +223,7 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) {
|
||||
Uint8x3ToFloat4(ub->materialDiffuse, gstate.materialdiffuse);
|
||||
}
|
||||
if (dirtyUniforms & DIRTY_MATSPECULAR) {
|
||||
Uint8x3ToFloat4_Alpha(ub->materialSpecular, gstate.materialspecular, std::max(0.0f, getFloat24(gstate.materialspecularcoef)));
|
||||
Uint8x3ToFloat4_Alpha(ub->materialSpecular, gstate.materialspecular, std::max(0.0f, PSPSpecularCoef(getFloat24(gstate.materialspecularcoef))));
|
||||
}
|
||||
if (dirtyUniforms & DIRTY_MATEMISSIVE) {
|
||||
// We're not touching the fourth f32 here, because we store an u32 of control bits in it.
|
||||
|
||||
@@ -40,7 +40,7 @@ Lighter::Lighter(int vertType) {
|
||||
materialDiffuse.a = 1.0f;
|
||||
materialSpecular.GetFromRGB(gstate.materialspecular);
|
||||
materialSpecular.a = 1.0f;
|
||||
specCoef_ = getFloat24(gstate.materialspecularcoef);
|
||||
specCoef_ = PSPSpecularCoef(getFloat24(gstate.materialspecularcoef));
|
||||
// viewer_ = Vec3f(-gstate.viewMatrix[9], -gstate.viewMatrix[10], -gstate.viewMatrix[11]);
|
||||
bool hasColor = (vertType & GE_VTYPE_COL_MASK) != 0;
|
||||
materialUpdate_ = hasColor ? (gstate.materialupdate & 7) : 0;
|
||||
@@ -121,7 +121,7 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[
|
||||
if (dot < 0.0f) dot = 0.0f;
|
||||
|
||||
if (poweredDiffuse)
|
||||
dot = powf(dot, specCoef_);
|
||||
dot = specCoef_ <= 0.0f ? 1.0f : PSPLightPow(dot, specCoef_);
|
||||
|
||||
// Attenuation
|
||||
switch (type) {
|
||||
@@ -136,7 +136,7 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[
|
||||
lightDir = ldir[l];
|
||||
angle = Dot(toLight.NormalizedOr001(cpu_info.bSSE4_1), lightDir.NormalizedOr001(cpu_info.bSSE4_1));
|
||||
if (angle >= lcutoff[l])
|
||||
lightScale = clamp(1.0f / (latt[l].x + latt[l].y * distanceToLight + latt[l].z * distanceToLight*distanceToLight), 0.0f, 1.0f) * powf(angle, lconv[l]);
|
||||
lightScale = clamp(1.0f / (latt[l].x + latt[l].y * distanceToLight + latt[l].z * distanceToLight*distanceToLight), 0.0f, 1.0f) * (lconv[l] <= 0.0f ? 1.0f : PSPLightPow(angle, lconv[l]));
|
||||
break;
|
||||
default:
|
||||
// ILLEGAL
|
||||
@@ -157,7 +157,8 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[
|
||||
dot = Dot(halfVec, norm);
|
||||
if (dot > 0.0f) {
|
||||
Color4 lightSpec(lcolor[2][l], 0.0f);
|
||||
lightSum1 += (lightSpec * *specular * (powf(dot, specCoef_) * lightScale));
|
||||
float specFactor = specCoef_ <= 0.0f ? 1.0f : PSPLightPow(dot, specCoef_);
|
||||
lightSum1 += (lightSpec * *specular * (specFactor * lightScale));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
@@ -60,6 +61,30 @@ struct Color4 {
|
||||
}
|
||||
};
|
||||
|
||||
// The GE's pow() for specular, powered diffuse and the spot exponent: exp2(e * log2(x)), with log2
|
||||
// and exp2 each a straight line between powers of two (Mitchell's approximation). Matches hardware
|
||||
// within one step of 255 (gpu/lighting/specular).
|
||||
inline float PSPLightPow(float x, float e) {
|
||||
if (!(x > 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
int ex;
|
||||
float m = frexpf(x, &ex); // x = m * 2^ex, m in [0.5, 1)
|
||||
float y = e * ((float)(ex - 1) + (2.0f * m - 1.0f));
|
||||
y = y < -64.0f ? -64.0f : (y > 64.0f ? 64.0f : y);
|
||||
float fl = floorf(y);
|
||||
return ldexpf(1.0f + (y - fl), (int)fl);
|
||||
}
|
||||
|
||||
// The GE only uses the top 4 bits of the specular coefficient's mantissa.
|
||||
inline float PSPSpecularCoef(float e) {
|
||||
u32 bits;
|
||||
memcpy(&bits, &e, sizeof(bits));
|
||||
bits &= 0xFFF80000;
|
||||
memcpy(&e, &bits, sizeof(bits));
|
||||
return e;
|
||||
}
|
||||
|
||||
// Convenient way to do precomputation to save the parts of the lighting calculation
|
||||
// that's common between the many vertices of a draw call.
|
||||
class Lighter {
|
||||
|
||||
@@ -428,6 +428,16 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
WRITE(p, " float len2 = dot(v, v);\n");
|
||||
WRITE(p, " return len2 == 0.0 ? vec3(0.0, 0.0, 1.0) : (v * inversesqrt(len2));\n");
|
||||
WRITE(p, "}\n");
|
||||
// The GE's pow for lighting: exp2(e * log2(x)), with log2 and exp2 each a straight line
|
||||
// between powers of two. Continuous, so floor() landing on the wrong side of a power of
|
||||
// two is harmless.
|
||||
WRITE(p, "float pspPow(float x, float e) {\n");
|
||||
WRITE(p, " if (x <= 0.0) return 0.0;\n");
|
||||
WRITE(p, " float ex = floor(log2(x));\n");
|
||||
WRITE(p, " float y = e * (ex + x * exp2(-ex) - 1.0);\n");
|
||||
WRITE(p, " float fl = floor(y);\n");
|
||||
WRITE(p, " return exp2(fl) * (1.0 + y - fl);\n");
|
||||
WRITE(p, "}\n");
|
||||
}
|
||||
|
||||
if (ShaderLanguageIsOpenGL(compat.shaderLanguage) || compat.shaderLanguage == GLSL_VULKAN) {
|
||||
@@ -643,7 +653,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
p.C(" } else {\n"); // type must be 0x02 - GE_LIGHTTYPE_SPOT
|
||||
p.F(" angle = dot(u_lightdir%s, toLight);\n", iStr);
|
||||
p.F(" if (angle >= u_lightangle_spotCoef%s.x) {\n", iStr);
|
||||
p.F(" lightScale = attenuation * (u_lightangle_spotCoef%s.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%s.y));\n", iStr, iStr, iStr);
|
||||
p.F(" lightScale = attenuation * (u_lightangle_spotCoef%s.y <= 0.0 ? 1.0 : pspPow(angle, u_lightangle_spotCoef%s.y));\n", iStr, iStr, iStr);
|
||||
p.C(" } else {\n");
|
||||
p.C(" lightScale = 0.0;\n");
|
||||
p.C(" }\n");
|
||||
@@ -653,14 +663,14 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
p.C(" }\n");
|
||||
p.C(" ldot = dot(toLight, worldnormal);\n");
|
||||
p.C(" if (comp == 0x2u) {\n"); // GE_LIGHTCOMP_ONLYPOWDIFFUSE
|
||||
p.C(" ldot = u_matspecular.a > 0.0 ? pow(max(ldot, 0.0), u_matspecular.a) : 1.0;\n");
|
||||
p.C(" ldot = u_matspecular.a > 0.0 ? pspPow(ldot, u_matspecular.a) : 1.0;\n");
|
||||
p.C(" }\n");
|
||||
p.F(" diffuse = (u_lightdiffuse%s * diffuseColor) * max(ldot, 0.0);\n", iStr);
|
||||
p.C(" if (comp == 0x1u && ldot >= 0.0) {\n"); // do specular. note - must allow for the >= case, since the u_matspecular.a <= 0.0 case relies on it.
|
||||
p.C(" if (u_matspecular.a > 0.0) {\n");
|
||||
p.C(" vec3 halfVec = toLight + vec3(0.0, 0.0, 1.0);\n");
|
||||
p.C(" float halfInvLen = inversesqrt(dot(halfVec, halfVec));\n");
|
||||
p.C(" ldot = pow(max(dot(halfVec, worldnormal) * halfInvLen, 0.0), u_matspecular.a);\n");
|
||||
p.C(" ldot = pspPow(dot(halfVec, worldnormal) * halfInvLen, u_matspecular.a);\n");
|
||||
p.C(" } else {\n");
|
||||
p.C(" ldot = 1.0;\n");
|
||||
p.C(" }\n");
|
||||
@@ -704,7 +714,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
// pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0.
|
||||
// Seen in Tales of the World: Radiant Mythology (#2424.)
|
||||
p.C(" if (u_matspecular.a > 0.0) {\n");
|
||||
p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
|
||||
p.C(" ldot = pspPow(ldot, u_matspecular.a);\n");
|
||||
p.C(" } else {\n");
|
||||
p.C(" ldot = 1.0;\n");
|
||||
p.C(" }\n");
|
||||
@@ -724,7 +734,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
case GE_LIGHTTYPE_UNKNOWN:
|
||||
p.F(" angle = dot(u_lightdir%s, toLight);\n", iStr, iStr);
|
||||
p.F(" if (angle >= u_lightangle_spotCoef%s.x) {\n", iStr);
|
||||
p.F(" lightScale = clamp(1.0 / dot(u_lightatt%s, vec3(1.0, distance, distSq)), 0.0, 1.0) * (u_lightangle_spotCoef%s.y <= 0.0 ? 1.0 : pow(max(angle, 0.0), u_lightangle_spotCoef%s.y));\n", iStr, iStr, iStr);
|
||||
p.F(" lightScale = clamp(1.0 / dot(u_lightatt%s, vec3(1.0, distance, distSq)), 0.0, 1.0) * (u_lightangle_spotCoef%s.y <= 0.0 ? 1.0 : pspPow(angle, u_lightangle_spotCoef%s.y));\n", iStr, iStr, iStr);
|
||||
p.C(" } else {\n");
|
||||
p.C(" lightScale = 0.0;\n");
|
||||
p.C(" }\n");
|
||||
@@ -740,7 +750,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
p.C(" if (u_matspecular.a > 0.0) {\n");
|
||||
p.C(" vec3 halfVec = toLight + vec3(0.0, 0.0, 1.0);\n");
|
||||
p.C(" float halfInvLen = inversesqrt(dot(halfVec, halfVec));\n");
|
||||
p.C(" ldot = pow(max(dot(halfVec, worldnormal) * halfInvLen, 0.0), u_matspecular.a);\n");
|
||||
p.C(" ldot = pspPow(dot(halfVec, worldnormal) * halfInvLen, u_matspecular.a);\n");
|
||||
p.C(" } else {\n");
|
||||
p.C(" ldot = 1.0;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
#include "GPU/Common/ShaderUniforms.h"
|
||||
#include "GPU/Common/TransformCommon.h"
|
||||
#include "GPU/GLES/ShaderManagerGLES.h"
|
||||
#include "GPU/GLES/DrawEngineGLES.h"
|
||||
|
||||
@@ -549,7 +550,7 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, const ShaderLanguageDesc
|
||||
SetColorUniform3(render_, &u_matemissive, gstate.materialemissive);
|
||||
}
|
||||
if (dirty & DIRTY_MATSPECULAR) {
|
||||
SetColorUniform3ExtraFloat(render_, &u_matspecular, gstate.materialspecular, getFloat24(gstate.materialspecularcoef));
|
||||
SetColorUniform3ExtraFloat(render_, &u_matspecular, gstate.materialspecular, PSPSpecularCoef(getFloat24(gstate.materialspecularcoef)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/Math/SIMDHeaders.h"
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Common/TransformCommon.h"
|
||||
#include "GPU/Software/Lighting.h"
|
||||
|
||||
#if PPSSPP_ARCH(SSE2)
|
||||
@@ -49,7 +50,7 @@ static inline float pspLightPow(float v, float e) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (v > 0.0f) {
|
||||
return pow(v, e);
|
||||
return PSPLightPow(v, e);
|
||||
}
|
||||
// Negative stays negative, so let's just return the original.
|
||||
return v;
|
||||
@@ -179,7 +180,7 @@ void ComputeState(State *state, bool hasColor0) {
|
||||
}
|
||||
|
||||
if (anyDiffuse || anySpecular) {
|
||||
state->specularExp = gstate.getMaterialSpecularCoef();
|
||||
state->specularExp = PSPSpecularCoef(gstate.getMaterialSpecularCoef());
|
||||
if (state->specularExp <= 0.0f)
|
||||
state->specularExp = 0.0f;
|
||||
else if (std::isnan(state->specularExp))
|
||||
|
||||
Reference in new issue
Block a user