From d3e3c55fd6272cf456df215f4c0791588f4f9b2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?=
 <fernandogarcialinan@gmail.com>
Date: Wed, 6 Nov 2019 01:37:49 +0100
Subject: [PATCH] Compositor: Initial support for clustered shading under the
 ALS pipeline

---
 Compositor/ALS/als.xml                        |  14 +-
 Compositor/Effects/model-default.eff          |   9 ++
 Compositor/Shaders/ALS/clustered-include.frag | 133 ++++++++++++++++++
 Compositor/Shaders/ALS/generic-base.vert      |   3 +-
 Compositor/Shaders/ALS/model-base.frag        |   4 +-
 5 files changed, 154 insertions(+), 9 deletions(-)
 create mode 100644 Compositor/Shaders/ALS/clustered-include.frag

diff --git a/Compositor/ALS/als.xml b/Compositor/ALS/als.xml
index 860627a89..84855f100 100644
--- a/Compositor/ALS/als.xml
+++ b/Compositor/ALS/als.xml
@@ -76,13 +76,13 @@
     <name>forward</name>
     <type>scene</type>
     <effect-scheme>als-lighting</effect-scheme>
-    <!--
-        <clustered-shading>
-        <tile-size>128</tile-size>
-        <num-threads>4</num-threads>
-        <depth-slices>16</depth-slices>
-        </clustered-shading>
-    -->
+
+    <clustered-shading>
+      <tile-size>128</tile-size>
+      <num-threads>1</num-threads>
+      <depth-slices>1</depth-slices>
+    </clustered-shading>
+
     <use-shadow-pass>csm0</use-shadow-pass>
     <use-shadow-pass>csm1</use-shadow-pass>
     <use-shadow-pass>csm2</use-shadow-pass>
diff --git a/Compositor/Effects/model-default.eff b/Compositor/Effects/model-default.eff
index aa3575b8b..72e1c2abc 100644
--- a/Compositor/Effects/model-default.eff
+++ b/Compositor/Effects/model-default.eff
@@ -481,6 +481,15 @@
         <fragment-shader>Shaders/ALS/noise.frag</fragment-shader>
         <fragment-shader>Shaders/ALS/filters.frag</fragment-shader>
         <fragment-shader>Shaders/ALS/shadows-include.frag</fragment-shader>
+        <fragment-shader>Shaders/ALS/clustered-include.frag</fragment-shader>
+        <uniform-block-binding>
+          <name>PointLightBlock</name>
+          <index>5</index>
+        </uniform-block-binding>
+        <uniform-block-binding>
+          <name>SpotLightBlock</name>
+          <index>6</index>
+        </uniform-block-binding>
       </program>
       <uniform>
         <name>visibility</name>
