Skip to content

Latest commit

 

History

History
79 lines (55 loc) · 4.13 KB

HOW_DOES_IT_WORK.md

File metadata and controls

79 lines (55 loc) · 4.13 KB

How does it work?

This is a little file explaining how the mod works and hopefully be as detailed as it can to allow people to maintain the code over newer versions and remove the monopole OptiFine has.

Not used by this mod.

UpcraftLP method can be found here: https://gist.github.com/UpcraftLP/93db478535cb3954cc9f3dc98bac91cf

AtomicStryker method can be found here: https://github.com/AtomicStryker/atomicstrykers-minecraft-mods/tree/1.14.3/DynamicLights

Both methods suffer from a "laggy" dynamic lighting, the way the light moves is too much tied to the block positions as it only injects at light level getters in WorldChunk or a lighting provider. It will only provide the dynamic light value of the light source if the block position is the position of the light source.

It's better in a way as the F3 menu will actually report a light value instead of the lightmap coordinates method, but will be significantly be less smooth in its transitions.

Lightmap coordinates method

This method is used by this mod.

Dynamic luminance injection

As @MaryWeeb suggested, WorldRenderer#getLightmapCoordinates is a great entrypoint.

This method gives lightmap coordinates in the format (skyLevel << 20 | blockLevel << 4) and to get the blockLevel from the lightmap use LightmapTextureManager#getBlockLightCoordinates.

The goal is to inject at TAIL into getLightmapCoordinates and get the vanilla value, then get the dynamic value at the specified block position (which is calculated from all the dynamic light sources, their distances and the luminance), if the dynamic value is higher than the block value then we replace it.

This also means that we can't just do like the previous method and only give the source luminance but we also have to calculate the surrounding light level created by the dynamic light sources in a specified range (which is 7.75 to limit chunk rebuilding to 8 chunks (which is still a lot)).

When getting the dynamic light value at the specified position, it has to be a double and not an integer as the light level calculated within the range has to be precise. To modify the lightmap with the dynamic light value, it has to be multiplied by 16.0 instead of using a bitshift to preserve as much as possible the precision.

Dynamic light level at a specified position is calculated in a for-loop with all the dynamic light sources, only the highest light level is kept. The light level is calculated as follow:

// dist being the distance between the light source origin and the position where the
// lightmap coordinates are requested.
double multiplier = 1.0 - dist / 7.75; // 7.75 because of the range
// luminace being the luminance of the dynamic light source.
double lightLevel = multiplier * (double) luminance;

EntityRenderDispatcher#getLight also needs an injection like getLightmapCoordinates, it needs the same replacement of the lightmap value to avoid dark entities in a lit place.

Chunk rebuilding

To apply the dynamic lighting we have to request the affected chunks a rebuild to rebuild the lightmap, which is the most critic part performance-wise. Rebuilding a chunk cost performances, that's why there's multiple mode: FASTEST, FAST and FANCY. The fast modes will limit the update.

To manage chunk rebuilding, we inject at WorldRenderer#setupTerrain (WorldRenderer#render at WorldRenderer#setupTerrain invoke if Sodium is present), and if fast mode then we check when the last update was done. If we can update, then we get the current dynamic light source position and get its chunk. After that we get the surrounding affect chunks and build a new Set of chunk coordinates which need to be updated.

Shader-based method

This would be more ideal performance-wise as it would give the smoothness of the second method and avoid too many chunk rebuilding.

Note: the shader-based method will break in the case of any other renderer registered. For compatibility, it's the less ideal.