let initShdr, updateShdr, drawShdr;
createCanvas(windowWidth, windowHeight, WEBGL);
initShdr = createShader( vsFullScreen, fsInitPos );
updateShdr = createShader( vsFullScreen, fsUpdate );
drawShdr = createShader( vsDraw, fsDraw );
fbHigh = ceil( N / fbWide );
let fbOptions = { format: FLOAT,
depth: false, antialias: false, density: 1,
width: fbWide, height: fbHigh };
posA = createFramebuffer( fbOptions );
posB = createFramebuffer( fbOptions );
posC = createFramebuffer( fbOptions );
function windowResized() {
resizeCanvas( windowWidth, windowHeight );
initShdr.setUniform( "seed", seed );
initShdr.setUniform( "aspect", width/height );
fbgl.drawArrays( fbgl.TRIANGLES, 0, 3 );
initShdr.unbindTextures();
updateShdr.setUniform( "seed", seed );
updateShdr.setUniform( 'time', t );
updateShdr.setUniform( 'data', posA );
updateShdr.bindTextures();
fbgl.drawArrays( fbgl.TRIANGLES, 0, 3 );
updateShdr.unbindTextures();
updateShdr.unbindShader();
camera( 0, 0, 3, 0, 0, 0, 0, 1, 0 );
perspective( PI/4, width/height, 1, 10 );
for( let iSeg=0; iSeg<nSegs; iSeg++ ) {
updateShdr.setUniform( "seed", seed );
updateShdr.setUniform( 'time', t );
updateShdr.setUniform( 'data', posB );
updateShdr.bindTextures();
fbgl.drawArrays( fbgl.TRIANGLES, 0, 3 );
updateShdr.unbindTextures();
updateShdr.unbindShader();
let gl = this._renderer.GL;
gl.blendEquation( gl.MAX );
drawShdr.setUniform( 'dataA', posA );
drawShdr.setUniform( 'dataB', posB );
drawShdr.setUniform( 'dataC', posC );
drawShdr.setUniform( 'N', N );
drawShdr.setUniform( 'pathWidth', pathWidth*width/height );
drawShdr.setUniform( 'uSeg', (iSeg+0.5)/nSegs )
gl.drawArraysInstanced( gl.TRIANGLE_STRIP, 0, 4, N );
drawShdr.unbindTextures();
[ posA, posB, posC ] = [ posB, posC, posA ];
let vsFullScreen = `#version 300 es
gl_Position = vec4( 4*ivec2(gl_VertexID&1, gl_VertexID&2)-1, 0., 1. );
let fsInitPos = `#version 300 es
#define TAU 6.283185307179586
// http://www.jcgt.org/published/0009/03/02/
// https://www.shadertoy.com/view/XlGcRh
v = v * 1664525u + 1013904223u;
v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z;
v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z;
vec4 pcg4df( int a, int b, int c, int d ) {
return vec4((pcg4d( uvec4( a, b, c, d ) ) >> 8)) / float( 1<<24 );
vec3 randomPointOnUnitSphere( vec2 rnd ) {
float z = 2. * rnd.y - 1.;
return vec3( sqrt( max( 1.-z*z, 0. ) ) * vec2( cos(a), sin(a) ), z );
vec3 randomPointInUnitSphere( vec3 rnd ) {
float r = pow( rnd.z, 1./3. );
return randomPointOnUnitSphere( rnd.xy ) * r;
vec4 rnd = pcg4df( int(seed), int(gl_FragCoord.x), int(gl_FragCoord.y), 42 );
fragColor = vec4( rnd.xy * 2. - 1., 0., 1. );
let fsUpdate = `#version 300 es
#define TAU 6.283185307179586
// http://www.jcgt.org/published/0009/03/02/
// https://www.shadertoy.com/view/XlGcRh
v = v * 1664525u + 1013904223u;
v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z;
v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z;
vec4 pcg4df( int a, int b, int c, int d ) {
return vec4((pcg4d( uvec4( a, b, c, d ) ) >> 8)) / float( 1<<24 );
vec3 randomPointOnUnitSphere( vec2 rnd ) {
float z = 2. * rnd.y - 1.;
return vec3( sqrt( max( 1.-z*z, 0. ) ) * vec2( cos(a), sin(a) ), z );
vec3 randomPointInUnitSphere( vec3 rnd ) {
float r = pow( rnd.z, 1./3. );
return randomPointOnUnitSphere( rnd.xy ) * r;
// psrdnoise (c) Stefan Gustavson and Ian McEwan,
// ver. 2021-12-02, published under the MIT license:
// https://github.com/stegu/psrdnoise/
return mod(((im*34.0)+10.0)*im, 289.0);
float psrdnoise(vec3 x, vec3 period, float alpha, out vec3 gradient)
const mat3 M = mat3(0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0);
const mat3 Mi = mat3(-0.5, 0.5, 0.5, 0.5,-0.5, 0.5, 0.5, 0.5,-0.5);
vec3 i0 = floor(uvw), f0 = fract(uvw);
vec3 g_ = step(f0.xyx, f0.yzz), l_ = 1.0 - g_;
vec3 g = vec3(l_.z, g_.xy), l = vec3(l_.xy, g_.z);
vec3 o1 = min( g, l ), o2 = max( g, l );
vec3 i1 = i0 + o1, i2 = i0 + o2, i3 = i0 + vec3(1.0);
vec3 v0 = Mi * i0, v1 = Mi * i1, v2 = Mi * i2, v3 = Mi * i3;
vec3 x0 = x - v0, x1 = x - v1, x2 = x - v2, x3 = x - v3;
if(any(greaterThan(period, vec3(0.0)))) {
vec4 vx = vec4(v0.x, v1.x, v2.x, v3.x);
vec4 vy = vec4(v0.y, v1.y, v2.y, v3.y);
vec4 vz = vec4(v0.z, v1.z, v2.z, v3.z);
if(period.x > 0.0) vx = mod(vx, period.x);
if(period.y > 0.0) vy = mod(vy, period.y);
if(period.z > 0.0) vz = mod(vz, period.z);
i0 = floor(M * vec3(vx.x, vy.x, vz.x) + 0.5);
i1 = floor(M * vec3(vx.y, vy.y, vz.y) + 0.5);
i2 = floor(M * vec3(vx.z, vy.z, vz.z) + 0.5);
i3 = floor(M * vec3(vx.w, vy.w, vz.w) + 0.5);
vec4 hash = permute( permute( permute(
vec4(i0.z, i1.z, i2.z, i3.z ))
+ vec4(i0.y, i1.y, i2.y, i3.y ))
+ vec4(i0.x, i1.x, i2.x, i3.x ));
vec4 theta = hash * 3.883222077;
vec4 sz = hash * -0.006920415 + 0.996539792;
vec4 psi = hash * 0.108705628;
vec4 Ct = cos(theta), St = sin(theta);
vec4 sz_prime = sqrt( 1.0 - sz*sz );
vec4 px = Ct * sz_prime, py = St * sz_prime, pz = sz;
vec4 Sp = sin(psi), Cp = cos(psi), Ctp = St*Sp - Ct*Cp;
vec4 qx = mix( Ctp*St, Sp, sz), qy = mix(-Ctp*Ct, Cp, sz);
vec4 qz = -(py*Cp + px*Sp);
vec4 Sa = vec4(sin(alpha)), Ca = vec4(cos(alpha));
gx = Ca*px + Sa*qx; gy = Ca*py + Sa*qy; gz = Ca*pz + Sa*qz;
gx = Ct * sz_prime; gy = St * sz_prime; gz = sz;
vec3 g0 = vec3(gx.x, gy.x, gz.x), g1 = vec3(gx.y, gy.y, gz.y);
vec3 g2 = vec3(gx.z, gy.z, gz.z), g3 = vec3(gx.w, gy.w, gz.w);
vec4 w = 0.5-vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3));
w = max(w, 0.0); vec4 w2 = w * w, w3 = w2 * w;
vec4 gdotx = vec4(dot(g0,x0), dot(g1,x1), dot(g2,x2), dot(g3,x3));
float n = dot(w3, gdotx);
vec4 dw = -6.0 * w2 * gdotx;
vec3 dn0 = w3.x * g0 + dw.x * x0;
vec3 dn1 = w3.y * g1 + dw.y * x1;
vec3 dn2 = w3.z * g2 + dw.z * x2;
vec3 dn3 = w3.w * g3 + dw.w * x3;
gradient = 39.5 * (dn0 + dn1 + dn2 + dn3);
float pnoise( float seed, vec3 x, vec3 prd, float a ) {
vec3 offs = vec3( mod(seed/65536.,256.), mod(seed/256.,256.), mod(seed,256.) );
float t = psrdnoise( (x+offs), prd, a, grd );
ivec2 res = textureSize( data, 0 );
ivec2 ij = ivec2(gl_FragCoord.xy);
int i = ij.y * res.x + ij.x;
vec3 pos = texelFetch( data, ij, 0).xyz;
vec4 rnd = pcg4df( int(seed), i, 432, 92368 );
float ang = TAU*(pnoise( seed, (pos+vec3(0.,0.,time*0.1)), vec3(0.), time*0.25 ));
ang += TAU*floor(rnd.x*2.)/2.;
pos.xy += 0.004 * vec2(cos(ang), sin(ang));
fragColor = vec4( pos, 1. );
let vsDraw = `#version 300 es
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
vec3 hsb2rgb( in vec3 c ) {
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
6.0)-3.0)-1.0, 0.0, 1.0 );
rgb = rgb*rgb*(3.0-2.0*rgb);
return c.z * mix( vec3(1.0), rgb, c.y);
ivec2 res = textureSize( dataA, 0 );
ivec2 ij = ivec2( idx%res.x, idx/res.x );
if( (gl_VertexID & 2) == 0 ) {
p0 = texelFetch( dataA, ij, 0 );
p1 = texelFetch( dataB, ij, 0 );
p0 = texelFetch( dataB, ij, 0 );
p1 = texelFetch( dataC, ij, 0 );
vec4 p = uModelViewMatrix * ((p0+p1) * 0.5);
if( length(v01) > 1e-6 ) {
p.xy -= vec2( -v01.y, v01.x ) * pathWidth * (float(gl_VertexID & 1)-0.5);
gl_Position = uProjectionMatrix * p;
//float u = float(gl_VertexID)/float(N);
float hue = atan(v.y,v.x)/3.14159+0.5;
float bri = 1.-pow(abs(uSeg*2.-1.), 1.); //pow(abs(normalize(v).x), 2.)*0.75+0.25;
vec3 c = hsb2rgb( vec3( hue, 0.4, bri ) );
let fsDraw = `#version 300 es