Moving source fragments trail five curved, fading echoes. Trail length stretches their paths and echo spacing separates the strands. Motion is procedural, so still photos animate too; no past video frames are stored.
Defaults as a layer
{ "effectId": "echoRibbons", "params": { "amount": 0.8, "speed": 0.45, "motion": 0.55, "size": 0.85, "coverage": 0.65, "softness": 0.15, "centerX": 0.5, "centerY": 0.5, "trail": 0.65, "spacing": 0.5, "width": 0.022 } }
GLSL fragment shader
The effect's own code. The uniforms and helpers above it — uTex, uFeedback, the depth proxy, the frame ring, depthAt(), timeAt() — are the same header every Ether-MOSH shader opens with, and are documented once on the registry rather than reprinted on all 143 pages.
void main(){ vec4 base = texture2D(uTex, vUv); vec2 aspect = vec2(uResolution.x / max(uResolution.y, 1.0), 1.0); vec2 origin = vec2(uCenterX, uCenterY); if(uOrganicShape>0.5) origin=vec2(0.5)+(origin-0.5)*0.35/vec2(max(aspect.x,1.0),max(1.0/aspect.x,1.0)); vec2 p = (vUv - origin) * aspect / max(uSize, 0.01); float t = uTime * uSpeed; float aa = max(1.25 / max(uResolution.y * uSize, 1.0), 0.001) + uSoftness * 0.025; float coverage = 0.0; vec4 art = base; vec2 sampleUv=vUv; float fade=1.0; for(int i=0;i<5;i++) { float k=float(i); float x=p.x+(k-2.0)*0.025*uSpacing; float phase=x*8.0-t*uMotion+k*0.22; float y=sin(phase)*0.115+sin(phase*1.7+k*0.3)*0.035+(k-2.0)*0.045*uSpacing; float len=0.04+uTrail*0.38; float tail=smoothstep(-len,-len*0.3,x)*(1.0-smoothstep(len*0.7,len,x)); float width=uWidth*(0.2+tail*0.8); float m=(1.0-smoothstep(width,width+aa,abs(p.y-y)))*tail; m*=pow(0.78,k)*(1.0-smoothstep(uCoverage*5.0,uCoverage*5.0+1.0,k)); if(m>coverage) { sampleUv=clamp(origin+vec2(x+k*0.04,(p.y-y)*3.0+sin(phase)*0.18)*uSize/aspect,0.0,1.0); fade=1.0-k*0.085; } coverage=max(coverage,m); } art=texture2D(uTex,sampleUv);art.rgb*=fade; gl_FragColor = floatingResult(base, art, coverage * smoothstep(0.0, 0.06, uCoverage)); }