Rendering
This page explains what Lambent does in a frame. You need it to work on Lambent itself, to draw something in step with its lights, or to work out why a light looks wrong.
When it draws
Lambent hooks the game renderer right after the level is drawn and before the hand:
- A mixin on
GameRenderer.renderLevel, right after it has the level drawn, keeps the view the level was drawn with: model view, projection (with view bobbing and the nausea warp in it), camera and partial tick. - Then (and, under a shader pack, after the pack’s last pass)
LightRenderer.afterLevelruns:- every source is asked for its lights (
Frame); - the spot lights’ shadow maps are drawn;
- the light pass lights the scene;
- every
AfterLightsregistered withLambentClient.afterLightsdraws.
- every source is asked for its lights (
So everything Lambent draws, and everything drawn after it, sits over the finished level, a shader pack’s included.
One frame
| Step | Class | What it does |
|---|---|---|
| Collect | Frame |
Sources hand over their lights, up to the room for each kind. |
| Shut-in air | Enclosure |
A few short block probes along each beam, eased toward, say how shut in the air is: more dust in a barn than in a field. |
| Shadow blocks | Occluders |
The blocks round each beam as meshes, built a little each frame and kept by key. See below. |
| Shadow maps | ShadowMaps |
Each spot light’s depth from just behind its lamps, one tile of a shared atlas each: the block meshes, then every entity in the beam but the holder. |
| Point shadows | Solids, Skyline |
Only while a point light is lit: which blocks round the eye stop light, as a 3D texture and a height map. |
| Bloom | Bloom |
The lenses at half size where the scene shows them, blurred twice into a glow and a halo. |
| Dust | Dust |
Motes placed and lit on the GPU, drawn as specks over the scene behind its depth. |
| Light pass | LightPass, lamps.fsh |
The light in the air at half size, then the lit scene. |
| After | AfterLights |
Whatever mods draw over the lights. |
Shadow blocks
The shadow map does not draw the level’s chunk meshes. It draws meshes of its own, built from the blocks round each beam:
- Plain full blocks give only the faces between them and something else.
- Every other block with a collision shape gives its model’s faces, so a fence is posts and rails and a door is a door.
- Where a texture has holes (a door’s windows, leaves, the clear middle of glass) the light goes through them.
- See-through colored blocks (stained glass, ice) go in a mesh of their own that tints the light instead of stopping it. The tint and how far the nearest pane is are kept in the shadow map’s color.
- Only faces turned away from the lamp are drawn. Nothing lit can shadow itself, however glancing the light, and anything behind a block is still behind that block’s back face.
The meshes cover a margin round the beam, longer in the direction the holder is moving, so they last a while. They are rebuilt a little each frame: when the beam nears the edge, at once when a block in them changes (a block change mixin reports it), and every so often in case one changed unseen. The old mesh is drawn until the new one is ready, so a rebuild never holds up a frame.
The light pass
lamps.fsh runs twice:
- The air, at half size, because it is soft and the costliest part: how much light each beam and point light scatters toward the eye along the view ray, through the shadow maps, with the haze thicker where the air is shut in.
- The lit scene: each pixel’s world position from the depth buffer, lit by every beam that reaches it (shadow-mapped, tinted through glass), every point light inside its box and not blocked, plus the air from step 1, the glows and their glare, and the bloom.
The added light is weighed against ambient, the lightmap color the scene was drawn with round each
light, so a lamp brightens a dark surface by about what it should and never turns it past white.
Where the hardware lets a pass read the pixel it writes (a texture barrier), the lit scene goes straight over the scene and leaves untouched what no light reaches. Elsewhere it is written to a target of its own and copied back.
Drawing after the lights
LambentClient.afterLights((modelView, projection, camera, partialTick) -> {
// draw over the lit level, with the view it was drawn with
});
Veltorque draws its smoke and fire here, so they sit over the lit level and in front of the beams’ haze.
While the shadow maps draw
Entities in a beam are drawn into its shadow map with their own renderers. During that,
LambentClient.drawingShadows() returns true, and:
- name tags are skipped;
- on 1.20.1 and 1.21.1, Fabulous graphics’ extra item and translucent targets stay unbound, since binding them would move the drawing off the shadow atlas (on 26.x the entities draw into a render pass over the atlas, so nothing needs holding back);
- a mod’s renderer can check
drawingShadows()to skip what should cast no shadow. Veltorque’s vehicles skip their own shadow and the back-face trimming they do for the eye.
Shader packs
Lambent checks net.irisshaders.iris.api.v0.IrisApi.isShaderPackInUse() by reflection, so Iris and
Oculus are optional. Under a pack, entity shadows in the beams are off (the pack’s programs replace
the entity renderers’), and Lambent’s own programs, buffers and meshes keep the pack from standing in
for them.
Lambent’s GL state
Lambent links its own GL programs and binds its own vertex arrays and buffers on every version. It puts back the state Minecraft tracks (the bound program, textures, vertex array, blend and depth) afterwards. On 1.20.1 and 1.21.1, two helpers are public for mods that draw with their own programs after the lights:
int program = LambentClient.program(mc, vertexId, fragmentId, "Position"); // throws IOException
LambentClient.texture(unit, textureId); // leaves GL_TEXTURE0 active
On 26.x the helpers are gone. Lambent sets state through GlStateManager wherever Minecraft keeps
its own copy, unbinds Minecraft’s sampler objects from the units it uses, and from 26.2 handles
Minecraft’s reversed depth. It needs the OpenGL backend: on Vulkan it draws no lights. See
Version differences.