diff --git a/Compositor/Shaders/ALS/clustered-include.frag b/Compositor/Shaders/ALS/clustered-include.frag
new file mode 100644
index 000000000..71f2f8c26
--- /dev/null
+++ b/Compositor/Shaders/ALS/clustered-include.frag
@@ -0,0 +1,133 @@
+#version 150
+
+uniform usampler3D fg_ClusteredLightGrid;
+uniform usamplerBuffer fg_ClusteredLightIndices;
+uniform int fg_ClusteredTileSize;
+uniform float fg_ClusteredSliceScale;
+uniform float fg_ClusteredSliceBias;
+
+const bool debug = true;
+const float shininess = 16.0;
+
+struct PointLight {
+    vec4 position;
+    vec4 ambient;
+    vec4 diffuse;
+    vec4 specular;
+    vec4 attenuation;
+};
+
+struct SpotLight {
+    vec4 position;
+    vec4 direction;
+    vec4 ambient;
+    vec4 diffuse;
+    vec4 specular;
+    vec4 attenuation;
+    float cos_cutoff;
+    float exponent;
+};
+
+layout (std140) uniform PointLightBlock {
+    PointLight pointLights[256];
+};
+layout (std140) uniform SpotLightBlock {
+    SpotLight spotLights[256];
+};
+
+
+vec3 addColors(vec3 a, vec3 b)
+{
+    return 0.14 * log(exp(a/0.14) + exp(b/0.14));
+}
+
+// @param p Fragment position in view space.
+// @param n Fragment normal in view space.
+vec3 addClusteredLightsContribution(vec3 inputColor, vec3 p, vec3 n)
+{
+    int slice = int(max(log2(-p.z) * fg_ClusteredSliceScale
+                        + fg_ClusteredSliceBias, 0.0));
+    ivec3 clusterCoord = ivec3(gl_FragCoord.xy / fg_ClusteredTileSize, slice);
+    uvec3 cluster = texelFetch(fg_ClusteredLightGrid,
+                               clusterCoord,
+                               0).rgb;
+    uint startIndex = cluster.r;
+    uint pointCount = cluster.g;
+    uint spotCount  = cluster.b;
+
+    vec3 color = vec3(0.0);
+
+    for (uint i = uint(0); i < pointCount; ++i) {
+        uint lightListIndex = texelFetch(fg_ClusteredLightIndices,
+                                         int(startIndex + i)).r;
+        PointLight light = pointLights[lightListIndex];
+
+        float range = light.attenuation.w;
+        vec3 toLight = light.position.xyz - p;
+        // Ignore fragments outside the light volume
+        if (dot(toLight, toLight) > (range * range))
+            continue;
+
+        ////////////////////////////////////////////////////////////////////////
+        // Actual lighting
+
+        float d = length(toLight);
+        float att = 1.0 / (light.attenuation.x             // constant
+                           + light.attenuation.y * d       // linear
+                           + light.attenuation.z * d * d); // quadratic
+        vec3 lightDir = normalize(toLight);
+        float NdotL = max(dot(n, lightDir), 0.0);
+
+        vec3 Iamb  = light.ambient.rgb;
+        vec3 Idiff = light.diffuse.rgb * NdotL;
+        vec3 Ispec = vec3(0.0);
+
+        if (NdotL > 0.0) {
+            vec3 halfVector = normalize(lightDir + normalize(-p));
+            float NdotHV = max(dot(n, halfVector), 0.0);
+            Ispec = light.specular.rgb * att * pow(NdotHV, shininess);
+        }
+
+        color += addColors(color, (Iamb + Idiff + Ispec) * att);
+    }
+
+    for (uint i = uint(0); i < spotCount; ++i) {
+        uint lightListIndex = texelFetch(fg_ClusteredLightIndices,
+                                         int(startIndex + i)).r;
+        SpotLight light = spotLights[lightListIndex];
+
+        vec3 toLight = light.position.xyz - p;
+
+        ////////////////////////////////////////////////////////////////////////
+        // Actual lighting
+
+        float d = length(toLight);
+        float att = 1.0 / (light.attenuation.x             // constant
+                           + light.attenuation.y * d       // linear
+                           + light.attenuation.z * d * d); // quadratic
+
+        vec3 lightDir = normalize(toLight);
+
+        float spotDot = dot(-lightDir, light.direction.xyz);
+        if (spotDot < light.cos_cutoff)
+            continue;
+
+        att *= pow(spotDot, light.exponent);
+
+        float NdotL = max(dot(n, lightDir), 0.0);
+
+        vec3 Iamb  = light.ambient.rgb;
+        vec3 Idiff = light.diffuse.rgb * NdotL;
+        vec3 Ispec = vec3(0.0);
+
+        if (NdotL > 0.0) {
+            vec3 halfVector = normalize(lightDir + normalize(-p));
+            float NdotHV = max(dot(n, halfVector), 0.0);
+            Ispec = light.specular.rgb * att * pow(NdotHV, shininess);
+        }
+
+        color += (Iamb + Idiff + Ispec) * att;
+    }
+
+    return clamp(color + inputColor, 0.0, 1.0);
+}
diff --git a/Compositor/Shaders/ALS/generic-base.vert b/Compositor/Shaders/ALS/generic-base.vert
index cd1150e29..6895e834b 100644
--- a/Compositor/Shaders/ALS/generic-base.vert
+++ b/Compositor/Shaders/ALS/generic-base.vert
@@ -22,6 +22,7 @@
 varying vec4 diffuse_term;
 varying vec3 normal;
 varying vec3 relPos;
+varying vec4 ecPosition;
 
 varying float yprime_alt;
 varying float mie_angle;
@@ -75,7 +76,7 @@ void main()
 
 // this code is copied from default.vert
 
-    //vec4 ecPosition = gl_ModelViewMatrix * gl_Vertex;
+    ecPosition = gl_ModelViewMatrix * gl_Vertex;
     gl_Position = ftransform();
     gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
     normal = gl_NormalMatrix * gl_Normal;
diff --git a/Compositor/Shaders/ALS/model-base.frag b/Compositor/Shaders/ALS/model-base.frag
index ecd756ff1..437b924fe 100644
--- a/Compositor/Shaders/ALS/model-base.frag
+++ b/Compositor/Shaders/ALS/model-base.frag
@@ -6,7 +6,7 @@
 varying vec4 diffuse_term;
 varying vec3 normal;
 varying vec3 relPos;
-
+varying vec4 ecPosition;
 
 uniform sampler2D texture;
 
@@ -57,6 +57,7 @@ vec3 landing_light(in float offset, in float offsetv);
 vec3 filter_combined (in vec3 color) ;
 
 float getShadowing();
+vec3 addClusteredLightsContribution(vec3 inputColor, vec3 v, vec3 N);
 
 float luminance(vec3 color)
 {
@@ -318,6 +319,7 @@ fragColor.rgb = mix(hazeColor  + secondary_light * fog_backscatter(mvisibility),
 
 }
 
+fragColor.rgb = addClusteredLightsContribution(fragColor.rgb, ecPosition.xyz, normal);
 fragColor.rgb = filter_combined(fragColor.rgb);
 
 gl_FragColor = fragColor;