hello to all
I ran into a problem when converting a shader from the shadertoy site
link:
https://www.shadertoy.com/view/ld3cWN
in the TouchDesigner program, when the mouse moves, its reaction is copied diagonally
What I mean by being copied is that when I move the mouse in a coordinate, the mouse movement is seen in the same way in other parts in addition to those coordinates
And this error is done according to a regular pattern
That is, the additional mouse movements continue diagonally from each other
And I think that this pattern will change over time
When I replaced bufferb with Noise Top the mouse problem was solved.
I have two guesses for this error
"The mouse force is injected only in buffera (dv += m * normz(vec3(d, 0.0))).
buffera then does two advection steps, the second one with a massive multiplier 48.0 * gcs * (u - auv).xy (where gcs = 2.87 + 10000.0 * gcurve). Curvature can make this displacement >> full screen size.
In ShaderToy the sampler clamps automatically. In TouchDesigner the default Extend mode is often Repeat, so when aUv goes outside [0,1] it wraps around the edges → perfect periodic ghosts.
The 12-direction diagonal stencils (diagH/diagV, nne/ene/etc.) + feedback through bufferb turn these wraps into a visible diagonal lattice of copies. The pattern evolves because the velocity field changes.
When i replaced bufferb with “Noise Texture”, the u field became incoherent → ghosts disappeared. This is why only bufferb was the “culprit” in my test.
But even without using the mouse, everything in Buffera , Bufferb falls apart within a few seconds of running the shader"
I first tried to solve the problem with this method
Set the input’s Extend mode to Hold (which is equivalent to GL_CLAMP_TO_EDGE)
But the problem didn’t work
I tried the second method
Add UV clamping in the shader code itself
“// Clamp UV to stay within texture bounds (emulates GL_CLAMP_TO_EDGE)
vec2 clampUV(vec2 uv) {
vec2 texel = 1.0 / iResolution.xy;
return clamp(uv, texel \* 0.5, 1.0 - texel \* 0.5);
}”
// === CLAMP all neighbor UVs ===
vec3 u = texture(sTD2DInputs\[0\], clampUV(aUv)).xyz;
vec3 u_n = texture(sTD2DInputs\[0\], clampUV(aUv+n)).xyz;
vec3 u_e = texture(sTD2DInputs\[0\], clampUV(aUv+e)).xyz;
vec3 u_s = texture(sTD2DInputs\[0\], clampUV(aUv+s)).xyz;
vec3 u_w = texture(sTD2DInputs\[0\], clampUV(aUv+w)).xyz;
vec3 u_nw = texture(sTD2DInputs\[0\], clampUV(aUv+nw)).xyz;
vec3 u_sw = texture(sTD2DInputs\[0\], clampUV(aUv+sw)).xyz;
vec3 u_ne = texture(sTD2DInputs\[0\], clampUV(aUv+ne)).xyz;
vec3 u_se = texture(sTD2DInputs\[0\], clampUV(aUv+se)).xyz;
// === CLAMP all textures read from sTD2DInputs\[1\] (bufferb) ===
vec3 u = texture(sTD2DInputs\[1\], clampUV(vUv)).xyz;
vec3 u_n = texture(sTD2DInputs\[1\], clampUV(vUv+texel*n.xy)).xyz;
vec3 u_e = texture(sTD2DInputs\[1\], clampUV(vUv+texel*e.xy)).xyz;
vec3 u_s = texture(sTD2DInputs\[1\], clampUV(vUv+texel*s.xy)).xyz;
vec3 u_w = texture(sTD2DInputs\[1\], clampUV(vUv+texel*w.xy)).xyz;
vec3 u_nw = texture(sTD2DInputs\[1\], clampUV(vUv+texel*nw.xy)).xyz;
vec3 u_sw = texture(sTD2DInputs\[1\], clampUV(vUv+texel*sw.xy)).xyz;
vec3 u_ne = texture(sTD2DInputs\[1\], clampUV(vUv+texel*ne.xy)).xyz;
vec3 u_se = texture(sTD2DInputs\[1\], clampUV(vUv+texel*se.xy)).xyz;
// === CLAMP all texture reads from sTD2DInputs\[0\] (buffer feedback) ===
vec3 v = texture(sTD2DInputs\[0\], clampUV(vUv)).xyz;
vec3 v_n = texture(sTD2DInputs\[0\], clampUV(vUv+texel*n.xy)).xyz;
vec3 v_e = texture(sTD2DInputs\[0\], clampUV(vUv+texel*e.xy)).xyz;
vec3 v_s = texture(sTD2DInputs\[0\], clampUV(vUv+texel*s.xy)).xyz;
vec3 v_w = texture(sTD2DInputs\[0\], clampUV(vUv+texel*w.xy)).xyz;
vec3 v_nw = texture(sTD2DInputs\[0\], clampUV(vUv+texel*nw.xy)).xyz;
vec3 v_sw = texture(sTD2DInputs\[0\], clampUV(vUv+texel*sw.xy)).xyz;
vec3 v_ne = texture(sTD2DInputs\[0\], clampUV(vUv+texel*ne.xy)).xyz;
vec3 v_se = texture(sTD2DInputs\[0\], clampUV(vUv+texel*se.xy)).xyz;
Again, the problem was not solved.
My second guess is this
“where NaN can appear in the code”
Buffer A - The NaN Factory
Look at the angle calculation:
float t0 = clamp(acos(dot(xy_n, xy_nne) / (length(xy_n) \* length(xy_nne))), 0.0, PI);
NaN case 1: If length(xy_n) or length(xy_nne) is zero, you get 0/0 = NaN. Then acos(NaN) = NaN. Then clamp(NaN, …) = NaN.
NaN case 2: Due to floating point precision, dot(a,b)/(length(a)\*length(b)) can slightly exceed 1.0 or go below -1.0. acos(1.0001) = NaN.
Then gcurve becomes NaN, then gcs = 2.87 + 10000.0 \* NaN = NaN.
Then 48.0 \* gcs \* (u - auv).xy = NaN vector, fed into advect() → every texture read uses NaN UV → returns 0 or undefined values.
Then dv becomes NaN, stored in output. Next frame, every neighbor that reads this pixel gets NaN. NaN spreads like wildfire through the feedback loop.
Also normz:
vec3 normz(vec3 x) {
return x == vec3(0.0) ? vec3(0.0) : normalize(x);
}
normalize(vec3(NaN)) = NaN. And vec3(NaN) == vec3(0.0) is false, so the guard doesn’t catch NaN.
The soft clamping:
dv = dv - 0.005 \* ld*ld*ld \* normz(dv);
If ld (length of dv) is very large, ld³ overflows to Inf, then Inf \* normz(dv) = Inf, then dv - Inf = -Inf or NaN.
I tried to solve this problem with this method
Solution: NaN-Safe Buffers
The idea: after each computation buffer, add a sanitizer buffer that catches NaN/Inf and replaces them with safe values. This acts as a firewall preventing NaN propagation.
But unfortunately the problem still did not get solved
I don’t know how to solve this problem
I would appreciate it if you could help me
Satin Flow - Dispersion.zip (1.1 MB)