Skip to content

Commit

Permalink
point light culling; cut out lights over limit
Browse files Browse the repository at this point in the history
  • Loading branch information
malytomas committed Dec 3, 2023
1 parent c0346c9 commit 7d1e44b
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 129 deletions.
126 changes: 35 additions & 91 deletions data/cage/shader/engine/fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ layout(location = 0) out vec4 outColor;

vec3 normal;

const float confLightThreshold = 0.05;

struct Material
{
vec3 albedo; // linear, not-premultiplied
Expand All @@ -29,103 +27,49 @@ struct Material
float fade;
};

vec3 lightingBrdf(Material material, vec3 L, vec3 V)
vec3 lightBrdf(Material material, UniLight light)
{
return brdf(normal, L, V, material.albedo, material.roughness, material.metalness);
}

vec3 lightDirectional(Material material, UniLight light)
{
return lightingBrdf(
material,
-light.direction.xyz,
normalize(uniViewport.eyePos.xyz - varPosition)
) * light.color.rgb;
}

vec3 lightPoint(Material material, UniLight light)
{
return lightingBrdf(
material,
normalize(light.position.xyz - varPosition),
normalize(uniViewport.eyePos.xyz - varPosition)
) * light.color.rgb;
}

vec3 lightSpot(Material material, UniLight light)
{
vec3 lightSourceToFragmentDirection = normalize(light.position.xyz - varPosition);
float d = max(dot(-light.direction.xyz, lightSourceToFragmentDirection), 0);
float a = light.fparams[0]; // angle
float e = light.fparams[1]; // exponent
if (d < a)
return vec3(0);
d = pow(d, e);
return lightingBrdf(
material,
lightSourceToFragmentDirection,
normalize(uniViewport.eyePos.xyz - varPosition)
) * light.color.rgb * d;
}

vec3 lightSwitch(Material material, UniLight light)
{
switch (light.iparams[0])
vec3 L;
if (light.iparams[0] == CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONAL || light.iparams[0] == CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONALSHADOW)
L = -light.direction.xyz;
else
L = normalize(light.position.xyz - varPosition);
vec3 V = normalize(uniViewport.eyePos.xyz - varPosition);
vec3 res = brdf(normal, L, V, material.albedo, material.roughness, material.metalness);
if (light.iparams[0] == CAGE_SHADER_OPTIONVALUE_LIGHTSPOT || light.iparams[0] == CAGE_SHADER_OPTIONVALUE_LIGHTSPOTSHADOW)
{
case CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONAL:
case CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONALSHADOW:
return lightDirectional(material, light);
case CAGE_SHADER_OPTIONVALUE_LIGHTPOINT:
case CAGE_SHADER_OPTIONVALUE_LIGHTPOINTSHADOW:
return lightPoint(material, light);
case CAGE_SHADER_OPTIONVALUE_LIGHTSPOT:
case CAGE_SHADER_OPTIONVALUE_LIGHTSPOTSHADOW:
return lightSpot(material, light);
default: return vec3(191, 85, 236) / 255;
float d = max(dot(-light.direction.xyz, L), 0);
if (d < light.fparams[0])
d = 0;
else
d = pow(d, light.fparams[1]);
res *= d;
}
return res * light.color.rgb;
}

vec4 shadowSamplingPosition4(UniShadowedLight uni)
float shadowedIntensity(UniShadowedLight uni)
{
float normalOffsetScale = uni.light.fparams[2];
vec3 p3 = varPosition + normal * normalOffsetScale;
return uni.shadowMat * vec4(p3, 1);
}

float shadowDirectional(UniShadowedLight uni)
{
vec3 shadowPos = vec3(shadowSamplingPosition4(uni));
return sampleShadowMap2d(texShadows2d[uni.light.iparams[1]], shadowPos);
}

float shadowPoint(UniShadowedLight uni)
{
vec3 shadowPos = vec3(shadowSamplingPosition4(uni));
return sampleShadowMapCube(texShadowsCube[uni.light.iparams[1]], shadowPos);
}

