mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-18 12:39:18 +02:00
Light the scene with lamps of models, vehicle compartments and the cab lamp in the GL 4.6 renderer
This commit is contained in:
@@ -337,6 +337,7 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46sky.cpp"
|
||||
"rendering/opengl46/opengl46exposure.cpp"
|
||||
"rendering/opengl46/opengl46ao.cpp"
|
||||
"rendering/opengl46/opengl46locallights.cpp"
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
"rendering/opengl46/opengl46terrain.cpp"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Clustered lighting
|
||||
|
||||
How the OpenGL 4.6 renderer (`rendering/opengl46/`) gives the shaders the lights of vehicle headlights.
|
||||
How the OpenGL 4.6 renderer (`rendering/opengl46/`) gives the shaders the lights of vehicle headlights and lamps.
|
||||
For the settings and what changed for players see [opengl46-renderer.md](opengl46-renderer.md).
|
||||
|
||||
## Idea
|
||||
@@ -29,13 +29,13 @@ The UBO numbers go together with the scene (0), model (1) and instance (3) block
|
||||
space of their own. Layouts of the structures are checked with `static_assert`s on sizes and offsets in
|
||||
`gl/ubo.h`, and have to stay equal to the blocks in `gl/glsl_common.cpp`.
|
||||
|
||||
Sizes: `MAX_LIGHTS` = 256 (the sun and 255 headlights), `MAX_LIGHTS_PER_CLUSTER` = 64. The buffers have a
|
||||
Sizes: `MAX_LIGHTS` = 1024 (the sun and 1023 headlights and lamps), `MAX_LIGHTS_PER_CLUSTER` = 64. The buffers have a
|
||||
fixed size, so changing the resolution, the field of view or the draw range only makes the grid parameters
|
||||
and boxes be recomputed.
|
||||
|
||||
`gl::light_data.lights_total` is the number of lights written to `lights[]`, which the culling reads.
|
||||
`lights_count` is the length of the plain loop over the lights (the sun and the first `dynamiclights` headlights) used
|
||||
when the lights are not clustered. `lights_clustered` is set per pass and tells the shading which one to use.
|
||||
when the lights are not clustered; then only headlights are written, from slot 1 on. `lights_clustered` is set per pass and tells the shading which one to use.
|
||||
|
||||
## Frame order
|
||||
|
||||
@@ -44,9 +44,11 @@ In `Render_pass()` for the color pass of a view (`opengl46_renderer.cpp`):
|
||||
1. `Update_Clusters(vp)`: `cluster_grid::update()` compares the projection and near / far plane with those the
|
||||
boxes were built for. When they differ, `cluster_build.comp` recomputes the boxes (one invocation per
|
||||
cluster, 54 work groups of 64). This is the "clusters" timer.
|
||||
2. `Update_Lights(...)`: sorts the lights of the scene (`simulation::Lights`) by distance squared over
|
||||
intensity, skips the lights that are off or farther than 1 km, writes each lit headlight to `lights[]`
|
||||
in view space, and uploads the header and the part of the array in use.
|
||||
2. `Update_Lights(...)`: with clusters, writes the cab lamp first (slot 1), when it is on. Then it sorts the lights of
|
||||
the scene (`simulation::Lights`) by distance squared over intensity, skips the lights that are off or farther than
|
||||
1 km and writes each lit headlight to `lights[]` in view space. With clusters the lamps of models and vehicles follow
|
||||
(`opengl46_local_lights`, in their order of importance), those whose sphere is in the view frustum of the pass, while
|
||||
there is room. Then it uploads the header and the part of the array in use.
|
||||
3. `Cull_Lights(vp)`: the CPU zeroes the counters, then `cluster_cull.comp` runs (one invocation per cluster, 54
|
||||
work groups of 64): it tests every light against the box of its cluster and writes the indices of the ones
|
||||
that reach it. Then `glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT)`. This is the
|
||||
@@ -81,6 +83,26 @@ reverse-Z with the far plane at infinity, so the `znear` / `zfar` of the pass bo
|
||||
cluster (`ndc.xy` times `w` at the near and far side of the slice) map back to view space through the
|
||||
inverse of the corresponding 3x3 part of the projection. The box is the min and max of the corners.
|
||||
|
||||
## Lamps
|
||||
|
||||
`opengl46_local_lights` (`rendering/opengl46/opengl46locallights.*`) gathers the other lights once per frame, in
|
||||
`Render()`, before the views are drawn, within `gfx.lights.range` of the camera:
|
||||
|
||||
- light points (`TP_FREESPOTLIGHT`) of scenery models: the instances on the translucent lists of the cells around the camera
|
||||
whose model has light points (that list is kept per cell and made anew when the list of the cell changes, and every frame
|
||||
while the editor is on). For each, `RaAnimate()` and `RaPrepare()` set the state of the shared model, as the render does,
|
||||
and the submodel tree is gone through the way `Render_Alpha(TSubModel *)` goes through it (visibility, lod ranges,
|
||||
transforms, animations). A point which is lit (emission above 0 and the light level below its `fLight`) becomes a spot light
|
||||
along its -z axis, with the cone of its glare;
|
||||
- the same for vehicles within the range, after `ABuLittleUpdate()`, with their main model, attachments and low poly interior,
|
||||
and a point light in the middle of each lit section of the low poly interior which has no light points of its own;
|
||||
- the cab lamp: a point light under the top of the bounds of the cab model, when the cab is drawn and its lamp is on.
|
||||
|
||||
Lamps are `POINT` or `SPOT` lights. The shading (`calc_local_light()`) lets them fall off with the square of the
|
||||
distance (no nearer than 10 cm) and to nothing at their range, which is written as `quadratic` = 1 / range^2 and
|
||||
which the culling takes as the end of the light. A spot light fades from `in_cutoff` to `out_cutoff` (cosines of the half
|
||||
angles); the culling tests a cone only when it is narrower than a half of the space.
|
||||
|
||||
## Culling
|
||||
|
||||
`cluster_cull.comp` handles one cluster per invocation and goes through `lights[1 .. lights_total)`.
|
||||
@@ -122,9 +144,8 @@ guarantee any storage blocks in the vertex and geometry stages.
|
||||
The work group size in the shaders and the number of groups dispatched both come from `CLUSTER_*_GROUP_SIZE`.
|
||||
- Check the shaders with `ci_shadervalidator` (see [opengl46-renderer.md](opengl46-renderer.md)); CI does it for
|
||||
changes in `shaders/` and `gl/`.
|
||||
- Only vehicle headlights feed `lights[]` today. The culling already knows `SPOT`, `POINT` and `DIR`, but
|
||||
`add_headlights()` runs `calc_headlights()` for every listed light, so a new kind of light also needs
|
||||
its own branch there.
|
||||
- A new kind of light needs its branch in `add_headlights()` (today `HEADLIGHTS` goes to `calc_headlights()`, the others
|
||||
to `calc_local_light()`) and in `light_range()` / `light_reaches_box()` of `cluster_cull.comp`.
|
||||
|
||||
## Not covered
|
||||
|
||||
|
||||
@@ -96,6 +96,40 @@ before the shading (a depth prepass), which is planned later.
|
||||
| `gfx.postfx.ao.bounce` | `0.3` | 0..1: albedo of the surfaces assumed for the light that comes back after more bounces (brightens the occlusion a little) |
|
||||
| `gfx.postfx.ao.quality slices steps` | `3 4` | directions searched around each pixel (1..8) and samples on each side of a direction (1..16) |
|
||||
|
||||
### Lamps
|
||||
|
||||
With the clustered lights on, lamps light their surroundings, besides the sun and the vehicle headlights. Without the
|
||||
clustered lights (`gfx.lights.clustered no`) none of this happens and the cab is lit as before. The switches and
|
||||
scales can also be changed in the **Light clusters** debug window.
|
||||
|
||||
- **Light points of models** (`freespotlight` submodels: street and platform lamps, signals, lamps of vehicles) are taken
|
||||
the way the experimental renderer takes them. A point which is visible, has self-illumination (`SelfIllum` `true`, or a
|
||||
threshold the light level is below) shines as a spot light along the -z axis of its submodel, in the cone of its glare
|
||||
(`FalloffAngle`, full brightness within `HotspotAngle`), in its colour; `hotspotpower` scales it. Lamps of scenery
|
||||
models switched with `Light_On` / `Light_Off` (`lights` of the model node, events) turn their light points on and off
|
||||
with them. Nothing in the models has to change; a light point without self-illumination stays a glare only.
|
||||
- **Lit compartments of vehicles** (the `cab`, `corridor`/`korytarz` and `compartment`/`przedzial` sections of the low
|
||||
poly interior) give a point light in the middle of the section, in the colour of the interior light, unless the
|
||||
section holds light points of its own (these light it then, through its self-illumination).
|
||||
- **The cab lamp** of the occupied cab is a point light under the top of the cab model. Before, it added to the ambient
|
||||
light of the whole cab and weakened the shadows of the sun in it; now a share of that ambient light is kept for the light
|
||||
which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay.
|
||||
|
||||
The lamps of models and compartments fade in from the light level at which scenery lamps switch on (`ls_Dark`) to full
|
||||
darkness; in daylight they are not gathered at all. None of these lights casts shadows yet, so they also light what is
|
||||
behind walls within their reach; that reach is kept short for compartments and the cab.
|
||||
|
||||
| Entry | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `gfx.lights.scenery yes/no` | `yes` | light points of scenery models light their surroundings |
|
||||
| `gfx.lights.vehicles yes/no` | `yes` | light points of vehicles and their lit compartments light their surroundings |
|
||||
| `gfx.lights.cab yes/no` | `yes` | the cab lamp is a point light; `no`: it adds to the ambient light of the cab, as before |
|
||||
| `gfx.lights.freespot.intensity` | `10` | brightness of a light point (the experimental renderer uses the same default) |
|
||||
| `gfx.lights.compartment.intensity` | `1.0` | brightness of the light of a lit compartment |
|
||||
| `gfx.lights.cab.intensity` | `1.5` | brightness of the cab lamp |
|
||||
| `gfx.lights.cab.bounce` | `0.3` | 0..1: share of the former ambient light of the cab lamp kept with the lamp on |
|
||||
| `gfx.lights.range` | `300` | distance from the camera the lamps of models and vehicles are gathered from, metres |
|
||||
|
||||
### Changed option
|
||||
|
||||
`dynamiclights` (0..7) used to limit the number of headlight sets that light the surroundings. It is still
|
||||
@@ -104,7 +138,8 @@ read, but it only takes effect with `gfx.lights.clustered no`.
|
||||
## What looks different from the previous renderer
|
||||
|
||||
- **No limit of 7 lit headlights.** All lit headlights within 1 km take part in the lighting (each lit end
|
||||
of a vehicle counts separately), up to 255 in the buffer and 64 reaching any one part of the view. Beyond
|
||||
of a vehicle counts separately), up to 1023 lights in the buffer (headlights, lamps and the cab lamp) and 64 reaching
|
||||
any one part of the view. Beyond
|
||||
these capacities the farthest and dimmest lights are dropped, and the statistics show how many.
|
||||
- **Outside the beam texture a headlight adds nothing.** Before, the edge of the texture was stretched
|
||||
beyond the beam, which could leave a faint glow around vehicles. This also applies with
|
||||
@@ -127,7 +162,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
|
||||
time, and `cluster lists:` the size of the lists and the number of dropped (cluster, light) pairs
|
||||
(`overflow`, with its peak). A non-zero overflow means some cluster held more than 64 lights.
|
||||
- **Light clusters** window (in debug mode, menu *Windows*, item *Headlight config* opens the renderer debug
|
||||
windows): the switch for clustered lights, the heat map, the grid state and the same counters.
|
||||
windows): the switch for clustered lights, the heat map, the grid state and the same counters, and the switches and
|
||||
scales of the lamps with their counts. Shift+F12 shows the lamps in the `local lights:` row: how many were gathered (from
|
||||
scenery models, vehicles and compartments, and the cab lamp), how many were placed in the light buffer of the main view
|
||||
(those that may light something in view) and did not fit, and the cpu time of gathering them.
|
||||
- **Sky and exposure** window (next to the Light clusters window): the switches and scales above, the share of the
|
||||
atmosphere, the colours of the sun, ambient light and fog in use, and the measured luminance and exposure.
|
||||
Shift+F12 shows the GPU time of `atmosphere:` (look-up tables, only when the sun moved or the weather changed),
|
||||
|
||||
2
gl/ubo.h
2
gl/ubo.h
@@ -169,7 +169,7 @@ namespace gl
|
||||
|
||||
// capacity of light_data.lights; slot 0 is the sun. a buffer size, not a limit of the shaders: they take the
|
||||
// number of lights from the header
|
||||
const size_t MAX_LIGHTS = 256;
|
||||
const size_t MAX_LIGHTS = 1024;
|
||||
|
||||
// contents of light_ssbo: global lighting header followed by the light array
|
||||
struct light_data
|
||||
|
||||
520
rendering/opengl46/opengl46locallights.cpp
Normal file
520
rendering/opengl46/opengl46locallights.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "rendering/opengl46/opengl46locallights.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "scene/scene.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "vehicle/Train.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "rendering/geometrybank.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// a light ends where its contribution falls under this. it is above the limit of the culling of the clusters, as there are
|
||||
// many more of these lights than headlights, and each of them lights only its surroundings
|
||||
float const LIGHT_THRESHOLD{0.01f};
|
||||
// no light of a model reaches farther than this, metres
|
||||
float const LIGHT_RANGE_MAX{60.f};
|
||||
// the self-illumination threshold of the lamps of scenery models (Light_On, ls_Dark) and full darkness: between them
|
||||
// the lights of models and compartments fade in, as they would be lost in the daylight anyway
|
||||
float const LIGHT_DUSK_LUMINANCE{0.325f};
|
||||
float const LIGHT_DAY_LUMINANCE{1.0f};
|
||||
|
||||
glm::vec3 to_linear(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::pow(glm::max(Color, glm::vec3{0.f}), glm::vec3{2.2f});
|
||||
}
|
||||
|
||||
float max_component(glm::vec3 const &Color)
|
||||
{
|
||||
return std::max(Color.r, std::max(Color.g, Color.b));
|
||||
}
|
||||
|
||||
// distance at which a light of the colour falls under the threshold with the square of the distance
|
||||
float light_range(glm::vec3 const &Color)
|
||||
{
|
||||
return std::min(std::sqrt(max_component(Color) / LIGHT_THRESHOLD), LIGHT_RANGE_MAX);
|
||||
}
|
||||
|
||||
// model space transform of the parent of the submodel, from the static transforms of the submodels above it; false if the
|
||||
// submodel is not in the tree
|
||||
bool find_parent_transform(TSubModel const *Submodel, TSubModel const *Target, glm::mat4 const &Parent, glm::mat4 &Result)
|
||||
{
|
||||
for (auto const *submodel{Submodel}; submodel != nullptr; submodel = submodel->Next)
|
||||
{
|
||||
if (submodel == Target)
|
||||
{
|
||||
Result = Parent;
|
||||
return true;
|
||||
}
|
||||
auto transform{Parent};
|
||||
if ((submodel->iFlags & 0xC000) && (submodel->fMatrix != nullptr))
|
||||
{
|
||||
transform *= glm::make_mat4(submodel->fMatrix->readArray());
|
||||
}
|
||||
if (find_parent_transform(submodel->Child, Target, transform, Result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// bounds of the geometry of the submodel, of its children and, with Siblings, of the submodels after it, in the space of Transform
|
||||
void extend_bounds(TSubModel const *Submodel, glm::mat4 const &Transform, gfx::geometrybank_manager const &Geometry, bool const Siblings, glm::vec3 &Min, glm::vec3 &Max,
|
||||
bool &Lightpoints)
|
||||
{
|
||||
for (auto const *submodel{Submodel}; submodel != nullptr; submodel = (Siblings ? submodel->Next : nullptr))
|
||||
{
|
||||
auto transform{Transform};
|
||||
if ((submodel->iFlags & 0xC000) && (submodel->fMatrix != nullptr))
|
||||
{
|
||||
transform *= glm::make_mat4(submodel->fMatrix->readArray());
|
||||
}
|
||||
if (submodel->eType == TP_FREESPOTLIGHT)
|
||||
{
|
||||
Lightpoints = true;
|
||||
}
|
||||
else if ((submodel->eType < TP_ROTATOR) && (submodel->m_geometry.handle != null_handle))
|
||||
{
|
||||
for (auto const &vertex : Geometry.vertices(submodel->m_geometry.handle))
|
||||
{
|
||||
auto const point{glm::vec3{transform * glm::vec4{vertex.position, 1.f}}};
|
||||
Min = glm::min(Min, point);
|
||||
Max = glm::max(Max, point);
|
||||
}
|
||||
}
|
||||
extend_bounds(submodel->Child, transform, Geometry, true, Min, Max, Lightpoints);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void opengl46_local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Region, gfx::geometrybank_manager const &Geometry, unsigned int const Framestamp)
|
||||
{
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
|
||||
m_lights.clear();
|
||||
m_cablampon = false;
|
||||
m_scenerycount = m_vehiclecount = m_compartmentcount = 0;
|
||||
|
||||
// the editor adds, moves and removes instances in the cells. it can also do it in the frame it is turned off in
|
||||
auto const rebuild{EditorModeFlag || m_editorseen};
|
||||
m_editorseen = EditorModeFlag;
|
||||
|
||||
if (Global.gfx_lights_clustered)
|
||||
{
|
||||
if (Global.gfx_lights_cab)
|
||||
{
|
||||
gather_cab_lamp(Geometry);
|
||||
}
|
||||
|
||||
// lamps are lost in the daylight; this also spares the work of gathering them in the middle of the day
|
||||
auto const darkness{1.f - glm::smoothstep(LIGHT_DUSK_LUMINANCE, LIGHT_DAY_LUMINANCE, static_cast<float>(Global.fLuminance))};
|
||||
auto const range{Global.gfx_lights_range};
|
||||
|
||||
if ((darkness > 0.f) && (range > 0.f) && Global.gfx_lights_scenery)
|
||||
{
|
||||
auto const freespotbrightness{Global.gfx_lights_freespot_intensity * darkness};
|
||||
// sections of the region within the range, the same way the render picks them
|
||||
auto const camerax{static_cast<int>(std::floor(Camera.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2))};
|
||||
auto const cameraz{static_cast<int>(std::floor(Camera.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2))};
|
||||
auto const reach{static_cast<int>(std::ceil(range / scene::EU07_SECTIONSIZE))};
|
||||
for (int row = std::max(0, cameraz - reach); row <= std::min(scene::EU07_REGIONSIDESECTIONCOUNT - 1, cameraz + reach); ++row)
|
||||
{
|
||||
for (int column = std::max(0, camerax - reach); column <= std::min(scene::EU07_REGIONSIDESECTIONCOUNT - 1, camerax + reach); ++column)
|
||||
{
|
||||
auto *section{Region.m_sections[row * scene::EU07_REGIONSIDESECTIONCOUNT + column]};
|
||||
if (section == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto &cell : section->m_cells)
|
||||
{
|
||||
if ((false == cell.m_active) || (glm::length(cell.m_area.center - Camera) > range + cell.m_area.radius))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto *instance : cell_instances(cell, rebuild).instances)
|
||||
{
|
||||
gather_instance(instance, Camera, freespotbrightness, Framestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((darkness > 0.f) && (range > 0.f) && Global.gfx_lights_vehicles)
|
||||
{
|
||||
auto const freespotbrightness{Global.gfx_lights_freespot_intensity * darkness};
|
||||
auto const compartmentbrightness{Global.gfx_lights_compartment_intensity * darkness};
|
||||
for (auto *vehicle : simulation::Vehicles.sequence())
|
||||
{
|
||||
if (glm::length(vehicle->GetPosition() - Camera) > range + vehicle->radius())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
gather_vehicle(vehicle, Camera, Geometry, freespotbrightness, compartmentbrightness);
|
||||
}
|
||||
}
|
||||
|
||||
// the most important lights first: when the light buffer or the list of a cluster fills up, the others are left out
|
||||
std::sort(std::begin(m_lights), std::end(m_lights), [&Camera](light const &Left, light const &Right) {
|
||||
return glm::length2(Left.position - Camera) / std::max(max_component(Left.color), 1e-6f) < glm::length2(Right.position - Camera) / std::max(max_component(Right.color), 1e-6f);
|
||||
});
|
||||
}
|
||||
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count()};
|
||||
m_gathertime = (m_gathertime == 0.0 ? elapsed : m_gathertime * 0.95 + elapsed * 0.05);
|
||||
}
|
||||
|
||||
void opengl46_local_lights::reset()
|
||||
{
|
||||
m_lights.clear();
|
||||
m_cablampon = false;
|
||||
m_modellights.clear();
|
||||
m_cells.clear();
|
||||
m_sections.clear();
|
||||
m_models.clear();
|
||||
m_scenerycount = m_vehiclecount = m_compartmentcount = m_placed = m_dropped = 0;
|
||||
}
|
||||
|
||||
void opengl46_local_lights::gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp)
|
||||
{
|
||||
if ((false == Instance->m_visible) || (Instance->pModel == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const offset{Instance->location() - Camera};
|
||||
if (glm::length(offset) > Global.gfx_lights_range + Instance->radius())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the same distance the render picks the lods and the range of the instance by
|
||||
auto const distancesquared{glm::length2(offset / static_cast<double>(Global.ZoomFactor)) / Global.fDistanceFactor};
|
||||
if ((distancesquared < Instance->m_rangesquaredmin) || (distancesquared >= Instance->m_rangesquaredmax))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instance->RaAnimate(Framestamp);
|
||||
Instance->RaPrepare();
|
||||
TSubModel::fSquareDist = static_cast<float>(distancesquared);
|
||||
|
||||
auto const count{m_lights.size()};
|
||||
auto const placement{glm::translate(glm::dmat4{1.0}, Instance->location()) * glm::dmat4{Instance->rotation_scale()}};
|
||||
walk(Instance->pModel->Root, glm::mat4{1.f}, placement, Brightness);
|
||||
m_scenerycount += static_cast<std::uint32_t>(m_lights.size() - count);
|
||||
}
|
||||
|
||||
void opengl46_local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness,
|
||||
float const Compartmentbrightness)
|
||||
{
|
||||
if ((false == Global.render_cab) && (Global.pCamera.m_owner == Vehicle))
|
||||
{
|
||||
// the render leaves the vehicle out as well
|
||||
return;
|
||||
}
|
||||
// light points: the models with any are gone through after the setup the render makes before it draws the vehicle
|
||||
std::array<TModel3d *, 2> models{Vehicle->mdModel, Vehicle->mdLowPolyInt};
|
||||
auto const haslightpoints = [this](TModel3d *Model) { return (Model != nullptr) && has_light_points(Model); };
|
||||
if (std::any_of(std::begin(models), std::end(models), haslightpoints) || std::any_of(std::begin(Vehicle->mdAttachments), std::end(Vehicle->mdAttachments), haslightpoints))
|
||||
{
|
||||
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>(Vehicle);
|
||||
TSubModel::iVariantSeed = Vehicle->variant_seed();
|
||||
auto const squaredistance{glm::length2(glm::vec3{Vehicle->vPosition - Camera} / Global.ZoomFactor)};
|
||||
Vehicle->ABuLittleUpdate(squaredistance);
|
||||
|
||||
auto const placement{glm::translate(glm::dmat4{1.0}, Vehicle->vPosition) * Vehicle->mMatrix};
|
||||
auto const count{m_lights.size()};
|
||||
auto const walkmodel = [&](TModel3d *Model) {
|
||||
if (haslightpoints(Model))
|
||||
{
|
||||
TSubModel::fSquareDist = squaredistance;
|
||||
walk(Model->Root, glm::mat4{1.f}, placement, Freespotbrightness);
|
||||
}
|
||||
};
|
||||
walkmodel(Vehicle->mdModel);
|
||||
for (auto *attachment : Vehicle->mdAttachments)
|
||||
{
|
||||
walkmodel(attachment);
|
||||
}
|
||||
walkmodel(Vehicle->mdLowPolyInt);
|
||||
m_vehiclecount += static_cast<std::uint32_t>(m_lights.size() - count);
|
||||
|
||||
if (Vehicle->btnOn)
|
||||
{
|
||||
Vehicle->TurnOff(); // the state of the shared model goes back to the default, as after the render
|
||||
}
|
||||
}
|
||||
|
||||
// lit compartments, the way TDynamicObject::Update() works out their self-illumination: cab sections come first and are lit at
|
||||
// the level of their lamp, the others at the level of the compartment lights
|
||||
if ((Vehicle->mdLowPolyInt != nullptr) && (Compartmentbrightness > 0.f))
|
||||
{
|
||||
auto const owncab{m_cablampon && (simulation::Train != nullptr) && (simulation::Train->Dynamic() == Vehicle)};
|
||||
auto cabsection{true};
|
||||
for (auto const §ion : Vehicle->Sections)
|
||||
{
|
||||
auto const &name{section.compartment->pName};
|
||||
cabsection &= (name.size() >= 4) && name.starts_with("cab");
|
||||
auto const level{section.light_level * (cabsection ? 1.f : Vehicle->MoverParameters->CompartmentLights.intensity)};
|
||||
if ((level <= 0.f) || (Global.fLuminance > section.compartment->fLight) || (section.compartment->iVisible == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (cabsection && owncab)
|
||||
{
|
||||
// the lamp of the occupied cab is a light of its own
|
||||
continue;
|
||||
}
|
||||
auto const &data{this->section(Vehicle->mdLowPolyInt, section.compartment, Geometry)};
|
||||
if (data.has_light_points || (data.extent == glm::vec3{0.f}))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
light compartmentlight;
|
||||
compartmentlight.position = Vehicle->GetWorldPosition(glm::dvec3{data.center});
|
||||
compartmentlight.color = to_linear(Vehicle->InteriorLight) * (level * Compartmentbrightness);
|
||||
// the light goes out of the windows a little; there are no shadows to keep it in, so it doesn't reach far
|
||||
compartmentlight.range = std::clamp(glm::length(data.extent) * 1.5f, 2.f, std::min(light_range(compartmentlight.color), 15.f));
|
||||
m_lights.emplace_back(compartmentlight);
|
||||
++m_compartmentcount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geometry)
|
||||
{
|
||||
// the cab is drawn, Render_cab() conditions
|
||||
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const *vehicle{simulation::Train->Dynamic()};
|
||||
if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (vehicle->InteriorLightLevel <= 0.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// as bright as the ambient light it used to add to the cab: lost in the daylight
|
||||
auto const luminance{Global.fLuminance * (vehicle->fShade > 0.0f ? vehicle->fShade : 1.0f)};
|
||||
auto const level{vehicle->InteriorLightLevel * std::clamp(1.25f - static_cast<float>(luminance), 0.f, 1.f)};
|
||||
if (level <= 0.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const &bounds{model_bounds(vehicle->mdKabina, Geometry)};
|
||||
if (bounds.extent == glm::vec3{0.f})
|
||||
{
|
||||
return;
|
||||
}
|
||||
// under the ceiling, in the middle of the cab
|
||||
auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}};
|
||||
m_cablamp = light{};
|
||||
m_cablamp.position = vehicle->GetWorldPosition(glm::dvec3{lamp});
|
||||
m_cablamp.color = to_linear(vehicle->InteriorLight) * (level * Global.gfx_lights_cab_intensity);
|
||||
// within the cab: with no shadows the lamp would light the track below and the body of the vehicle around it
|
||||
m_cablamp.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f);
|
||||
m_cablampon = (max_component(m_cablamp.color) > 0.f);
|
||||
}
|
||||
|
||||
void opengl46_local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
|
||||
{
|
||||
// visibility, lod ranges, transforms and animations as in opengl46_renderer::Render_Alpha(TSubModel *)
|
||||
for (auto *submodel{Submodel}; submodel != nullptr; submodel = submodel->Next)
|
||||
{
|
||||
if ((submodel->iVisible == 0) || (TSubModel::fSquareDist < submodel->fSquareMinDist) || (TSubModel::fSquareDist >= submodel->fSquareMaxDist))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto transform{Transform};
|
||||
if (submodel->iFlags & 0xC000)
|
||||
{
|
||||
if (submodel->fMatrix != nullptr)
|
||||
{
|
||||
transform *= glm::make_mat4(submodel->fMatrix->readArray());
|
||||
}
|
||||
if (submodel->b_aAnim != TAnimType::at_None)
|
||||
{
|
||||
submodel->RaAnimation(transform, submodel->b_aAnim);
|
||||
}
|
||||
}
|
||||
if (submodel->eType == TP_FREESPOTLIGHT)
|
||||
{
|
||||
add_light_point(*submodel, transform, Placement, Brightness);
|
||||
}
|
||||
if (submodel->Child != nullptr)
|
||||
{
|
||||
walk(submodel->Child, transform, Placement, Brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
|
||||
{
|
||||
// lit points: with self-illumination, past its threshold. the compartments of vehicles set it with their lights
|
||||
if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const diffuse{Submodel.DiffuseOverride.r < 0.f ? // -1 indicates no override
|
||||
glm::vec3{Submodel.f4Diffuse} :
|
||||
Submodel.DiffuseOverride};
|
||||
// hotspotpower of the model; this renderer keeps it apart from the colour
|
||||
auto const multiplier{Submodel.diffuseMultiplier > 0.f ? Submodel.diffuseMultiplier : 1.f};
|
||||
auto const color{to_linear(diffuse) * (Brightness * multiplier * Submodel.fVisible * std::min(Submodel.f4Emision.a, 1.f))};
|
||||
if (max_component(color) <= LIGHT_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
light lightpoint;
|
||||
lightpoint.position = glm::dvec3{Placement * glm::dvec4{glm::dvec3{Transform[3]}, 1.0}};
|
||||
auto const axis{glm::vec3{glm::dmat3{Placement} * glm::dvec3{-glm::vec3{Transform[2]}}}};
|
||||
lightpoint.direction = (glm::length2(axis) > 0.f ? glm::normalize(axis) : glm::vec3{0.f, -1.f, 0.f});
|
||||
lightpoint.color = color;
|
||||
// the cone of the glare: where the point is seen from is where it shines. the glare goes from no brightness at the falloff
|
||||
// angle to full at the hotspot angle, which some models have the wrong way round
|
||||
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);
|
||||
m_lights.emplace_back(lightpoint);
|
||||
}
|
||||
|
||||
bool opengl46_local_lights::has_light_points(TModel3d *Model)
|
||||
{
|
||||
auto lookup{m_modellights.find(Model)};
|
||||
if (lookup != m_modellights.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
auto found{false};
|
||||
std::vector<TSubModel const *> pending{Model->Root};
|
||||
while ((false == found) && (false == pending.empty()))
|
||||
{
|
||||
auto const *submodel{pending.back()};
|
||||
pending.pop_back();
|
||||
for (; submodel != nullptr; submodel = submodel->Next)
|
||||
{
|
||||
if (submodel->eType == TP_FREESPOTLIGHT)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (submodel->Child != nullptr)
|
||||
{
|
||||
pending.emplace_back(submodel->Child);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_modellights.emplace(Model, found);
|
||||
return found;
|
||||
}
|
||||
|
||||
opengl46_local_lights::cell_data const &opengl46_local_lights::cell_instances(scene::basic_cell &Cell, bool const Rebuild)
|
||||
{
|
||||
// light points are drawn in the translucent phase, so the instances which have them are on the translucent list of the cell
|
||||
auto &instances{Cell.m_instancetranslucent};
|
||||
auto &data{m_cells[&Cell]};
|
||||
if (Rebuild || (data.source_size != instances.size()) || (data.source_data != static_cast<void const *>(instances.data())))
|
||||
{
|
||||
data.instances.clear();
|
||||
for (auto *instance : instances)
|
||||
{
|
||||
if ((instance->pModel != nullptr) && has_light_points(instance->pModel))
|
||||
{
|
||||
data.instances.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
data.source_size = instances.size();
|
||||
data.source_data = instances.data();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
opengl46_local_lights::section_data const &opengl46_local_lights::section(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry)
|
||||
{
|
||||
auto lookup{m_sections.find(Section)};
|
||||
if (lookup != m_sections.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
section_data data;
|
||||
glm::mat4 transform{1.f};
|
||||
if (find_parent_transform(Model->Root, Section, glm::mat4{1.f}, transform))
|
||||
{
|
||||
glm::vec3 min{std::numeric_limits<float>::max()};
|
||||
glm::vec3 max{std::numeric_limits<float>::lowest()};
|
||||
extend_bounds(Section, transform, Geometry, false, min, max, data.has_light_points);
|
||||
if (min.x <= max.x)
|
||||
{
|
||||
data.center = 0.5f * (min + max);
|
||||
data.extent = 0.5f * (max - min);
|
||||
}
|
||||
}
|
||||
return m_sections.emplace(Section, data).first->second;
|
||||
}
|
||||
|
||||
opengl46_local_lights::section_data const &opengl46_local_lights::model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry)
|
||||
{
|
||||
auto lookup{m_models.find(Model)};
|
||||
if (lookup != m_models.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
section_data data;
|
||||
glm::vec3 min{std::numeric_limits<float>::max()};
|
||||
glm::vec3 max{std::numeric_limits<float>::lowest()};
|
||||
extend_bounds(Model->Root, glm::mat4{1.f}, Geometry, true, min, max, data.has_light_points);
|
||||
if (min.x <= max.x)
|
||||
{
|
||||
data.center = 0.5f * (min + max);
|
||||
data.extent = 0.5f * (max - min);
|
||||
}
|
||||
return m_models.emplace(Model, data).first->second;
|
||||
}
|
||||
|
||||
std::string opengl46_local_lights::info() const
|
||||
{
|
||||
return "local lights: " + std::to_string(m_lights.size()) + " (scenery " + std::to_string(m_scenerycount) + ", vehicles " + std::to_string(m_vehiclecount) + ", compartments " +
|
||||
std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lamp" : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) +
|
||||
", gather " + to_string(m_gathertime, 2) + " ms";
|
||||
}
|
||||
|
||||
void opengl46_local_lights::draw_debug_ui()
|
||||
{
|
||||
ImGui::TextUnformatted("local lights (need clustered lights)");
|
||||
ImGui::Checkbox("light points of scenery models", &Global.gfx_lights_scenery);
|
||||
ImGui::Checkbox("light points and compartments of vehicles", &Global.gfx_lights_vehicles);
|
||||
ImGui::Checkbox("cab lamp as a light", &Global.gfx_lights_cab);
|
||||
ImGui::SliderFloat("light point brightness", &Global.gfx_lights_freespot_intensity, 0.f, 50.f);
|
||||
ImGui::SliderFloat("compartment brightness", &Global.gfx_lights_compartment_intensity, 0.f, 10.f);
|
||||
ImGui::SliderFloat("cab lamp brightness", &Global.gfx_lights_cab_intensity, 0.f, 10.f);
|
||||
ImGui::SliderFloat("cab ambient kept", &Global.gfx_lights_cab_bounce, 0.f, 1.f);
|
||||
ImGui::SliderFloat("range, m", &Global.gfx_lights_range, 0.f, 1000.f);
|
||||
ImGui::Text("gathered %u: scenery %u, vehicles %u, compartments %u%s", static_cast<unsigned>(m_lights.size()), m_scenerycount, m_vehiclecount, m_compartmentcount,
|
||||
m_cablampon ? ", cab lamp" : "");
|
||||
ImGui::Text("in the light buffer %u, over capacity %u", m_placed, m_dropped);
|
||||
ImGui::Text("gather %.2f ms (cpu)", m_gathertime);
|
||||
}
|
||||
125
rendering/opengl46/opengl46locallights.h
Normal file
125
rendering/opengl46/opengl46locallights.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace scene
|
||||
{
|
||||
class basic_region;
|
||||
class basic_cell;
|
||||
} // namespace scene
|
||||
namespace gfx
|
||||
{
|
||||
class geometrybank_manager;
|
||||
}
|
||||
class TModel3d;
|
||||
class TSubModel;
|
||||
class TAnimModel;
|
||||
class TDynamicObject;
|
||||
|
||||
// lights of the opengl 4.6 renderer other than the sun and the vehicle headlights, which go to the light clusters:
|
||||
// - the light points (freespot lights) of scenery models and of vehicles, taken the way the experimental renderer takes them:
|
||||
// a point which is visible, has self-illumination (emission) and is past its self-illumination threshold shines as a spot
|
||||
// light along the -z axis of its submodel, with the cone of its glare. lamps of scenery models switched with Light_On / Light_Off
|
||||
// and the self-illumination of vehicle compartments (TDynamicObject::Sections) work through the same state
|
||||
// - the lit compartments of vehicles (low poly interior sections) without light points of their own: a point light in the middle
|
||||
// - the lamp of the occupied cab: a point light under the top of the cab model, in place of the ambient light it used to add
|
||||
// gathered once per frame around the camera, written to the light buffer by opengl46_renderer::Update_Lights()
|
||||
class opengl46_local_lights
|
||||
{
|
||||
public:
|
||||
struct light
|
||||
{
|
||||
glm::dvec3 position; // world space
|
||||
glm::vec3 direction{0.f, -1.f, 0.f}; // world space, unit; axis of the cone
|
||||
glm::vec3 color; // linear, the brightness included
|
||||
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
|
||||
};
|
||||
|
||||
opengl46_local_lights() = default;
|
||||
opengl46_local_lights(const opengl46_local_lights &) = delete;
|
||||
opengl46_local_lights &operator=(const opengl46_local_lights &) = delete;
|
||||
|
||||
// collects the lights within Global.gfx_lights_range of Camera, for the frame about to be drawn. it sets the state of the
|
||||
// shared models of the instances and vehicles it goes through, as the render does before it draws each of them.
|
||||
// Geometry: the geometry the models were loaded to, for the bounds of compartments and cabs; Framestamp: the number of the frame
|
||||
void gather(glm::dvec3 const &Camera, scene::basic_region &Region, gfx::geometrybank_manager const &Geometry, unsigned int const Framestamp);
|
||||
// forgets the lights and everything kept about the scene, which is about to be replaced (a scenery being loaded)
|
||||
void reset();
|
||||
// the lights of models and vehicles, the most important (closest and brightest) first
|
||||
std::vector<light> const &lights() const
|
||||
{
|
||||
return m_lights;
|
||||
}
|
||||
// the lamp of the occupied cab, when it is on and the cab is drawn; nullptr otherwise
|
||||
light const *cab_lamp() const
|
||||
{
|
||||
return m_cablampon ? &m_cablamp : nullptr;
|
||||
}
|
||||
// counts of the last pass which wrote the lights to the light buffer: placed, and left out for the lack of room
|
||||
void placed(std::uint32_t const Placed, std::uint32_t const Dropped)
|
||||
{
|
||||
m_placed = Placed;
|
||||
m_dropped = Dropped;
|
||||
}
|
||||
// line for the statistics of the debug panel
|
||||
std::string info() const;
|
||||
// controls and counters for the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
struct section_data
|
||||
{
|
||||
glm::vec3 center{0.f}; // middle of the bounds, model space
|
||||
glm::vec3 extent{0.f}; // half the size of the bounds
|
||||
bool has_light_points{false}; // the section holds light points, which light it in place of the one made for it
|
||||
};
|
||||
struct cell_data
|
||||
{
|
||||
std::vector<TAnimModel *> instances; // instances of models with light points
|
||||
// what the list was made from: when the instance list of the cell changes, the list is made anew
|
||||
std::size_t source_size{0};
|
||||
void const *source_data{nullptr};
|
||||
};
|
||||
|
||||
// lights of the light points of the submodel tree, gone through the way the render goes through it. Transform: model space of
|
||||
// Submodel's parent, Placement: model space to world space, Brightness: of a light point of white colour
|
||||
void walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness);
|
||||
void add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness);
|
||||
void gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp);
|
||||
void gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness, float const Compartmentbrightness);
|
||||
void gather_cab_lamp(gfx::geometrybank_manager const &Geometry);
|
||||
bool has_light_points(TModel3d *Model);
|
||||
cell_data const &cell_instances(scene::basic_cell &Cell, bool const Rebuild);
|
||||
section_data const §ion(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry);
|
||||
section_data const &model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry);
|
||||
|
||||
std::vector<light> m_lights;
|
||||
light m_cablamp;
|
||||
bool m_cablampon{false};
|
||||
std::unordered_map<TModel3d const *, bool> m_modellights; // whether the model has light points
|
||||
std::unordered_map<scene::basic_cell const *, cell_data> m_cells;
|
||||
std::unordered_map<TSubModel const *, section_data> m_sections; // compartments, by their submodel
|
||||
std::unordered_map<TModel3d const *, section_data> m_models; // bounds of cab models
|
||||
bool m_editorseen{false}; // the editor was on in the last frame: the instance lists of the cells may have changed
|
||||
// statistics
|
||||
std::uint32_t m_scenerycount{0};
|
||||
std::uint32_t m_vehiclecount{0};
|
||||
std::uint32_t m_compartmentcount{0};
|
||||
std::uint32_t m_placed{0};
|
||||
std::uint32_t m_dropped{0};
|
||||
double m_gathertime{0.0}; // milliseconds, smoothed
|
||||
};
|
||||
@@ -617,6 +617,14 @@ bool opengl46_renderer::Render()
|
||||
// used, the directional part of the ambient light
|
||||
m_fogcolor = Global.FogColor;
|
||||
m_sky.update(m_sunlight, m_fogcolor, simulation::Environment.skydome(), simulation::Environment.sun().getDirection(), simulation::Environment.sun().getAngle());
|
||||
// lamps of models and vehicles around the camera and the cab lamp, for the views of the frame to take into their light buffers
|
||||
if ((false == Global.gfx_skiprendering) && (simulation::Region != nullptr))
|
||||
m_locallights.gather(Global.pCamera.Pos, *simulation::Region, m_geometry, m_framestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// a scenery is being loaded: the cells, instances and models the lights were taken from may be gone
|
||||
m_locallights.reset();
|
||||
}
|
||||
// generate new frame
|
||||
opengl_texture::reset_unit_cache();
|
||||
@@ -739,7 +747,8 @@ void opengl46_renderer::SwapBuffers()
|
||||
+ "particles: " + to_string( m_colorpass.draw_stats.particles, 7 )
|
||||
+ "\nlights: " + to_string( static_cast<int>( m_lights_lit ), 7 )
|
||||
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), "
|
||||
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) );
|
||||
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
|
||||
+ "\n" + m_locallights.info();
|
||||
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
||||
m_debugstatstext +=
|
||||
"\ncluster lists: " + std::to_string( cullstats->index_count )
|
||||
@@ -778,13 +787,15 @@ void opengl46_renderer::draw_debug_ui()
|
||||
auto const ¶ms{clusters->params()};
|
||||
ImGui::Text("grid %u x %u x %u, %s", gl::CLUSTER_GRID_X, gl::CLUSTER_GRID_Y, gl::CLUSTER_GRID_Z, params.valid ? "built" : "none (orthographic view)");
|
||||
ImGui::Text("near %.3f m, far %.1f m", params.znear, params.zfar);
|
||||
ImGui::Text("lights %u (sun excluded), %u over capacity of %u", m_lights_lit, m_lights_dropped, static_cast<unsigned>(gl::MAX_LIGHTS - 1));
|
||||
ImGui::Text("headlights %u, %u over capacity of %u", m_lights_lit, m_lights_dropped, static_cast<unsigned>(gl::MAX_LIGHTS - 1));
|
||||
if (auto const cullstats = clusters->stats())
|
||||
{
|
||||
ImGui::Text("index list %u of %u", cullstats->index_count, gl::CLUSTER_COUNT * gl::MAX_LIGHTS_PER_CLUSTER);
|
||||
ImGui::Text("overflow: %u lights dropped from full clusters (limit %u each), peak %u", cullstats->overflow_count, gl::MAX_LIGHTS_PER_CLUSTER, cullstats->overflow_peak);
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
m_locallights.draw_debug_ui();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
@@ -1019,7 +1030,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
glDebug("render opaque cab");
|
||||
model_ubs.future = glm::mat4();
|
||||
auto const *vehicle{ simulation::Train->Dynamic() };
|
||||
if( vehicle->InteriorLightLevel > 0.f ) {
|
||||
// the lamp lighting the shadows of the sun, unless it is a light of its own
|
||||
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
|
||||
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
|
||||
}
|
||||
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
|
||||
@@ -1052,7 +1064,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
{
|
||||
model_ubs.future = glm::mat4();
|
||||
auto *vehicle { simulation::Train->Dynamic() };
|
||||
if( vehicle->InteriorLightLevel > 0.f ) {
|
||||
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
|
||||
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
|
||||
}
|
||||
if (Global.Overcast > 1.0f)
|
||||
@@ -3978,8 +3990,10 @@ bool opengl46_renderer::Render_cab(TDynamicObject const *Dynamic, float const Li
|
||||
auto const old_ambient { light_data.ambient };
|
||||
auto const luminance { Global.fLuminance * ( Dynamic->fShade > 0.0f ? Dynamic->fShade : 1.0f ) };
|
||||
if( Lightlevel > 0.f ) {
|
||||
// crude way to light the cabin, until we have something more complete in place
|
||||
light_data.ambient += packed_vec3( ( Dynamic->InteriorLight * Lightlevel ) * std::clamp( 1.25f - (float)luminance, 0.f, 1.f ) );
|
||||
// crude way to light the cabin, until we have something more complete in place. with the lamp of the cab among the
|
||||
// lights a share of it is kept for the light which bounces around the cab
|
||||
auto const share { m_cablamplit ? Global.gfx_lights_cab_bounce : 1.f };
|
||||
light_data.ambient += packed_vec3( ( Dynamic->InteriorLight * Lightlevel ) * std::clamp( 1.25f - (float)luminance, 0.f, 1.f ) * share );
|
||||
upload_light_header();
|
||||
}
|
||||
|
||||
@@ -6039,6 +6053,18 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
size_t light_i = 1;
|
||||
m_lights_dropped = 0;
|
||||
|
||||
// the lights other than the headlights are only taken from the lists of the clusters. the lamp of the occupied cab goes first:
|
||||
// it is next to the camera, and it is not to be the one left out of a full cluster
|
||||
auto const locallights{Headlights && Clustered};
|
||||
m_cablamplit = false;
|
||||
if( locallights ) {
|
||||
if( auto const *lamp{ m_locallights.cab_lamp() } ) {
|
||||
Write_Local_Light( light_data.lights[ light_i++ ], *lamp, camera, mv );
|
||||
m_cablamplit = true;
|
||||
}
|
||||
}
|
||||
auto const headlightsstart{light_i};
|
||||
|
||||
for (auto const &scenelight : Lights.data)
|
||||
{
|
||||
if (!Headlights || scenelight.intensity == 0.f) {
|
||||
@@ -6137,11 +6163,28 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
|
||||
++light_i;
|
||||
}
|
||||
m_lights_lit = static_cast<uint32_t>(light_i - headlightsstart);
|
||||
// lamps of models and vehicles, the most important first, those which may be seen lighting something in this view
|
||||
if( locallights ) {
|
||||
uint32_t placed{ 0 };
|
||||
uint32_t dropped{ 0 };
|
||||
for( auto const &local : m_locallights.lights() ) {
|
||||
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ local.position, local.range } ) ) {
|
||||
continue;
|
||||
}
|
||||
if( light_i >= gl::MAX_LIGHTS ) {
|
||||
++dropped;
|
||||
continue;
|
||||
}
|
||||
Write_Local_Light( light_data.lights[ light_i++ ], local, camera, mv );
|
||||
++placed;
|
||||
}
|
||||
m_locallights.placed( placed, dropped );
|
||||
}
|
||||
// every light is there for the clusters to be culled from, while the shading loop over the lights keeps to the number
|
||||
// of them it always went through
|
||||
// of headlights it always went through (without the clusters only the headlights are written, from the first slot on)
|
||||
light_data.lights_total = static_cast<uint32_t>(light_i);
|
||||
m_lights_lit = static_cast<uint32_t>(light_i - 1);
|
||||
light_data.lights_count = 1 + std::min<uint32_t>(static_cast<uint32_t>(light_i - 1), static_cast<uint32_t>(std::max(Global.DynamicLightCount, 0)));
|
||||
light_data.lights_count = 1 + std::min<uint32_t>(m_lights_lit, static_cast<uint32_t>(std::max(Global.DynamicLightCount, 0)));
|
||||
light_data.lights_clustered = Clustered ? 1 : 0;
|
||||
light_data.env_ggx = ( Global.gfx_envmap_ggx && m_envmap_bound ) ? 1 : 0;
|
||||
// fill sunlight data
|
||||
@@ -6169,6 +6212,29 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
light_ssbo->update(&light_data, 0, offsetof(gl::light_data, lights) + sizeof(gl::light_element) * light_i);
|
||||
}
|
||||
|
||||
void opengl46_renderer::Write_Local_Light(gl::light_element &Light, opengl46_local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View)
|
||||
{
|
||||
// a cone wider than a half of the space is no limit for the culling, but the shading still fades the light out of it
|
||||
Light.type = gl::light_element::SPOT;
|
||||
Light.pos = View * glm::vec4(glm::vec3{Source.position - Camera}, 1.0f);
|
||||
Light.dir = glm::normalize(glm::vec3{View * glm::vec4(Source.direction, 0.0f)});
|
||||
Light.in_cutoff = Source.cos_inner;
|
||||
Light.out_cutoff = Source.cos_outer;
|
||||
if (Source.cos_outer <= -1.f)
|
||||
{
|
||||
// all around
|
||||
Light.type = gl::light_element::POINT;
|
||||
}
|
||||
Light.color = Source.color;
|
||||
Light.intensity = 1.0f;
|
||||
Light.ambient = 0.0f;
|
||||
// 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};
|
||||
Light.headlight_weights = glm::vec4{0.0f};
|
||||
}
|
||||
|
||||
void opengl46_renderer::Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize)
|
||||
{
|
||||
glDebug("Update_Clusters");
|
||||
|
||||
@@ -35,6 +35,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
#include "rendering/opengl46/opengl46ao.h"
|
||||
#include "rendering/opengl46/opengl46locallights.h"
|
||||
|
||||
// bare-bones render controller, in lack of anything better yet
|
||||
class opengl46_renderer : public gfx_renderer {
|
||||
@@ -351,8 +352,11 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
|
||||
// writes the lighting of the pass about to be drawn to the light buffer: the sun, the ambient light and the fog, and the headlights of Lights
|
||||
// unless Headlights is false, as in the pass which draws the environment map, where lights of vehicles would only get baked into the map.
|
||||
// Clustered tells the shading to take the headlights from the lists of the clusters (see Cull_Lights()) and not to go through the first few
|
||||
// Clustered tells the shading to take the headlights from the lists of the clusters (see Cull_Lights()) and not to go through the first few.
|
||||
// with Headlights and Clustered the cab lamp (first) and the lamps of models and vehicles (after the headlights) are written as well
|
||||
void Update_Lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
|
||||
// writes a light of a model or a vehicle, or the cab lamp, to the slot of the light buffer, in the view space of the pass
|
||||
void Write_Local_Light(gl::light_element &Light, opengl46_local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
|
||||
// brings the light cluster grid of the viewport up to date with the projection of the color pass being drawn.
|
||||
// Targetsize is the size in pixels of what the pass draws to, which gl_FragCoord of the shading counts in
|
||||
void Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize);
|
||||
@@ -481,6 +485,10 @@ class opengl46_renderer : public gfx_renderer {
|
||||
// headlights placed in the light buffer by the last Update_Lights(), and those which are on but did not fit in it
|
||||
uint32_t m_lights_lit = 0;
|
||||
uint32_t m_lights_dropped = 0;
|
||||
// lights of models and vehicles and the cab lamp, gathered once per frame for the light clusters
|
||||
opengl46_local_lights m_locallights;
|
||||
// the last Update_Lights() placed the cab lamp in the light buffer: the cab is lit by it, not by an addition to the ambient light
|
||||
bool m_cablamplit = false;
|
||||
|
||||
double m_precipitationrotation;
|
||||
|
||||
|
||||
@@ -44,8 +44,9 @@ float light_range(light_s light)
|
||||
float denominator = b + sqrt(b * b - 4.0 * a * c);
|
||||
return (denominator > 1e-12) ? min(-2.0 * c / denominator, UNLIMITED_RANGE) : UNLIMITED_RANGE;
|
||||
}
|
||||
// point and spot lights fall off with the square of the distance
|
||||
return sqrt(k);
|
||||
// point and spot lights fall off with the square of the distance; the shading ends them at 1 / sqrt(quadratic) where it is set
|
||||
float range = sqrt(k);
|
||||
return (light.quadratic > 0.0) ? min(range, inversesqrt(light.quadratic)) : range;
|
||||
}
|
||||
|
||||
bool sphere_touches_box(vec3 center, float radius, vec3 lower, vec3 upper)
|
||||
@@ -103,8 +104,9 @@ bool light_reaches_box(light_s light, float range, vec3 lower, vec3 upper)
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
return box_in_headlight_beam(light.headlight_projection, center, extent);
|
||||
|
||||
if (light.type == LIGHT_SPOT)
|
||||
// the cone is tested against the sphere around the box, which lets some of the corners through
|
||||
if ((light.type == LIGHT_SPOT) && (light.out_cutoff > 0.0))
|
||||
// the cone is tested against the sphere around the box, which lets some of the corners through. a cone wider than
|
||||
// a half of the space is taken as the whole sphere
|
||||
return sphere_touches_cone(center - light.pos, length(extent), normalize(light.dir), light.out_cutoff);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -229,6 +229,29 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
|
||||
return part * 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;
|
||||
|
||||
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
|
||||
// and y the specular part. 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
|
||||
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);
|
||||
if (light.type == LIGHT_SPOT)
|
||||
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
|
||||
if (atten <= 0.0)
|
||||
return vec2(0.0);
|
||||
vec2 part = calc_light(light_dir, fragnormal);
|
||||
// the highlight of a tiny light on a polished surface is kept within what the image can take
|
||||
return vec2(part.x, min(part.y, 16.0) * part.x) * 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
|
||||
// space w, which is 1 / gl_FragCoord.w. a tile is kept in the grid, as the far edge of the render target and rounding
|
||||
// right below it give one past the last
|
||||
@@ -240,9 +263,9 @@ uint fragment_cluster()
|
||||
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
|
||||
}
|
||||
|
||||
// adds the light of the headlights which reach the fragment to its colour. these are the ones in the list of its cluster
|
||||
// adds the light of the headlights and lamps which reach the fragment to its colour. these are the ones in the list of its cluster
|
||||
// when the lights are clustered (the sun is not on the list, the callers take care of it), and the first lights_count - 1 of
|
||||
// lights[] otherwise
|
||||
// lights[] otherwise, which are headlights only
|
||||
vec3 add_headlights(vec3 fragcolor, vec3 fragnormal)
|
||||
{
|
||||
// worked out before the loops, which the fragments of a quad do not leave together
|
||||
@@ -255,7 +278,7 @@ vec3 add_headlights(vec3 fragcolor, vec3 fragnormal)
|
||||
for (uint i = 0U; i < range.count; i++)
|
||||
{
|
||||
light_s light = lights[cluster_light_indices[range.offset + i]];
|
||||
vec2 part = calc_headlights(light, fragnormal, dpdx, dpdy);
|
||||
vec2 part = (light.type == LIGHT_HEADLIGHTS) ? calc_headlights(light, fragnormal, dpdx, dpdy) : calc_local_light(light, fragnormal);
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1593,6 +1593,14 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
|
||||
Parser >> gfx_postfx_ao_slices >> gfx_postfx_ao_steps;
|
||||
gfx_postfx_ao_slices = std::clamp( gfx_postfx_ao_slices, 1, 8 );
|
||||
gfx_postfx_ao_steps = std::clamp( gfx_postfx_ao_steps, 1, 16 ); }
|
||||
else if( Token == "gfx.lights.scenery" ) { readswitch( gfx_lights_scenery ); }
|
||||
else if( Token == "gfx.lights.vehicles" ) { readswitch( gfx_lights_vehicles ); }
|
||||
else if( Token == "gfx.lights.cab" ) { readswitch( gfx_lights_cab ); }
|
||||
else if( Token == "gfx.lights.freespot.intensity" ) { readvalue( gfx_lights_freespot_intensity, 0.f, 1000.f ); }
|
||||
else if( Token == "gfx.lights.compartment.intensity" ) { readvalue( gfx_lights_compartment_intensity, 0.f, 100.f ); }
|
||||
else if( Token == "gfx.lights.cab.intensity" ) { readvalue( gfx_lights_cab_intensity, 0.f, 100.f ); }
|
||||
else if( Token == "gfx.lights.cab.bounce" ) { readvalue( gfx_lights_cab_bounce, 0.f, 1.f ); }
|
||||
else if( Token == "gfx.lights.range" ) { readvalue( gfx_lights_range, 0.f, 2000.f ); }
|
||||
else { return false; }
|
||||
|
||||
return true;
|
||||
@@ -1859,6 +1867,14 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "gfx.postfx.ao.strength", gfx_postfx_ao_strength );
|
||||
export_as_text( Output, "gfx.postfx.ao.bounce", gfx_postfx_ao_bounce );
|
||||
Output << "gfx.postfx.ao.quality " << gfx_postfx_ao_slices << " " << gfx_postfx_ao_steps << "\n";
|
||||
export_as_text( Output, "gfx.lights.scenery", gfx_lights_scenery );
|
||||
export_as_text( Output, "gfx.lights.vehicles", gfx_lights_vehicles );
|
||||
export_as_text( Output, "gfx.lights.cab", gfx_lights_cab );
|
||||
export_as_text( Output, "gfx.lights.freespot.intensity", gfx_lights_freespot_intensity );
|
||||
export_as_text( Output, "gfx.lights.compartment.intensity", gfx_lights_compartment_intensity );
|
||||
export_as_text( Output, "gfx.lights.cab.intensity", gfx_lights_cab_intensity );
|
||||
export_as_text( Output, "gfx.lights.cab.bounce", gfx_lights_cab_bounce );
|
||||
export_as_text( Output, "gfx.lights.range", gfx_lights_range );
|
||||
export_as_text( Output, "python.enabled", python_enabled );
|
||||
export_as_text( Output, "python.threadedupload", python_threadedupload );
|
||||
export_as_text( Output, "python.uploadmain", python_uploadmain );
|
||||
|
||||
@@ -332,6 +332,16 @@ struct global_settings {
|
||||
float gfx_postfx_ao_bounce { 0.3f }; // albedo of the surfaces assumed for the light coming back after more bounces
|
||||
int gfx_postfx_ao_slices { 3 }; // directions searched around each pixel
|
||||
int gfx_postfx_ao_steps { 4 }; // samples on each side of a direction
|
||||
// local lights of the opengl 4.6 renderer, besides the sun and the vehicle headlights (debug window "Light clusters").
|
||||
// they go through the light clusters, so they need gfx_lights_clustered
|
||||
bool gfx_lights_scenery { true }; // light points (freespot lights) of scenery models light their surroundings
|
||||
bool gfx_lights_vehicles { true }; // light points of vehicles and their lit compartments light their surroundings
|
||||
bool gfx_lights_cab { true }; // the lamp of the occupied cab is a point light instead of an addition to the ambient light
|
||||
float gfx_lights_freespot_intensity { 10.f }; // brightness of a light point of a model
|
||||
float gfx_lights_compartment_intensity { 1.f }; // brightness of the light of a lit vehicle compartment
|
||||
float gfx_lights_cab_intensity { 1.5f }; // brightness of the cab lamp
|
||||
float gfx_lights_cab_bounce { 0.3f }; // share of the former ambient light of the cab lamp kept, for the light which bounces around the cab
|
||||
float gfx_lights_range { 300.f }; // distance from the camera the lights of models and vehicles are taken from, metres
|
||||
|
||||
float ui_fontsize = 13.0f;
|
||||
float ui_scale = 1.0f;
|
||||
|
||||
Reference in New Issue
Block a user