diff --git a/DeepDrftPublic/Interop/visualizer/MixVisualizer.ts b/DeepDrftPublic/Interop/visualizer/MixVisualizer.ts index a83709f..f2c0d0f 100644 --- a/DeepDrftPublic/Interop/visualizer/MixVisualizer.ts +++ b/DeepDrftPublic/Interop/visualizer/MixVisualizer.ts @@ -502,7 +502,7 @@ float smin(float a, float b, float k) { // edge; y is screen-row time as before. Loudness at this row sets the attached // half-width; loudness at neighbouring rows lets the metaball smooth-min coalesce // vertically into a continuous liquid column rather than discrete per-row slabs. -float ribbonField(vec2 px, float nowY, float pixelsPerSecond, float maxHalfWidth, out float ampOut) { +float ribbonField(vec2 px, float nowY, float pixelsPerSecond, float maxHalfWidth, float playheadFeed, out float ampOut) { float screenYTop = px.y; float screenX = px.x; @@ -546,8 +546,10 @@ float ribbonField(vec2 px, float nowY, float pixelsPerSecond, float maxHalfWidth float dtRow = reach * maxHalfWidth / pixelsPerSecond; // xn-reach back to seconds float ampUp = sampleAt(t - dtRow); float ampDn = sampleAt(t + dtRow); - float up = sdRoundBox(vec2(xn, reach), vec2(max(ampUp, 0.001), reach), cornerR); - float dn = sdRoundBox(vec2(xn, -reach), vec2(max(ampDn, 0.001), reach), cornerR); + vec2 boxUp = vec2(max(ampUp, 0.001), reach); + vec2 boxDn = vec2(max(ampDn, 0.001), reach); + float up = sdRoundBox(vec2(xn, reach), boxUp, min(cornerR, min(boxUp.x, boxUp.y) - 1e-3)); + float dn = sdRoundBox(vec2(xn, -reach), boxDn, min(cornerR, min(boxDn.x, boxDn.y) - 1e-3)); float k = BUBBLE_SMOOTHMIN_K * uBubblyness; attached = smin(attached, smin(up, dn, k), k); } @@ -569,8 +571,8 @@ float ribbonField(vec2 px, float nowY, float pixelsPerSecond, float maxHalfWidth // Spawn column: spread across the ribbon width, biased by loudness presence. float colX = (seed * 2.0 - 1.0) * 0.8; // Loudness feeding this blob's column at the now line — a louder mix sheds - // bigger blobs. Sampled once at the playhead (not per-pixel-varying). - float feed = sampleAt(uPlayheadSeconds); + // bigger blobs. playheadFeed is pre-computed once in main() (fragment-invariant). + float feed = playheadFeed; float radius = (0.05 + 0.10 * seed) * (0.4 + 0.6 * feed) * uDetach; // Rise phase: 0→1 over the blob's life, looping. Different phase offset per // blob so they don't pulse in unison. Speed scales mildly with detach. @@ -619,20 +621,25 @@ void main() { return; } + // Hoist the playhead-feed tap: sampleAt(uPlayheadSeconds) is fragment-invariant + // (depends only on a uniform) and would otherwise run inside the 7-iteration detach + // blob loop × 5 ribbonField calls = up to 35× per pixel. Compute it once here. + float playheadFeed = sampleAt(uPlayheadSeconds); + // ── EFFECT 2+3 geometry: evaluate the liquid SDF + its gradient (surface normal). ── // The gradient of the SDF is the outward surface normal — we need it for the glass // (specular, Fresnel, refraction). Central differences cost 4 extra field evals; the // step is one device-pixel mapped into the field's xn/yTop frame. vec2 px = vec2(screenX, screenYTop); float amp; - float d = ribbonField(px, nowY, pixelsPerSecond, maxHalfWidth, amp); + float d = ribbonField(px, nowY, pixelsPerSecond, maxHalfWidth, playheadFeed, amp); float e = 1.0; // 1px central-difference step float ignore; - float dRx = ribbonField(px + vec2(e, 0.0), nowY, pixelsPerSecond, maxHalfWidth, ignore); - float dLx = ribbonField(px - vec2(e, 0.0), nowY, pixelsPerSecond, maxHalfWidth, ignore); - float dUy = ribbonField(px + vec2(0.0, e), nowY, pixelsPerSecond, maxHalfWidth, ignore); - float dDy = ribbonField(px - vec2(0.0, e), nowY, pixelsPerSecond, maxHalfWidth, ignore); + float dRx = ribbonField(px + vec2(e, 0.0), nowY, pixelsPerSecond, maxHalfWidth, playheadFeed, ignore); + float dLx = ribbonField(px - vec2(e, 0.0), nowY, pixelsPerSecond, maxHalfWidth, playheadFeed, ignore); + float dUy = ribbonField(px + vec2(0.0, e), nowY, pixelsPerSecond, maxHalfWidth, playheadFeed, ignore); + float dDy = ribbonField(px - vec2(0.0, e), nowY, pixelsPerSecond, maxHalfWidth, playheadFeed, ignore); // Surface normal in screen space (points OUT of the liquid). y flipped because our // field y is screen-down. Guard the zero-length case at flat interiors. vec2 grad = vec2(dRx - dLx, dUy - dDy);