diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index c9ac4da2..31c342ae 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -2,7 +2,6 @@ #include "compiled.inc" #include "std/gbuffer.glsl" -#include "std/light.glsl" #ifdef _Clusters #include "std/clusters.glsl" #endif @@ -80,14 +79,11 @@ uniform mat4 invVP; #ifdef _ShadowMap #ifdef _SinglePoint //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #endif #ifdef _Clusters //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -109,21 +105,36 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #else //!uniform samplerCubeShadow shadowMapPoint[1]; //!uniform vec2 lightProj; #endif #endif #ifdef _Clusters - //!uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _ShadowMapAtlas + #ifdef _SingleAtlas + uniform sampler2DShadow shadowMapAtlas; + #endif + #endif + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasPoint; + #endif + //!uniform vec4 pointLightDataArray[4]; + #else + //!uniform samplerCubeShadow shadowMapPoint[4]; + #endif //!uniform vec2 lightProj; #ifdef _Spot - //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + //!uniform sampler2DShadow shadowMapSpot[4]; + #endif + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -132,7 +143,13 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -159,6 +176,8 @@ uniform sampler2D texClouds; uniform float time; #endif +#include "std/light.glsl" + in vec2 texCoord; in vec3 viewRay; out vec4 fragColor; @@ -289,10 +308,32 @@ void main() { #ifdef _ShadowMap #ifdef _CSM - svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias); + svisibility = shadowTestCascade( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , eye, p + n * shadowsBias * 10, shadowsBias + ); #else - vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); - if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias); + vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); + if (lPos.w > 0.0) svisibility = shadowTest( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , lPos.xyz / lPos.w, shadowsBias + ); #endif #endif @@ -335,7 +376,18 @@ void main() { int casi, casindex; mat4 LWVP = getCascadeMat(distance(eye, p), casi, casindex); #endif - fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVP, p, n, sunDir, lightPlane.y, shadowMap); + fragColor.rgb += fragColor.rgb * SSSSTransmittance( + LWVP, p, n, sunDir, lightPlane.y, + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + ); } #endif diff --git a/Shaders/deferred_light/deferred_light.json b/Shaders/deferred_light/deferred_light.json index 06d5a80f..d0d02f1f 100755 --- a/Shaders/deferred_light/deferred_light.json +++ b/Shaders/deferred_light/deferred_light.json @@ -199,43 +199,37 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "pointLightDataArray", + "link": "_pointLightsAtlasArray", + "ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot3", - "link": "_biasLightWorldViewProjectionMatrixSpot3", - "ifdef": ["_Spot", "_ShadowMap"] - }, - { - "name": "LWVPSpot0", - "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot1", - "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot2", - "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_LTC", "_ShadowMap"] } ], diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index c549d1ff..cc8ce302 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -3,7 +3,6 @@ #include "compiled.inc" #include "std/gbuffer.glsl" #include "std/math.glsl" -#include "std/light_mobile.glsl" #ifdef _Clusters #include "std/clusters.glsl" #endif @@ -47,21 +46,36 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #else //!uniform samplerCubeShadow shadowMapPoint[1]; //!uniform vec2 lightProj; #endif #endif #ifdef _Clusters - //!uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _ShadowMapAtlas + #ifdef _SingleAtlas + uniform sampler2DShadow shadowMapAtlas; + #endif + #endif + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasPoint; + #endif + //!uniform vec4 pointLightDataArray[4]; + #else + //!uniform samplerCubeShadow shadowMapPoint[4]; + #endif //!uniform vec2 lightProj; #ifdef _Spot - //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + //!uniform sampler2DShadow shadowMapSpot[4]; + #endif + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -70,7 +84,13 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -90,6 +110,8 @@ uniform float pointBias; #endif #endif +#include "std/light_mobile.glsl" + in vec2 texCoord; in vec3 viewRay; out vec4 fragColor; @@ -168,10 +190,32 @@ void main() { #ifdef _ShadowMap #ifdef _CSM - svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias, shadowmapSize * vec2(shadowmapCascades, 1.0)); + svisibility = shadowTestCascade( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , eye, p + n * shadowsBias * 10, shadowsBias + ); #else - vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); - if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias, shadowmapSize); + vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); + if (lPos.w > 0.0) svisibility = shadowTest( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , lPos.xyz / lPos.w, shadowsBias + ); #endif #endif diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 118bcdf3..620e27e1 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -138,24 +138,38 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "pointLightDataArray", + "link": "_pointLightsAtlasArray", + "ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] } ], "vertex_shader": "../include/pass_viewray.vert.glsl", diff --git a/Shaders/std/clusters.glsl b/Shaders/std/clusters.glsl index c9af62bf..04570ef6 100644 --- a/Shaders/std/clusters.glsl +++ b/Shaders/std/clusters.glsl @@ -1,7 +1,4 @@ -const int maxLights = 16; -const int maxLightsCluster = 4; // Ensure fast loop unroll before going higher -const float clusterNear = 3.0; const vec3 clusterSlices = vec3(16, 16, 16); int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) { diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index 293dad04..61c26771 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -21,27 +21,41 @@ #endif #ifdef _ShadowMap -#ifdef _SinglePoint - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; - #else - uniform samplerCubeShadow shadowMapPoint[1]; - uniform vec2 lightProj; + #ifdef _SinglePoint + #ifdef _Spot + #ifndef _LTC + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[1]; + uniform vec2 lightProj; + #endif #endif -#endif -#ifdef _Clusters - uniform samplerCubeShadow shadowMapPoint[4]; - uniform vec2 lightProj; - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + #ifdef _Clusters + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif + uniform vec2 lightProj; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasPoint; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[4]; + #endif + #ifdef _Spot + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; + #endif #endif #endif -#endif #ifdef _LTC uniform vec3 lightArea0; @@ -51,17 +65,14 @@ uniform vec3 lightArea3; uniform sampler2D sltcMat; uniform sampler2D sltcMag; #ifdef _ShadowMap - #ifndef _Spot +#ifndef _Spot #ifdef _SinglePoint - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; #endif #ifdef _Clusters - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif #endif @@ -132,24 +143,24 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); } else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[1] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); } else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[2] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); } else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[3] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); } #endif @@ -168,26 +179,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters - if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); - } - else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); - } - else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); - } - else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); - } + vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); + #ifdef _ShadowMapAtlas + direct *= shadowTest( + #ifndef _SingleAtlas + shadowMapAtlasSpot + #else + shadowMapAtlas + #endif + , lPos.xyz / lPos.w, bias + ); + #else + if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); + else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); + else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); + else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); + #endif #endif } #endif @@ -207,10 +218,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif #endif #ifdef _Clusters - if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); - else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); - else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); - else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #ifdef _ShadowMapAtlas + direct *= PCFFakeCube( + #ifndef _SingleAtlas + shadowMapAtlasPoint + #else + shadowMapAtlas + #endif + , ld, -l, bias, lightProj, n, index + ); + #else + if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); + else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); + else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); + else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #endif #endif } #endif diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index 86558dd7..f6a966c9 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -11,21 +11,33 @@ #ifdef _SinglePoint #ifdef _Spot uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform mat4 LWVPSpot[1]; #else uniform samplerCubeShadow shadowMapPoint[1]; uniform vec2 lightProj; #endif #endif #ifdef _Clusters - uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif uniform vec2 lightProj; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasPoint; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[4]; + #endif #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif #endif @@ -62,26 +74,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters - if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); - } - else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); - } - else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); - } - else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); - } + vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); + #ifdef _ShadowMapAtlas + direct *= shadowTest( + #ifndef _SingleAtlas + shadowMapAtlasSpot + #else + shadowMapAtlas + #endif + , lPos.xyz / lPos.w, bias + ); + #else + if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); + else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); + else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); + else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); + #endif #endif } #endif @@ -96,10 +108,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); #endif #ifdef _Clusters - if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); - else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); - else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); - else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #ifdef _ShadowMapAtlas + direct *= PCFFakeCube( + #ifndef _SingleAtlas + shadowMapAtlasPoint + #else + shadowMapAtlas + #endif + , ld, -l, bias, lightProj, n, index + ); + #else + if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); + else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); + else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); + else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #endif #endif } #endif diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index d610676a..81a12cc4 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -11,6 +11,41 @@ uniform vec4 casData[shadowmapCascades * 4 + 4]; uniform vec2 smSizeUniform; #endif +#ifdef _ShadowMap + #ifdef _Clusters + #ifdef _ShadowMapAtlas + uniform vec4 pointLightDataArray[maxLightsCluster * 6]; + #endif + #endif +#endif + +#ifdef _ShadowMapAtlas +// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168 +// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/ +vec2 sampleCube(vec3 dir, out int faceIndex) { + vec3 dirAbs = abs(dir); + float ma; + vec2 uv; + if(dirAbs.z >= dirAbs.x && dirAbs.z >= dirAbs.y) { + faceIndex = dir.z < 0.0 ? 5 : 4; + ma = 0.5 / dirAbs.z; + uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y); + } + else if(dirAbs.y >= dirAbs.x) { + faceIndex = dir.y < 0.0 ? 3 : 2; + ma = 0.5 / dirAbs.y; + uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z); + } + else { + faceIndex = dir.x < 0.0 ? 1 : 0; + ma = 0.5 / dirAbs.x; + uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y); + } + // downscale uv a little to hide seams + return uv * 0.9976 * ma + 0.5; +} +#endif + float PCF(sampler2DShadow shadowMap, const vec2 uv, const float compare, const vec2 smSize) { float result = texture(shadowMap, vec3(uv + (vec2(-1.0, -1.0) / smSize), compare)); result += texture(shadowMap, vec3(uv + (vec2(-1.0, 0.0) / smSize), compare)); @@ -50,6 +85,148 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo return result / 9.0; } +#ifdef _ShadowMapAtlas +// transform "out-of-bounds" coordinates to the correct face/coordinate system +vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { + if (uv.x < 0.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 1) { // X- + newFaceIndex = 5; // Z- + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 1; // X- + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 1; // X- + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 1; // X- + } + else { // Z- + newFaceIndex = 0; // X+ + } + uv = vec2(1.0 + uv.x, uv.y); + } + else if (uv.x > 1.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 5; // Z- + } + else if (faceIndex == 1) { // X- + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 0; // X+ + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 0; // X+ + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 0; // X+ + } + else { // Z- + newFaceIndex = 1; // X- + } + uv = vec2(1.0 - uv.x, uv.y); + } + else if (uv.y < 0.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 2; // Y+ + } + else if (faceIndex == 1) { // X- + newFaceIndex = 2; // Y+ + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 5; // Z- + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 2; // Y+ + } + else { // Z- + newFaceIndex = 2; // Y+ + } + uv = vec2(uv.x, 1.0 + uv.y); + } + else if (uv.y > 1.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 3; // Y- + } + else if (faceIndex == 1) { // X- + newFaceIndex = 3; // Y- + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 5; // Z- + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 3; // Y- + } + else { // Z- + newFaceIndex = 3; // Y- + } + uv = vec2(uv.x, 1.0 - uv.y); + } else { + newFaceIndex = faceIndex; + } + // cover corner cases too + return uv; +} + +float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index) { + const vec2 smSize = smSizeUniform; // TODO: incorrect... + const float compare = lpToDepth(lp, lightProj) - bias * 1.5; + ml = ml + n * bias * 20; + + int faceIndex = 0; + const int lightIndex = index * 6; + const vec2 uv = sampleCube(ml, faceIndex); + + vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas + float result = texture(shadowMap, vec3(pointLightTile.z * uv + pointLightTile.xy, compare)); + // soft shadowing + int newFaceIndex = 0; + vec2 uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + return result / 9.0; +} +#endif + float shadowTest(sampler2DShadow shadowMap, const vec3 lPos, const float shadowsBias) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; @@ -95,7 +272,7 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) { float shadowTestCascade(sampler2DShadow shadowMap, const vec3 eye, const vec3 p, const float shadowsBias) { #ifdef _SMSizeUniform - vec2 smSize = smSizeUniform * vec2(shadowmapCascades, 1.0); + vec2 smSize = smSizeUniform; #else const vec2 smSize = shadowmapSize * vec2(shadowmapCascades, 1.0); #endif diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index 84ca0a90..e1306b06 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -24,7 +24,7 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform mat4 LWVPSpot[1]; #else uniform samplerCubeShadow shadowMapPoint[1]; uniform vec2 lightProj; @@ -35,10 +35,7 @@ uniform vec2 cameraPlane; uniform vec2 lightProj; #ifdef _Spot uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + uniform mat4 LWVPSpot[maxLightsCluster]; #endif #endif #endif @@ -103,7 +100,7 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter #ifdef _SinglePoint #ifdef _Spot - vec4 lPos = LWVPSpot0 * vec4(curPos, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(curPos, 1.0); visibility = shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, pointBias); float spotEffect = dot(spotDir, normalize(pointPos - curPos)); // lightDir if (spotEffect < spotData.x) { // x - cutoff, y - cutoff - exponent diff --git a/Shaders/volumetric_light/volumetric_light.json b/Shaders/volumetric_light/volumetric_light.json index 06724e62..a8e96a56 100755 --- a/Shaders/volumetric_light/volumetric_light.json +++ b/Shaders/volumetric_light/volumetric_light.json @@ -108,23 +108,32 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] } ], diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 9c57929a..940732a3 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -1,6 +1,7 @@ package armory.renderpath; import iron.RenderPath; +import iron.object.LightObject; class Inc { @@ -39,6 +40,171 @@ class Inc { #end } + #if arm_shadowmap_atlas + public static function updatePointLightAtlasData(): Void { + var atlas = ShadowMapAtlas.shadowMapAtlases.get(ShadowMapAtlas.shadowMapAtlasName("point")); + if (atlas != null) { + if(LightObject.pointLightsData == null) { + LightObject.pointLightsData = new kha.arrays.Float32Array( + LightObject.maxLightsCluster * ShadowMapTile.tilesLightType("point") * 4 ); // max possible visible lights * 6 or 2 (faces) * 4 (xyzw) + } + + var n = iron.Scene.active.lights.length > LightObject.maxLightsCluster ? LightObject.maxLightsCluster : iron.Scene.active.lights.length; + var i = 0; + var j = 0; + for (light in iron.Scene.active.lights) { + if (i >= n) + break; + if (LightObject.discardLightCulled(light)) continue; + if (light.data.raw.type == "point") { + for(k in 0...light.tileOffsetX.length) { + LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx + LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy + LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas + LightObject.pointLightsData[j + 3] = 0; // padding + j += 4; + } + } + i++; + } + } + } + + public static function bindShadowMapAtlas() { + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + path.bindTarget(atlas.target, atlas.target); + } + } + + static function getShadowMapAtlas(atlas:ShadowMapAtlas):String { + inline function createDepthTarget(name: String, size: Int) { + var t = new RenderTargetRaw(); + t.name = name; + t.width = t.height = size; + t.format = "DEPTH16"; + return path.createRenderTarget(t); + } + + var rt = path.renderTargets.get(atlas.target); + // Create shadowmap atlas texture on the fly and replace existing on size change + if (rt == null) { + rt = createDepthTarget(atlas.target, atlas.sizew); + } + else if (atlas.updateRenderTarget) { + atlas.updateRenderTarget = false; + // Resize shadow map + rt.unload(); + rt = createDepthTarget(atlas.target, atlas.sizew); + } + return atlas.target; + } + + public static function drawShadowMapAtlas() { + #if rp_shadowmap + #if rp_probes + // Share shadow map with probe + if (lastFrame == RenderPath.active.frame) + return; + lastFrame = RenderPath.active.frame; + #end + // add new lights to the atlases + for (light in iron.Scene.active.lights) { + if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 + && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { + light.lightInAtlas = ShadowMapAtlas.addLight(light); + } + } + // update point light data before rendering + updatePointLightAtlasData(); + + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + var tilesToRemove = []; + #if arm_shadowmap_atlas_lod + var tilesToChangeSize = []; + #end + + var shadowmap = getShadowMapAtlas(atlas); + path.setTargetStream(shadowmap); + path.clearTarget(null, 1.0); + + for (tile in atlas.activeTiles) { + if (tile.light == null || !tile.light.visible || tile.light.culledLight + || !tile.light.data.raw.cast_shadow || tile.light.data.raw.strength == 0) { + tile.unlockLight = true; + tilesToRemove.push(tile); + continue; + } + + #if arm_shadowmap_atlas_lod + var newTileSize = atlas.getTileSize(tile.light.shadowMapScale); + if (newTileSize != tile.size) { + if (newTileSize == 0) { + tile.unlockLight = true; + tilesToRemove.push(tile); + continue; + } + // queue for size change + tile.newTileSize = newTileSize; + tilesToChangeSize.push(tile); + } + #end + // set the tile offset for this tile and every linked tile to this one + var j = 0; + tile.forEachTileLinked(function (lTile) { + tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew; + tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew; + tile.light.tileScale[j] = lTile.size / atlas.sizew; + j++; + }); + // set shadowmap size for uniform + tile.light.data.raw.shadowmap_size = atlas.sizew; + + path.light = tile.light; + + var face = 0; + var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type); + + tile.forEachTileLinked(function (lTile) { + if (faces > 1) { + #if arm_csm + switch (tile.light.data.raw.type) { + case "sun": tile.light.setCascade(iron.Scene.active.camera, face); + case "point": path.currentFace = face; + } + #else + path.currentFace = face; + #end + face++; + } + path.setCurrentViewportWithOffset(lTile.size, lTile.size, lTile.coordsX, lTile.coordsY); + + path.drawMeshesStream("shadowmap"); + }); + + path.currentFace = -1; + } + path.endStream(); + + #if arm_shadowmap_atlas_lod + for (tile in tilesToChangeSize) { + tilesToRemove.push(tile); + + var newTile = ShadowMapTile.assignTiles(tile.light, atlas, tile); + if (newTile != null) + atlas.activeTiles.push(newTile); + } + // update point light data after changing size of tiles to avoid render issues + updatePointLightAtlasData(); + #end + + for (tile in tilesToRemove) { + atlas.activeTiles.remove(tile); + tile.freeTile(); + } + } + #end // rp_shadowmap + } + #else public static function bindShadowMap() { for (l in iron.Scene.active.lights) { if (!l.visible || l.data.raw.type != "sun") continue; @@ -56,10 +222,15 @@ class Inc { } } - static function shadowMapName(l: iron.object.LightObject): String { - if (l.data.raw.type == "sun") return "shadowMap"; - if (l.data.raw.type == "point") return "shadowMapPoint[" + pointIndex + "]"; - else return "shadowMapSpot[" + spotIndex + "]"; + static function shadowMapName(light: LightObject): String { + switch (light.data.raw.type) { + case "sun": + return "shadowMap"; + case "point": + return "shadowMapPoint[" + pointIndex + "]"; + default: + return "shadowMapSpot[" + spotIndex + "]"; + } } static function getShadowMap(l: iron.object.LightObject): String { @@ -130,6 +301,7 @@ class Inc { #end // rp_shadowmap } + #end public static function applyConfig() { #if arm_config @@ -203,7 +375,11 @@ class Inc { path.setTarget("accum", ["revealage"]); #if rp_shadowmap { + #if arm_shadowmap_atlas + bindShadowMapAtlas(); + #else bindShadowMap(); + #end } #end path.drawMeshes("translucent"); @@ -342,3 +518,490 @@ class Inc { #end } } + +#if arm_shadowmap_atlas +class ShadowMapAtlas { + + public var target: String; + public var baseTileSizeConst: Int; + public var maxAtlasSizeConst: Int; + + public var sizew: Int; + public var sizeh: Int; + + public var currTileOffset = 0; + public var tiles: Array = []; + public var activeTiles: Array = []; + public var depth = 1; + #if arm_shadowmap_atlas_lod + static var tileSizes: Array = []; + static var tileSizeFactor: Array = []; + #end + public var updateRenderTarget = false; + public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type + + function new(light: LightObject) { + + var maxTileSize = shadowMapAtlasSize(light); + this.target = shadowMapAtlasName(light.data.raw.type); + this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize; + this.depth = getSubdivisions(); + this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type); + + #if arm_shadowmap_atlas_lod + if (tileSizes.length == 0) + computeTileSizes(maxTileSize, depth); + #end + + } + + /** + * Adds a light to an atlas. The atlas is decided based on the type of the light + * @param light of type LightObject to be added to an yatlas + * @return if the light was added succesfully + */ + public static function addLight(light: LightObject): Bool { + // check if light can be added based on culling + if (light.culledLight || light.shadowMapScale == 0.0) + return false; + + var atlasName = shadowMapAtlasName(light.data.raw.type); + var atlas = shadowMapAtlases.get(atlasName); + if (atlas == null) { + // create a new atlas + atlas = new ShadowMapAtlas(light); + shadowMapAtlases.set(atlasName, atlas); + } + + // find a free tile for this light + var mainTile = ShadowMapTile.assignTiles(light, atlas, null); + if (mainTile == null) + return false; + // push main tile to active tiles + atlas.activeTiles.push(mainTile); + return true; + } + + static inline function shadowMapAtlasSize(light:LightObject):Int { + // TODO: this can break because we are changing shadowmap_size elsewhere. + return light.data.raw.shadowmap_size; + } + + public function getTileSize(shadowMapScale: Float): Int { + #if arm_shadowmap_atlas_lod + // find the first scale factor that is smaller to the shadowmap scale, and then return the previous one. + var i = 0; + for (sizeFactor in tileSizeFactor) { + if (sizeFactor < shadowMapScale) break; + i++; + } + return tileSizes[i - 1]; + #else + return this.baseTileSizeConst; + #end + } + + #if arm_shadowmap_atlas_lod + static function computeTileSizes(maxTileSize: Int, depth: Int): Void { + // find the highest value based on the calculation done in the cluster code + var base = LightObject.zToShadowMapScale(0, 16); + var subdiv = base / depth; + for(i in 0...depth){ + tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); + tileSizeFactor.push(base); + base -= subdiv; + } + tileSizes.push(0); + tileSizeFactor.push(0.0); + } + #end + + public inline function atlasLimitReached() { + // asume square atlas + return (currTileOffset + 1) * baseTileSizeConst > maxAtlasSizeConst; + } + + public static inline function shadowMapAtlasName(type: String): String { + #if arm_shadowmap_atlas_single_map + return "shadowMapAtlas"; + #else + switch (type) { + case "point": + return "shadowMapAtlasPoint"; + case "sun": + return "shadowMapAtlasSun"; + default: + return "shadowMapAtlasSpot"; + } + #end + } + + public static inline function getSubdivisions(): Int { + #if (rp_shadowmap_atlas_lod_subdivisions == 2) + return 2; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 3) + return 3; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 4) + return 4; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 5) + return 5; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 6) + return 6; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 7) + return 7; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 8) + return 8; + #elseif (!arm_shadowmap_atlas_lod) + return 1; + #end + } + + public static inline function getMaxAtlasSize(type: String): Int { + #if arm_shadowmap_atlas_single_map + #if (rp_shadowmap_atlas_max_size == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size == 16384) + return 16384; + #end + #else + switch (type) { + case "point": { + #if (rp_shadowmap_atlas_max_size_point == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_point == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_point == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_point == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_point == 16384) + return 16384; + #end + } + case "spot": { + #if (rp_shadowmap_atlas_max_size_spot == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_spot == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_spot == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_spot == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_spot == 16384) + return 16384; + #end + } + case "sun": { + #if (rp_shadowmap_atlas_max_size_sun == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_sun == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_sun == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_sun == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_sun == 16384) + return 16384; + #end + } + default: { + #if (rp_shadowmap_atlas_max_size == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size == 16384) + return 16384; + #end + } + + } + #end + } +} + +class ShadowMapTile { + + public var light:Null = null; + public var coordsX:Int; + public var coordsY:Int; + public var size:Int; + public var tiles:Array = []; + public var linkedTile:ShadowMapTile = null; + + #if arm_shadowmap_atlas_lod + public var parentTile: ShadowMapTile = null; + public var activeSubTiles: Int = 0; + public var newTileSize: Int = -1; + + static var tilePattern = [[0, 0], [1, 0], [0, 1], [1, 1]]; + #end + + function new(coordsX: Int, coordsY: Int, size: Int) { + this.coordsX = coordsX; + this.coordsY = coordsY; + this.size = size; + } + + public static function assignTiles(light: LightObject, atlas: ShadowMapAtlas, oldTile: ShadowMapTile): ShadowMapTile { + var tileSize = 0; + + #if arm_shadowmap_atlas_lod + if (oldTile != null && oldTile.newTileSize != -1) { + // reuse tilesize instead of computing it again + tileSize = oldTile.newTileSize; + oldTile.newTileSize = -1; + } + else + #end + tileSize = atlas.getTileSize(light.shadowMapScale); + + if (tileSize == 0) + return null; + + var tiles = []; + tiles = findCreateTiles(light, oldTile, atlas, tilesLightType(light.data.raw.type), tileSize); + + // lock new tiles with light + for (tile in tiles) + tile.lockTile(light); + + return linkTiles(tiles); + } + + static inline function linkTiles(tiles: Array): ShadowMapTile { + if (tiles.length > 1) { + var linkedTile = tiles[0]; + for (i in 1...tiles.length) { + linkedTile.linkedTile = tiles[i]; + linkedTile = tiles[i]; + } + } + return tiles[0]; + } + + static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array { + var tilesFound: Array = []; + + var updateAtlas = false; + while (tilesFound.length < tilesPerLightType) { + findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound); + + if (tilesFound.length < tilesPerLightType) { + tilesFound = []; // empty tilesFound + // skip creating more tiles if limit has been reached + if (atlas.atlasLimitReached()) + break; + + createTiles(atlas.tiles, atlas.baseTileSizeConst, atlas.depth, atlas.currTileOffset, atlas.currTileOffset); + atlas.currTileOffset++; + // update texture to accomodate new size + atlas.updateRenderTarget = true; + atlas.sizew = atlas.sizeh = atlas.currTileOffset * atlas.baseTileSizeConst; + } + } + return tilesFound; + } + + inline static function findTiles(light:LightObject, oldTile: ShadowMapTile, + tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { + #if arm_shadowmap_atlas_lod + if (oldTile != null) { + // reuse children tiles + if (size < oldTile.size) { + oldTile.forEachTileLinked(function(lTile) { + var childTile = findFreeChildTile(lTile, size); + tilesFound.push(childTile); + }); + } + // reuse parent tiles + else { + oldTile.forEachTileLinked(function(lTile) { + // find out if parents tiles are not occupied + var parentTile = findFreeParentTile(lTile, size); + // if parent is free, add it to found tiles + if (parentTile != null) + tilesFound.push(parentTile); + }); + if (tilesFound.length < tilesCount) { + // find naively the rest of the tiles that couldn't be reused + findTilesNaive(light, tiles, size, tilesCount, tilesFound); + } + } + } + else + #end + findTilesNaive(light, tiles, size, tilesCount, tilesFound); + } + + #if arm_shadowmap_atlas_lod + static inline function findFreeChildTile(tile: ShadowMapTile, size: Int): ShadowMapTile { + var childrenTile = tile; + while (size < childrenTile.size) { + childrenTile = childrenTile.tiles[0]; + } + return childrenTile; + } + + static inline function findFreeParentTile(tile: ShadowMapTile, size: Int): ShadowMapTile { + var parentTile = tile; + while (size > parentTile.size) { + parentTile = parentTile.parentTile; + // stop if parent tile is occupied + if (parentTile.activeSubTiles > 1) { + parentTile = null; + break; + } + } + return parentTile; + } + #end + + static function findTilesNaive(light:LightObject, tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { + for (tile in tiles) { + if (tile.size == size) { + if (tile.light == null #if arm_shadowmap_atlas_lod && tile.activeSubTiles == 0 #end) { + tilesFound.push(tile); + // stop after finding enough tiles + if (tilesFound.length == tilesCount) + return; + } + } + else { + // skip over if end of the tree or tile is occupied + if (tile.tiles.length == 0 || tile.light != null) + continue; + findTilesNaive(light, tile.tiles, size, tilesCount, tilesFound); + // skip iterating over the rest of the tiles if found enough + if (tilesFound.length == tilesCount) + return; + } + } + } + + // create a basic tile and subdivide it if needed + public static function createTiles(tiles:Array, size:Int, depth: Int, baseX:Int, baseY:Int) { + var i = baseX; + var j = 0; + var lastTile = tiles.length; + // assume occupied tiles start from 1 line before the base x + var occupiedTiles = baseX - 1; + + while (i >= 0) { + if (i <= occupiedTiles) { // avoid overriding tiles + j = baseY; + } + while (j <= baseY) { + // create base tile of max-size + tiles.push(new ShadowMapTile(size * i, size * j, size)); + #if arm_shadowmap_atlas_lod + tiles[lastTile].tiles = subDivTile(tiles[lastTile], size, size * i, size * j, depth - 1); + #end + lastTile++; + j++; + } + i--; + } + } + + #if arm_shadowmap_atlas_lod + static function subDivTile(parent: ShadowMapTile, size: Int, baseCoordsX: Int, baseCoordsY: Int, depth: Int): Array { + var tileSize = Std.int(size / 2); + + var tiles = []; + + for (i in 0...4) { + var coordsX = baseCoordsX + tilePattern[i][0] * tileSize; + var coordsY = baseCoordsY + tilePattern[i][1] * tileSize; + + var tile = new ShadowMapTile(coordsX, coordsY, tileSize); + tile.parentTile = parent; + + if (depth > 1) + tile.tiles = subDivTile(tile, tileSize, coordsX, coordsY, depth - 1); + tiles.push(tile); + } + + return tiles; + } + #end + + public static inline function tilesLightType(type: String): Int { + switch (type) { + case "sun": + return LightObject.cascadeCount; + case "point": + return 6; + default: + return 1; + } + } + + inline function lockTile(light: LightObject): Void { + if (this.light != null) + return; + this.light = light; + #if arm_shadowmap_atlas_lod + // update the count of used tiles for parents + this.forEachParentTile(function (pTile) { + pTile.activeSubTiles++; + }); + #end + } + + public var unlockLight: Bool = false; + + public function freeTile(): Void { + // prevent duplicates + if (light != null && unlockLight) { + light.lightInAtlas = false; + unlockLight = false; + } + + var linkedTile = this; + var tempTile = this; + while (linkedTile != null) { + linkedTile.light = null; + #if arm_shadowmap_atlas_lod + // update the count of used tiles for parents + linkedTile.forEachParentTile(function (pTile) { + if (pTile.activeSubTiles > 0) + pTile.activeSubTiles--; + }); + #end + + linkedTile = linkedTile.linkedTile; + // unlink linked tiles + tempTile.linkedTile = null; + tempTile = linkedTile; + } + } + + public inline function forEachTileLinked(action: ShadowMapTile->Void): Void { + var linkedTile = this; + while (linkedTile != null) { + action(linkedTile); + linkedTile = linkedTile.linkedTile; + } + } + + #if arm_shadowmap_atlas_lod + public inline function forEachParentTile(action: ShadowMapTile->Void): Void { + var parentTile = this.parentTile; + while (parentTile != null) { + action(parentTile); + parentTile = parentTile.parentTile; + } + } + #end +} +#end diff --git a/Sources/armory/renderpath/RenderPathCreator.hx b/Sources/armory/renderpath/RenderPathCreator.hx index cc676369..5b97a69b 100644 --- a/Sources/armory/renderpath/RenderPathCreator.hx +++ b/Sources/armory/renderpath/RenderPathCreator.hx @@ -7,6 +7,8 @@ class RenderPathCreator { public static var path: RenderPath; + public static var commands: Void->Void = function() {}; + #if (rp_renderer == "Forward") public static var setTargetMeshes: Void->Void = RenderPathForward.setTargetMeshes; public static var drawMeshes: Void->Void = RenderPathForward.drawMeshes; @@ -33,13 +35,22 @@ class RenderPathCreator { #if (rp_renderer == "Forward") RenderPathForward.init(path); - path.commands = RenderPathForward.commands; + path.commands = function() { + RenderPathForward.commands(); + commands(); + } #elseif (rp_renderer == "Deferred") RenderPathDeferred.init(path); - path.commands = RenderPathDeferred.commands; + path.commands = function() { + RenderPathDeferred.commands(); + commands(); + } #elseif (rp_renderer == "Raytracer") RenderPathRaytracer.init(path); - path.commands = RenderPathRaytracer.commands; + path.commands = function() { + RenderPathRaytracer.commands(); + commands(); + } #end return path; } diff --git a/Sources/armory/renderpath/RenderPathDeferred.hx b/Sources/armory/renderpath/RenderPathDeferred.hx index 8f263349..302cbace 100644 --- a/Sources/armory/renderpath/RenderPathDeferred.hx +++ b/Sources/armory/renderpath/RenderPathDeferred.hx @@ -502,8 +502,13 @@ class RenderPathDeferred { #end #if (rp_shadowmap) + // atlasing is exclusive for now + #if arm_shadowmap_atlas + Inc.drawShadowMapAtlas(); + #else Inc.drawShadowMap(); #end + #end // Voxels #if rp_voxelao @@ -572,7 +577,11 @@ class RenderPathDeferred { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end } #end @@ -624,7 +633,11 @@ class RenderPathDeferred { { path.setTarget("singlea"); path.bindTarget("_main", "gbufferD"); + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end path.drawShader("shader_datas/volumetric_light/volumetric_light"); path.setTarget("singleb"); diff --git a/Sources/armory/renderpath/RenderPathForward.hx b/Sources/armory/renderpath/RenderPathForward.hx index 4b2724ab..a72fbfd8 100644 --- a/Sources/armory/renderpath/RenderPathForward.hx +++ b/Sources/armory/renderpath/RenderPathForward.hx @@ -300,7 +300,11 @@ class RenderPathForward { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.drawShadowMapAtlas(); + #else Inc.drawShadowMap(); + #end } #end @@ -352,7 +356,11 @@ class RenderPathForward { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end } #end @@ -466,7 +474,11 @@ class RenderPathForward { { path.setTarget("singlea"); path.bindTarget("_main", "gbufferD"); + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end path.drawShader("shader_datas/volumetric_light/volumetric_light"); path.setTarget("singleb"); diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 2bb6bfa0..03096bb3 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -421,6 +421,9 @@ class DebugConsole extends Trait { var lightHandle = Id.handle(); lightHandle.value = light.data.raw.strength / 10; light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; + #if arm_shadowmap_atlas + ui.text("status: " + (light.culledLight ? "culled" : "rendered")); + #end } else if (Std.is(selectedObject, iron.object.CameraObject)) { selectedType = "(Camera)"; diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index bcd1716b..81c00161 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -43,6 +43,22 @@ def add_world_defs(): if rpdat.rp_shadowmap_cascades != '1': wrd.world_defs += '_CSM' assets.add_khafile_def('arm_csm') + if rpdat.rp_shadowmap_atlas: + assets.add_khafile_def('arm_shadowmap_atlas') + wrd.world_defs += '_ShadowMapAtlas' + if rpdat.rp_shadowmap_atlas_single_map: + assets.add_khafile_def('arm_shadowmap_atlas_single_map') + wrd.world_defs += '_SingleAtlas' + assets.add_khafile_def('rp_shadowmap_atlas_max_size_point={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_point))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size_spot={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_spot))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size_sun={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_sun))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size))) + + assets.add_khafile_def('rp_max_lights_cluster={0}'.format(int(rpdat.rp_max_lights_cluster))) + assets.add_khafile_def('rp_max_lights={0}'.format(int(rpdat.rp_max_lights))) + if rpdat.rp_shadowmap_atlas_lod: + assets.add_khafile_def('arm_shadowmap_atlas_lod') + assets.add_khafile_def('rp_shadowmap_atlas_lod_subdivisions={0}'.format(int(rpdat.rp_shadowmap_atlas_lod_subdivisions))) # SS if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO': if rpdat.rp_ssgi == 'RTGI': @@ -89,9 +105,14 @@ def add_world_defs(): wrd.world_defs += '_Spot' assets.add_khafile_def('arm_spot') - if point_lights == 1: - wrd.world_defs += '_SinglePoint' - elif point_lights > 1: + if not rpdat.rp_shadowmap_atlas: + if point_lights == 1: + wrd.world_defs += '_SinglePoint' + elif point_lights > 1: + wrd.world_defs += '_Clusters' + assets.add_khafile_def('arm_clusters') + else: + wrd.world_defs += '_SMSizeUniform' wrd.world_defs += '_Clusters' assets.add_khafile_def('arm_clusters') diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 6864386d..34589d42 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -3,8 +3,10 @@ import bpy def write(vert, frag): wrd = bpy.data.worlds['Arm'] is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs - frag.add_include('std/clusters.glsl') + frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') frag.add_uniform('vec2 cameraPlane', link='_cameraPlane') frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray') @@ -12,7 +14,12 @@ def write(vert, frag): if is_shadows: frag.add_uniform('bool receiveShadow') frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True) - frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) + if is_shadows_atlas: + if not is_single_atlas: + frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True) + frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True) + else: + frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) vert.add_out('vec4 wvpposition') vert.write('wvpposition = gl_Position;') # wvpposition.z / wvpposition.w @@ -29,11 +36,12 @@ def write(vert, frag): frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);') frag.write('int numPoints = numLights - numSpots;') if is_shadows: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True) - frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True) - frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True) - frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True) - frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) + if is_shadows_atlas and not is_single_atlas: + frag.add_uniform(f'sampler2DShadow shadowMapAtlasSpot', included=True) + elif not is_shadows_atlas: + frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) + # FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files + frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) frag.write('for (int i = 0; i < min(numLights, maxLightsCluster); i++) {') frag.write('int li = int(texelFetch(clustersData, ivec2(clusterI, i + 1), 0).r * 255);') @@ -52,7 +60,7 @@ def write(vert, frag): if is_shadows: frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias if '_Spot' in wrd.world_defs: - frag.write(' , li > numPoints - 1') + frag.write(' , lightsArray[li * 2 + 1].w != 0.0') frag.write(' , lightsArray[li * 2 + 1].w') # cutoff frag.write(' , lightsArraySpot[li].w') # cutoff - exponent frag.write(' , lightsArraySpot[li].xyz') # spotDir diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 1d9d1010..18608347 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -354,6 +354,12 @@ def make_forward_mobile(con_mesh): return is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + shadowmap_sun = 'shadowMap' + if is_shadows_atlas: + shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' + frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 direct = vec3(0.0);') if '_Sun' in wrd.world_defs: @@ -366,7 +372,7 @@ def make_forward_mobile(con_mesh): vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') vert.write('lightPosition = LWVP * spos;') frag.add_uniform('bool receiveShadow') - frag.add_uniform('sampler2DShadow shadowMap') + frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') frag.add_uniform('float shadowsBias', '_sunShadowsBias') frag.write('if (receiveShadow) {') @@ -374,14 +380,14 @@ def make_forward_mobile(con_mesh): frag.add_include('std/shadows.glsl') frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True) frag.add_uniform('vec3 eye', '_cameraPosition') - frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);') + frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);') else: frag.write('if (lightPosition.w > 0.0) {') frag.write(' vec3 lPos = lightPosition.xyz / lightPosition.w;') if '_Legacy' in wrd.world_defs: - frag.write(' svisibility = float(texture(shadowMap, vec2(lPos.xy)).r > lPos.z - shadowsBias);') + frag.write(f' svisibility = float(texture({shadowmap_sun}, vec2(lPos.xy)).r > lPos.z - shadowsBias);') else: - frag.write(' svisibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)).r;') + frag.write(f' svisibility = texture({shadowmap_sun}, vec3(lPos.xy, lPos.z - shadowsBias)).r;') frag.write('}') frag.write('}') # receiveShadow frag.write('direct += basecol * sdotNL * sunCol * svisibility;') @@ -404,8 +410,8 @@ def make_forward_mobile(con_mesh): frag.write('if (receiveShadow) {') if '_Spot' in wrd.world_defs: vert.add_out('vec4 spotPosition') - vert.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0') - vert.write('spotPosition = LWVPSpot0 * spos;') + vert.add_uniform('mat4 LWVPSpotArray[1]', link='_biasLightWorldViewProjectionMatrixSpotArray') + vert.write('spotPosition = LWVPSpotArray[0] * spos;') frag.add_uniform('sampler2DShadow shadowMapSpot[1]') frag.write('if (spotPosition.w > 0.0) {') frag.write(' vec3 lPos = spotPosition.xyz / spotPosition.w;') @@ -532,13 +538,10 @@ def make_forward(con_mesh): frag.add_uniform('sampler2D sltcMag', '_ltcMag', included=True) if '_ShadowMap' in wrd.world_defs: if '_SinglePoint' in wrd.world_defs: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True) + frag.add_uniform('mat4 LWVPSpot[0]', link='_biasLightViewProjectionMatrixSpot0', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True) if '_Clusters' in wrd.world_defs: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True) - frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True) - frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True) - frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True) + frag.add_uniform('mat4 LWVPSpotArray[4]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) if not blend: @@ -605,6 +608,12 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.add_include('std/light.glsl') is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + shadowmap_sun = 'shadowMap' + if is_shadows_atlas: + shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' + frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 albedo = surfaceAlbedo(basecol, metallic);') frag.write('vec3 f0 = surfaceF0(basecol, metallic);') @@ -661,14 +670,14 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.write('float sdotVH = dot(vVec, sh);') if is_shadows: frag.add_uniform('bool receiveShadow') - frag.add_uniform('sampler2DShadow shadowMap') + frag.add_uniform(f'sampler2DShadow {shadowmap_sun}', top=True) frag.add_uniform('float shadowsBias', '_sunShadowsBias') frag.write('if (receiveShadow) {') if '_CSM' in wrd.world_defs: frag.add_include('std/shadows.glsl') frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True) frag.add_uniform('vec3 eye', '_cameraPosition') - frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);') + frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);') else: if tese is not None: tese.add_out('vec4 lightPosition') @@ -685,7 +694,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): vert.write('lightPosition = LWVP * spos;') frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write('const vec2 smSize = shadowmapSize;') - frag.write('svisibility = PCF(shadowMap, lPos.xy, lPos.z - shadowsBias, smSize);') + frag.write(f'svisibility = PCF({shadowmap_sun}, lPos.xy, lPos.z - shadowsBias, smSize);') frag.write('}') # receiveShadow if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs: frag.write('svisibility *= 1.0 - traceShadow(voxels, voxpos, sunDir);') @@ -703,7 +712,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.add_uniform('float pointBias', link='_pointShadowsBias') if '_Spot' in wrd.world_defs: # Skip world matrix, already in world-space - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True) + frag.add_uniform('mat4 LWVPSpot[1]', link='_biasLightViewProjectionMatrixSpotArray', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True) else: frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True) diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 7b00ed6d..cecd6ebc 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -179,6 +179,7 @@ class Shader: self.includes = [] self.ins = [] self.outs = [] + self.uniforms_top = [] self.uniforms = [] self.constants = [] self.functions = {} @@ -205,6 +206,10 @@ class Shader: if not self.has_include(s): self.includes.append(s) + def add_include_front(self, s): + if not self.has_include(s): + self.includes.insert(0, s) + def add_in(self, s): if s not in self.ins: self.ins.append(s) @@ -213,7 +218,7 @@ class Shader: if s not in self.outs: self.outs.append(s) - def add_uniform(self, s, link=None, included=False): + def add_uniform(self, s, link=None, included=False, top=False): ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] @@ -236,8 +241,12 @@ class Shader: ar[0] = 'floats' ar[1] = ar[1].split('[', 1)[0] self.context.add_constant(ar[0], ar[1], link=link) - if not included and s not in self.uniforms: - self.uniforms.append(s) + if top: + if not included and s not in self.uniforms_top: + self.uniforms_top.append(s) + else: + if not included and s not in self.uniforms: + self.uniforms.append(s) def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0): """ @@ -375,6 +384,8 @@ class Shader: s += 'layout(triangle_strip) out;\n' s += 'layout(max_vertices=3) out;\n' + for a in self.uniforms_top: + s += 'uniform ' + a + ';\n' for a in self.includes: s += '#include "' + a + '"\n' if self.geom_passthrough: diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index e5ba673b..246c505c 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -242,6 +242,62 @@ class ArmRPListItem(bpy.types.PropertyGroup): rp_autoexposure: BoolProperty(name="Auto Exposure", description="Adjust exposure based on luminance", default=False, update=update_renderpath) rp_compositornodes: BoolProperty(name="Compositor", description="Draw compositor nodes", default=True, update=update_renderpath) rp_shadows: BoolProperty(name="Shadows", description="Enable shadow casting", default=True, update=update_renderpath) + rp_max_lights: EnumProperty( + items=[('4', '4', '4'), + ('8', '8', '8'), + ('16', '16', '16'), + ('24', '24', '24'), + ('32', '32', '32'), + ('64', '64', '64'),], + name="Max Lights", description="Max number of lights that can be visible in the screen", default='16') + rp_max_lights_cluster: EnumProperty( + items=[('4', '4', '4'), + ('8', '8', '8'), + ('16', '16', '16'), + ('24', '24', '24'), + ('32', '32', '32'), + ('64', '64', '64'),], + name="Max Lights Shadows", description="Max number of rendered shadow maps that can be visible in the screen. Always equal or lower than Max Lights", default='16') + rp_shadowmap_atlas: BoolProperty(name="Shadow Map Atlasing", description="Group shadow maps of lights of the same type in the same texture", default=False, update=update_renderpath) + rp_shadowmap_atlas_single_map: BoolProperty(name="Shadow Map Atlas single map", description="Use a single texture for all different light types.", default=False, update=update_renderpath) + rp_shadowmap_atlas_lod: BoolProperty(name="Shadow Map Atlas LOD (Experimental)", description="When enabled, the size of the shadow map will be determined on runtime based on the distance of the light to the camera", default=False, update=update_renderpath) + rp_shadowmap_atlas_lod_subdivisions: EnumProperty( + items=[('2', '2', '2'), + ('3', '3', '3'), + ('4', '4', '4'), + ('5', '5', '5'), + ('6', '6', '6'), + ('7', '7', '7'), + ('8', '8', '8'),], + name="LOD Subdivisions", description="Number of subdivisions of the default tile size for LOD", default='2', update=update_renderpath) + rp_shadowmap_atlas_max_size_point: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size_spot: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size_sun: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) rp_shadowmap_cube: EnumProperty( items=[('256', '256', '256'), ('512', '512', '512'), diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d58b3012..504486f7 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1125,6 +1125,13 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_shadows", text="") + def compute_subdivs(self, max, subdivs): + l = [max] + for i in range(subdivs - 1): + l.append(int(max / 2)) + max = max / 2 + return l + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -1144,6 +1151,55 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): col2.prop(rpdat, 'arm_shadowmap_split') col.prop(rpdat, 'arm_shadowmap_bounds') col.prop(rpdat, 'arm_pcfsize') + layout.separator() + + layout.prop(rpdat, 'rp_shadowmap_atlas') + colatlas = layout.column() + colatlas.enabled = rpdat.rp_shadowmap_atlas + colatlas.prop(rpdat, 'rp_max_lights') + colatlas.prop(rpdat, 'rp_max_lights_cluster') + colatlas.prop(rpdat, 'rp_shadowmap_atlas_lod') + + colatlas_lod = colatlas.column() + colatlas_lod.enabled = rpdat.rp_shadowmap_atlas_lod + colatlas_lod.prop(rpdat, 'rp_shadowmap_atlas_lod_subdivisions') + + colatlas_lod_info = colatlas_lod.row() + colatlas_lod_info.alignment = 'RIGHT' + subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) + subdiv_text = "Subdivisions: " + ', '.join(map(str, subdivs_list)) + colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") + + colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map') + # show size for single texture + if rpdat.rp_shadowmap_atlas_single_map: + colatlas_single = colatlas.column() + colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size): + print(rpdat.rp_shadowmap_atlas_max_size) + colatlas_warning = colatlas_single.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + else: + # show size for all types + colatlas_mixed = colatlas.column() + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot') + if int(rpdat.rp_shadowmap_cascade) > int(rpdat.rp_shadowmap_atlas_max_size_spot): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_spot} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_point): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_point} is too small for the shadowmap size: {rpdat.rp_shadowmap_cube}', icon="ERROR") + + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_sun): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_sun} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") class ARM_PT_RenderPathVoxelsPanel(bpy.types.Panel): bl_label = "Voxel AO" @@ -2671,4 +2727,4 @@ def unregister(): bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) - bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) \ No newline at end of file + bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index dcb5a57b..0803f273 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -517,6 +517,7 @@ def write_indexhtml(w, h, is_publish): add_compiledglsl = '' def write_compiledglsl(defs, make_variants): rpdat = arm.utils.get_rp() + wrd = bpy.data.worlds['Arm'] shadowmap_size = arm.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0 with open(arm.utils.build_dir() + '/compiled/Shaders/compiled.inc', 'w') as f: f.write( @@ -689,6 +690,22 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100) if rpdat.arm_skin == 'On': f.write( """const int skinMaxBones = """ + str(rpdat.arm_skin_max_bones) + """; +""") + + if '_Clusters' in wrd.world_defs: + max_lights = "4" + max_lights_clusters = "4" + if rpdat.rp_shadowmap_atlas: + max_lights = str(rpdat.rp_max_lights) + max_lights_clusters = str(rpdat.rp_max_lights_cluster) + # prevent max lights cluster being higher than max lights + if (int(max_lights_clusters) > int(max_lights)): + max_lights_clusters = max_lights + + f.write( +"""const int maxLights = """ + max_lights + """; +const int maxLightsCluster = """ + max_lights_clusters + """; +const float clusterNear = 4.0; """) f.write(add_compiledglsl + '\n') # External defined constants