// Pipeline step 6: advection — the field carries itself downstream. // // Semi-Lagrangian: instead of pushing each texel forward (which leaves gaps), trace backwards // along the velocity to find where this texel's contents came from, and sample there. // // MANUAL_FILTERING does that sample with an explicit bilinear fetch rather than relying on the // hardware's, for platforms that cannot linearly filter float textures. varying vec2 vUv; uniform sampler2D u_tVelocity; uniform sampler2D u_tSource; uniform vec2 u_texelSize; uniform float u_dt; uniform float u_dissipation; vec4 bilerp(sampler2D sam, vec2 uv, vec2 tsize) { vec2 st = uv / tsize - 0.5; vec2 iuv = floor(st); vec2 fuv = fract(st); vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); } void main() { vec4 result; #ifdef MANUAL_FILTERING vec2 coord = vUv - u_dt * bilerp(u_tVelocity, vUv, u_texelSize).xy * u_texelSize; result = bilerp(u_tSource, coord, u_texelSize); #else vec2 coord = vUv - u_dt * texture2D(u_tVelocity, vUv).xy * u_texelSize; result = texture2D(u_tSource, coord); #endif // dissipation is a per-second factor; raise it to (dt*60) so the trail fades at the same rate // regardless of refresh rate (at 60fps the exponent is 1, preserving the tuned look). gl_FragColor.rgb = result.rgb * pow(u_dissipation, u_dt * 60.0); gl_FragColor.a = 1.0; }