diff --git a/gl/ubo.h b/gl/ubo.h index af4142f6..38157bcf 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -124,7 +124,8 @@ namespace gl // x: how many lamps and headlights a pixel traces the rays to (0: only the sun), yzw unused glm::vec4 contact_shadow_lamps; // x: 1 when the cascades of the sun shadows are fitted to the depth of the view (gfx.shadow.sdsm, opengl46_shadow_fit): - // a point outside the map of its cascade goes to the next cascade whose map holds it. yzw unused + // a point outside the map of its cascade goes to the next cascade whose map holds it. y: the near plane of the cube shadow + // map of a scenery lamp being drawn by the vulkan renderer (shadowcube.vert), 0 otherwise. zw unused glm::vec4 shadow_fit; // the screen space reflections (gfx.ssr, gfx::screen_reflections), set only for the colour pass of the main view, zero // elsewhere: the view space of the pass to the clip space of the frame whose image the history (SSR_HISTORY_TEX) holds, for @@ -250,12 +251,15 @@ namespace gl float quadratic; float intensity; + // SPOT and POINT (lamps): the radius of the sphere the light shines from, metres (0: a point), below 0 when its light falls + // off as 1 / (d^2 + r^2) (the soft falloff of blender), see lamp_falloff() of common.glsl float ambient; // HEADLIGHTS: view space to the space of the beam texture, and the weights of its channels; w: 1 + the shadow map of the // headlights (scene_ubs::headlight_shadow), 0 when they have none. // SPOT and POINT (lamps): view space to the space of the cube shadow map of the lamp (origin at the lamp), and - // x: layer of the cube map array the map is in, below 0 when the lamp has no shadows; y: near plane of the map; + // x: layer of the cube map array the map is in, below 0 when the lamp has no shadow map, y then 1 when it casts no shadows at + // all (no contact shadows either); y: near plane of the map; // z: size of a texel of the map one metre from the lamp; w: radius of the filter, in texels. the map is in the array of // the scenery lamps (LOCAL_SHADOW_LAMPS_TEX) when linear is 1, in the one of the cab lamp (LOCAL_SHADOW_TEX) otherwise glm::mat4 headlight_projection; diff --git a/model/Model3d.cpp b/model/Model3d.cpp index 2456d556..983fc7ae 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -223,6 +223,21 @@ inline void readMatrix(cParser &parser, float4x4 &matrix) parser >> matrix(x)[y]; }; +// flags of the optional parameters of a light point in its submodel record of an e3d file, after its radius +static constexpr std::uint32_t E3D_LIGHT_SHADESELF{0x1}; +static constexpr std::uint32_t E3D_LIGHT_NOSHADOWS{0x2}; +static constexpr std::uint32_t E3D_LIGHT_SOFTFALLOFF{0x4}; + +// a light point whose cone takes the whole sphere (FalloffAngle 360) is a point light: it shines and is seen all around +static void point_light_cones(float &Falloff, float &Hotspot) +{ + if (Falloff <= -0.9999f) + { + Falloff = -1.f; + Hotspot = -1.f; + } +} + template void UserdataParse(cParser &parser, gfx::vertex_userdata &vertex) { static_assert(sizeof(T) == sizeof(float), "userdata is stored as raw 32-bit values"); @@ -360,24 +375,58 @@ std::pair TSubModel::Load(cParser &parser, bool dynamic) parser >> fNearAttenStart >> discard >> fNearAttenEnd >> discard >> bUseNearAtten >> discard >> iFarAttenDecay >> discard >> fFarDecayRadius >> discard >> fCosFalloffAngle // kąt liczony dla średnicy, a nie promienia >> discard >> fCosHotspotAngle; // kąt liczony dla średnicy, a nie promienia - // opcjonalne natężenie światła w procentach. trzeba pobrać do kolejki 2 tokeny; jeśli to nie hotspotpower, - // zostają w kolejce dla odczytu zasięgu widoczności, który przywraca autoclear - parser.getTokens(2); - if (parser.peek() == "hotspotpower:") + // opcjonalne parametry, każdy to nazwa i wartość, w dowolnej kolejności: natężenie światła w procentach (hotspotpower:), + // promień kuli świecącej (radius:), cienie od własnego modelu (shadeself:), rzucanie cieni (castshadows:), spadek natężenia + // jak przy soft falloff blendera (softfalloff:). trzeba pobrać do kolejki 2 tokeny; jeśli to nie żaden z nich, zostają w + // kolejce dla odczytu zasięgu widoczności, który przywraca autoclear + while (true) { - float power; - parser >> discard >> power; - if (power > 0.f) + parser.getTokens(2); + auto const key{parser.peek()}; + if (key == "hotspotpower:") { - fHotspotPower = power * 0.01f; + float power; + parser >> discard >> power; + if (power > 0.f) + { + fHotspotPower = power * 0.01f; + } + else + { + ErrorLog("Bad model: non-positive hotspotpower " + std::to_string(power) + " of light \"" + pName + "\" in model \"" + parser.Name() + "\", ignored", logtype::model); + } + } + else if (key == "radius:") + { + float radius; + parser >> discard >> radius; + if (radius >= 0.f) + { + fLightRadius = radius; + } + else + { + ErrorLog("Bad model: negative radius " + std::to_string(radius) + " of light \"" + pName + "\" in model \"" + parser.Name() + "\", ignored", logtype::model); + } + } + else if (key == "shadeself:") + { + parser >> discard >> bShadeSelf; + } + else if (key == "castshadows:") + { + parser >> discard >> bCastShadows; + } + else if (key == "softfalloff:") + { + parser >> discard >> bSoftFalloff; } else { - ErrorLog("Bad model: non-positive hotspotpower " + std::to_string(power) + " of light \"" + pName + "\" in model \"" + parser.Name() + "\", ignored", logtype::model); + parser.autoclear(false); + break; } } - else - parser.autoclear(false); // convert conve parameters if specified in degrees if (fCosFalloffAngle > 1.0) @@ -388,6 +437,7 @@ std::pair TSubModel::Load(cParser &parser, bool dynamic) { fCosHotspotAngle = std::cos(glm::radians(0.5f * fCosHotspotAngle)); } + point_light_cones(fCosFalloffAngle, fCosHotspotAngle); m_geometry.vertex_count = 1; iFlags |= 0x4030; // drawn both in solid (light point) and transparent (light glare) phases } @@ -1924,6 +1974,9 @@ void TSubModel::serialize(std::ostream &s, std::vector &models, std // the renderer the model was converted with). the colour is written as it is now, 1 makes older programs leave it so sn_utils::ls_float32(s, 1.f); sn_utils::ls_float32(s, fHotspotPower); + // the optional parameters of light points. the zeros of the fill of older files are their defaults + sn_utils::ls_float32(s, fLightRadius); + sn_utils::ls_uint32(s, (bShadeSelf ? E3D_LIGHT_SHADESELF : 0u) | (bCastShadows ? 0u : E3D_LIGHT_NOSHADOWS) | (bSoftFalloff ? E3D_LIGHT_SOFTFALLOFF : 0u)); // fill empty space size_t fill = end - s.tellp(); @@ -2106,6 +2159,11 @@ void TSubModel::deserialize(std::istream &s) f4Diffuse /= fHotspotPower; } } + fLightRadius = std::max(sn_utils::ld_float32(s), 0.f); + auto const lightflags{sn_utils::ld_uint32(s)}; + bShadeSelf = (lightflags & E3D_LIGHT_SHADESELF) != 0; + bCastShadows = (lightflags & E3D_LIGHT_NOSHADOWS) == 0; + bSoftFalloff = (lightflags & E3D_LIGHT_SOFTFALLOFF) != 0; // necessary rotations were already done during t3d->e3d conversion m_rotation_init_done = true; @@ -2725,6 +2783,10 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, { fCosHotspotAngle = std::cos(glm::radians(0.5f * fCosHotspotAngle)); } + if (eType == TP_FREESPOTLIGHT) + { + point_light_cones(fCosFalloffAngle, fCosHotspotAngle); + } // cap specular values for legacy models f4Specular = glm::vec4{std::clamp(f4Specular.r, 0.0f, 1.0f), std::clamp(f4Specular.g, 0.0f, 1.0f), std::clamp(f4Specular.b, 0.0f, 1.0f), std::clamp(f4Specular.a, 0.0f, 1.0f)}; diff --git a/model/Model3d.h b/model/Model3d.h index b646b2c5..3ce02236 100644 --- a/model/Model3d.h +++ b/model/Model3d.h @@ -127,6 +127,11 @@ public: float fFarDecayRadius { 100.0f }; // normalizacja j.w. float fCosFalloffAngle { 0.5f }; // cosinus kąta stożka pod którym widać światło float fCosHotspotAngle { 0.3f }; // cosinus kąta stożka pod którym widać aureolę i zwiększone natężenie światła + // opcjonalne parametry światła punktowego (np. z eksportu z blendera): + float fLightRadius { 0.0f }; // promień kuli świecącej (radius:), metry; 0 to punkt. świeci powierzchnia kuli, więc środek może być np. wewnątrz klosza + bool bShadeSelf { false }; // cienie światła rzuca także model, do którego ono należy (shadeself:) + bool bCastShadows { true }; // światło rzuca cienie (castshadows:) + bool bSoftFalloff { false }; // natężenie maleje jak 1 / (d^2 + r^2), jak przy soft falloff blendera, zamiast świecenia z powierzchni kuli (softfalloff:) float fCosViewAngle { 0.0f }; // cos kata pod jakim sie teraz patrzy bool m_rotation_init_done = false; diff --git a/rendering/common/locallights.cpp b/rendering/common/locallights.cpp index 2e1eafc0..d46aec47 100644 --- a/rendering/common/locallights.cpp +++ b/rendering/common/locallights.cpp @@ -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; diff --git a/rendering/common/locallights.h b/rendering/common/locallights.h index 3f3c7125..97282bb4 100644 --- a/rendering/common/locallights.h +++ b/rendering/common/locallights.h @@ -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}; diff --git a/rendering/common/localshadows.cpp b/rendering/common/localshadows.cpp index 59c03a2a..266d1da3 100644 --- a/rendering/common/localshadows.cpp +++ b/rendering/common/localshadows.cpp @@ -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 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 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 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(cube), shadow_near, 2.f / static_cast(std::max(m_lampssize, 1)), setup(m_level).radius}; + return glm::vec4{static_cast(cube), m_slots[Slot].nearplane, 2.f / static_cast(std::max(m_lampssize, 1)), setup(m_level).radius}; } std::vector const &local_shadows::lamp_vehicles(glm::dvec3 const &Camera) diff --git a/rendering/common/localshadows.h b/rendering/common/localshadows.h index 5cb00265..84b6dbb2 100644 --- a/rendering/common/localshadows.h +++ b/rendering/common/localshadows.h @@ -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 m_slots; + float m_lampnear{shadow_near}; // near plane of the map of a lamp begin_lamp() set up the drawing to std::vector 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 diff --git a/rendering/common/scenewalk.cpp b/rendering/common/scenewalk.cpp index 1f1e2ff4..d73341bc 100644 --- a/rendering/common/scenewalk.cpp +++ b/rendering/common/scenewalk.cpp @@ -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(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(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 diff --git a/rendering/opengl46/opengl46localshadows.cpp b/rendering/opengl46/opengl46localshadows.cpp index 81b00049..6c297397 100644 --- a/rendering/opengl46/opengl46localshadows.cpp +++ b/rendering/opengl46/opengl46localshadows.cpp @@ -153,6 +153,16 @@ void opengl46_local_shadows::init() glClearTexImage(m_emptyheadlights, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest); } +void opengl46_local_shadows::set_near(float const Near) +{ + glProgramUniform1f(*m_program, NEAR_LOCATION, Near); + glProgramUniform1f(*m_alpha_program, NEAR_LOCATION, Near); + if (m_multidraw_program) + glProgramUniform1f(*m_multidraw_program, NEAR_LOCATION, Near); + if (m_alpha_multidraw_program) + glProgramUniform1f(*m_alpha_multidraw_program, NEAR_LOCATION, Near); +} + void opengl46_local_shadows::set_layer(int const Layer) { glProgramUniform1i(*m_program, LAYER_LOCATION, Layer); @@ -229,6 +239,7 @@ void opengl46_local_shadows::gpu_begin_cab() { m_timer.begin(); set_layer(CAB_SLOT * 6); + set_near(SHADOW_NEAR); setup_drawing(m_framebuffer, m_size); float const farthest{0.f}; glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest); @@ -254,6 +265,8 @@ void opengl46_local_shadows::gpu_begin_lamp(int const Scenerycube, int const Wor glClearTexSubImage(m_lampstexture, 0, 0, 0, Scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest); } set_layer((Vehicles ? Workingcube : Scenerycube) * 6); + // the near plane of the lamp, which the shading reads the map with (lamp_weights()) + set_near(m_lampnear); setup_drawing(m_lampsframebuffer, m_lampssize); } diff --git a/rendering/opengl46/opengl46localshadows.h b/rendering/opengl46/opengl46localshadows.h index b58127a9..2a7a1317 100644 --- a/rendering/opengl46/opengl46localshadows.h +++ b/rendering/opengl46/opengl46localshadows.h @@ -68,6 +68,7 @@ class opengl46_local_shadows : public gfx::local_shadows private: void set_layer(int const Layer); + void set_near(float const Near); std::unique_ptr m_program; std::unique_ptr m_alpha_program; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 7784dd38..382e11ef 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -6260,13 +6260,14 @@ void opengl46_renderer::Write_Local_Light(gl::light_element &Light, gfx::local_l } Light.color = Source.color; Light.intensity = 1.0f; - Light.ambient = 0.0f; + // the sphere the light shines from, see gl::light_element + Light.ambient = (Source.soft_falloff ? -Source.radius : Source.radius); // the shading takes the light to end at 1 / sqrt(quadratic) Light.linear = 0.0f; Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f)); Light.headlight_projection = glm::mat4{1.0f}; - // no shadow map - Light.headlight_weights = glm::vec4{-1.0f, 0.0f, 0.0f, 0.0f}; + // no shadow map; a light which casts no shadows has no contact shadows either + Light.headlight_weights = glm::vec4{-1.0f, (Source.cast_shadows ? 0.0f : 1.0f), 0.0f, 0.0f}; } void opengl46_renderer::Render_local_shadows() @@ -6426,7 +6427,9 @@ void opengl46_renderer::Render_lamp_shadow(std::size_t const Slot, bool const Ve else { m_cubescenery = true; - m_cubeskipinstance = static_cast(slot.owner); + // the model of the lamp is left out, unless its shadows are wanted (shadeself:): what is within the near plane of the map + // (the radius of the light) casts none even then + m_cubeskipinstance = (slot.shade_self ? nullptr : static_cast(slot.owner)); // the instances culled on the gpu, each scenery part drawn in a frame with a culling of its own m_lightshadowcull = Light_Shadow_Cull(m_lampcull, m_lampscenerydrawn++, Global.gfx_gpucull_shadows_lamps); Render(simulation::Region); diff --git a/rendering/vulkan14/vulkan14frame.cpp b/rendering/vulkan14/vulkan14frame.cpp index eeef125d..0e4f2137 100644 --- a/rendering/vulkan14/vulkan14frame.cpp +++ b/rendering/vulkan14/vulkan14frame.cpp @@ -3995,13 +3995,14 @@ void vulkan14_renderer::write_local_light(gl::light_element &Light, gfx::local_l } Light.color = Source.color; Light.intensity = 1.0f; - Light.ambient = 0.0f; + // the sphere the light shines from, see gl::light_element + Light.ambient = (Source.soft_falloff ? -Source.radius : Source.radius); // the shading takes the light to end at 1 / sqrt(quadratic) Light.linear = 0.0f; Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f)); Light.headlight_projection = glm::mat4{1.0f}; - // no shadow map - Light.headlight_weights = glm::vec4{-1.0f, 0.0f, 0.0f, 0.0f}; + // no shadow map; a light which casts no shadows has no contact shadows either + Light.headlight_weights = glm::vec4{-1.0f, (Source.cast_shadows ? 0.0f : 1.0f), 0.0f, 0.0f}; } void vulkan14_renderer::headlight_projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview) diff --git a/rendering/vulkan14/vulkan14lamps.cpp b/rendering/vulkan14/vulkan14lamps.cpp index 34b7a562..5cece737 100644 --- a/rendering/vulkan14/vulkan14lamps.cpp +++ b/rendering/vulkan14/vulkan14lamps.cpp @@ -225,27 +225,33 @@ void vulkan14_renderer::render_lamp_shadow(std::size_t const Slot, bool const Ve { cull = light_shadow_cull(m_context->scene.cull.lamps, m_lampscenerydrawn++, Global.gfx_gpucull_shadows_lamps); } - auto const *owner{static_cast(slot.owner)}; + // the model of the lamp is left out, unless its shadows are wanted (shadeself:): what is within the near plane of the map (the + // radius of the light) casts none even then + auto const *owner{slot.shade_self ? nullptr : static_cast(slot.owner)}; + auto const nearplane{slot.nearplane}; if (walks_wanted()) { // walked by a walker (gfx.vulkan.parallelpasses), its draws counted once it's done - fork_walk([target = m_draw->local_target, Vehicles, vehicles, Cab, owner, cull](vulkan14_renderer &Walker) { + fork_walk([target = m_draw->local_target, Vehicles, vehicles, Cab, owner, cull, nearplane](vulkan14_renderer &Walker) { Walker.m_draw->local_target = target; - Walker.walk_lamp(Vehicles, vehicles, Cab, owner, cull); + Walker.walk_lamp(Vehicles, vehicles, Cab, owner, cull, nearplane); }, walk_kind::lamp); lamps.end_lamp(0); } else { - walk_lamp(Vehicles, vehicles, Cab, owner, cull); + walk_lamp(Vehicles, vehicles, Cab, owner, cull, nearplane); lamps.end_lamp(static_cast(m_renderpass.draw_stats.drawcalls)); } m_renderpass = m_colorpass; } // the walk of the map of a lamp set up by render_lamp_shadow() -void vulkan14_renderer::walk_lamp(bool const Vehicles, std::vector const &Dynamics, TDynamicObject *Cab, TAnimModel const *Owner, vk14::instance_cull *Cull) +void vulkan14_renderer::walk_lamp(bool const Vehicles, std::vector const &Dynamics, TDynamicObject *Cab, TAnimModel const *Owner, vk14::instance_cull *Cull, float const Near) { + // the near plane of the map, for shadowcube.vert, in the scene block begin_local_shadow_target() uploads + auto const previousnear{scene_ubs.shadow_fit.y}; + scene_ubs.shadow_fit.y = Near; begin_local_shadow_target(); m_cubeshadows = true; if (Vehicles) @@ -271,6 +277,7 @@ void vulkan14_renderer::walk_lamp(bool const Vehicles, std::vector const &Dynamics, TDynamicObject *Cab, TAnimModel const *Owner, vk14::instance_cull *Cull); + void walk_lamp(bool const Vehicles, std::vector const &Dynamics, TDynamicObject *Cab, TAnimModel const *Owner, vk14::instance_cull *Cull, float const Near); void walk_headlight(TDynamicObject const *Owner, TDynamicObject *Cab, vk14::instance_cull *Cull); // the walk lock taken by the main thread for the frame (walk_sync::open()) and the walkers given passes in it bool m_walkwindow{false}; diff --git a/shaders/ogl46/cabprobe.glsl b/shaders/ogl46/cabprobe.glsl index 225ff1f7..d33196e6 100644 --- a/shaders/ogl46/cabprobe.glsl +++ b/shaders/ogl46/cabprobe.glsl @@ -10,7 +10,7 @@ layout(location = 0) out vec4 out_light; layout(location = 1) out vec4 out_albedo; -// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl +// closest distance (squared) the light of the lamp is computed for, as lamp_falloff() of common.glsl takes it for a lamp without a radius const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01; // the light of the sun on a diffuse surface, as SUN_DIFFUSE_SCALE and SUN_NDOTL_SHARPNESS of light_common.glsl const float PROBE_SUN_SCALE = 1.5; diff --git a/shaders/ogl46/common.glsl b/shaders/ogl46/common.glsl index 09af27a7..bf7a06c0 100644 --- a/shaders/ogl46/common.glsl +++ b/shaders/ogl46/common.glsl @@ -149,6 +149,24 @@ struct light_s vec4 headlight_weights; }; +// the falloff of the light of a lamp (SPOT, POINT) with the squared distance Distance2 from its middle. the lamps shine from a +// sphere, of the radius abs(ambient) (see gl::light_element, 0: a point): 1 / d^2 past it and 1 / r^2 within it, as a sphere light +// of blender gives it, or 1 / (d^2 + r^2) with its soft falloff (ambient below 0). a point is no brighter than 0.1 m from it +float lamp_falloff(light_s light, float distance2) +{ + float radius = abs(light.ambient); + if (light.ambient < 0.0) + return 1.0 / max(distance2 + radius * radius, 1e-6); + return 1.0 / max(distance2, (radius > 0.0) ? radius * radius : 0.01); +} + +// whether the lamp casts shadows: one without a shadow map (headlight_weights.x below 0) has y 1 when it casts none at all (no +// contact shadows either) +bool lamp_casts_shadows(light_s light) +{ + return (light.headlight_weights.x >= 0.0) || (light.headlight_weights.y < 0.5); +} + // storage blocks are guaranteed only in fragment and compute shaders (GL 4.6 minimum for the other stages is 0) #if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE) layout(std430, binding = LIGHT_SSBO_BINDING) readonly buffer light_ssbo diff --git a/shaders/ogl46/fog_inject.comp b/shaders/ogl46/fog_inject.comp index c5452c19..443daece 100644 --- a/shaders/ogl46/fog_inject.comp +++ b/shaders/ogl46/fog_inject.comp @@ -147,7 +147,7 @@ vec3 lamp_light(light_s light, vec3 position) vec3 tolight = light.pos - position; float distance2 = dot(tolight, tolight); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, 0.01); + float atten = edge * edge * lamp_falloff(light, distance2); if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir))); return light.color * (atten * light.intensity); @@ -205,7 +205,8 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float vec3 sum = vec3(0.0); float closest = dot(lamp.pos - start, direction); vec3 nearest = start + direction * closest; - float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width); + // the sphere of a lamp with a radius spreads its light as the width of the cell does + float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width + lamp.ambient * lamp.ambient); float first = atan(-closest, reach); float last = atan(span - closest, reach); for (int i = 0; i < 2; i++) @@ -214,7 +215,7 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float vec3 tolight = cone.pos - position; // the light without its falloff, which the integral holds vec3 fromlamp = position - lamp.pos; - sum += lamp_light(cone, position) * max(dot(tolight, tolight), 0.01) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4); + sum += lamp_light(cone, position) / lamp_falloff(cone, dot(tolight, tolight)) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4); } return sum * (0.5 * (last - first) / (reach * max(span, 1e-4))); } diff --git a/shaders/ogl46/light_common.glsl b/shaders/ogl46/light_common.glsl index e5fa7862..67c3320c 100644 --- a/shaders/ogl46/light_common.glsl +++ b/shaders/ogl46/light_common.glsl @@ -473,9 +473,6 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) return vec2(part.x, 1.0) * (atten * lightintensity); } -// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit -const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; - // one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the // map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps) @@ -525,27 +522,65 @@ float local_light_shadow(light_s light, vec3 fragnormal) return shadow / float(count); } +// light reaching a surface from a sphere of uniform brightness (a lamp with a radius, as a sphere light of blender), in the units +// in which a point gives n.l / d^2: the same while the sphere is whole above the horizon of the surface, the part above it at +// grazing angles, and the light from all around within the sphere. Snyder, "Area light sources for real-time graphics", 1996, in +// the form of Lagarde, de Rousiers, "Moving Frostbite to physically based rendering", 2014 +float sphere_light_irradiance(float NdotL, float distance2, float radius) +{ + float radius2 = radius * radius; + if (distance2 <= radius2) + return 1.0 / radius2; + float sin_sigma2 = radius2 / distance2; + if (NdotL * NdotL > sin_sigma2) + return max(NdotL, 0.0) / distance2; + float sin_theta = sqrt(max(1.0 - NdotL * NdotL, 1e-8)); + float x = sqrt(max(1.0 / sin_sigma2 - 1.0, 1e-8)); + float y = clamp(-x * NdotL / sin_theta, -1.0, 1.0); + float sin_theta_sqrt_y = sin_theta * sqrt(max(1.0 - y * y, 0.0)); + float form = (NdotL * acos(y) - x * sin_theta_sqrt_y) * sin_sigma2 + atan(sin_theta_sqrt_y / x); + return max(form, 0.0) / (BRDF_PI * radius2); +} + +// the point of the sphere of a lamp (Radius, around the end of Tolight, view space) closest to the reflected ray of the view, where +// its highlight is, and in Energy the share of the light of the highlight widened by the sphere which the highlight of a point +// takes. Karis, "Real shading in Unreal Engine 4", 2013 +vec3 sphere_light_specular(vec3 tolight, vec3 fragnormal, float radius, float roughness, out float energy) +{ + vec3 reflected = reflect(normalize(f_pos.xyz), fragnormal); + vec3 toray = dot(tolight, reflected) * reflected - tolight; + vec3 closest = tolight + toray * clamp(radius * inversesqrt(max(dot(toray, toray), 1e-8)), 0.0, 1.0); + float alpha = roughness * roughness; + float widened = clamp(alpha + 0.5 * radius * inversesqrt(max(dot(tolight, tolight), 1e-8)), 1e-4, 1.0); + energy = (alpha / widened) * (alpha / widened); + return closest; +} + // light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse -// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light, -// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the -// outer one. a lamp with a shadow map is held back by what is between it and the fragment +// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the +// distance (lamp_falloff(), a lamp with a radius shines from its sphere: sphere_light_irradiance()), and smoothly to nothing at the +// range of the light, 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its +// inner cone to the outer one. a lamp with a shadow map is held back by what is between it and the fragment vec2 calc_local_light(light_s light, vec3 fragnormal) { vec3 tolight = light.pos - f_pos.xyz; float distance2 = dot(tolight, tolight); vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2); + float atten = edge * edge; if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); - if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0)) + float falloff = lamp_falloff(light, distance2); + float NdotL = dot(fragnormal, light_dir); + float diffuse = (light.ambient > 0.0) ? sphere_light_irradiance(NdotL, distance2, light.ambient) : max(NdotL, 0.0) * falloff; + if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (diffuse > 0.0)) atten *= 1.0 - local_light_shadow(light, fragnormal); - if ((atten > 0.0) && (contact_shadow_budget > 0)) + if ((atten > 0.0) && (contact_shadow_budget > 0) && lamp_casts_shadows(light)) atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2), - atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); + atten * diffuse * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); if (atten <= 0.0) return vec2(0.0); - return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten; + return vec2(diffuse, falloff) * atten; } // index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip @@ -568,7 +603,12 @@ void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffu diffuse += color * part.x; if (matte) return; - specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); + // the highlight of a lamp with a radius is that of its sphere + vec3 tolight = light.pos - f_pos.xyz; + float energy = 1.0; + if ((light.type != LIGHT_HEADLIGHTS) && (light.ambient != 0.0)) + tolight = sphere_light_specular(tolight, fragnormal, abs(light.ambient), spec_roughness, energy); + specular += color * (part.y * param[1].y * energy) * specular_light(normalize(tolight), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); } // adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in diff --git a/shaders/ogl46/particle_light.glsl b/shaders/ogl46/particle_light.glsl index 0cceda65..c546869c 100644 --- a/shaders/ogl46/particle_light.glsl +++ b/shaders/ogl46/particle_light.glsl @@ -93,7 +93,7 @@ vec3 particle_lamp(light_s light, vec3 position) vec3 tolight = light.pos - position; float distance2 = dot(tolight, tolight); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, 0.01); + float atten = edge * edge * lamp_falloff(light, distance2); if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir))); return light.color * (atten * light.intensity); diff --git a/shaders/vk14/cabprobe.glsl b/shaders/vk14/cabprobe.glsl index 5b98ef7b..42903add 100644 --- a/shaders/vk14/cabprobe.glsl +++ b/shaders/vk14/cabprobe.glsl @@ -10,7 +10,7 @@ layout(location = 0) out vec4 out_light; layout(location = 1) out vec4 out_albedo; -// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl +// closest distance (squared) the light of the lamp is computed for, as lamp_falloff() of common.glsl takes it for a lamp without a radius const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01; // the light of the sun on a diffuse surface, as SUN_DIFFUSE_SCALE and SUN_NDOTL_SHARPNESS of light_common.glsl const float PROBE_SUN_SCALE = 1.5; diff --git a/shaders/vk14/common.glsl b/shaders/vk14/common.glsl index d3391a14..1146a17c 100644 --- a/shaders/vk14/common.glsl +++ b/shaders/vk14/common.glsl @@ -182,6 +182,24 @@ struct light_s vec4 headlight_weights; }; +// the falloff of the light of a lamp (SPOT, POINT) with the squared distance Distance2 from its middle. the lamps shine from a +// sphere, of the radius abs(ambient) (see gl::light_element, 0: a point): 1 / d^2 past it and 1 / r^2 within it, as a sphere light +// of blender gives it, or 1 / (d^2 + r^2) with its soft falloff (ambient below 0). a point is no brighter than 0.1 m from it +float lamp_falloff(light_s light, float distance2) +{ + float radius = abs(light.ambient); + if (light.ambient < 0.0) + return 1.0 / max(distance2 + radius * radius, 1e-6); + return 1.0 / max(distance2, (radius > 0.0) ? radius * radius : 0.01); +} + +// whether the lamp casts shadows: one without a shadow map (headlight_weights.x below 0) has y 1 when it casts none at all (no +// contact shadows either) +bool lamp_casts_shadows(light_s light) +{ + return (light.headlight_weights.x >= 0.0) || (light.headlight_weights.y < 0.5); +} + // light clusters, see gl/cluster_grid.h struct cluster_aabb_s { diff --git a/shaders/vk14/fog_inject.comp b/shaders/vk14/fog_inject.comp index ff952120..873a874e 100644 --- a/shaders/vk14/fog_inject.comp +++ b/shaders/vk14/fog_inject.comp @@ -152,7 +152,7 @@ vec3 lamp_light(light_s light, vec3 position) vec3 tolight = light.pos - position; float distance2 = dot(tolight, tolight); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, 0.01); + float atten = edge * edge * lamp_falloff(light, distance2); if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir))); return light.color * (atten * light.intensity); @@ -210,7 +210,8 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float vec3 sum = vec3(0.0); float closest = dot(lamp.pos - start, direction); vec3 nearest = start + direction * closest; - float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width); + // the sphere of a lamp with a radius spreads its light as the width of the cell does + float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width + lamp.ambient * lamp.ambient); float first = atan(-closest, reach); float last = atan(span - closest, reach); for (int i = 0; i < 2; i++) @@ -219,7 +220,7 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float vec3 tolight = cone.pos - position; // the light without its falloff, which the integral holds vec3 fromlamp = position - lamp.pos; - sum += lamp_light(cone, position) * max(dot(tolight, tolight), 0.01) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4); + sum += lamp_light(cone, position) / lamp_falloff(cone, dot(tolight, tolight)) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4); } return sum * (0.5 * (last - first) / (reach * max(span, 1e-4))); } diff --git a/shaders/vk14/light_common.glsl b/shaders/vk14/light_common.glsl index 9a3ed0cc..e2bd2f84 100644 --- a/shaders/vk14/light_common.glsl +++ b/shaders/vk14/light_common.glsl @@ -477,9 +477,6 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) return vec2(part.x, 1.0) * (atten * lightintensity); } -// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit -const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; - // one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the // map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps) @@ -529,27 +526,65 @@ float local_light_shadow(light_s light, vec3 fragnormal) return shadow / float(count); } +// light reaching a surface from a sphere of uniform brightness (a lamp with a radius, as a sphere light of blender), in the units +// in which a point gives n.l / d^2: the same while the sphere is whole above the horizon of the surface, the part above it at +// grazing angles, and the light from all around within the sphere. Snyder, "Area light sources for real-time graphics", 1996, in +// the form of Lagarde, de Rousiers, "Moving Frostbite to physically based rendering", 2014 +float sphere_light_irradiance(float NdotL, float distance2, float radius) +{ + float radius2 = radius * radius; + if (distance2 <= radius2) + return 1.0 / radius2; + float sin_sigma2 = radius2 / distance2; + if (NdotL * NdotL > sin_sigma2) + return max(NdotL, 0.0) / distance2; + float sin_theta = sqrt(max(1.0 - NdotL * NdotL, 1e-8)); + float x = sqrt(max(1.0 / sin_sigma2 - 1.0, 1e-8)); + float y = clamp(-x * NdotL / sin_theta, -1.0, 1.0); + float sin_theta_sqrt_y = sin_theta * sqrt(max(1.0 - y * y, 0.0)); + float form = (NdotL * acos(y) - x * sin_theta_sqrt_y) * sin_sigma2 + atan(sin_theta_sqrt_y / x); + return max(form, 0.0) / (BRDF_PI * radius2); +} + +// the point of the sphere of a lamp (Radius, around the end of Tolight, view space) closest to the reflected ray of the view, where +// its highlight is, and in Energy the share of the light of the highlight widened by the sphere which the highlight of a point +// takes. Karis, "Real shading in Unreal Engine 4", 2013 +vec3 sphere_light_specular(vec3 tolight, vec3 fragnormal, float radius, float roughness, out float energy) +{ + vec3 reflected = reflect(normalize(f_pos.xyz), fragnormal); + vec3 toray = dot(tolight, reflected) * reflected - tolight; + vec3 closest = tolight + toray * clamp(radius * inversesqrt(max(dot(toray, toray), 1e-8)), 0.0, 1.0); + float alpha = roughness * roughness; + float widened = clamp(alpha + 0.5 * radius * inversesqrt(max(dot(tolight, tolight), 1e-8)), 1e-4, 1.0); + energy = (alpha / widened) * (alpha / widened); + return closest; +} + // light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse -// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light, -// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the -// outer one. a lamp with a shadow map is held back by what is between it and the fragment +// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the +// distance (lamp_falloff(), a lamp with a radius shines from its sphere: sphere_light_irradiance()), and smoothly to nothing at the +// range of the light, 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its +// inner cone to the outer one. a lamp with a shadow map is held back by what is between it and the fragment vec2 calc_local_light(light_s light, vec3 fragnormal) { vec3 tolight = light.pos - f_pos.xyz; float distance2 = dot(tolight, tolight); vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2); + float atten = edge * edge; if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); - if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0)) + float falloff = lamp_falloff(light, distance2); + float NdotL = dot(fragnormal, light_dir); + float diffuse = (light.ambient > 0.0) ? sphere_light_irradiance(NdotL, distance2, light.ambient) : max(NdotL, 0.0) * falloff; + if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (diffuse > 0.0)) atten *= 1.0 - local_light_shadow(light, fragnormal); - if ((atten > 0.0) && (contact_shadow_budget > 0)) + if ((atten > 0.0) && (contact_shadow_budget > 0) && lamp_casts_shadows(light)) atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2), - atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); + atten * diffuse * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); if (atten <= 0.0) return vec2(0.0); - return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten; + return vec2(diffuse, falloff) * atten; } // index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip @@ -572,7 +607,12 @@ void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffu diffuse += color * part.x; if (matte) return; - specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); + // the highlight of a lamp with a radius is that of its sphere + vec3 tolight = light.pos - f_pos.xyz; + float energy = 1.0; + if ((light.type != LIGHT_HEADLIGHTS) && (light.ambient != 0.0)) + tolight = sphere_light_specular(tolight, fragnormal, abs(light.ambient), spec_roughness, energy); + specular += color * (part.y * param[1].y * energy) * specular_light(normalize(tolight), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); } // adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in diff --git a/shaders/vk14/particle_light.glsl b/shaders/vk14/particle_light.glsl index e7605acd..db27daf0 100644 --- a/shaders/vk14/particle_light.glsl +++ b/shaders/vk14/particle_light.glsl @@ -93,7 +93,7 @@ vec3 particle_lamp(light_s light, vec3 position) vec3 tolight = light.pos - position; float distance2 = dot(tolight, tolight); float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, 0.01); + float atten = edge * edge * lamp_falloff(light, distance2); if (light.type == LIGHT_SPOT) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir))); return light.color * (atten * light.intensity); diff --git a/shaders/vk14/shadowcube.vert b/shaders/vk14/shadowcube.vert index 30f6f27a..c6d15e06 100644 --- a/shaders/vk14/shadowcube.vert +++ b/shaders/vk14/shadowcube.vert @@ -5,8 +5,9 @@ // geometry shader shadowcube.geom of the opengl renderer). the modelview takes the geometry to the space of the map, with the lamp at // the origin. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, -z), each looks the way glm::lookAt() with // the usual up vectors of cube maps sets it, with a 90 degree projection of infinite reach and reversed depth like the one of the -// scene: the depth is near / d, d the distance along the axis of the face. the near plane of the map, LOCAL_SHADOW_NEAR: closer -// surfaces (the lamp itself) cast no shadows +// scene: the depth is near / d, d the distance along the axis of the face. the near plane of the map, shadow_fit.y of the scene block +// for the map of a scenery lamp (past the radius of its light), LOCAL_SHADOW_NEAR otherwise: closer surfaces (the lamp itself) cast no +// shadows #include "common.glsl" layout(location = 0) in vec3 v_vert; layout(location = 2) in vec2 v_coord; @@ -22,7 +23,8 @@ void main() { vec3 position = (effective_modelview() * vec4(v_vert, 1.0f)).xyz; int face = int(gl_ViewIndex); - gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), LOCAL_SHADOW_NEAR, dot(position, face_forward[face])); + float nearplane = (shadow_fit.y > 0.0) ? shadow_fit.y : LOCAL_SHADOW_NEAR; + gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), nearplane, dot(position, face_forward[face])); f_coord = v_coord; #ifdef MODEL_DRAWS // which block of the multi draw the fragments read (common.glsl)