float shadowSpot(UniShadowedLight uni)
{
vec4 shadowPos4 = shadowSamplingPosition4(uni);
vec3 shadowPos = shadowPos4.xyz / shadowPos4.w;
return sampleShadowMap2d(texShadows2d[uni.light.iparams[1]], shadowPos);
}

float shadowSwitch(UniShadowedLight uni)
{
vec4 shadowPos4 = uni.shadowMat * vec4(p3, 1);
switch (uni.light.iparams[0])
{
case CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONALSHADOW: return shadowDirectional(uni);
case CAGE_SHADER_OPTIONVALUE_LIGHTPOINTSHADOW: return shadowPoint(uni);
case CAGE_SHADER_OPTIONVALUE_LIGHTSPOTSHADOW: return shadowSpot(uni);
default: return 1;
case CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONALSHADOW:
return sampleShadowMap2d(texShadows2d[uni.light.iparams[1]], vec3(shadowPos4));
case CAGE_SHADER_OPTIONVALUE_LIGHTPOINTSHADOW:
return sampleShadowMapCube(texShadowsCube[uni.light.iparams[1]], vec3(shadowPos4));
case CAGE_SHADER_OPTIONVALUE_LIGHTSPOTSHADOW:
return sampleShadowMap2d(texShadows2d[uni.light.iparams[1]], shadowPos4.xyz / shadowPos4.w);
default:
return 1;
}
}

float lightInitialIntensity(UniLight light, float ssao)
{
float intensity = light.color[3];
intensity *= attenuation(light.attenuation.xyz, length(light.position.xyz - varPosition));
intensity *= attenuation(light.attenuation, length(light.position.xyz - varPosition));
intensity *= mix(1.0, ssao, light.fparams[3]);
return intensity;
}
Expand Down Expand Up @@ -154,28 +98,28 @@ vec4 lighting(Material material)
// ambient
res.rgb += material.albedo * uniViewport.ambientLight.rgb * ssao;

