vram-guild / canvas

Shader-Based Minecraft Renderer for Fabric
GNU Lesser General Public License v3.0
418 stars 40 forks source link

Colored flood fill lighting #428

Open spiralhalo opened 1 year ago

spiralhalo commented 1 year ago

Overview

This PR adds client-side colored lights that works via flood fill propagation. The light source data is gathered from the Block Light API, which falls back to Item Light API, then finally vanilla MC light level.

Light propagation hooks to region (chunk) building for block updates, which was already optimized through occlusion culling. This ensures minimal memory usage.

The actual propagation and texture uploads are done in the Render Thread. Only queued regions are updated to minimize impact on frame time.

API

Below is the description of the API. You may also find a basic implementation of the API in this PR's files and by turning on the Abstract pipeline.

Block Light API

Is described as .json files located in namespace:lights/block and namespace:lights/fluid paths.

The design of the json API is based on both Material Map and Item Light APIs.

There are two root objects: "defaultLight", which specifies the default light values, and "variants" which specifies light values for each block state variant.

The light values are described with "red", "green", and "blue", but with one main difference which is that light intensity is specified as "lightLevel" and uses values of 0-15. Each field is optional and will fall back to the "defaultLight" value when absent. If "lightLevel" is absent from "defaultLight", it will fall back to the server-side value.

Example json:

{
  "defaultLight": {
    "red": 0.2,
    "green": 0.2,
    "blue": 0.2
  },
  "variants": {
    "color=red": {
      "lightLevel": 14.0,
      "red": 1.0
    },
    "color=green": {
      "lightLevel": 14.0,
      "green": 1.0
    },
    "color=blue": {
      "lightLevel": 14.0,
      "blue": 1.0
    }
  }
}

Note that as this feature is client-side, it obviously won't affect server-side light levels. To achieve harmony between visuals and gameplay, mods need to synchronize server-side light level via other means. Care needs to be taken as luminance is perceived differently in colored lights than vanilla lighting.

Pipeline API

To enable colored lights, the pipeline needs to contain a coloredLights json object. It may have one optional field useOcclusionData to declare whether the pipeline intends to use occlusion data. Note that occlusion data isn't guaranteed to be available, see the Shader API section for the specifics.

Example json:

  coloredLights: {
    useOcclusionData: false
  }

Pipeline pass shaders may access the light data by passing "frex:textures/auto/colored_lights" to a Sampler2D sampler. For material shaders, see the Shader API section.

Shader API

TODO: explain

frex:shaders/api/sampler.glsl

For material and pipeline write shaders:

#ifdef COLORED_LIGHTS_ENABLED

uniform sampler2D frxs_lightData;

vec4 frx_getLightFiltered(worldPos);
vec4 frx_getLightRaw(worldPos);
vec3 frx_getLight(worldPos, fallback);

#endif

frex:shaders/api/light.glsl

For pipeline pass shaders:

#ifdef COLORED_LIGHTS_ENABLED

vec4 frx_getLightFiltered(sampler2D lightSampler, vec3 worldPos);

vec4 frx_getLightRaw(sampler2D lightSampler, vec3 worldPos);

vec3 frx_getLight(sampler2D lightSampler, vec3 worldPos, vec3 fallback);

bool frx_lightDataExists(sampler2D lightSampler, vec3 worldPos);

#ifdef LIGHT_DATA_HAS_OCCLUSION
struct frx_LightData {
    vec4 light;
    bool isLightSource;
    bool isOccluder;
    bool isFullCube;
};

frx_LightData frx_getLightOcclusionData(sampler2D lightSampler, vec3 worldPos);
#endif

#endif

Pipeline pass shaders may access the light data by passing "frex:textures/auto/colored_lights" to a Sampler2D sampler.

Shader usage technical limitations

Technical Details

TBA (Grondag mostly knows these already as he came up with some of it)

Region building hook

Sparse allocation

Propagation / Region light updates

Future Performance Considerations

The following are features that are not part of this PR, but may be implemented in the future:

Using swap texture to prevent pipeline stalling

Currently not considered due to the rarity of light texture update. This could be beneficial however in situations where light is changed very often, e.g. TNT explosion or multiplayer with many on-screen players.

Doing light propagation in a separate thread

This could help with initial chunk building time by not committing texture update until light propagation finishes in a background thread. Texture and uniform updates still need to happen in the Render thread, naturally.

Light data caching on the disk

This might help alleviate long propagation times by saving and loading propagation results. This however could pose a problem as unlike vanilla lighting, colored lights is client-side and is easily changed through resource packs. Therefore, any form of light cache are expected to be invalidated frequently and care needs to be taken to minimize these invalidation as to not defeat the purpose of having a cache.

In addition to that, this requires benchmarks so that caching (specifically the disk read/write part) is actually faster than computing on-the-fly.

Lastly, it's important to note that this system is non-trivial to implement against the current "volatile" data management system.

Additional Changes

In addition to colored flood fill, there are some incidental changes:

spiralhalo commented 1 year ago

TODO: move the API stuff to Frex before merging