# Clustered Lighting Specification

## 1. GPU Data structures

Data is organized into 3 main textures:

 - Clusters. `type: UINT, format: RGBA, dimension: 3D, filtering: nearest(none)`
   - R - Offset into lookup texture for lights
   - G - number of lights
   - B - Offset into lookup texture for decals
   - A - number of decals
 - Lookup. `type: UINT, format: R, dimensions: 2D, filtering: nearest(none)`
   - R - encoded reference into data texture, where actual lighting information is stored
 - Data. `type: FLOAT, format: RGBA, dimensions: 2D, filtering: nearest(none)`
   - RGBA - single `word` of data, you can think of it as a collection of `VEC4`s

Below is a glsl snippet with initialization of uniforms for these textures:
```glsl
uniform usampler3D fp_t_light_clusters;
uniform usampler2D fp_t_light_lookup;
uniform sampler2D fp_t_light_data;
```

## 2. Light Data Format

Each light is represented as a collection of `VEC4`s inside the `Data` texture. 

 - Point Light:
   - `[ position.x, position.y, position.z, radius ]`
   - `[ color.r, color.g, color.b, intensity ]`
 - Directional Light:
   - `[ direction.x, direction.y, direction.z, UNUSED ]`
   - `[ color.r, color.g, color.b, intensity ]`

## 3. Light reference encoding

Lookup texture contains integers that encode both the address of light data, and the type of light found at that address, this is done by encoding light type into lower 2 bits of a UINT32 

Light type codes:
 - 0 - PointLight
 - 1 - Directional Light
 - 2 - Spotlight

Encoding snippet in JavaScript:
```js
/**
 *
 * @param {number} address
 * @param {AbstractLight} light
 * @returns {number}
 */
function encode_light_descriptor(address, light) {
    const light_type = encode_light_type(light);
    return light_type | (address << 2);
}
```

Decoding snippet in GLSL
```glsl
ivec2 address_to_data_texture_coordinates(uint address){
    // Lookup texture has 128 width
    uint lookup_index_x = address % 128u;
    uint lookup_index_y = address >> 7;

    return ivec2(int(lookup_index_x),int(lookup_index_y));
}

// ...
uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;

uint type = (light_descriptor) & 0x3u;
uint light_address = light_descriptor >> 2;
```

## 4. Auxiliary material

Convert projection-space depth to linear space
```glsl
float convert_depth_to_linear(in float d){
    float d_n = 2.0*d - 1.0;

    float f = fp_f_camera_far;
    float n = fp_f_camera_near;

    float fn = f*n;
    
    float z_diff = f - n;
    
    float denominator = (f + n - d_n * z_diff );

    float z_view = (2.0*fn) / denominator;

    return (z_view - n) / z_diff;
}
```

Fetching cluster information from fragment shader inside a forward pass
```glsl
    // v3_cluster_resolution is resolution of our cluster 3d texture
    ivec3 v3_cluster_resolution = textureSize(fp_t_light_tiles, 0);
    ivec3 v3_cluster_position = ivec3(  clip_v.x * float(v3_cluster_resolution.x), clip_v.y*float(v3_cluster_resolution.y),  (clip_v.z)*float(v3_cluster_resolution.z) );

    uvec4 fp_cluster_metadata = texelFetch( fp_t_light_tiles, v3_cluster_position, 0 ).rgba;
```

Perform lighting (point-light only)
```glsl
// read light data
for(uint i=0u; i < fp_cluster_metadata.y; i++){
    uint lookup_index = fp_cluster_metadata.x + i;

    uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;

    uint type = (light_descriptor) & 0x3u;
    uint light_address = light_descriptor >> 2;
    

    if(type == 0u){
        // point light
        
        vec4 light_data_0 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address), 0);
        vec4 light_data_1 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address+1u), 0);
        
        vec3 light_position = light_data_0.xyz;
        float light_radius = light_data_0.w;
        
        vec3 light_color = light_data_1.xyz;
        float light_intensity = light_data_1.w;
        
        PointLight pointlight;
        
        pointlight.position = light_position;
        pointlight.distance = light_radius;
        pointlight.color = light_color*light_intensity;
        pointlight.decay = 1.0;
        
        fp_getPointDirectLightIrradiance( pointlight, directLight, vViewPosition );
        RE_Direct( directLight, geometry, material, reflectedLight );
    
    } else if(type == 1u){
        // directional light
        
        // ...
    }
}
```

## References:
- [Dr.Strangelight or: How I learned to Stop Worrying and Love the Cluster](https://discourse.threejs.org/t/dr-strangelight-or-how-i-learned-to-stop-worrying-and-love-the-cluster/23104)
- [Practical Clustered Shading by Emil Persson](http://www.humus.name/Articles/PracticalClusteredShading.pdf)

---
Author: Alexander Goldring, Company Named Limited

Private and confidential, copyright (c) Company Named Limited, 2022
