From e922cc38e694a5675dda93dfecebf330ca73d07f Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 9 Jul 2025 23:17:55 +0000 Subject: [PATCH 1/8] Update leenkx/Shaders/std/shadows.glsl --- leenkx/Shaders/std/shadows.glsl | 145 ++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 61 deletions(-) diff --git a/leenkx/Shaders/std/shadows.glsl b/leenkx/Shaders/std/shadows.glsl index d994eff..a1c01f4 100644 --- a/leenkx/Shaders/std/shadows.glsl +++ b/leenkx/Shaders/std/shadows.glsl @@ -58,7 +58,15 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { } #endif -vec3 PCF(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec2 uv, const float compare, const vec2 smSize, const bool transparent) { +vec3 PCF(sampler2DShadow shadowMap, + #ifdef _ShadowMapTransparent + sampler2D shadowMapTransparent, + #endif + const vec2 uv, const float compare, const vec2 smSize + #ifdef _ShadowMapTransparent + , const bool transparent + #endif + ) { vec3 result = vec3(0.0); result.x = texture(shadowMap, vec3(uv + (vec2(-1.0, -1.0) / smSize), compare)); result.x += texture(shadowMap, vec3(uv + (vec2(-1.0, 0.0) / smSize), compare)); @@ -71,11 +79,13 @@ vec3 PCF(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec2 u result.x += texture(shadowMap, vec3(uv + (vec2(1.0, 1.0) / smSize), compare)); result = result.xxx / 9.0; + #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapTransparent, uv); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } + #endif return result; } @@ -87,41 +97,15 @@ float lpToDepth(vec3 lp, const vec2 lightProj) { return zcomp * 0.5 + 0.5; } -#ifndef _ShadowMapAtlas -vec3 PCFCube(samplerCubeShadow shadowMapCube, samplerCube shadowMapCubeTransparent, vec3 lp, vec3 ml, float bias, vec2 lightProj, vec3 n, const bool transparent) { - const float s = shadowmapCubePcfSize; - float compare = lpToDepth(lp, lightProj) - bias * 1.5; - ml = ml + n * bias * 20; - #ifdef _InvY - ml.y = -ml.y; - #endif - - float shadowFactor = 0.0; - shadowFactor = texture(shadowMapCube, vec4(ml, compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, s, s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, s, s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, -s, s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, s, -s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, -s, s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, -s, -s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, s, -s), compare)); - shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, -s, -s), compare)); - shadowFactor /= 9.0; - - vec3 result = vec3(shadowFactor); - - if (transparent == false) { - vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml); - if (shadowmap_transparent.a < compare) - result *= shadowmap_transparent.rgb; - } - - return result; -} -#endif - -#ifdef _ShadowMapAtlas -vec3 PCFCube(samplerCubeShadow shadowMapCube, samplerCube shadowMapCubeTransparent, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const bool transparent) { +vec3 PCFCube(samplerCubeShadow shadowMapCube, + #ifdef _ShadowMapTransparent + samplerCube shadowMapCubeTransparent, + #endif + const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n + #ifdef _ShadowMapTransparent + , const bool transparent + #endif + ) { const float s = shadowmapCubePcfSize; // TODO: incorrect... float compare = lpToDepth(lp, lightProj) - bias * 1.5; ml = ml + n * bias * 20; @@ -140,16 +124,18 @@ vec3 PCFCube(samplerCubeShadow shadowMapCube, samplerCube shadowMapCubeTranspare result.x += texture(shadowMapCube, vec4(ml + vec3(-s, -s, -s), compare)); result = result.xxx / 9.0; + #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } + #endif return result; } - +#ifdef _ShadowMapAtlas // transform "out-of-bounds" coordinates to the correct face/coordinate system // https://www.khronos.org/opengl/wiki/File:CubeMapAxes.png vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { @@ -243,21 +229,31 @@ vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { return uv; } -vec3 PCFFakeCube(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index, const bool transparent) { +vec3 PCFFakeCube(sampler2DShadow shadowMap, + #ifdef _ShadowMapTransparent + sampler2D shadowMapTransparent, + #endif + const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index + #ifdef _ShadowMapTransparent + , const bool transparent + #endif + ) { 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 vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif + if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) { + return vec3(1.0); // Handle edge cases by returning full light + } + vec3 result = vec3(0.0); result.x += texture(shadowMap, vec3(uvtiled, compare)); // soft shadowing @@ -270,14 +266,6 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, cons #endif result.x += texture(shadowMap, vec3(uvtiled, compare)); - uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); - pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _FlipY - uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system - #endif - result.x += texture(shadowMap, vec3(uvtiled, compare)); - uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; @@ -334,30 +322,47 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, cons uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif + #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapTransparent, uvtiled); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } + #endif return result; } #endif -vec3 shadowTest(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 lPos, const float shadowsBias, const bool transparent) { +vec3 shadowTest(sampler2DShadow shadowMap, + #ifdef _ShadowMapTransparent + sampler2D shadowMapTransparent, + #endif + const vec3 lPos, const float shadowsBias + #ifdef _ShadowMapTransparent + , const bool transparent + #endif + ) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; #else const vec2 smSize = shadowmapSize; #endif if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0); - return PCF(shadowMap, shadowMapTransparent, lPos.xy, lPos.z - shadowsBias, smSize, transparent); + return PCF(shadowMap, + #ifdef _ShadowMapTransparent + shadowMapTransparent, + #endif + lPos.xy, lPos.z - shadowsBias, smSize + #ifdef _ShadowMapTransparent + , transparent + #endif + ); } #ifdef _CSM mat4 getCascadeMat(const float d, out int casi, out int casIndex) { const int c = shadowmapCascades; - // Get cascade index // TODO: use bounding box slice selection instead of sphere const vec4 ci = vec4(float(c > 0), float(c > 1), float(c > 2), float(c > 3)); @@ -373,21 +378,26 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) { float(d > casData[c * 4].z), float(d > casData[c * 4].w)); casi = int(min(dot(ci, comp), c)); - // Get cascade mat casIndex = casi * 4; - return mat4( casData[casIndex ], casData[casIndex + 1], casData[casIndex + 2], casData[casIndex + 3]); - // if (casIndex == 0) return mat4(casData[0], casData[1], casData[2], casData[3]); // .. } -vec3 shadowTestCascade(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 eye, const vec3 p, const float shadowsBias, const bool transparent) { +vec3 shadowTestCascade(sampler2DShadow shadowMap, + #ifdef _ShadowMapTransparent + sampler2D shadowMapTransparent, + #endif + const vec3 eye, const vec3 p, const float shadowsBias + #ifdef _ShadowMapTransparent + , const bool transparent + #endif + ) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; #else @@ -395,16 +405,22 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, sampler2D shadowMapTransparent #endif const int c = shadowmapCascades; float d = distance(eye, p); - int casi; int casIndex; mat4 LWVP = getCascadeMat(d, casi, casIndex); - vec4 lPos = LWVP * vec4(p, 1.0); lPos.xyz /= lPos.w; vec3 visibility = vec3(1.0); - if (lPos.w > 0.0) visibility = PCF(shadowMap, shadowMapTransparent, lPos.xy, lPos.z - shadowsBias, smSize, transparent); + if (lPos.w > 0.0) visibility = PCF(shadowMap, + #ifdef _ShadowMapTransparent + shadowMapTransparent, + #endif + lPos.xy, lPos.z - shadowsBias, smSize + #ifdef _ShadowMapTransparent + , transparent + #endif + ); // Blend cascade // https://github.com/TheRealMJP/Shadows @@ -423,13 +439,20 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, sampler2D shadowMapTransparent vec4 lPos2 = LWVP2 * vec4(p, 1.0); lPos2.xyz /= lPos2.w; vec3 visibility2 = vec3(1.0); - if (lPos2.w > 0.0) visibility2 = PCF(shadowMap, shadowMapTransparent, lPos2.xy, lPos2.z - shadowsBias, smSize, transparent); + if (lPos2.w > 0.0) visibility2 = PCF(shadowMap, + #ifdef _ShadowMapTransparent + shadowMapTransparent, + #endif + lPos.xy, lPos.z - shadowsBias, smSize + #ifdef _ShadowMapTransparent + , transparent + #endif + ); float lerpAmt = smoothstep(0.0, blendThres, splitDist); return mix(visibility2, visibility, lerpAmt); } return visibility; - // Visualize cascades // if (ci == 0) albedo.rgb = vec3(1.0, 0.0, 0.0); // if (ci == 4) albedo.rgb = vec3(0.0, 1.0, 0.0); @@ -437,4 +460,4 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, sampler2D shadowMapTransparent // if (ci == 12) albedo.rgb = vec3(1.0, 1.0, 0.0); } #endif -#endif +#endif \ No newline at end of file From b9848cd2dc26dd4858483914d29e3b882f60fb41 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 9 Jul 2025 23:20:46 +0000 Subject: [PATCH 2/8] revert e922cc38e694a5675dda93dfecebf330ca73d07f revert Update leenkx/Shaders/std/shadows.glsl --- leenkx/Shaders/std/shadows.glsl | 145 ++++++++++++++------------------ 1 file changed, 61 insertions(+), 84 deletions(-) diff --git a/leenkx/Shaders/std/shadows.glsl b/leenkx/Shaders/std/shadows.glsl index a1c01f4..d994eff 100644 --- a/leenkx/Shaders/std/shadows.glsl +++ b/leenkx/Shaders/std/shadows.glsl @@ -58,15 +58,7 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { } #endif -vec3 PCF(sampler2DShadow shadowMap, - #ifdef _ShadowMapTransparent - sampler2D shadowMapTransparent, - #endif - const vec2 uv, const float compare, const vec2 smSize - #ifdef _ShadowMapTransparent - , const bool transparent - #endif - ) { +vec3 PCF(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec2 uv, const float compare, const vec2 smSize, const bool transparent) { vec3 result = vec3(0.0); result.x = texture(shadowMap, vec3(uv + (vec2(-1.0, -1.0) / smSize), compare)); result.x += texture(shadowMap, vec3(uv + (vec2(-1.0, 0.0) / smSize), compare)); @@ -79,13 +71,11 @@ vec3 PCF(sampler2DShadow shadowMap, result.x += texture(shadowMap, vec3(uv + (vec2(1.0, 1.0) / smSize), compare)); result = result.xxx / 9.0; - #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapTransparent, uv); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } - #endif return result; } @@ -97,15 +87,41 @@ float lpToDepth(vec3 lp, const vec2 lightProj) { return zcomp * 0.5 + 0.5; } -vec3 PCFCube(samplerCubeShadow shadowMapCube, - #ifdef _ShadowMapTransparent - samplerCube shadowMapCubeTransparent, - #endif - const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n - #ifdef _ShadowMapTransparent - , const bool transparent - #endif - ) { +#ifndef _ShadowMapAtlas +vec3 PCFCube(samplerCubeShadow shadowMapCube, samplerCube shadowMapCubeTransparent, vec3 lp, vec3 ml, float bias, vec2 lightProj, vec3 n, const bool transparent) { + const float s = shadowmapCubePcfSize; + float compare = lpToDepth(lp, lightProj) - bias * 1.5; + ml = ml + n * bias * 20; + #ifdef _InvY + ml.y = -ml.y; + #endif + + float shadowFactor = 0.0; + shadowFactor = texture(shadowMapCube, vec4(ml, compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, s, s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, s, s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, -s, s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, s, -s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, -s, s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(s, -s, -s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, s, -s), compare)); + shadowFactor += texture(shadowMapCube, vec4(ml + vec3(-s, -s, -s), compare)); + shadowFactor /= 9.0; + + vec3 result = vec3(shadowFactor); + + if (transparent == false) { + vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml); + if (shadowmap_transparent.a < compare) + result *= shadowmap_transparent.rgb; + } + + return result; +} +#endif + +#ifdef _ShadowMapAtlas +vec3 PCFCube(samplerCubeShadow shadowMapCube, samplerCube shadowMapCubeTransparent, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const bool transparent) { const float s = shadowmapCubePcfSize; // TODO: incorrect... float compare = lpToDepth(lp, lightProj) - bias * 1.5; ml = ml + n * bias * 20; @@ -124,18 +140,16 @@ vec3 PCFCube(samplerCubeShadow shadowMapCube, result.x += texture(shadowMapCube, vec4(ml + vec3(-s, -s, -s), compare)); result = result.xxx / 9.0; - #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } - #endif return result; } -#ifdef _ShadowMapAtlas + // transform "out-of-bounds" coordinates to the correct face/coordinate system // https://www.khronos.org/opengl/wiki/File:CubeMapAxes.png vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { @@ -229,31 +243,21 @@ vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { return uv; } -vec3 PCFFakeCube(sampler2DShadow shadowMap, - #ifdef _ShadowMapTransparent - sampler2D shadowMapTransparent, - #endif - const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index - #ifdef _ShadowMapTransparent - , const bool transparent - #endif - ) { +vec3 PCFFakeCube(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index, const bool transparent) { 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 vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif - if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) { - return vec3(1.0); // Handle edge cases by returning full light - } - vec3 result = vec3(0.0); result.x += texture(shadowMap, vec3(uvtiled, compare)); // soft shadowing @@ -266,6 +270,14 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap, #endif result.x += texture(shadowMap, vec3(uvtiled, compare)); + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _FlipY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result.x += texture(shadowMap, vec3(uvtiled, compare)); + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; @@ -322,47 +334,30 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap, uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif - #ifdef _ShadowMapTransparent if (transparent == false) { vec4 shadowmap_transparent = texture(shadowMapTransparent, uvtiled); if (shadowmap_transparent.a < compare) result *= shadowmap_transparent.rgb; } - #endif return result; } #endif -vec3 shadowTest(sampler2DShadow shadowMap, - #ifdef _ShadowMapTransparent - sampler2D shadowMapTransparent, - #endif - const vec3 lPos, const float shadowsBias - #ifdef _ShadowMapTransparent - , const bool transparent - #endif - ) { +vec3 shadowTest(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 lPos, const float shadowsBias, const bool transparent) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; #else const vec2 smSize = shadowmapSize; #endif if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0); - return PCF(shadowMap, - #ifdef _ShadowMapTransparent - shadowMapTransparent, - #endif - lPos.xy, lPos.z - shadowsBias, smSize - #ifdef _ShadowMapTransparent - , transparent - #endif - ); + return PCF(shadowMap, shadowMapTransparent, lPos.xy, lPos.z - shadowsBias, smSize, transparent); } #ifdef _CSM mat4 getCascadeMat(const float d, out int casi, out int casIndex) { const int c = shadowmapCascades; + // Get cascade index // TODO: use bounding box slice selection instead of sphere const vec4 ci = vec4(float(c > 0), float(c > 1), float(c > 2), float(c > 3)); @@ -378,26 +373,21 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) { float(d > casData[c * 4].z), float(d > casData[c * 4].w)); casi = int(min(dot(ci, comp), c)); + // Get cascade mat casIndex = casi * 4; + return mat4( casData[casIndex ], casData[casIndex + 1], casData[casIndex + 2], casData[casIndex + 3]); + // if (casIndex == 0) return mat4(casData[0], casData[1], casData[2], casData[3]); // .. } -vec3 shadowTestCascade(sampler2DShadow shadowMap, - #ifdef _ShadowMapTransparent - sampler2D shadowMapTransparent, - #endif - const vec3 eye, const vec3 p, const float shadowsBias - #ifdef _ShadowMapTransparent - , const bool transparent - #endif - ) { +vec3 shadowTestCascade(sampler2DShadow shadowMap, sampler2D shadowMapTransparent, const vec3 eye, const vec3 p, const float shadowsBias, const bool transparent) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; #else @@ -405,22 +395,16 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, #endif const int c = shadowmapCascades; float d = distance(eye, p); + int casi; int casIndex; mat4 LWVP = getCascadeMat(d, casi, casIndex); + vec4 lPos = LWVP * vec4(p, 1.0); lPos.xyz /= lPos.w; vec3 visibility = vec3(1.0); - if (lPos.w > 0.0) visibility = PCF(shadowMap, - #ifdef _ShadowMapTransparent - shadowMapTransparent, - #endif - lPos.xy, lPos.z - shadowsBias, smSize - #ifdef _ShadowMapTransparent - , transparent - #endif - ); + if (lPos.w > 0.0) visibility = PCF(shadowMap, shadowMapTransparent, lPos.xy, lPos.z - shadowsBias, smSize, transparent); // Blend cascade // https://github.com/TheRealMJP/Shadows @@ -439,20 +423,13 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, vec4 lPos2 = LWVP2 * vec4(p, 1.0); lPos2.xyz /= lPos2.w; vec3 visibility2 = vec3(1.0); - if (lPos2.w > 0.0) visibility2 = PCF(shadowMap, - #ifdef _ShadowMapTransparent - shadowMapTransparent, - #endif - lPos.xy, lPos.z - shadowsBias, smSize - #ifdef _ShadowMapTransparent - , transparent - #endif - ); + if (lPos2.w > 0.0) visibility2 = PCF(shadowMap, shadowMapTransparent, lPos2.xy, lPos2.z - shadowsBias, smSize, transparent); float lerpAmt = smoothstep(0.0, blendThres, splitDist); return mix(visibility2, visibility, lerpAmt); } return visibility; + // Visualize cascades // if (ci == 0) albedo.rgb = vec3(1.0, 0.0, 0.0); // if (ci == 4) albedo.rgb = vec3(0.0, 1.0, 0.0); @@ -460,4 +437,4 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap, // if (ci == 12) albedo.rgb = vec3(1.0, 1.0, 0.0); } #endif -#endif \ No newline at end of file +#endif From a65675ef752a924cdc7afd21c6e5275d840ff82d Mon Sep 17 00:00:00 2001 From: Onek8 Date: Tue, 15 Jul 2025 17:57:38 +0000 Subject: [PATCH 3/8] Update leenkx/blender/lnx/handlers.py --- leenkx/blender/lnx/handlers.py | 127 +++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 21 deletions(-) diff --git a/leenkx/blender/lnx/handlers.py b/leenkx/blender/lnx/handlers.py index ef47589..5d62a25 100644 --- a/leenkx/blender/lnx/handlers.py +++ b/leenkx/blender/lnx/handlers.py @@ -2,7 +2,10 @@ import importlib import os import queue import sys +import threading +import time import types +from typing import Dict, Tuple, Callable, Set import bpy from bpy.app.handlers import persistent @@ -30,6 +33,10 @@ if lnx.is_reload(__name__): else: lnx.enable_reload(__name__) +# Module-level storage for active threads (eliminates re-queuing overhead) +_active_threads: Dict[threading.Thread, Callable] = {} +_last_poll_time = 0.0 +_consecutive_empty_polls = 0 @persistent def on_depsgraph_update_post(self): @@ -135,35 +142,113 @@ def always() -> float: def poll_threads() -> float: - """Polls the thread callback queue and if a thread has finished, it - is joined with the main thread and the corresponding callback is - executed in the main thread. """ + Improved thread polling with: + - No re-queuing overhead + - Batch processing of completed threads + - Adaptive timing based on activity + - Better memory management + - Simplified logic flow + """ + global _last_poll_time, _consecutive_empty_polls + current_time = time.time() + + # Process all new threads from queue at once (batch processing) + new_threads_added = 0 try: - thread, callback = make.thread_callback_queue.get(block=False) + while True: + thread, callback = make.thread_callback_queue.get(block=False) + _active_threads[thread] = callback + new_threads_added += 1 except queue.Empty: + pass + + # Early return if no active threads + if not _active_threads: + _consecutive_empty_polls += 1 + # Adaptive timing: longer intervals when consistently empty + if _consecutive_empty_polls > 10: + return 0.5 # Back off when no activity return 0.25 - if thread.is_alive(): - try: - make.thread_callback_queue.put((thread, callback), block=False) - except queue.Full: - return 0.5 - return 0.1 + + # Reset empty poll counter when we have active threads + _consecutive_empty_polls = 0 + + # Find completed threads (single pass, no re-queuing) + completed_threads = [] + for thread in list(_active_threads.keys()): + if not thread.is_alive(): + completed_threads.append(thread) + + # Batch process all completed threads + if completed_threads: + _process_completed_threads(completed_threads) + + # Adaptive timing based on activity level + active_count = len(_active_threads) + if active_count == 0: + return 0.25 + elif active_count <= 3: + return 0.05 # Medium frequency for low activity else: + return 0.01 # High frequency for high activity + +def _process_completed_threads(completed_threads: list) -> None: + """Process a batch of completed threads with robust error handling.""" + for thread in completed_threads: + callback = _active_threads.pop(thread) # Remove from tracking + try: - thread.join() + thread.join() # Should be instant since thread is dead callback() except Exception as e: - # If there is an exception, we can no longer return the time to - # the next call to this polling function, so to keep it running - # we re-register it and then raise the original exception. - try: - bpy.app.timers.unregister(poll_threads) - except ValueError: - pass - bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True) - # Quickly check if another thread has finished - return 0.01 + # Robust error recovery + _handle_callback_error(e) + continue # Continue processing other threads + + # Explicit cleanup for better memory management + del thread, callback + +def _handle_callback_error(exception: Exception) -> None: + """Centralized error handling with better recovery.""" + try: + # Try to unregister existing timer + bpy.app.timers.unregister(poll_threads) + except ValueError: + pass # Timer wasn't registered, that's fine + + # Re-register timer with slightly longer interval for stability + bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True) + + # Re-raise the original exception after ensuring timer continuity + raise exception + +def cleanup_polling_system() -> None: + """Optional cleanup function for proper shutdown.""" + global _active_threads, _consecutive_empty_polls + + # Wait for remaining threads to complete (with timeout) + for thread in list(_active_threads.keys()): + if thread.is_alive(): + thread.join(timeout=1.0) # 1 second timeout + + # Clear tracking structures + _active_threads.clear() + _consecutive_empty_polls = 0 + + # Unregister timer + try: + bpy.app.timers.unregister(poll_threads) + except ValueError: + pass + +def get_polling_stats() -> dict: + """Get statistics about the polling system for monitoring.""" + return { + 'active_threads': len(_active_threads), + 'consecutive_empty_polls': _consecutive_empty_polls, + 'thread_ids': [t.ident for t in _active_threads.keys()] + } loaded_py_libraries: dict[str, types.ModuleType] = {} From e9aae53be9a0646bfbd8956c72f930f46f8bb31d Mon Sep 17 00:00:00 2001 From: Onek8 Date: Tue, 15 Jul 2025 19:05:14 +0000 Subject: [PATCH 4/8] t3du - Fix attribute error rp_gi --- leenkx/blender/lnx/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leenkx/blender/lnx/props.py b/leenkx/blender/lnx/props.py index 7ab49fb..e8babe4 100644 --- a/leenkx/blender/lnx/props.py +++ b/leenkx/blender/lnx/props.py @@ -613,7 +613,7 @@ def update_leenkx_world(): if bpy.data.filepath != '' and (file_version < sdk_version or wrd.lnx_commit != lnx_commit): # This allows for seamless migration from earlier versions of Leenkx for rp in wrd.lnx_rplist: # TODO: deprecated - if rp.rp_gi != 'Off': + if hasattr(rp, 'rp_gi') and rp.rp_gi != 'Off': rp.rp_gi = 'Off' rp.rp_voxels = rp.rp_gi From 6afc209db72e41a31831a7e350721892e0523494 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Tue, 15 Jul 2025 22:06:11 +0000 Subject: [PATCH 5/8] Add leenkx/blender/lnx/logicnode/logic/LN_once.py --- leenkx/blender/lnx/logicnode/logic/LN_once.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 leenkx/blender/lnx/logicnode/logic/LN_once.py diff --git a/leenkx/blender/lnx/logicnode/logic/LN_once.py b/leenkx/blender/lnx/logicnode/logic/LN_once.py new file mode 100644 index 0000000..5bb9d02 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/logic/LN_once.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * +import bpy + +class OnceNode(LnxLogicTreeNode): + bl_idname = 'LNOnceNode' + bl_label = 'Once' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'Run Once') + self.add_input('LnxNodeSocketAction', 'Reset') + + self.add_output('LnxNodeSocketAction', 'Out') + + From 82412dbf81ed8a2ab5ca7c44681b955fd57c8f16 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Tue, 15 Jul 2025 22:07:02 +0000 Subject: [PATCH 6/8] Add leenkx/Sources/leenkx/logicnode/OnceNode.hx --- leenkx/Sources/leenkx/logicnode/OnceNode.hx | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 leenkx/Sources/leenkx/logicnode/OnceNode.hx diff --git a/leenkx/Sources/leenkx/logicnode/OnceNode.hx b/leenkx/Sources/leenkx/logicnode/OnceNode.hx new file mode 100644 index 0000000..960c4f8 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/OnceNode.hx @@ -0,0 +1,23 @@ +package leenkx.logicnode; + +class OnceNode extends LogicNode { + + var triggered:Bool = false; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + if(from == 1){ + triggered = false; + return; + } + + if (!triggered) { + triggered = true; + runOutput(0); + } + } + +} From df4feac132c1f8dff4807d59f44449b4e52d85fe Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 16 Jul 2025 05:14:28 +0000 Subject: [PATCH 7/8] Add leenkx/blender/lnx/logicnode/object/LN_set_object_delayed_location.py --- .../object/LN_set_object_delayed_location.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 leenkx/blender/lnx/logicnode/object/LN_set_object_delayed_location.py diff --git a/leenkx/blender/lnx/logicnode/object/LN_set_object_delayed_location.py b/leenkx/blender/lnx/logicnode/object/LN_set_object_delayed_location.py new file mode 100644 index 0000000..7a40091 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_set_object_delayed_location.py @@ -0,0 +1,28 @@ +from lnx.logicnode.lnx_nodes import * +import bpy +from bpy.props import BoolProperty + +class LNSetObjectDelayedLocationNode(LnxLogicTreeNode): + bl_idname = 'LNSetObjectDelayedLocationNode' + bl_label = 'Set Object Delayed Location' + lnx_section = 'props' + lnx_version = 1 + + use_local_space: BoolProperty( + name="Use Local Space", + description="Move follower in local space instead of world space", + default=False + ) + + def lnx_init(self, context): + self.inputs.new('LnxNodeSocketAction', 'In') + self.inputs.new('LnxNodeSocketObject', 'Source Object') + self.inputs.new('LnxNodeSocketObject', 'Target Object') + self.inputs.new('LnxFloatSocket', 'Delay') + self.outputs.new('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'use_local_space') + + def draw_label(self): + return "Set Object Delayed Location" From 28943f1522da937c797939b9e23b858c7ea9fb91 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 16 Jul 2025 05:15:59 +0000 Subject: [PATCH 8/8] Add leenkx/Sources/leenkx/logicnode/SetObjectDelayedLocationNode.hx --- .../logicnode/SetObjectDelayedLocationNode.hx | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 leenkx/Sources/leenkx/logicnode/SetObjectDelayedLocationNode.hx diff --git a/leenkx/Sources/leenkx/logicnode/SetObjectDelayedLocationNode.hx b/leenkx/Sources/leenkx/logicnode/SetObjectDelayedLocationNode.hx new file mode 100644 index 0000000..ccc8b51 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetObjectDelayedLocationNode.hx @@ -0,0 +1,74 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; +import iron.math.Mat4; +import iron.system.Time; + +class SetObjectDelayedLocationNode extends LogicNode { + public var use_local_space: Bool = false; + + private var initialOffset: Vec4 = null; + private var targetPos: Vec4 = new Vec4(); + private var currentPos: Vec4 = new Vec4(); + private var deltaVec: Vec4 = new Vec4(); + private var tempVec: Vec4 = new Vec4(); + + private var lastParent: Object = null; + private var invParentMatrix: Mat4 = null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var follower: Object = inputs[1].get(); + var target: Object = inputs[2].get(); + var delay: Float = inputs[3].get(); + + if (follower == null || target == null || delay == null) return runOutput(0); + + if (initialOffset == null) { + initialOffset = new Vec4(); + var followerPos = follower.transform.world.getLoc(); + var targetPos = target.transform.world.getLoc(); + initialOffset.setFrom(followerPos); + initialOffset.sub(targetPos); + } + + targetPos.setFrom(target.transform.world.getLoc()); + currentPos.setFrom(follower.transform.world.getLoc()); + + tempVec.setFrom(targetPos).add(initialOffset); + + deltaVec.setFrom(tempVec).sub(currentPos); + + if (deltaVec.length() < 0.001 && delay < 0.01) { + runOutput(0); + return; + } + + if (delay == 0.0) { + currentPos.setFrom(tempVec); + } else { + var smoothFactor = Math.exp(-Time.delta / Math.max(0.0001, delay)); + currentPos.x = tempVec.x + (currentPos.x - tempVec.x) * smoothFactor; + currentPos.y = tempVec.y + (currentPos.y - tempVec.y) * smoothFactor; + currentPos.z = tempVec.z + (currentPos.z - tempVec.z) * smoothFactor; + } + if (use_local_space && follower.parent != null) { + if (follower.parent != lastParent || invParentMatrix == null) { + lastParent = follower.parent; + invParentMatrix = Mat4.identity(); + invParentMatrix.getInverse(follower.parent.transform.world); + } + tempVec.setFrom(currentPos); + tempVec.applymat(invParentMatrix); + follower.transform.loc.set(tempVec.x, tempVec.y, tempVec.z); + } else { + follower.transform.loc.set(currentPos.x, currentPos.y, currentPos.z); + } + follower.transform.buildMatrix(); + runOutput(0); + } +}