17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-19 02:39:19 +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:
2026-10-16 00:07:08 +02:00
parent 5f365a00c5
commit fd98d4eea5
12 changed files with 859 additions and 29 deletions

View File

@@ -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

View File

@@ -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),