From c27d9913743bf0bb9a5c5db5b040282ea638e159 Mon Sep 17 00:00:00 2001 From: CookedBunny Date: Sat, 9 Aug 2025 01:07:37 +0800 Subject: [PATCH 1/7] updated pom effect --- .../materials/materialMethods/pomEffects.glsl | 175 ++++++++++++------ 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 834c57e4..1d915f93 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -9,55 +9,134 @@ vec4 ReadNormal(vec2 coord) { return textureGrad(normals, coord, dcdx, dcdy); } +#define ATLAS(_local_) (fract(_local_) * vTexCoordAM.pq + vTexCoordAM.st) + +// Created a new function to mitigate the side effects in case the original one has any reference outside this scope +vec4 ReadNormalLocal(vec2 localCoord) { + vec2 atlasCoord = ATLAS(localCoord); + return textureGrad(normals, atlasCoord, dcdx, dcdy); +} + vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, inout float texDepth, inout vec3 traceCoordDepth) { float invParallaxQuality = 1.0 / POM_QUALITY; + float minHeight = 1.0 - invParallaxQuality; + vec4 normalMap = ReadNormal(vTexCoord.st); vec2 normalMapM = normalMap.xy * 2.0 - 1.0; float normalCheck = normalMapM.x + normalMapM.y; - float minHeight = 1.0 - invParallaxQuality; - if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999) return vTexCoord.st; + // Early-outs: grazing view, flat height, extreme normal, or fully faded + if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999 || parallaxFade >= 0.98) return vTexCoord.st; + // Layer step in tangent plane (scaled by depth & fade) + vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (-viewVector.z * POM_QUALITY); - vec2 interval = viewVector.xy * 0.25 * (1.0 - parallaxFade) * POM_DEPTH / (-viewVector.z * POM_QUALITY); + float i = clamp(dither, 0.0, max(POM_QUALITY - 1.0, 0.0)); - float i = 0.0; - vec2 localCoord; - #if defined GBUFFERS_TERRAIN || defined GBUFFERS_BLOCK - if (texDepth <= 1.0 - i * invParallaxQuality) { - localCoord = vTexCoord.st + i * interval; - texDepth = ReadNormal(localCoord).a; - i = dither; - } - #endif + // Tiny cache to avoid re-fetching the same texel + vec2 prevLC = vec2(1e9); // impossible to hit + float prevH = 0.0; + + #define SAMPLE_H(local) ( \ + (all(lessThan(abs((local) - prevLC), vec2(1e-6)))) ? \ + prevH : (prevLC = (local), prevH = ReadNormalLocal(prevLC).a)) + + // Coarse march: fast but rough (bigger stride = faster, less precise) + const float COARSE_STRIDE = 2.0; + vec2 baseLC = vTexCoord.st; + float h = texDepth; // if a caller-provided history exists; otherwise first sample overwrites - for (; i < POM_QUALITY && texDepth <= 1.0 - i * invParallaxQuality; i++) { - localCoord = vTexCoord.st + i * interval; - texDepth = ReadNormal(localCoord).a; + // Ensure texDepth matches current start layer + { + vec2 lc0 = baseLC + i * layerStep; + h = SAMPLE_H(lc0); } - float pI = float(max(i - 1, 0)); - traceCoordDepth.xy -= pI * interval; - traceCoordDepth.z -= pI * invParallaxQuality; + // March forward by stride until we cross the height threshold + float iPrev = i; + float hPrev = h; + for (; i < POM_QUALITY && h <= (1.0 - i * invParallaxQuality); i += COARSE_STRIDE) { + vec2 lc = baseLC + i * layerStep; + hPrev = h; + iPrev = i; + h = SAMPLE_H(lc); + } + + // If we ran out of layers without crossing, clamp to last valid layer and return + if (i >= POM_QUALITY && h <= (1.0 - (POM_QUALITY - 1.0) * invParallaxQuality)) { + i = POM_QUALITY; + float pI = float(max(int(i) - 1, 0)); + traceCoordDepth.xy -= pI * layerStep; + traceCoordDepth.z -= pI * invParallaxQuality; + vec2 localCoord = fract(baseLC + pI * layerStep); + newCoord = ATLAS(localCoord); + #undef SAMPLE_H + return localCoord; + } + + // Refine with a short binary search in the [iPrev, i] bracket + float lo = max(iPrev, 0.0); + float hi = clamp(i, 0.0, POM_QUALITY); + float hLo = hPrev; + float hHi = h; + + int binaryIters = clamp(int(POM_QUALITY * 0.25), 2, 6); + for (int it = 0; binaryIters < 4; ++it) { + float mid = 0.5 * (lo + hi); + float threshold = 1.0 - mid * invParallaxQuality; + vec2 lcMid = baseLC + mid * layerStep; + float hMid = SAMPLE_H(lcMid); + + bool below = (hMid <= threshold); + lo = below ? mid : lo; + hi = below ? hi : mid; + hLo = below ? hMid: hLo; + hHi = below ? hHi : hMid; + } + + // Pick the layer just before the crossing + float pI = max(hi - 1.0, 0.0); + + // Accumulate trace offsets (xy: coord, z: layer depth) + traceCoordDepth.xy -= pI * layerStep; + traceCoordDepth.z -= pI * invParallaxQuality; + + vec2 localCoord = fract(baseLC + pI * layerStep); + newCoord = ATLAS(localCoord); - localCoord = fract(vTexCoord.st + pI * interval); - newCoord = localCoord * vTexCoordAM.pq + vTexCoordAM.st; + #undef SAMPLE_H return localCoord; } float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { - float parallaxshadow = 1.0; + // Skip shadowing when far or almost faded + if (parallaxFade >= 0.98) return 1.0; vec3 parallaxdir = tbn * lightVec; - parallaxdir.xy *= 1.0 * POM_DEPTH; // Angle + // Degenerate / near-parallel to plane + if (abs(parallaxdir.z) < 1e-4) return 1.0; + + parallaxdir.xy *= POM_DEPTH; + + // Fewer steps as fade increases (scene-aware) + int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; + + float parallaxshadow = 1.0; - for (int i = 0; i < 4 && parallaxshadow >= 0.01; i++) { - float stepLC = 0.025 * (i + dither); + vec2 pq = vTexCoordAM.pq; + vec2 st = vTexCoordAM.st; + + vec2 baseLocal = fract(coord); + + for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { + float stepLC = 0.025 * (float(i) + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 parallaxCoord = fract(coord + parallaxdir.xy * stepLC) * vTexCoordAM.pq + vTexCoordAM.st; - float offsetHeight = textureGrad(normals, parallaxCoord, dcdx, dcdy).a; + vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); + vec2 atlasCoord = lc * pq + st; + float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; + // Attenuate when the traced surface (offsetHeight) rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } @@ -68,7 +147,7 @@ float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coo vec3 GetParallaxSlopeNormal(vec2 texCoord, float traceDepth, vec3 viewDir) { vec2 atlasPixelSize = 1.0 / atlasSize; float atlasAspect = atlasSize.x / atlasSize.y; - vec2 atlasCoord = fract(texCoord) * vTexCoordAM.pq + vTexCoordAM.st; + vec2 atlasCoord = ATLAS(texCoord); vec2 tileSize = atlasSize * vTexCoordAM.pq; vec2 tilePixelSize = 1.0 / tileSize; @@ -79,43 +158,33 @@ vec3 GetParallaxSlopeNormal(vec2 texCoord, float traceDepth, vec3 viewDir) { vec2 stepSign = sign(tex_offset); vec2 viewSign = sign(viewDir.xy); + // Choose axis with larger normalized offset (accounts for atlas aspect) bool dir = abs(tex_offset.x * atlasAspect) < abs(tex_offset.y); - vec2 tex_x, tex_y; - if (dir) { - tex_x = texCoord - vec2(tilePixelSize.x * viewSign.x, 0.0); - tex_y = texCoord + vec2(0.0, stepSign.y * tilePixelSize.y); - } - else { - tex_x = texCoord + vec2(tilePixelSize.x * stepSign.x, 0.0); - tex_y = texCoord - vec2(0.0, viewSign.y * tilePixelSize.y); - } + // Neighbor texels (picked based on view/offset signs to reduce popping) + vec2 tex_x_A = texCoord - vec2(tilePixelSize.x * viewSign.x, 0.0); + vec2 tex_x_B = texCoord + vec2(tilePixelSize.x * stepSign.x, 0.0); + vec2 tex_y_A = texCoord + vec2(0.0, stepSign.y * tilePixelSize.y); + vec2 tex_y_B = texCoord - vec2(0.0, viewSign.y * tilePixelSize.y); + + vec2 tex_x = dir ? tex_x_A : tex_x_B; + vec2 tex_y = dir ? tex_y_A : tex_y_B; - float height_x = ReadNormal(tex_x).a; - float height_y = ReadNormal(tex_y).a; + float height_x = ReadNormalLocal(tex_x).a; + float height_y = ReadNormalLocal(tex_y).a; + // prefer the edge that is actually in front along the view if (dir) { if (!(traceDepth > height_y && viewSign.y != stepSign.y)) { if (traceDepth > height_x) return vec3(-viewSign.x, 0.0, 0.0); - - if (abs(viewDir.y) > abs(viewDir.x)) - return vec3(0.0, -viewSign.y, 0.0); - else - return vec3(-viewSign.x, 0.0, 0.0); + return (abs(viewDir.y) > abs(viewDir.x)) ? vec3(0.0, -viewSign.y, 0.0) : vec3(-viewSign.x, 0.0, 0.0); } - return vec3(0.0, -viewSign.y, 0.0); - } - else { + } else { if (!(traceDepth > height_x && viewSign.x != stepSign.x)) { if (traceDepth > height_y) return vec3(0.0, -viewSign.y, 0.0); - - if (abs(viewDir.y) > abs(viewDir.x)) - return vec3(0.0, -viewSign.y, 0.0); - else - return vec3(-viewSign.x, 0.0, 0.0); + return (abs(viewDir.y) > abs(viewDir.x)) ? vec3(0.0, -viewSign.y, 0.0) : vec3(-viewSign.x, 0.0, 0.0); } - return vec3(-viewSign.x, 0.0, 0.0); } -} \ No newline at end of file +} From d2f4caa8c9484e5f6e2d1192e7fa3486be219a37 Mon Sep 17 00:00:00 2001 From: CookedBunny Date: Wed, 13 Aug 2025 03:21:06 +0800 Subject: [PATCH 2/7] expanded the macro `SAMPLE_H` --- .../lib/materials/materialMethods/pomEffects.glsl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 1d915f93..b2275a2d 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -36,10 +36,6 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 prevLC = vec2(1e9); // impossible to hit float prevH = 0.0; - #define SAMPLE_H(local) ( \ - (all(lessThan(abs((local) - prevLC), vec2(1e-6)))) ? \ - prevH : (prevLC = (local), prevH = ReadNormalLocal(prevLC).a)) - // Coarse march: fast but rough (bigger stride = faster, less precise) const float COARSE_STRIDE = 2.0; vec2 baseLC = vTexCoord.st; @@ -48,7 +44,8 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino // Ensure texDepth matches current start layer { vec2 lc0 = baseLC + i * layerStep; - h = SAMPLE_H(lc0); + h = (all(lessThan(abs(lc0 - prevLC), vec2(1e-6)))) ? prevH + : (prevLC = lc0, prevH = ReadNormalLocal(prevLC).a); } // March forward by stride until we cross the height threshold @@ -58,7 +55,8 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 lc = baseLC + i * layerStep; hPrev = h; iPrev = i; - h = SAMPLE_H(lc); + h = (all(lessThan(abs(lc - prevLC), vec2(1e-6)))) ? prevH + : (prevLC = lc, prevH = ReadNormalLocal(prevLC).a); } // If we ran out of layers without crossing, clamp to last valid layer and return @@ -69,7 +67,6 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino traceCoordDepth.z -= pI * invParallaxQuality; vec2 localCoord = fract(baseLC + pI * layerStep); newCoord = ATLAS(localCoord); - #undef SAMPLE_H return localCoord; } @@ -84,7 +81,8 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino float mid = 0.5 * (lo + hi); float threshold = 1.0 - mid * invParallaxQuality; vec2 lcMid = baseLC + mid * layerStep; - float hMid = SAMPLE_H(lcMid); + float hMid = (all(lessThan(abs(lcMid - prevLC), vec2(1e-6)))) ? prevH + : (prevLC = lcMid, prevH = ReadNormalLocal(prevLC).a); bool below = (hMid <= threshold); lo = below ? mid : lo; @@ -103,7 +101,6 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 localCoord = fract(baseLC + pI * layerStep); newCoord = ATLAS(localCoord); - #undef SAMPLE_H return localCoord; } From 8c5b41dd65ee6615fa4b7504503530ee27b0bcd3 Mon Sep 17 00:00:00 2001 From: CookedBunny Date: Wed, 13 Aug 2025 04:04:06 +0800 Subject: [PATCH 3/7] fixed flat normal and inconsist shadow --- .../materials/materialMethods/pomEffects.glsl | 113 +++++++++--------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index b2275a2d..90a82e9d 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -25,38 +25,43 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 normalMapM = normalMap.xy * 2.0 - 1.0; float normalCheck = normalMapM.x + normalMapM.y; - // Early-outs: grazing view, flat height, extreme normal, or fully faded - if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999 || parallaxFade >= 0.98) return vTexCoord.st; + // Early-outs: grazing view, flat height, extreme normal + if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999) return vTexCoord.st; // Layer step in tangent plane (scaled by depth & fade) vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (-viewVector.z * POM_QUALITY); - float i = clamp(dither, 0.0, max(POM_QUALITY - 1.0, 0.0)); + float i = 0.0; - // Tiny cache to avoid re-fetching the same texel - vec2 prevLC = vec2(1e9); // impossible to hit - float prevH = 0.0; - - // Coarse march: fast but rough (bigger stride = faster, less precise) - const float COARSE_STRIDE = 2.0; vec2 baseLC = vTexCoord.st; float h = texDepth; // if a caller-provided history exists; otherwise first sample overwrites // Ensure texDepth matches current start layer { vec2 lc0 = baseLC + i * layerStep; - h = (all(lessThan(abs(lc0 - prevLC), vec2(1e-6)))) ? prevH - : (prevLC = lc0, prevH = ReadNormalLocal(prevLC).a); + h = ReadNormalLocal(lc0).a; } + #if defined GBUFFERS_TERRAIN || defined GBUFFERS_BLOCK + if (texDepth <= 1.0 - i * invParallaxQuality) { + i = dither; + vec2 lc1 = baseLC + i * layerStep; + h = texDepth = ReadNormalLocal(lc1).a; + } + #endif + + // Calculate stride based on |z| + float viewFlat = clamp(1.0 - abs(viewVector.z), 0.0, 1.0); + float coarseStride = mix(1.0, 2.0, clamp(0.4 * parallaxFade + 0.4 * viewVector.z*viewVector.z, 0.0, 1.0)); + coarseStride = (viewFlat > 0.7) ? 1.0 : coarseStride; + // March forward by stride until we cross the height threshold float iPrev = i; float hPrev = h; - for (; i < POM_QUALITY && h <= (1.0 - i * invParallaxQuality); i += COARSE_STRIDE) { + for (; i < POM_QUALITY && h <= (1.0 - i * invParallaxQuality); i += coarseStride) { vec2 lc = baseLC + i * layerStep; hPrev = h; iPrev = i; - h = (all(lessThan(abs(lc - prevLC), vec2(1e-6)))) ? prevH - : (prevLC = lc, prevH = ReadNormalLocal(prevLC).a); + h = ReadNormalLocal(lc).a; } // If we ran out of layers without crossing, clamp to last valid layer and return @@ -67,6 +72,7 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino traceCoordDepth.z -= pI * invParallaxQuality; vec2 localCoord = fract(baseLC + pI * layerStep); newCoord = ATLAS(localCoord); + texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; return localCoord; } @@ -76,13 +82,11 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino float hLo = hPrev; float hHi = h; - int binaryIters = clamp(int(POM_QUALITY * 0.25), 2, 6); - for (int it = 0; binaryIters < 4; ++it) { + for (int it = 0; it < 3; ++it) { float mid = 0.5 * (lo + hi); float threshold = 1.0 - mid * invParallaxQuality; vec2 lcMid = baseLC + mid * layerStep; - float hMid = (all(lessThan(abs(lcMid - prevLC), vec2(1e-6)))) ? prevH - : (prevLC = lcMid, prevH = ReadNormalLocal(prevLC).a); + float hMid = ReadNormalLocal(lcMid).a; bool below = (hMid <= threshold); lo = below ? mid : lo; @@ -92,7 +96,7 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino } // Pick the layer just before the crossing - float pI = max(hi - 1.0, 0.0); + float pI = max(lo, 0.0); // Accumulate trace offsets (xy: coord, z: layer depth) traceCoordDepth.xy -= pI * layerStep; @@ -100,40 +104,25 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 localCoord = fract(baseLC + pI * layerStep); newCoord = ATLAS(localCoord); + texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; return localCoord; } float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { - // Skip shadowing when far or almost faded - if (parallaxFade >= 0.98) return 1.0; - - vec3 parallaxdir = tbn * lightVec; - // Degenerate / near-parallel to plane - if (abs(parallaxdir.z) < 1e-4) return 1.0; - - parallaxdir.xy *= POM_DEPTH; - - // Fewer steps as fade increases (scene-aware) - int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; - float parallaxshadow = 1.0; - vec2 pq = vTexCoordAM.pq; - vec2 st = vTexCoordAM.st; - - vec2 baseLocal = fract(coord); + vec3 parallaxdir = tbn * lightVec; + parallaxdir.xy *= 1.0 * POM_DEPTH; // Angle - for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { - float stepLC = 0.025 * (float(i) + dither); + for (int i = 0; i < 4 && parallaxshadow >= 0.01; i++) { + float stepLC = 0.025 * (i + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); - vec2 atlasCoord = lc * pq + st; + vec2 parallaxCoord = fract(coord + parallaxdir.xy * stepLC) * vTexCoordAM.pq + vTexCoordAM.st; + float offsetHeight = textureGrad(normals, parallaxCoord, dcdx, dcdy).a; - float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; - // Attenuate when the traced surface (offsetHeight) rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } @@ -144,7 +133,7 @@ float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coo vec3 GetParallaxSlopeNormal(vec2 texCoord, float traceDepth, vec3 viewDir) { vec2 atlasPixelSize = 1.0 / atlasSize; float atlasAspect = atlasSize.x / atlasSize.y; - vec2 atlasCoord = ATLAS(texCoord); + vec2 atlasCoord = fract(texCoord) * vTexCoordAM.pq + vTexCoordAM.st; vec2 tileSize = atlasSize * vTexCoordAM.pq; vec2 tilePixelSize = 1.0 / tileSize; @@ -155,33 +144,43 @@ vec3 GetParallaxSlopeNormal(vec2 texCoord, float traceDepth, vec3 viewDir) { vec2 stepSign = sign(tex_offset); vec2 viewSign = sign(viewDir.xy); - // Choose axis with larger normalized offset (accounts for atlas aspect) bool dir = abs(tex_offset.x * atlasAspect) < abs(tex_offset.y); + vec2 tex_x, tex_y; - // Neighbor texels (picked based on view/offset signs to reduce popping) - vec2 tex_x_A = texCoord - vec2(tilePixelSize.x * viewSign.x, 0.0); - vec2 tex_x_B = texCoord + vec2(tilePixelSize.x * stepSign.x, 0.0); - vec2 tex_y_A = texCoord + vec2(0.0, stepSign.y * tilePixelSize.y); - vec2 tex_y_B = texCoord - vec2(0.0, viewSign.y * tilePixelSize.y); - - vec2 tex_x = dir ? tex_x_A : tex_x_B; - vec2 tex_y = dir ? tex_y_A : tex_y_B; + if (dir) { + tex_x = texCoord - vec2(tilePixelSize.x * viewSign.x, 0.0); + tex_y = texCoord + vec2(0.0, stepSign.y * tilePixelSize.y); + } + else { + tex_x = texCoord + vec2(tilePixelSize.x * stepSign.x, 0.0); + tex_y = texCoord - vec2(0.0, viewSign.y * tilePixelSize.y); + } - float height_x = ReadNormalLocal(tex_x).a; - float height_y = ReadNormalLocal(tex_y).a; + float height_x = ReadNormal(tex_x).a; + float height_y = ReadNormal(tex_y).a; - // prefer the edge that is actually in front along the view if (dir) { if (!(traceDepth > height_y && viewSign.y != stepSign.y)) { if (traceDepth > height_x) return vec3(-viewSign.x, 0.0, 0.0); - return (abs(viewDir.y) > abs(viewDir.x)) ? vec3(0.0, -viewSign.y, 0.0) : vec3(-viewSign.x, 0.0, 0.0); + + if (abs(viewDir.y) > abs(viewDir.x)) + return vec3(0.0, -viewSign.y, 0.0); + else + return vec3(-viewSign.x, 0.0, 0.0); } + return vec3(0.0, -viewSign.y, 0.0); - } else { + } + else { if (!(traceDepth > height_x && viewSign.x != stepSign.x)) { if (traceDepth > height_y) return vec3(0.0, -viewSign.y, 0.0); - return (abs(viewDir.y) > abs(viewDir.x)) ? vec3(0.0, -viewSign.y, 0.0) : vec3(-viewSign.x, 0.0, 0.0); + + if (abs(viewDir.y) > abs(viewDir.x)) + return vec3(0.0, -viewSign.y, 0.0); + else + return vec3(-viewSign.x, 0.0, 0.0); } + return vec3(-viewSign.x, 0.0, 0.0); } } From 7c88a1c7e2c7ed67d7d09be66b5983eb56eac5a1 Mon Sep 17 00:00:00 2001 From: CookedBunny Date: Wed, 13 Aug 2025 12:31:21 +0800 Subject: [PATCH 4/7] brought back some strategies that were proved to work --- .../materials/materialMethods/pomEffects.glsl | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 90a82e9d..00fcbac2 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -25,8 +25,8 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino vec2 normalMapM = normalMap.xy * 2.0 - 1.0; float normalCheck = normalMapM.x + normalMapM.y; - // Early-outs: grazing view, flat height, extreme normal - if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999) return vTexCoord.st; + // Early-outs: grazing view, flat height, extreme normal and nearly faded + if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999 || parallaxFade > 0.98) return vTexCoord.st; // Layer step in tangent plane (scaled by depth & fade) vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (-viewVector.z * POM_QUALITY); @@ -110,19 +110,35 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino } float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { - float parallaxshadow = 1.0; + // Skip shadowing when far or almost faded + if (parallaxFade >= 0.98) return 1.0; vec3 parallaxdir = tbn * lightVec; - parallaxdir.xy *= 1.0 * POM_DEPTH; // Angle + // Degenerate / near-parallel to plane + if (abs(parallaxdir.z) < 1e-4) return 1.0; + + parallaxdir.xy *= POM_DEPTH; + + // Fewer steps as fade increases (scene-aware) + int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; + + float parallaxshadow = 1.0; + + vec2 pq = vTexCoordAM.pq; + vec2 st = vTexCoordAM.st; + + vec2 baseLocal = fract(coord); - for (int i = 0; i < 4 && parallaxshadow >= 0.01; i++) { - float stepLC = 0.025 * (i + dither); + for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { + float stepLC = 0.025 * (float(i) + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 parallaxCoord = fract(coord + parallaxdir.xy * stepLC) * vTexCoordAM.pq + vTexCoordAM.st; - float offsetHeight = textureGrad(normals, parallaxCoord, dcdx, dcdy).a; + vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); + vec2 atlasCoord = lc * pq + st; + float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; + // Attenuate when the traced surface (offsetHeight) rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } From 79de694c65d661a2ff8d6aaceb1bf5bb2cdc0058 Mon Sep 17 00:00:00 2001 From: Rachel Date: Tue, 7 Oct 2025 01:54:24 +0800 Subject: [PATCH 5/7] fixing artifacts by fixing wrapping during the march and shadow --- .../materials/materialMethods/pomEffects.glsl | 146 ++++++++++-------- 1 file changed, 85 insertions(+), 61 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 00fcbac2..89d75f2c 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -9,6 +9,14 @@ vec4 ReadNormal(vec2 coord) { return textureGrad(normals, coord, dcdx, dcdy); } +vec4 ReadNormalLocalNoWrap(vec2 localCoord) { + vec2 tileSize = atlasSize * vTexCoordAM.pq; + vec2 eps = 0.5 / tileSize; + vec2 lc = clamp(localCoord, eps, 1.0 - eps); + vec2 atlasCoord = lc * vTexCoordAM.pq + vTexCoordAM.st; + return textureGrad(normals, atlasCoord, dcdx, dcdy); +} + #define ATLAS(_local_) (fract(_local_) * vTexCoordAM.pq + vTexCoordAM.st) // Created a new function to mitigate the side effects in case the original one has any reference outside this scope @@ -17,66 +25,78 @@ vec4 ReadNormalLocal(vec2 localCoord) { return textureGrad(normals, atlasCoord, dcdx, dcdy); } -vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, inout float texDepth, inout vec3 traceCoordDepth) { - float invParallaxQuality = 1.0 / POM_QUALITY; - float minHeight = 1.0 - invParallaxQuality; +vec2 GetParallaxCoord(float parallaxFade, float dither, + inout vec2 newCoord, inout float texDepth, inout vec3 traceCoordDepth) +{ + float invQ = 1.0 / POM_QUALITY; + float minHeight = 1.0 - invQ; - vec4 normalMap = ReadNormal(vTexCoord.st); - vec2 normalMapM = normalMap.xy * 2.0 - 1.0; - float normalCheck = normalMapM.x + normalMapM.y; + vec4 nm = ReadNormal(vTexCoord.st); + vec2 nmM = nm.xy * 2.0 - 1.0; + float normalCheck = nmM.x + nmM.y; - // Early-outs: grazing view, flat height, extreme normal and nearly faded - if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999 || parallaxFade > 0.98) return vTexCoord.st; - // Layer step in tangent plane (scaled by depth & fade) - vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (-viewVector.z * POM_QUALITY); + if (viewVector.z >= 0.0 || nm.a >= minHeight || normalCheck <= -1.999 || parallaxFade > 0.98) + return vTexCoord.st; - float i = 0.0; + vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (max(-viewVector.z, 1e-4) * POM_QUALITY); - vec2 baseLC = vTexCoord.st; - float h = texDepth; // if a caller-provided history exists; otherwise first sample overwrites + float i = 0.0; + vec2 local0 = vTexCoord.st; + vec2 eps = TileEpsilon(); - // Ensure texDepth matches current start layer - { - vec2 lc0 = baseLC + i * layerStep; - h = ReadNormalLocal(lc0).a; - } + // Start height (use no-wrap read) + float h = ReadNormalLocalNoWrap(local0).a; + float iPrev = 0.0, hPrev = h; #if defined GBUFFERS_TERRAIN || defined GBUFFERS_BLOCK - if (texDepth <= 1.0 - i * invParallaxQuality) { - i = dither; - vec2 lc1 = baseLC + i * layerStep; - h = texDepth = ReadNormalLocal(lc1).a; + if (texDepth <= 1.0 - i * invQ) { + i = dither; + vec2 lc1 = local0 + i * layerStep; + // stop POM if we left the tile already + if (any(lessThan(lc1, eps)) || any(greaterThan(lc1, 1.0 - eps))) { + newCoord = AtlasClamp(clamp(local0, 0.0, 1.0)); + texDepth = h; + return clamp(local0, 0.0, 1.0); } + h = texDepth = ReadNormalLocalNoWrap(lc1).a; + } #endif - // Calculate stride based on |z| float viewFlat = clamp(1.0 - abs(viewVector.z), 0.0, 1.0); float coarseStride = mix(1.0, 2.0, clamp(0.4 * parallaxFade + 0.4 * viewVector.z*viewVector.z, 0.0, 1.0)); coarseStride = (viewFlat > 0.7) ? 1.0 : coarseStride; // March forward by stride until we cross the height threshold - float iPrev = i; - float hPrev = h; - for (; i < POM_QUALITY && h <= (1.0 - i * invParallaxQuality); i += coarseStride) { - vec2 lc = baseLC + i * layerStep; - hPrev = h; - iPrev = i; - h = ReadNormalLocal(lc).a; + // But no wrap + for (; i < POM_QUALITY && h <= (1.0 - i * invQ); i += coarseStride) { + vec2 lc = local0 + i * layerStep; + if (any(lessThan(lc, eps)) || any(greaterThan(lc, 1.0 - eps))) { + // left tile -> clamp to edge, return last valid + float pI = max(iPrev, 0.0); + vec2 lcSafe = clamp(local0 + pI * layerStep, eps, 1.0 - eps); + traceCoordDepth.xy -= pI * layerStep; + traceCoordDepth.z -= pI * invQ; + newCoord = lcSafe * vTexCoordAM.pq + vTexCoordAM.st; + texDepth = ReadNormalLocalNoWrap(lcSafe).a; + return lcSafe; + } + hPrev = h; iPrev = i; + h = ReadNormalLocalNoWrap(lc).a; } // If we ran out of layers without crossing, clamp to last valid layer and return - if (i >= POM_QUALITY && h <= (1.0 - (POM_QUALITY - 1.0) * invParallaxQuality)) { - i = POM_QUALITY; - float pI = float(max(int(i) - 1, 0)); + if (i >= POM_QUALITY && h <= (1.0 - (POM_QUALITY - 1.0) * invQ)) { + float pI = float(max(int(POM_QUALITY) - 1, 0)); + vec2 lcSafe = clamp(local0 + pI * layerStep, eps, 1.0 - eps); traceCoordDepth.xy -= pI * layerStep; - traceCoordDepth.z -= pI * invParallaxQuality; - vec2 localCoord = fract(baseLC + pI * layerStep); - newCoord = ATLAS(localCoord); - texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; - return localCoord; + traceCoordDepth.z -= pI * invQ; + newCoord = lcSafe * vTexCoordAM.pq + vTexCoordAM.st; + texDepth = ReadNormalLocalNoWrap(lcSafe).a; + return lcSafe; } // Refine with a short binary search in the [iPrev, i] bracket + // no wrap again float lo = max(iPrev, 0.0); float hi = clamp(i, 0.0, POM_QUALITY); float hLo = hPrev; @@ -84,61 +104,65 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino for (int it = 0; it < 3; ++it) { float mid = 0.5 * (lo + hi); - float threshold = 1.0 - mid * invParallaxQuality; - vec2 lcMid = baseLC + mid * layerStep; - float hMid = ReadNormalLocal(lcMid).a; + float thr = 1.0 - mid * invQ; + vec2 lcMid = local0 + mid * layerStep; - bool below = (hMid <= threshold); + if (any(lessThan(lcMid, eps)) || any(greaterThan(lcMid, 1.0 - eps))) { + hi = mid; + continue; + } + + float hMid = ReadNormalLocalNoWrap(lcMid).a; + bool below = (hMid <= thr); lo = below ? mid : lo; hi = below ? hi : mid; hLo = below ? hMid: hLo; hHi = below ? hHi : hMid; } - // Pick the layer just before the crossing float pI = max(lo, 0.0); - - // Accumulate trace offsets (xy: coord, z: layer depth) traceCoordDepth.xy -= pI * layerStep; - traceCoordDepth.z -= pI * invParallaxQuality; - - vec2 localCoord = fract(baseLC + pI * layerStep); - newCoord = ATLAS(localCoord); - texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; + traceCoordDepth.z -= pI * invQ; - return localCoord; + vec2 lcFinal = clamp(local0 + pI * layerStep, eps, 1.0 - eps); + newCoord = lcFinal * vTexCoordAM.pq + vTexCoordAM.st; + texDepth = ReadNormalLocalNoWrap(lcFinal).a; + return lcFinal; } + float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { // Skip shadowing when far or almost faded if (parallaxFade >= 0.98) return 1.0; vec3 parallaxdir = tbn * lightVec; - // Degenerate / near-parallel to plane if (abs(parallaxdir.z) < 1e-4) return 1.0; + // scale to depth parallaxdir.xy *= POM_DEPTH; // Fewer steps as fade increases (scene-aware) int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; float parallaxshadow = 1.0; - - vec2 pq = vTexCoordAM.pq; - vec2 st = vTexCoordAM.st; - - vec2 baseLocal = fract(coord); + vec2 baseLocal = coord; + vec2 eps = TileEpsilon(); for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { float stepLC = 0.025 * (float(i) + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); - vec2 atlasCoord = lc * pq + st; + vec2 lc = baseLocal + parallaxdir.xy * stepLC; + + // trying to prevent seam shadows + if (any(lessThan(lc, eps)) || any(greaterThan(lc, 1.0 - eps))) { + break; + } + + float offsetHeight = ReadNormalLocalNoWrap(lc).a; - float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; - // Attenuate when the traced surface (offsetHeight) rises above the ray + // soften when the surface rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } From abad7b8f8102ced65517c382d90d3ede43f5a1e8 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 13 Dec 2025 18:05:56 +0800 Subject: [PATCH 6/7] Revert "fixing artifacts by fixing wrapping during the march and shadow" This reverts commit 79de694c65d661a2ff8d6aaceb1bf5bb2cdc0058. --- .../materials/materialMethods/pomEffects.glsl | 146 ++++++++---------- 1 file changed, 61 insertions(+), 85 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 89d75f2c..00fcbac2 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -9,14 +9,6 @@ vec4 ReadNormal(vec2 coord) { return textureGrad(normals, coord, dcdx, dcdy); } -vec4 ReadNormalLocalNoWrap(vec2 localCoord) { - vec2 tileSize = atlasSize * vTexCoordAM.pq; - vec2 eps = 0.5 / tileSize; - vec2 lc = clamp(localCoord, eps, 1.0 - eps); - vec2 atlasCoord = lc * vTexCoordAM.pq + vTexCoordAM.st; - return textureGrad(normals, atlasCoord, dcdx, dcdy); -} - #define ATLAS(_local_) (fract(_local_) * vTexCoordAM.pq + vTexCoordAM.st) // Created a new function to mitigate the side effects in case the original one has any reference outside this scope @@ -25,78 +17,66 @@ vec4 ReadNormalLocal(vec2 localCoord) { return textureGrad(normals, atlasCoord, dcdx, dcdy); } -vec2 GetParallaxCoord(float parallaxFade, float dither, - inout vec2 newCoord, inout float texDepth, inout vec3 traceCoordDepth) -{ - float invQ = 1.0 / POM_QUALITY; - float minHeight = 1.0 - invQ; - - vec4 nm = ReadNormal(vTexCoord.st); - vec2 nmM = nm.xy * 2.0 - 1.0; - float normalCheck = nmM.x + nmM.y; +vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, inout float texDepth, inout vec3 traceCoordDepth) { + float invParallaxQuality = 1.0 / POM_QUALITY; + float minHeight = 1.0 - invParallaxQuality; - if (viewVector.z >= 0.0 || nm.a >= minHeight || normalCheck <= -1.999 || parallaxFade > 0.98) - return vTexCoord.st; + vec4 normalMap = ReadNormal(vTexCoord.st); + vec2 normalMapM = normalMap.xy * 2.0 - 1.0; + float normalCheck = normalMapM.x + normalMapM.y; - vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (max(-viewVector.z, 1e-4) * POM_QUALITY); + // Early-outs: grazing view, flat height, extreme normal and nearly faded + if (viewVector.z >= 0.0 || normalMap.a >= minHeight || normalCheck <= -1.999 || parallaxFade > 0.98) return vTexCoord.st; + // Layer step in tangent plane (scaled by depth & fade) + vec2 layerStep = viewVector.xy * (0.25 * (1.0 - parallaxFade) * POM_DEPTH) / (-viewVector.z * POM_QUALITY); float i = 0.0; - vec2 local0 = vTexCoord.st; - vec2 eps = TileEpsilon(); - // Start height (use no-wrap read) - float h = ReadNormalLocalNoWrap(local0).a; - float iPrev = 0.0, hPrev = h; + vec2 baseLC = vTexCoord.st; + float h = texDepth; // if a caller-provided history exists; otherwise first sample overwrites + + // Ensure texDepth matches current start layer + { + vec2 lc0 = baseLC + i * layerStep; + h = ReadNormalLocal(lc0).a; + } #if defined GBUFFERS_TERRAIN || defined GBUFFERS_BLOCK - if (texDepth <= 1.0 - i * invQ) { - i = dither; - vec2 lc1 = local0 + i * layerStep; - // stop POM if we left the tile already - if (any(lessThan(lc1, eps)) || any(greaterThan(lc1, 1.0 - eps))) { - newCoord = AtlasClamp(clamp(local0, 0.0, 1.0)); - texDepth = h; - return clamp(local0, 0.0, 1.0); + if (texDepth <= 1.0 - i * invParallaxQuality) { + i = dither; + vec2 lc1 = baseLC + i * layerStep; + h = texDepth = ReadNormalLocal(lc1).a; } - h = texDepth = ReadNormalLocalNoWrap(lc1).a; - } #endif + // Calculate stride based on |z| float viewFlat = clamp(1.0 - abs(viewVector.z), 0.0, 1.0); float coarseStride = mix(1.0, 2.0, clamp(0.4 * parallaxFade + 0.4 * viewVector.z*viewVector.z, 0.0, 1.0)); coarseStride = (viewFlat > 0.7) ? 1.0 : coarseStride; // March forward by stride until we cross the height threshold - // But no wrap - for (; i < POM_QUALITY && h <= (1.0 - i * invQ); i += coarseStride) { - vec2 lc = local0 + i * layerStep; - if (any(lessThan(lc, eps)) || any(greaterThan(lc, 1.0 - eps))) { - // left tile -> clamp to edge, return last valid - float pI = max(iPrev, 0.0); - vec2 lcSafe = clamp(local0 + pI * layerStep, eps, 1.0 - eps); - traceCoordDepth.xy -= pI * layerStep; - traceCoordDepth.z -= pI * invQ; - newCoord = lcSafe * vTexCoordAM.pq + vTexCoordAM.st; - texDepth = ReadNormalLocalNoWrap(lcSafe).a; - return lcSafe; - } - hPrev = h; iPrev = i; - h = ReadNormalLocalNoWrap(lc).a; + float iPrev = i; + float hPrev = h; + for (; i < POM_QUALITY && h <= (1.0 - i * invParallaxQuality); i += coarseStride) { + vec2 lc = baseLC + i * layerStep; + hPrev = h; + iPrev = i; + h = ReadNormalLocal(lc).a; } // If we ran out of layers without crossing, clamp to last valid layer and return - if (i >= POM_QUALITY && h <= (1.0 - (POM_QUALITY - 1.0) * invQ)) { - float pI = float(max(int(POM_QUALITY) - 1, 0)); - vec2 lcSafe = clamp(local0 + pI * layerStep, eps, 1.0 - eps); + if (i >= POM_QUALITY && h <= (1.0 - (POM_QUALITY - 1.0) * invParallaxQuality)) { + i = POM_QUALITY; + float pI = float(max(int(i) - 1, 0)); traceCoordDepth.xy -= pI * layerStep; - traceCoordDepth.z -= pI * invQ; - newCoord = lcSafe * vTexCoordAM.pq + vTexCoordAM.st; - texDepth = ReadNormalLocalNoWrap(lcSafe).a; - return lcSafe; + traceCoordDepth.z -= pI * invParallaxQuality; + vec2 localCoord = fract(baseLC + pI * layerStep); + newCoord = ATLAS(localCoord); + texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; + return localCoord; } // Refine with a short binary search in the [iPrev, i] bracket - // no wrap again float lo = max(iPrev, 0.0); float hi = clamp(i, 0.0, POM_QUALITY); float hLo = hPrev; @@ -104,65 +84,61 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, for (int it = 0; it < 3; ++it) { float mid = 0.5 * (lo + hi); - float thr = 1.0 - mid * invQ; - vec2 lcMid = local0 + mid * layerStep; + float threshold = 1.0 - mid * invParallaxQuality; + vec2 lcMid = baseLC + mid * layerStep; + float hMid = ReadNormalLocal(lcMid).a; - if (any(lessThan(lcMid, eps)) || any(greaterThan(lcMid, 1.0 - eps))) { - hi = mid; - continue; - } - - float hMid = ReadNormalLocalNoWrap(lcMid).a; - bool below = (hMid <= thr); + bool below = (hMid <= threshold); lo = below ? mid : lo; hi = below ? hi : mid; hLo = below ? hMid: hLo; hHi = below ? hHi : hMid; } + // Pick the layer just before the crossing float pI = max(lo, 0.0); + + // Accumulate trace offsets (xy: coord, z: layer depth) traceCoordDepth.xy -= pI * layerStep; - traceCoordDepth.z -= pI * invQ; + traceCoordDepth.z -= pI * invParallaxQuality; - vec2 lcFinal = clamp(local0 + pI * layerStep, eps, 1.0 - eps); - newCoord = lcFinal * vTexCoordAM.pq + vTexCoordAM.st; - texDepth = ReadNormalLocalNoWrap(lcFinal).a; - return lcFinal; -} + vec2 localCoord = fract(baseLC + pI * layerStep); + newCoord = ATLAS(localCoord); + texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; + return localCoord; +} float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { // Skip shadowing when far or almost faded if (parallaxFade >= 0.98) return 1.0; vec3 parallaxdir = tbn * lightVec; + // Degenerate / near-parallel to plane if (abs(parallaxdir.z) < 1e-4) return 1.0; - // scale to depth parallaxdir.xy *= POM_DEPTH; // Fewer steps as fade increases (scene-aware) int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; float parallaxshadow = 1.0; - vec2 baseLocal = coord; - vec2 eps = TileEpsilon(); + + vec2 pq = vTexCoordAM.pq; + vec2 st = vTexCoordAM.st; + + vec2 baseLocal = fract(coord); for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { float stepLC = 0.025 * (float(i) + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 lc = baseLocal + parallaxdir.xy * stepLC; - - // trying to prevent seam shadows - if (any(lessThan(lc, eps)) || any(greaterThan(lc, 1.0 - eps))) { - break; - } - - float offsetHeight = ReadNormalLocalNoWrap(lc).a; + vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); + vec2 atlasCoord = lc * pq + st; - // soften when the surface rises above the ray + float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; + // Attenuate when the traced surface (offsetHeight) rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } From 4452bdba32f1477d8faf9ee9f50afa855c535afd Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 13 Dec 2025 18:08:06 +0800 Subject: [PATCH 7/7] fixed unexpected graphic issues --- .../materials/materialMethods/pomEffects.glsl | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/shaders/lib/materials/materialMethods/pomEffects.glsl b/shaders/lib/materials/materialMethods/pomEffects.glsl index 00fcbac2..b5589f91 100644 --- a/shaders/lib/materials/materialMethods/pomEffects.glsl +++ b/shaders/lib/materials/materialMethods/pomEffects.glsl @@ -9,6 +9,22 @@ vec4 ReadNormal(vec2 coord) { return textureGrad(normals, coord, dcdx, dcdy); } +vec2 TileEpsilon() { + vec2 tileSize = atlasSize * vTexCoordAM.pq; + return 0.5 / tileSize; +} + +vec2 AtlasClamp(vec2 local) { + vec2 eps = TileEpsilon(); + vec2 lc = clamp(local, eps, 1.0 - eps); + return lc * vTexCoordAM.pq + vTexCoordAM.st; +} + +vec4 ReadNormalLocalNoWrap(vec2 localCoord) { + vec2 atlasCoord = AtlasClamp(localCoord); + return textureGrad(normals, atlasCoord, dcdx, dcdy); +} + #define ATLAS(_local_) (fract(_local_) * vTexCoordAM.pq + vTexCoordAM.st) // Created a new function to mitigate the side effects in case the original one has any reference outside this scope @@ -70,10 +86,10 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino float pI = float(max(int(i) - 1, 0)); traceCoordDepth.xy -= pI * layerStep; traceCoordDepth.z -= pI * invParallaxQuality; - vec2 localCoord = fract(baseLC + pI * layerStep); - newCoord = ATLAS(localCoord); - texDepth = ReadNormalLocal(baseLC + pI * layerStep).a; - return localCoord; + vec2 localCoord = baseLC + pI * layerStep; + newCoord = AtlasClamp(localCoord); + texDepth = ReadNormalLocalNoWrap(localCoord).a; + return clamp(localCoord, 0.0, 1.0); } // Refine with a short binary search in the [iPrev, i] bracket @@ -109,42 +125,46 @@ vec2 GetParallaxCoord(float parallaxFade, float dither, inout vec2 newCoord, ino return localCoord; } -float GetParallaxShadow(float parallaxFade, float dither, float height, vec2 coord, vec3 lightVec, mat3 tbn) { - // Skip shadowing when far or almost faded +float GetParallaxShadow(float parallaxFade, float dither, float height, + vec2 coord /* local [0..1] */, vec3 lightVec, mat3 tbn) { if (parallaxFade >= 0.98) return 1.0; vec3 parallaxdir = tbn * lightVec; - // Degenerate / near-parallel to plane if (abs(parallaxdir.z) < 1e-4) return 1.0; + // scale to your POM depth (same as main trace) parallaxdir.xy *= POM_DEPTH; - // Fewer steps as fade increases (scene-aware) + // steps tuned by fade int MAX_STEPS = (parallaxFade < 0.25) ? 4 : 2; float parallaxshadow = 1.0; - - vec2 pq = vTexCoordAM.pq; - vec2 st = vTexCoordAM.st; - - vec2 baseLocal = fract(coord); + vec2 baseLocal = coord; + vec2 eps = TileEpsilon(); for (int i = 0; i < MAX_STEPS && parallaxshadow >= 0.01; ++i) { float stepLC = 0.025 * (float(i) + dither); float currentHeight = height + parallaxdir.z * stepLC; - vec2 lc = fract(baseLocal + parallaxdir.xy * stepLC); - vec2 atlasCoord = lc * pq + st; + // NO fract: march in local, and bail if we’d leave the tile + vec2 lc = baseLocal + parallaxdir.xy * stepLC; - float offsetHeight = textureGrad(normals, atlasCoord, dcdx, dcdy).a; - // Attenuate when the traced surface (offsetHeight) rises above the ray + // If off the tile, treat as unoccluded and stop (prevents seam shadows) + if (any(lessThan(lc, eps)) || any(greaterThan(lc, 1.0 - eps))) { + break; + } + + float offsetHeight = ReadNormalLocalNoWrap(lc).a; + + // soften when the surface rises above the ray parallaxshadow *= clamp(1.0 - (offsetHeight - currentHeight) * 4.0, 0.0, 1.0); } return mix(parallaxshadow, 1.0, parallaxFade); } + // Big thanks to null511 for slope normals vec3 GetParallaxSlopeNormal(vec2 texCoord, float traceDepth, vec3 viewDir) { vec2 atlasPixelSize = 1.0 / atlasSize;