17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-09 00:17:39 +01:00

Add the optional Radius, ShadeSelf, CastShadows and SoftFalloff of FreeSpotLight and make FalloffAngle 360 a point light

This commit is contained in:
2026-10-28 16:18:03 +01:00
parent 413cebed9c
commit c4e23932bf
25 changed files with 327 additions and 72 deletions

View File

@@ -402,6 +402,10 @@ void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 con
lightpoint.cos_outer = std::clamp(Submodel.fCosFalloffAngle, -1.f, 0.999f);
lightpoint.cos_inner = std::clamp(std::max(Submodel.fCosHotspotAngle, lightpoint.cos_outer + 0.05f), lightpoint.cos_outer + 0.001f, 1.f);
lightpoint.range = light_range(color);
lightpoint.radius = Submodel.fLightRadius;
lightpoint.soft_falloff = Submodel.bSoftFalloff;
lightpoint.cast_shadows = Submodel.bCastShadows;
lightpoint.shade_self = Submodel.bShadeSelf;
if (m_walkowner != nullptr)
{
lightpoint.owner = m_walkowner;

View File

@@ -50,6 +50,14 @@ class local_lights
float cos_outer{-1.f}; // cosine of the half angle of the cone; not above 0: the light is not limited to a cone
float cos_inner{-1.f}; // cosine of the half angle within which the light is at its full brightness; above cos_outer
float range{0.f}; // distance at which the light ends, metres
// light points: the radius of the sphere the light shines from (metres, 0: a point), and its light falling off as
// 1 / (d^2 + r^2) (the soft falloff of blender) instead of the way the surface of the sphere gives it
float radius{0.f};
bool soft_falloff{false};
// the light casts shadows (the shadow maps of the lamps, the contact shadows), and its shadows are cast also by the model
// it comes from (the instance, owner)
bool cast_shadows{true};
bool shade_self{false};
// light point of a scenery model: the instance and the submodel it comes from, which name the lamp from frame to frame
// (opengl46_local_shadows keeps the shadow maps of scenery lamps by them). nullptr for the other lights
void const *owner{nullptr};

View File

@@ -30,6 +30,12 @@ double const LAMP_MOVED{0.01};
// lamps with a shorter reach aren't worth a map, metres
float const LAMP_RANGE_MIN{1.f};
// near plane of the map of a lamp: out of its sphere, and well within its reach
float lamp_near(gfx::local_lights::light const &Light)
{
return std::clamp(Light.radius, local_shadows::shadow_near, std::max(local_shadows::shadow_near, Light.range * 0.5f));
}
} // namespace
local_shadows::level_setup local_shadows::setup(int const Level)
@@ -203,7 +209,7 @@ void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Li
{
break;
}
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
if ((light.owner == nullptr) || (false == light.cast_shadows) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
{
continue;
}
@@ -226,6 +232,8 @@ void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Li
}
slot.position = light->position;
slot.range = light->range;
slot.nearplane = lamp_near(*light);
slot.shade_self = light->shade_self;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
placed = true;
@@ -252,6 +260,8 @@ void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Li
slot.part = light->part;
slot.position = light->position;
slot.range = light->range;
slot.nearplane = lamp_near(*light);
slot.shade_self = light->shade_self;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
break;
@@ -312,6 +322,7 @@ void local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std:
slot.vehicles = false;
++m_lampsscenery;
}
m_lampnear = slot.nearplane;
gpu_begin_lamp(scenerycube, workingcube, Vehicles);
}
@@ -367,7 +378,7 @@ local_shadows::lamp_slot const *local_shadows::lamp(gfx::local_lights::light con
glm::vec4 local_shadows::lamp_weights(std::size_t const Slot) const
{
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
return glm::vec4{static_cast<float>(cube), shadow_near, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
return glm::vec4{static_cast<float>(cube), m_slots[Slot].nearplane, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
}
std::vector<TDynamicObject *> const &local_shadows::lamp_vehicles(glm::dvec3 const &Camera)

View File

@@ -93,6 +93,10 @@ class local_shadows
void const *part{nullptr};
glm::dvec3 position{0.0}; // world space
float range{0.f}; // of the light, metres
// near plane of the map, metres: what is closer to the lamp casts no shadow. the light of a lamp with a radius comes from
// the surface of its sphere, what is within it (the glass of the lamp the light point is in) doesn't hold it back
float nearplane{shadow_near};
bool shade_self{false}; // the instance of the lamp casts its shadows (local_lights::light::shade_self)
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
bool scenery{false}; // the scenery part of the map is drawn
std::uint64_t version{0}; // of the scene the scenery part was drawn for
@@ -124,7 +128,8 @@ class local_shadows
m_slots[Slot].vehicles = false;
}
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
// the geometry is to be drawn with the programs of the cube maps and a modelview to world axes centred on the lamp
// the geometry is to be drawn with the programs of the cube maps and a modelview to world axes centred on the lamp, with the
// near plane of the slot
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
// ends the drawing to the map of a lamp
void end_lamp(std::uint32_t const Drawcalls);
@@ -261,6 +266,7 @@ class local_shadows
// scenery lamps: the scenery parts of the slots first, then their working copies
int m_lampssize{0};
std::vector<lamp_slot> m_slots;
float m_lampnear{shadow_near}; // near plane of the map of a lamp begin_lamp() set up the drawing to
std::vector<std::size_t> m_wanted;
unsigned int m_frame{0}; // framestamp of the last update_lamps()
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame

View File

@@ -3078,13 +3078,23 @@ void scene_walk::Render_Alpha(TSubModel *Submodel)
// NOTE: we're forced here to redo view angle calculations etc, because this data isn't instanced but stored along with the single mesh
// TODO: separate instance data from reusable geometry
auto const &modelview = OpenGLMatrices.data(GL_MODELVIEW);
auto const lightcenter = modelview * glm::mix(glm::vec4(0.f, 0.f, -0.05f, 1.f), glm::vec4(0.f, 0.f, -0.25f, 1.f),
static_cast<float>(TSubModel::fSquareDist / Submodel->fSquareMaxDist)); // pozycja punktu świecącego względem kamery
auto lightcenter = modelview * glm::mix(glm::vec4(0.f, 0.f, -0.05f, 1.f), glm::vec4(0.f, 0.f, -0.25f, 1.f),
static_cast<float>(TSubModel::fSquareDist / Submodel->fSquareMaxDist)); // pozycja punktu świecącego względem kamery
if (Submodel->fLightRadius > 0.f)
{
// the light shines from the surface of its sphere: the point is seen on the side of it towards the camera, out of
// the glass of the lamp its middle may be in
auto const center{modelview * glm::vec4(0.f, 0.f, 0.f, 1.f)};
auto const centerdistance{glm::length(glm::vec3{center})};
lightcenter = (centerdistance > Submodel->fLightRadius ? center - glm::vec4(glm::vec3{center} * (Submodel->fLightRadius / centerdistance), 0.f) : center);
}
Submodel->fCosViewAngle = glm::dot(glm::normalize(modelview * glm::vec4(0.f, 0.f, -1.f, 1.f) - lightcenter), glm::normalize(-lightcenter));
// a point light (FalloffAngle 360) is seen all around
auto const pointlight{Submodel->fCosFalloffAngle <= -1.f};
if (Global.fLuminance < Submodel->fLight || Global.Overcast > 1.0f)
{
if (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle)
if (pointlight || (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle))
{
// only bother if the viewer is inside the visibility cone
// luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness'
@@ -3092,7 +3102,7 @@ void scene_walk::Render_Alpha(TSubModel *Submodel)
Global.Overcast - 1.f), // ensure some glare in rainy/foggy conditions
0.f, 1.f)};
// view angle attenuation
float const anglefactor{std::clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f)};
float const anglefactor{pointlight ? 1.f : std::clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f)};
glarelevel *= anglefactor;
@@ -3124,12 +3134,12 @@ void scene_walk::Render_Alpha(TSubModel *Submodel)
}
}
if (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle)
if (pointlight || (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle))
{
// kąt większy niż maksymalny stożek swiatła
float lightlevel = 1.f; // TODO, TBD: parameter to control light strength
// view angle attenuation
float const anglefactor = std::clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f);
float const anglefactor = pointlight ? 1.f : std::clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f);
lightlevel *= anglefactor;
// distance attenuation. NOTE: since it's fixed pipeline with built-in gamma correction we're using linear attenuation