From 1c3e24a8fd17999df98a39c9e64aa347be3d5cf0 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Tue, 26 Jan 2021 22:01:06 -0300 Subject: [PATCH 1/2] Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature. --- .../deferred_light/deferred_light.frag.glsl | 86 ++- Shaders/deferred_light/deferred_light.json | 48 +- .../deferred_light.frag.glsl | 66 +- .../deferred_light_mobile.json | 30 +- Shaders/std/clusters.glsl | 3 - Shaders/std/light.glsl | 126 ++-- Shaders/std/light_mobile.glsl | 79 ++- Shaders/std/shadows.glsl | 179 ++++- .../volumetric_light.frag.glsl | 9 +- .../volumetric_light/volumetric_light.json | 17 +- Sources/armory/renderpath/Inc.hx | 671 +++++++++++++++++- .../armory/renderpath/RenderPathCreator.hx | 17 +- .../armory/renderpath/RenderPathDeferred.hx | 13 + .../armory/renderpath/RenderPathForward.hx | 12 + Sources/armory/trait/internal/DebugConsole.hx | 3 + blender/arm/make_renderpath.py | 27 +- blender/arm/material/make_cluster.py | 24 +- blender/arm/material/make_mesh.py | 39 +- blender/arm/material/shader.py | 17 +- blender/arm/props_renderpath.py | 56 ++ blender/arm/props_ui.py | 58 +- blender/arm/write_data.py | 17 + 22 files changed, 1403 insertions(+), 194 deletions(-) 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 From c64b47548ef28717e7f3933cd1baf4af5ce0f3c3 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 10 Feb 2021 20:57:50 -0300 Subject: [PATCH 2/2] Added support for direct3d-like texture coords uv for shadow atlases See http://thedev-log.blogspot.com/2012/07/texture-coordinates-tutorial-opengl-and.html, the "opengl coordinates" where inverted for proper support of direct3d texture coordinate system. --- Shaders/std/light.glsl | 6 +++- Shaders/std/light_mobile.glsl | 6 +++- Shaders/std/shadows.glsl | 55 +++++++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index 61c26771..aa3024ae 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -185,13 +185,17 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas + vec3 uv = lPos.xyz / lPos.w; + #ifdef _InvY + uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system + #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , lPos.xyz / lPos.w, bias + , uv, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index f6a966c9..e547129c 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -80,13 +80,17 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas + vec3 uv = lPos.xyz / lPos.w; + #ifdef _InvY + uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system + #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , lPos.xyz / lPos.w, bias + , uv, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index 81a12cc4..baf940a4 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -188,40 +188,77 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float 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)); + vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + + float result = texture(shadowMap, vec3(uvtiled, compare)); // soft shadowing int newFaceIndex = 0; - vec2 uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, 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 = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); return result / 9.0; }