// direct
{ // unshadowed
{ // direct unshadowed
int lightsCount = getOption(CAGE_SHADER_OPTIONINDEX_LIGHTSCOUNT);
for (int i = 0; i < lightsCount; i++)
{
UniLight light = uniLights[i];
float intensity = lightInitialIntensity(light, ssao);
if (intensity < confLightThreshold)
if (intensity < CAGE_SHADER_MAX_LIGHTINTENSITYTHRESHOLD)
continue;
res.rgb += lightSwitch(material, light) * intensity;
res.rgb += lightBrdf(material, light) * intensity;
}
}
{ // shadowed

{ // direct shadowed
int lightsCount = getOption(CAGE_SHADER_OPTIONINDEX_SHADOWEDLIGHTSCOUNT);
for (int i = 0; i < lightsCount; i++)
{
UniShadowedLight uni = uniShadowedLights[i];
float intensity = lightInitialIntensity(uni.light, ssao);
if (intensity < confLightThreshold)
if (intensity < CAGE_SHADER_MAX_LIGHTINTENSITYTHRESHOLD)
continue;
intensity *= shadowSwitch(uni);
res.rgb += lightSwitch(material, uni.light) * intensity;
intensity *= shadowedIntensity(uni);
res.rgb += lightBrdf(material, uni.light) * intensity;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion data/cage/shader/functions/attenuation.glsl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

float attenuation(vec3 att, float dist)
float attenuation(vec4 att, float dist)
{
//return 1 / (att.x + dist * (att.y + dist * (att.z + dist * att.w)));
return 1 / (att.x + dist * (att.y + dist * att.z));
}
10 changes: 5 additions & 5 deletions sources/include/cage-engine/scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace cage

struct CAGE_ENGINE_API RenderComponent
{
Vec3 color = Vec3::Nan();
Vec3 color = Vec3::Nan(); // sRGB
Real intensity = Real::Nan();
Real opacity = Real::Nan();
uint32 object = 0;
Expand Down Expand Up @@ -46,8 +46,8 @@ namespace cage

struct CAGE_ENGINE_API LightComponent
{
Vec3 attenuation = Vec3(0, 0, 1); // constant, linear, quadratic
Vec3 color = Vec3(1);
Vec4 attenuation = Vec4(1, 0, 1, 0); // constant, linear, quadratic, ~cubic~
Vec3 color = Vec3(1); // sRGB
Real intensity = 1;
Rads spotAngle = Degs(40);
Real spotExponent = 80;
Expand All @@ -68,7 +68,7 @@ namespace cage
struct CAGE_ENGINE_API TextComponent
{
String value; // list of parameters separated by '|' when formatted, otherwise the string as is
Vec3 color = Vec3(1);
Vec3 color = Vec3(1); // sRGB
Real intensity = 1;
// real opacity; // todo
uint32 assetName = 0;
Expand All @@ -87,7 +87,7 @@ namespace cage

struct CameraCommonProperties
{
Vec3 ambientColor = Vec3();
Vec3 ambientColor = Vec3(); // sRGB
Real ambientIntensity = 1;
uint32 sceneMask = 1;
};
Expand Down
1 change: 1 addition & 0 deletions sources/include/cage-engine/shaderConventions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#define CAGE_SHADER_MAX_OPTIONS 16
#define CAGE_SHADER_MAX_SHADOWMAPS2D 8
#define CAGE_SHADER_MAX_SHADOWMAPSCUBE 8
#define CAGE_SHADER_MAX_LIGHTINTENSITYTHRESHOLD 0.05

// attribute in locations

Expand Down
4 changes: 4 additions & 0 deletions sources/libcore/math/vectors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace cage
{
static_assert(sizeof(Vec2) == sizeof(Vec2i));
static_assert(sizeof(Vec3) == sizeof(Vec3i));
static_assert(sizeof(Vec4) == sizeof(Vec4i));

Vec2 Vec2::parse(const String &str)
{
Vec2 data;
Expand Down
97 changes: 65 additions & 32 deletions sources/libengine/graphics/renderPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ namespace cage
struct UniLight
{
Vec4 color; // linear RGB, intensity
Vec4 position;
Vec4 position; // xyz, light radius
Vec4 direction;
Vec4 attenuation;
Vec4 fparams; // spotAngle, spotExponent, normalOffsetScale, ssaoFactor
Expand Down Expand Up @@ -188,6 +188,20 @@ namespace cage
return uni;
}

Real lightRadius(Real intensity, const Vec3 &attenuation)
{
if (intensity <= 1e-5)
return 0;
const Real e = intensity / CAGE_SHADER_MAX_LIGHTINTENSITYTHRESHOLD;
const Real x = attenuation[0], y = attenuation[1], z = attenuation[2];
if (z < 1e-5)
{
CAGE_ASSERT(y > 1e-5);
return (e - x) / y;
}
return (sqrt(y * y - 4 * z * (x - e)) - y) / (2 * z);
}

UniLight initializeLightUni(const Mat4 &model, const LightComponent &lc)
{
UniLight uni;
Expand All @@ -201,13 +215,57 @@ namespace cage
}
uni.position = model * Vec4(0, 0, 0, 1);
uni.direction = model * Vec4(0, 0, -1, 0);
uni.attenuation = lc.lightType == LightTypeEnum::Directional ? Vec4(1, 0, 0, 0) : Vec4(lc.attenuation, 0);
if (lc.lightType == LightTypeEnum::Directional)
{
uni.attenuation = Vec4(1, 0, 0, 0);
uni.position[3] = Real::Infinity();
}
else
{
uni.attenuation = lc.attenuation;
uni.position[3] = lightRadius(uni.color[3], Vec3(uni.attenuation));
}
uni.fparams[0] = cos(lc.spotAngle * 0.5);
uni.fparams[1] = lc.spotExponent;
uni.fparams[3] = lc.ssaoFactor;
uni.iparams[0] = [&]()
{
switch (lc.lightType)
{
case LightTypeEnum::Directional:
return CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONAL;
case LightTypeEnum::Spot:
return CAGE_SHADER_OPTIONVALUE_LIGHTSPOT;
case LightTypeEnum::Point:
return CAGE_SHADER_OPTIONVALUE_LIGHTPOINT;
default:
CAGE_THROW_CRITICAL(Exception, "invalid light type");
}
}();
return uni;
}

void filterLightsOverLimit(std::vector<UniLight> &lights, Vec3 cameraCenter)
{
std::sort(lights.begin(), lights.end(),
[&](const UniLight &a, const UniLight &b)
{
const Real aa = a.position[3] / (1 + distance(Vec3(a.position), cameraCenter));
const Real bb = b.position[3] / (1 + distance(Vec3(b.position), cameraCenter));
return aa > bb;
});
if (lights.size() > CAGE_SHADER_MAX_LIGHTS)
lights.resize(CAGE_SHADER_MAX_LIGHTS);

// fade-out lights close to limit
Real intensity = 1;
for (uint32 i = CAGE_SHADER_MAX_LIGHTS * 85 / 100; i < lights.size(); i++)
{
intensity *= 0.9;
lights[i].color[3] *= intensity;
}
}

void updateShaderRoutinesForTextures(const std::array<Holder<Texture>, MaxTexturesCountPerMaterial> &textures, UniOptions &options)
{
if (textures[CAGE_SHADER_TEXTURE_ALBEDO])
Expand Down Expand Up @@ -857,33 +915,21 @@ namespace cage
{ // add unshadowed lights
std::vector<UniLight> lights;
lights.reserve(CAGE_SHADER_MAX_LIGHTS);
const Frustum frustum(data.viewProj);
entitiesVisitor(
[&](Entity *e, const LightComponent &lc)
{
if ((lc.sceneMask & data.camera.sceneMask) == 0)
return;
if (e->has<ShadowmapComponent>())
return;
if (lights.size() == CAGE_SHADER_MAX_LIGHTS)
CAGE_THROW_ERROR(Exception, "too many lights");
UniLight uni = initializeLightUni(modelTransform(e), lc);
uni.iparams[0] = [&]()
{
switch (lc.lightType)
{
case LightTypeEnum::Directional:
return CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONAL;
case LightTypeEnum::Spot:
return CAGE_SHADER_OPTIONVALUE_LIGHTSPOT;
case LightTypeEnum::Point:
return CAGE_SHADER_OPTIONVALUE_LIGHTPOINT;
default:
CAGE_THROW_CRITICAL(Exception, "invalid light type");
}
}();
if (lc.lightType == LightTypeEnum::Point && !intersects(frustum, Sphere(Vec3(uni.position), uni.position[3])))
return;
lights.push_back(uni);
},
+scene, false);
filterLightsOverLimit(lights, data.transform.position);
data.lightsCount = numeric_cast<uint32>(lights.size());
if (!lights.empty())
renderQueue->universalUniformArray<UniLight>(lights, CAGE_SHADER_UNIBLOCK_LIGHTS);
Expand Down Expand Up @@ -1239,20 +1285,7 @@ namespace cage
UniLight &uni = data.shadowUni;
uni = initializeLightUni(data.model, data.lightComponent);
uni.fparams[2] = e->value<ShadowmapComponent>().normalOffsetScale;
uni.iparams[0] = [&]()
{
switch (data.lightComponent.lightType)
{
case LightTypeEnum::Directional:
return CAGE_SHADER_OPTIONVALUE_LIGHTDIRECTIONALSHADOW;
case LightTypeEnum::Spot:
return CAGE_SHADER_OPTIONVALUE_LIGHTSPOTSHADOW;
case LightTypeEnum::Point:
return CAGE_SHADER_OPTIONVALUE_LIGHTPOINTSHADOW;
default:
CAGE_THROW_CRITICAL(Exception, "invalid light type");
}
}();
uni.iparams[0]++; // shadowed light type
}

return tasksRunAsync<ShadowmapData>("render shadowmap task", Delegate<void(ShadowmapData &, uint32)>().bind<RenderPipelineImpl, &RenderPipelineImpl::taskShadowmap>(this), Holder<ShadowmapData>(&data, nullptr), 1, tasksCurrentPriority() + 9);
Expand Down

0 comments on commit 7d1e44b

Please sign in to comment.