From eed40a0cfaf10bf39a66d721fa6dcebfba9c67c9 Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Wed, 28 Jan 2026 22:01:49 +0100 Subject: [PATCH 1/5] contour test --- docs/src/app/(shaders)/warp/page.tsx | 1 + packages/shaders-react/src/shaders/warp.tsx | 8 +++++ packages/shaders/src/shaders/warp.ts | 33 +++++++++++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/src/app/(shaders)/warp/page.tsx b/docs/src/app/(shaders)/warp/page.tsx index 6ec92ae40..21e4884cc 100644 --- a/docs/src/app/(shaders)/warp/page.tsx +++ b/docs/src/app/(shaders)/warp/page.tsx @@ -22,6 +22,7 @@ const WarpWithControls = () => { const [params, setParams] = useControls(() => { return { + edgeTrap: { value: defaults.edgeTrap, min: 0, max: 1, order: 10 }, proportion: { value: defaults.proportion, min: 0, max: 1, order: 100 }, softness: { value: defaults.softness, min: 0, max: 1, order: 101 }, distortion: { value: defaults.distortion, min: 0, max: 1, order: 102 }, diff --git a/packages/shaders-react/src/shaders/warp.tsx b/packages/shaders-react/src/shaders/warp.tsx index b2fe60180..4691e6b5f 100644 --- a/packages/shaders-react/src/shaders/warp.tsx +++ b/packages/shaders-react/src/shaders/warp.tsx @@ -32,6 +32,7 @@ export const defaultPreset: WarpPreset = { swirlIterations: 10, shapeScale: 0.1, shape: 'checks', + edgeTrap: 0.5 }, }; @@ -51,6 +52,7 @@ export const presetCauldron: WarpPreset = { swirlIterations: 7, shapeScale: 0.6, shape: 'edge', + edgeTrap: 0.5 }, }; @@ -71,6 +73,7 @@ export const presetInk: WarpPreset = { swirlIterations: 10, shapeScale: 0.28, shape: 'checks', + edgeTrap: 0.5 }, }; @@ -90,6 +93,7 @@ export const presetKelp: WarpPreset = { swirlIterations: 3, shapeScale: 1, shape: 'stripes', + edgeTrap: 0.5 }, }; @@ -110,6 +114,7 @@ export const presetNectar: WarpPreset = { swirlIterations: 10, shapeScale: 0.75, shape: 'edge', + edgeTrap: 0.5 }, }; @@ -129,6 +134,7 @@ export const presetPassion: WarpPreset = { swirlIterations: 6, shapeScale: 0.25, shape: 'checks', + edgeTrap: 0.5 }, }; @@ -153,6 +159,7 @@ export const Warp: React.FC = memo(function WarpImpl({ swirlIterations = defaultPreset.params.swirlIterations, shapeScale = defaultPreset.params.shapeScale, shape = defaultPreset.params.shape, + edgeTrap = defaultPreset.params.edgeTrap, // Sizing props fit = defaultPreset.params.fit, @@ -177,6 +184,7 @@ export const Warp: React.FC = memo(function WarpImpl({ u_swirlIterations: swirlIterations, u_shapeScale: shapeScale, u_shape: WarpPatterns[shape], + u_edgeTrap: edgeTrap, u_noiseTexture: getShaderNoiseTexture(), // Sizing uniforms diff --git a/packages/shaders/src/shaders/warp.ts b/packages/shaders/src/shaders/warp.ts index 95a0bfc94..8dfb14523 100644 --- a/packages/shaders/src/shaders/warp.ts +++ b/packages/shaders/src/shaders/warp.ts @@ -62,8 +62,11 @@ uniform float u_shapeScale; uniform float u_distortion; uniform float u_swirl; uniform float u_swirlIterations; +uniform float u_edgeTrap; in vec2 v_patternUV; +in vec2 v_responsiveUV; +in vec2 v_responsiveBoxGivenSize; out vec4 fragColor; @@ -91,20 +94,38 @@ void main() { vec2 uv = v_patternUV; uv *= .5; + // Canvas edge - fade out effects near edges + vec2 borderUV = v_responsiveUV + .5; + vec2 mask = min(borderUV, 1. - borderUV); + vec2 pixel_thickness = 450. / v_responsiveBoxGivenSize; + float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); + float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); + maskX = pow(maskX, .25); + maskY = pow(maskY, .25); + float edge = clamp(1. - maskX * maskY, 0., 1.); + const float firstFrameOffset = 118.; float t = 0.0625 * (u_time + firstFrameOffset); float n1 = valueNoise(uv * 1. + t); float n2 = valueNoise(uv * 2. - t); float angle = n1 * TWO_PI; - uv.x += 4. * u_distortion * n2 * cos(angle); - uv.y += 4. * u_distortion * n2 * sin(angle); - - float swirl = u_swirl; + + float radius = smoothstep(0., 1., length(uv - .5)); + float edgeFadeW = u_edgeTrap * edge; + float edgeFade = u_edgeTrap * pow(edge, 3.); + uv -= vec2(.5); + float angleTest = 3. * edgeFade * angle; + uv = rotate(uv, -angleTest); + uv += vec2(.5); + + uv.x += 4. * u_distortion * n2 * cos(angle) * (1. - edgeFadeW); + uv.y += 4. * u_distortion * n2 * sin(angle) * (1. - edgeFadeW); + + float swirl = u_swirl * (1. - edgeFadeW); for (int i = 1; i <= 20; i++) { if (i >= int(u_swirlIterations)) break; float iFloat = float(i); - // swirl *= (1. - smoothstep(.0, .25, length(fwidth(uv)))); uv.x += swirl / iFloat * cos(t + iFloat * 1.5 * uv.y); uv.y += swirl / iFloat * cos(t + iFloat * 1. * uv.x); } @@ -167,6 +188,7 @@ export interface WarpUniforms extends ShaderSizingUniforms { u_distortion: number; u_swirl: number; u_swirlIterations: number; + u_edgeTrap: number; u_noiseTexture?: HTMLImageElement; } @@ -180,6 +202,7 @@ export interface WarpParams extends ShaderSizingParams, ShaderMotionParams { distortion?: number; swirl?: number; swirlIterations?: number; + edgeTrap?: number; } export const WarpPatterns = { From 3e566f5924c33b7c1a035309fd03cf43e6b0dd5e Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Thu, 29 Jan 2026 16:29:05 +0100 Subject: [PATCH 2/5] responsive rect to heatmap --- packages/shaders/src/shaders/heatmap.ts | 117 +++++++++++++++++------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/packages/shaders/src/shaders/heatmap.ts b/packages/shaders/src/shaders/heatmap.ts index f2d2b955c..f388da49b 100644 --- a/packages/shaders/src/shaders/heatmap.ts +++ b/packages/shaders/src/shaders/heatmap.ts @@ -49,6 +49,8 @@ precision highp float; in mediump vec2 v_imageUV; in mediump vec2 v_objectUV; +in mediump vec2 v_responsiveUV; +in mediump vec2 v_responsiveBoxGivenSize; out vec4 fragColor; uniform sampler2D u_image; @@ -89,6 +91,11 @@ float sst(float edge0, float edge1, float x) { return smoothstep(edge0, edge1, x); } +float sdRoundedBox(vec2 p, vec2 halfSize, float radius) { + vec2 d = abs(p) - halfSize + radius; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius; +} + float shadowShape(vec2 uv, float t, float contour) { vec2 scaledUV = uv; @@ -187,23 +194,65 @@ float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, fl } void main() { - vec2 uv = v_objectUV + .5; - uv.y = 1. - uv.y; - - vec2 imgUV = v_imageUV; - imgUV -= .5; - imgUV *= 0.5714285714285714; - imgUV += .5; - float imgSoftFrame = getImgFrame(imgUV, .03); - - vec4 img = texture(u_image, imgUV); - vec2 dudx = dFdx(imgUV); - vec2 dudy = dFdy(imgUV); - - if (img.a == 0.) { - fragColor = u_colorBack; - return; - } + vec2 uv = v_responsiveUV; +// vec2 uv = v_objectUV + .5; +// uv.y = 1. - uv.y; +// vec2 imgUV = v_imageUV; +// imgUV -= .5; +// imgUV *= 0.5714285714285714; +// imgUV += .5; +// float imgSoftFrame = getImgFrame(imgUV, .03); +// +// vec4 img = texture(u_image, imgUV); +// vec2 dudx = dFdx(imgUV); +// vec2 dudy = dFdy(imgUV); +// +// if (img.a == 0.) { +// fragColor = u_colorBack; +// return; +// } + + float canvasRatio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; + vec2 shapeUV = uv; + shapeUV.x *= max(canvasRatio, 1.); + shapeUV.y /= min(canvasRatio, 1.); + + vec2 halfSize = vec2( + 1. * max(canvasRatio, 1.), + 1. / min(canvasRatio, 1.) + ) * 0.5; + + float roundness = 0.05; + float radius = roundness * min(halfSize.x, halfSize.y); + + float dist = sdRoundedBox(shapeUV, halfSize, radius); + + float blurSize = .4; + float contourSize = .01; + + // Shape: 0 inside, 1 outside + float shape = smoothstep(-0.001, 0.001, dist); + + // Outer blur: falloff outside the shape + float outerBlurRaw = smoothstep(0.0, blurSize, dist); + float outerBlur = 1. - mix(1., 1. - outerBlurRaw, shape); + outerBlur = shape * (1. - pow(outerBlur, .2)); + + // Inner blur: falloff inside the shape + float innerBlurRaw = smoothstep(0.0, -blurSize, dist); + float innerBlur = innerBlurRaw; + + // Contour: edge detection + float contourRaw = 1.0 - smoothstep(0.0, contourSize, abs(dist)); + float contour = mix(contourRaw, 0., shape); + +// float edgeFade = 1.0; +// float edgeThreshold = 0.05; +// edgeFade *= smoothstep(0., edgeThreshold, uv.y + 0.5); +// edgeFade *= 1. - smoothstep(1. - edgeThreshold, 1., uv.y + 0.5); +// edgeFade *= smoothstep(0., edgeThreshold, uv.x + 0.5); +// edgeFade *= 1. - smoothstep(1. - edgeThreshold, 1., uv.x + 0.5); +// outerBlur *= edgeFade; float t = .1 * u_time; t -= .3; @@ -215,30 +264,34 @@ void main() { tCopy = mod(tCopy, 1.); tCopy2 = mod(tCopy2, 1.); - vec2 animationUV = imgUV - vec2(.5); +// vec2 animationUV = imgUV - vec2(.5); + vec2 animationUV = uv + 0.5; float angle = -u_angle * PI / 180.; float cosA = cos(angle); float sinA = sin(angle); + animationUV -= 0.5; animationUV = vec2( animationUV.x * cosA - animationUV.y * sinA, animationUV.x * sinA + animationUV.y * cosA ) + vec2(.5); - float shape = img[0]; - - img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]); - - float outerBlur = 1. - mix(1., img[1], shape); - float innerBlur = mix(img[1], 0., shape); - float contour = mix(img[2], 0., shape); - - outerBlur *= imgSoftFrame; - +// float shape = img[0]; +// +// img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]); +// +// float outerBlur = 1. - mix(1., img[1], shape); +// float innerBlur = mix(img[1], 0., shape); +// float contour = mix(img[2], 0., shape); +// +// outerBlur *= imgSoftFrame; + + innerBlur = 1. - innerBlur; float shadow = shadowShape(animationUV, t, innerBlur); float shadowCopy = shadowShape(animationUV, tCopy, innerBlur); float shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur); - float inner = .8 + .8 * innerBlur; +// float inner = .8 + .8 * innerBlur; + float inner = innerBlur; inner = mix(inner, 0., shadow); inner = mix(inner, 0., shadowCopy); inner = mix(inner, 0., shadowCopy2); @@ -254,13 +307,14 @@ void main() { t *= 3.; t = mod(t - .1, 1.); - outer = .9 * pow(outerBlur, .8); + outer = outerBlur; float y = mod(animationUV.y - t, 1.); float animatedMask = sst(.3, .65, y) * (1. - sst(.65, 1., y)); animatedMask = .5 + animatedMask; outer *= animatedMask; outer *= mix(0., 5., pow(u_outerGlow, 2.)); - outer *= imgSoftFrame; +// outer *= imgSoftFrame; +// outer *= edgeFade; } inner = pow(inner, 1.2); @@ -293,6 +347,7 @@ void main() { color += .02 * (fract(sin(dot(uv + 1., vec2(12.9898, 78.233))) * 43758.5453123) - .5); fragColor = vec4(color, opacity); +// fragColor = vec4(vec3(shape * (1. - outerBlurRaw)), 1.); } `; From 8509e87e82654e892dca2ae7a86a2f1b7ecc7545 Mon Sep 17 00:00:00 2001 From: ksenia Date: Wed, 8 Jul 2026 23:22:26 +0400 Subject: [PATCH 3/5] reset heatmap --- packages/shaders/src/shaders/heatmap.ts | 117 +++++++----------------- 1 file changed, 31 insertions(+), 86 deletions(-) diff --git a/packages/shaders/src/shaders/heatmap.ts b/packages/shaders/src/shaders/heatmap.ts index f388da49b..f2d2b955c 100644 --- a/packages/shaders/src/shaders/heatmap.ts +++ b/packages/shaders/src/shaders/heatmap.ts @@ -49,8 +49,6 @@ precision highp float; in mediump vec2 v_imageUV; in mediump vec2 v_objectUV; -in mediump vec2 v_responsiveUV; -in mediump vec2 v_responsiveBoxGivenSize; out vec4 fragColor; uniform sampler2D u_image; @@ -91,11 +89,6 @@ float sst(float edge0, float edge1, float x) { return smoothstep(edge0, edge1, x); } -float sdRoundedBox(vec2 p, vec2 halfSize, float radius) { - vec2 d = abs(p) - halfSize + radius; - return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius; -} - float shadowShape(vec2 uv, float t, float contour) { vec2 scaledUV = uv; @@ -194,65 +187,23 @@ float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, fl } void main() { - vec2 uv = v_responsiveUV; -// vec2 uv = v_objectUV + .5; -// uv.y = 1. - uv.y; -// vec2 imgUV = v_imageUV; -// imgUV -= .5; -// imgUV *= 0.5714285714285714; -// imgUV += .5; -// float imgSoftFrame = getImgFrame(imgUV, .03); -// -// vec4 img = texture(u_image, imgUV); -// vec2 dudx = dFdx(imgUV); -// vec2 dudy = dFdy(imgUV); -// -// if (img.a == 0.) { -// fragColor = u_colorBack; -// return; -// } - - float canvasRatio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; - vec2 shapeUV = uv; - shapeUV.x *= max(canvasRatio, 1.); - shapeUV.y /= min(canvasRatio, 1.); - - vec2 halfSize = vec2( - 1. * max(canvasRatio, 1.), - 1. / min(canvasRatio, 1.) - ) * 0.5; - - float roundness = 0.05; - float radius = roundness * min(halfSize.x, halfSize.y); - - float dist = sdRoundedBox(shapeUV, halfSize, radius); - - float blurSize = .4; - float contourSize = .01; - - // Shape: 0 inside, 1 outside - float shape = smoothstep(-0.001, 0.001, dist); - - // Outer blur: falloff outside the shape - float outerBlurRaw = smoothstep(0.0, blurSize, dist); - float outerBlur = 1. - mix(1., 1. - outerBlurRaw, shape); - outerBlur = shape * (1. - pow(outerBlur, .2)); - - // Inner blur: falloff inside the shape - float innerBlurRaw = smoothstep(0.0, -blurSize, dist); - float innerBlur = innerBlurRaw; - - // Contour: edge detection - float contourRaw = 1.0 - smoothstep(0.0, contourSize, abs(dist)); - float contour = mix(contourRaw, 0., shape); - -// float edgeFade = 1.0; -// float edgeThreshold = 0.05; -// edgeFade *= smoothstep(0., edgeThreshold, uv.y + 0.5); -// edgeFade *= 1. - smoothstep(1. - edgeThreshold, 1., uv.y + 0.5); -// edgeFade *= smoothstep(0., edgeThreshold, uv.x + 0.5); -// edgeFade *= 1. - smoothstep(1. - edgeThreshold, 1., uv.x + 0.5); -// outerBlur *= edgeFade; + vec2 uv = v_objectUV + .5; + uv.y = 1. - uv.y; + + vec2 imgUV = v_imageUV; + imgUV -= .5; + imgUV *= 0.5714285714285714; + imgUV += .5; + float imgSoftFrame = getImgFrame(imgUV, .03); + + vec4 img = texture(u_image, imgUV); + vec2 dudx = dFdx(imgUV); + vec2 dudy = dFdy(imgUV); + + if (img.a == 0.) { + fragColor = u_colorBack; + return; + } float t = .1 * u_time; t -= .3; @@ -264,34 +215,30 @@ void main() { tCopy = mod(tCopy, 1.); tCopy2 = mod(tCopy2, 1.); -// vec2 animationUV = imgUV - vec2(.5); - vec2 animationUV = uv + 0.5; + vec2 animationUV = imgUV - vec2(.5); float angle = -u_angle * PI / 180.; float cosA = cos(angle); float sinA = sin(angle); - animationUV -= 0.5; animationUV = vec2( animationUV.x * cosA - animationUV.y * sinA, animationUV.x * sinA + animationUV.y * cosA ) + vec2(.5); -// float shape = img[0]; -// -// img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]); -// -// float outerBlur = 1. - mix(1., img[1], shape); -// float innerBlur = mix(img[1], 0., shape); -// float contour = mix(img[2], 0., shape); -// -// outerBlur *= imgSoftFrame; - - innerBlur = 1. - innerBlur; + float shape = img[0]; + + img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]); + + float outerBlur = 1. - mix(1., img[1], shape); + float innerBlur = mix(img[1], 0., shape); + float contour = mix(img[2], 0., shape); + + outerBlur *= imgSoftFrame; + float shadow = shadowShape(animationUV, t, innerBlur); float shadowCopy = shadowShape(animationUV, tCopy, innerBlur); float shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur); -// float inner = .8 + .8 * innerBlur; - float inner = innerBlur; + float inner = .8 + .8 * innerBlur; inner = mix(inner, 0., shadow); inner = mix(inner, 0., shadowCopy); inner = mix(inner, 0., shadowCopy2); @@ -307,14 +254,13 @@ void main() { t *= 3.; t = mod(t - .1, 1.); - outer = outerBlur; + outer = .9 * pow(outerBlur, .8); float y = mod(animationUV.y - t, 1.); float animatedMask = sst(.3, .65, y) * (1. - sst(.65, 1., y)); animatedMask = .5 + animatedMask; outer *= animatedMask; outer *= mix(0., 5., pow(u_outerGlow, 2.)); -// outer *= imgSoftFrame; -// outer *= edgeFade; + outer *= imgSoftFrame; } inner = pow(inner, 1.2); @@ -347,7 +293,6 @@ void main() { color += .02 * (fract(sin(dot(uv + 1., vec2(12.9898, 78.233))) * 43758.5453123) - .5); fragColor = vec4(color, opacity); -// fragColor = vec4(vec3(shape * (1. - outerBlurRaw)), 1.); } `; From 69289b067f94707c3d33d6c00e20f3c0a9cecedb Mon Sep 17 00:00:00 2001 From: ksenia Date: Wed, 8 Jul 2026 23:22:49 +0400 Subject: [PATCH 4/5] warp contour + antialiasing --- docs/src/app/(shaders)/warp/page.tsx | 3 +- docs/src/shader-defs/warp-def.ts | 7 + packages/shaders-react/src/shaders/warp.tsx | 24 ++- packages/shaders/src/shaders/warp.ts | 178 +++++++++++++------- 4 files changed, 139 insertions(+), 73 deletions(-) diff --git a/docs/src/app/(shaders)/warp/page.tsx b/docs/src/app/(shaders)/warp/page.tsx index 21e4884cc..eae0ee5e0 100644 --- a/docs/src/app/(shaders)/warp/page.tsx +++ b/docs/src/app/(shaders)/warp/page.tsx @@ -22,7 +22,8 @@ const WarpWithControls = () => { const [params, setParams] = useControls(() => { return { - edgeTrap: { value: defaults.edgeTrap, min: 0, max: 1, order: 10 }, + contour: { value: defaults.contour, min: 0, max: 1, order: 10 }, + antialiasing: { value: defaults.antialiasing, order: 11 }, proportion: { value: defaults.proportion, min: 0, max: 1, order: 100 }, softness: { value: defaults.softness, min: 0, max: 1, order: 101 }, distortion: { value: defaults.distortion, min: 0, max: 1, order: 102 }, diff --git a/docs/src/shader-defs/warp-def.ts b/docs/src/shader-defs/warp-def.ts index e7742c6f3..33ac00ec5 100644 --- a/docs/src/shader-defs/warp-def.ts +++ b/docs/src/shader-defs/warp-def.ts @@ -71,6 +71,13 @@ export const warpDef: ShaderDef = { defaultValue: defaultParams.shapeScale, description: 'Zoom level of the base pattern', }, + { + name: 'antialiasing', + type: 'boolean', + defaultValue: defaultParams.antialiasing, + description: 'Fixed 4x4 supersampling; needed for crisp low-softness bands under strong distortion/swirl', + options: ['true', 'false'], + }, ...animatedCommonParams, ], }; diff --git a/packages/shaders-react/src/shaders/warp.tsx b/packages/shaders-react/src/shaders/warp.tsx index 4691e6b5f..01c0dd657 100644 --- a/packages/shaders-react/src/shaders/warp.tsx +++ b/packages/shaders-react/src/shaders/warp.tsx @@ -32,7 +32,8 @@ export const defaultPreset: WarpPreset = { swirlIterations: 10, shapeScale: 0.1, shape: 'checks', - edgeTrap: 0.5 + contour: 0, + antialiasing: false }, }; @@ -52,7 +53,8 @@ export const presetCauldron: WarpPreset = { swirlIterations: 7, shapeScale: 0.6, shape: 'edge', - edgeTrap: 0.5 + contour: 0, + antialiasing: false }, }; @@ -73,7 +75,8 @@ export const presetInk: WarpPreset = { swirlIterations: 10, shapeScale: 0.28, shape: 'checks', - edgeTrap: 0.5 + contour: 0, + antialiasing: true }, }; @@ -93,7 +96,8 @@ export const presetKelp: WarpPreset = { swirlIterations: 3, shapeScale: 1, shape: 'stripes', - edgeTrap: 0.5 + contour: 0, + antialiasing: false }, }; @@ -114,7 +118,8 @@ export const presetNectar: WarpPreset = { swirlIterations: 10, shapeScale: 0.75, shape: 'edge', - edgeTrap: 0.5 + contour: 0, + antialiasing: false }, }; @@ -134,7 +139,8 @@ export const presetPassion: WarpPreset = { swirlIterations: 6, shapeScale: 0.25, shape: 'checks', - edgeTrap: 0.5 + contour: 0, + antialiasing: false }, }; @@ -159,7 +165,8 @@ export const Warp: React.FC = memo(function WarpImpl({ swirlIterations = defaultPreset.params.swirlIterations, shapeScale = defaultPreset.params.shapeScale, shape = defaultPreset.params.shape, - edgeTrap = defaultPreset.params.edgeTrap, + contour = defaultPreset.params.contour, + antialiasing = defaultPreset.params.antialiasing, // Sizing props fit = defaultPreset.params.fit, @@ -184,7 +191,8 @@ export const Warp: React.FC = memo(function WarpImpl({ u_swirlIterations: swirlIterations, u_shapeScale: shapeScale, u_shape: WarpPatterns[shape], - u_edgeTrap: edgeTrap, + u_contour: contour, + u_antialiasing: antialiasing, u_noiseTexture: getShaderNoiseTexture(), // Sizing uniforms diff --git a/packages/shaders/src/shaders/warp.ts b/packages/shaders/src/shaders/warp.ts index 8dfb14523..8ce3b7a52 100644 --- a/packages/shaders/src/shaders/warp.ts +++ b/packages/shaders/src/shaders/warp.ts @@ -24,6 +24,10 @@ export const warpMeta = { * - u_distortion (float): Strength of noise-based distortion (0 to 1) * - u_swirl (float): Strength of the swirl distortion (0 to 1) * - u_swirlIterations (float): Number of layered swirl passes, effective with swirl > 0 (0 to 20) + * - u_contour (float): Strength of the contour band traced around the canvas element borders (0 to 1) + * - u_antialiasing (bool): Enables fixed 4x4 supersampling; needed for crisp low-softness bands under strong distortion/swirl + * - u_resolution (vec2): Canvas resolution in pixels, used to keep the contour locked to the canvas borders + * - u_pixelRatio (float): Device pixel ratio, used to keep the contour thickness consistent across displays * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture * * Vertex shader outputs (used in fragment shader): @@ -53,7 +57,7 @@ uniform float u_scale; uniform sampler2D u_noiseTexture; -uniform vec4 u_colors[${ warpMeta.maxColorCount }]; +uniform vec4 u_colors[${warpMeta.maxColorCount}]; uniform float u_colorsCount; uniform float u_proportion; uniform float u_softness; @@ -62,16 +66,17 @@ uniform float u_shapeScale; uniform float u_distortion; uniform float u_swirl; uniform float u_swirlIterations; -uniform float u_edgeTrap; +uniform float u_contour; +uniform bool u_antialiasing; +uniform vec2 u_resolution; +uniform float u_pixelRatio; in vec2 v_patternUV; -in vec2 v_responsiveUV; -in vec2 v_responsiveBoxGivenSize; out vec4 fragColor; -${ declarePI } -${ rotation2 } +${declarePI} +${rotation2} float randomG(vec2 p) { vec2 uv = floor(p) / 100. + .5; return texture(u_noiseTexture, fract(uv)).g; @@ -89,77 +94,43 @@ float valueNoise(vec2 st) { return mix(x1, x2, u.y); } - -void main() { - vec2 uv = v_patternUV; - uv *= .5; - - // Canvas edge - fade out effects near edges - vec2 borderUV = v_responsiveUV + .5; - vec2 mask = min(borderUV, 1. - borderUV); - vec2 pixel_thickness = 450. / v_responsiveBoxGivenSize; - float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); - float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); - maskX = pow(maskX, .25); - maskY = pow(maskY, .25); - float edge = clamp(1. - maskX * maskY, 0., 1.); - - const float firstFrameOffset = 118.; - float t = 0.0625 * (u_time + firstFrameOffset); - - float n1 = valueNoise(uv * 1. + t); - float n2 = valueNoise(uv * 2. - t); - float angle = n1 * TWO_PI; - - float radius = smoothstep(0., 1., length(uv - .5)); - float edgeFadeW = u_edgeTrap * edge; - float edgeFade = u_edgeTrap * pow(edge, 3.); - uv -= vec2(.5); - float angleTest = 3. * edgeFade * angle; - uv = rotate(uv, -angleTest); - uv += vec2(.5); - - uv.x += 4. * u_distortion * n2 * cos(angle) * (1. - edgeFadeW); - uv.y += 4. * u_distortion * n2 * sin(angle) * (1. - edgeFadeW); - - float swirl = u_swirl * (1. - edgeFadeW); - for (int i = 1; i <= 20; i++) { - if (i >= int(u_swirlIterations)) break; - float iFloat = float(i); - uv.x += swirl / iFloat * cos(t + iFloat * 1.5 * uv.y); - uv.y += swirl / iFloat * cos(t + iFloat * 1. * uv.x); - } - +// The scalar base pattern (checks / stripes / edge) evaluated at a warped UV. +float patternShape(vec2 uv) { float proportion = clamp(u_proportion, 0., 1.); - - float shape = 0.; + float proportionOffset = .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5); + float shape; if (u_shape < .5) { - vec2 checksShape_uv = uv * (.5 + 3.5 * u_shapeScale); - shape = .5 + .5 * sin(checksShape_uv.x) * cos(checksShape_uv.y); - shape += .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5); + vec2 p = uv * (.5 + 3.5 * u_shapeScale); + shape = .5 + .5 * sin(p.x) * cos(p.y); + shape += proportionOffset; } else if (u_shape < 1.5) { - vec2 stripesShape_uv = uv * (2. * u_shapeScale); - float f = fract(stripesShape_uv.y); + float f = fract(uv.y * (2. * u_shapeScale)); shape = smoothstep(.0, .55, f) * (1.0 - smoothstep(.45, 1., f)); - shape += .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5); + shape += proportionOffset; } else { float shapeScaling = 5. * (1. - u_shapeScale); float e0 = 0.45 - shapeScaling; float e1 = 0.55 + shapeScaling; shape = smoothstep(min(e0, e1), max(e0, e1), 1.0 - uv.y + 0.3 * (proportion - 0.5)); } + return shape; +} +// Map the scalar pattern onto the color bands. u_softness sets band sharpness; aaW is +// a screen-space floor that antialiases individual band edges (needed when a pixel +// takes only 1 sample). Multi-crossing AA is handled by supersampling in main. +vec4 shadeBands(float shape, float aaW) { float mixer = shape * (u_colorsCount - 1.); vec4 gradient = u_colors[0]; gradient.rgb *= gradient.a; - float aa = fwidth(shape); - for (int i = 1; i < ${ warpMeta.maxColorCount }; i++) { + for (int i = 1; i < ${warpMeta.maxColorCount}; i++) { if (i >= int(u_colorsCount)) break; float m = clamp(mixer - float(i - 1), 0.0, 1.0); float localMixerStart = floor(m); - float softness = .5 * u_softness + fwidth(m); - float smoothed = smoothstep(max(0., .5 - softness - aa), min(1., .5 + softness + aa), m - localMixerStart); + float w = .5 * u_softness + aaW; + float e = m - localMixerStart; + float smoothed = w > 0. ? smoothstep(max(0., .5 - w), min(1., .5 + w), e) : step(.5, e); float stepped = localMixerStart + smoothed; m = mix(stepped, m, u_softness); @@ -168,11 +139,88 @@ void main() { c.rgb *= c.a; gradient = mix(gradient, c, m); } + return gradient; +} + + +// Contour band mask, locked to the borders. Smooth in screen space, so it is +// a reliable per-pixel signal (unlike derivatives of the folded warp). +float contourEdge(vec2 fragCoord) { + const float MAX_THICKNESS = .5; + vec2 borderUV = fragCoord / u_resolution; + vec2 mask = min(borderUV, 1. - borderUV); + vec2 pixel_thickness = min(400. * u_pixelRatio / u_resolution, vec2(MAX_THICKNESS)); + float maskX = pow(smoothstep(0.0, pixel_thickness.x, mask.x), .2); + float maskY = pow(smoothstep(0.0, pixel_thickness.y, mask.y), .2); + return clamp(1. - maskX * maskY, 0., 1.); +} + +vec2 warpUV(vec2 uv, vec2 fragCoord, float t) { + float edge = contourEdge(fragCoord); + + float n1 = valueNoise(uv * 1. + t); + float n2 = valueNoise(uv * 2. - t); + float angle = n1 * TWO_PI; + + float edgeFadeW = u_contour * edge; + float edgeFade = u_contour * pow(edge, 5.); + uv -= vec2(.5); + uv = rotate(uv, -edgeFade * angle); + uv += vec2(.5); + + uv.x += 4. * u_distortion * n2 * cos(angle) * (1. - edgeFadeW); + uv.y += 4. * u_distortion * n2 * sin(angle) * (1. - edgeFadeW); + + float swirl = u_swirl * (1. - edgeFadeW); + for (int i = 1; i <= 20; i++) { + if (i >= int(u_swirlIterations)) break; + float iFloat = float(i); + uv.x += swirl / iFloat * cos(t + iFloat * 1.5 * uv.y); + uv.y += swirl / iFloat * cos(t + iFloat * 1. * uv.x); + } + return uv; +} + + +void main() { + vec2 uv = v_patternUV * .5; + + const float firstFrameOffset = 118.; + float t = 0.0625 * (u_time + firstFrameOffset); + + vec4 acc; + if (u_antialiasing) { + // Non-adaptive supersampling: fixed AA_ROOT x AA_ROOT grid, every pixel. The base + // UV is a linear varying so its screen gradients are exact; we reconstruct each + // sub-sample's pre-warp UV from them and run the whole warp per tap, so the taps + // follow the true folded footprint the swirl produces. Hard-banded samples (aaW=0) + // are averaged -> coverage-based AA that holds up under strong distortion/swirl. + vec2 duvdx = dFdx(uv); + vec2 duvdy = dFdy(uv); + const int AA_ROOT = 4; + acc = vec4(0.); + for (int i = 0; i < AA_ROOT * AA_ROOT; i++) { + int iy = i / AA_ROOT; + int ix = i - iy * AA_ROOT; + vec2 cell = (vec2(float(ix), float(iy)) + .5) / float(AA_ROOT) - .5; + // rotate ~26.57deg so the grid never resonates with the checks/stripes axes + vec2 o = vec2(cell.x * .8944 - cell.y * .4472, cell.x * .4472 + cell.y * .8944); + vec2 sBase = uv + o.x * duvdx + o.y * duvdy; + acc += shadeBands(patternShape(warpUV(sBase, gl_FragCoord.xy + o, t)), 0.); + } + acc /= float(AA_ROOT * AA_ROOT); + } else { + // Single sample + a cheap analytic floor that antialiases lone band edges. + vec2 wc = warpUV(uv, gl_FragCoord.xy, t); + float shapeC = patternShape(wc); + float aaW = min(fwidth(shapeC * (u_colorsCount - 1.)), .5); + acc = shadeBands(shapeC, aaW); + } - vec3 color = gradient.rgb; - float opacity = gradient.a; + vec3 color = acc.rgb; + float opacity = acc.a; - ${ colorBandingFix } + ${colorBandingFix} fragColor = vec4(color, opacity); } @@ -188,7 +236,8 @@ export interface WarpUniforms extends ShaderSizingUniforms { u_distortion: number; u_swirl: number; u_swirlIterations: number; - u_edgeTrap: number; + u_contour: number; + u_antialiasing: boolean; u_noiseTexture?: HTMLImageElement; } @@ -202,7 +251,8 @@ export interface WarpParams extends ShaderSizingParams, ShaderMotionParams { distortion?: number; swirl?: number; swirlIterations?: number; - edgeTrap?: number; + contour?: number; + antialiasing?: boolean; } export const WarpPatterns = { From 67bd3fbcf06edb617fa0718b79458f1e43af8125 Mon Sep 17 00:00:00 2001 From: ksenia Date: Wed, 8 Jul 2026 23:42:03 +0400 Subject: [PATCH 5/5] warp to lowp --- packages/shaders/src/shaders/warp.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/shaders/src/shaders/warp.ts b/packages/shaders/src/shaders/warp.ts index 8ce3b7a52..e999ca301 100644 --- a/packages/shaders/src/shaders/warp.ts +++ b/packages/shaders/src/shaders/warp.ts @@ -50,7 +50,7 @@ export const warpMeta = { // language=GLSL export const warpFragmentShader: string = `#version 300 es -precision mediump float; +precision lowp float; uniform float u_time; uniform float u_scale; @@ -68,8 +68,8 @@ uniform float u_swirl; uniform float u_swirlIterations; uniform float u_contour; uniform bool u_antialiasing; -uniform vec2 u_resolution; -uniform float u_pixelRatio; +uniform mediump vec2 u_resolution; +uniform mediump float u_pixelRatio; in vec2 v_patternUV; @@ -190,11 +190,6 @@ void main() { vec4 acc; if (u_antialiasing) { - // Non-adaptive supersampling: fixed AA_ROOT x AA_ROOT grid, every pixel. The base - // UV is a linear varying so its screen gradients are exact; we reconstruct each - // sub-sample's pre-warp UV from them and run the whole warp per tap, so the taps - // follow the true folded footprint the swirl produces. Hard-banded samples (aaW=0) - // are averaged -> coverage-based AA that holds up under strong distortion/swirl. vec2 duvdx = dFdx(uv); vec2 duvdy = dFdy(uv); const int AA_ROOT = 4;