Dissolve
A metallic-dark torus knot that dissolves and reforms on an endless loop, driven entirely by a hand-written shaderMaterial: a 3D noise field is compared against an animated threshold uniform — fragments below it vanish (discard), fragments near the boundary glow from a warm orange core out to a hot near-white edge. A second points layer sits on the exact same geometry, faint everywhere except right where the dissolve line is currently sweeping past, so the effect reads as a boundary traveling through a volume rather than a screen-space wipe.
How it works
shaderMaterialviaargs: neither the mesh nor the points material is a namedTHREEmaterial —{ shaderMaterial: null, args: [{ vertexShader, fragmentShader, uniforms, ... }] }passes a single options object straight tonew THREE.ShaderMaterial(...), the sameargsreconstruction mechanism every other example uses for geometry constructor args.- Uniform objects, not reactive props:
uniformsis a plain{ uTime: { value }, uThreshold: { value }, ... }map handed to the constructor once. Nothing here goes through aState— see duck-typed props for the one special case (ShaderMaterial.uniformsmerges per-named-uniform in place if you do re-apply it as a prop, which this example doesn't need). - Driving the threshold from
onFrame:onFrame: (root, delta, self) => { self.material.uniforms.uThreshold.value = ... }—selfis this node's ownTHREE.Mesh/THREE.Pointsinstance, soself.materialis the exactShaderMaterialthat was attached, and mutating.uniforms.<name>.valuewrites straight into the compiled program without re-creating anything. This is rule 2 (onFrame), theuseFrame()analog — see Animation & Loop. - One threshold formula, two independent
onFramecallbacks: the mesh's and the points'onFrameeach readroot.clock.getElapsedTime()and compute the samedissolveThreshold(elapsed)sine wave independently — no shared mutable state needed, they just agree by construction, which is what keeps the discard boundary and the glowing points in lockstep. discard+ edge glow: the fragment shader samples a 3D value-noise field (hash + trilinear smoothstep, with a cheap 2-octavefbmon top) at each fragment's object-space position, compares it touThreshold, anddiscards anything below it. Fragments that survive but sit close to the boundary blend fromuGlowOuter(#ff7a3c) touGlowInner(#ffffdd) over the backdrop#0a0c14— a Fresnel-free half-lambert (dot(normal, lightDir) * 0.5 + 0.5) keeps the surviving shell reading as a solid past the edge.- Points hugging the surface: the point cloud reuses the same
torusKnotGeometryargs, so every point sits exactly on the mesh's surface. Its vertex shader runs the identical noise sample and setsvGlow = 1 - clamp(abs(density - uThreshold) / uEdgeWidth, 0, 1)— symmetric around the boundary rather than gated bydiscard— so points brighten and grow (gl_PointSize) as the dissolve line sweeps past them from either side, then fade back to a faint base color. side: DoubleSideon the mesh material keeps the torus knot's inner tube wall visible through the holesdiscardpunches in it, instead of showing flat backdrop where a face got backface-culled.onCreatedsetsroot.scene.backgroundonce, and callsroot.camera.lookAt(0, 0, 0)because the camera sits off-axis at[3.4, 1.8, 3.6]— same pattern as every other off-center camera on this site.