<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script id="vertexShaderParticle" type="x-shader/x-vertex">
    uniform vec2 u_resolution;
    uniform vec2 u_mouse;
    uniform float u_time;
    uniform sampler2D u_noise;
    attribute vec2 reference;
    uniform sampler2D texturePosition;
    uniform bool u_clicked;
    varying float v_op;
  
  
  float random(vec2 st) {
    return fract(sin(dot(st,
                         vec2(12.9898,78.233)))*
        43758.5453123);
  }
  
    void main() {
      vec3 position = texture2D(texturePosition, reference).xyz;
      position *= 3.;
      // position -= 10.;

      vec3 transformed = vec3( position );
      
      vec4 mvpos = modelViewMatrix * vec4( transformed, 1.0 );

      // gl_PointSize = 30.0 * (1.0 / (mvpos.z * mvpos.z));
      // gl_PointSize = 1.;
      // gl_PointSize = clamp(2. - length(transformed) * .01, 0., 2.);
      gl_PointSize = random(reference) * 50. * (1. / length(mvpos.xyz) * 5.51);
      v_op = 1. / length(position) * 8.;
      // gl_PointSize = 2.;
      gl_Position = projectionMatrix * mvpos;
    }
</script>
<script id="fragmentShaderParticle" type="x-shader/x-fragment">
  uniform vec2 u_resolution;
  uniform vec2 u_mouse;
  uniform float u_time;
  uniform sampler2D u_noise;
  uniform bool u_clicked;
    varying float v_op;

  vec2 hash2(vec2 p)
  {
    vec2 o = texture2D( u_noise, (p+0.5)/256.0, -100.0 ).xy;
    return o;
  }

  void main() {
    // vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.x, u_resolution.y);
    vec2 uv = gl_PointCoord.xy - .5;
    
    vec3 particlecolour = vec3(.5, .53, .53) * 1.8;
    vec3 outercolour = vec3(1.);
    
    if(u_clicked) {
      particlecolour = vec3(.05, .15, .2) * .5;
      outercolour = vec3(0.);
    }
    
    float l = length(uv);
    vec3 colour = mix(outercolour, particlecolour, smoothstep(.5, -.1, l));
    colour = mix(vec3(2., 0.5, 0.), colour, smoothstep(3., 0.5, v_op));

    gl_FragColor = vec4(colour, 1. - l * 2.);
  }
</script>
<script id="fragmentShaderVelocity" type="x-shader/x-fragment">
  uniform vec2 u_resolution;
  uniform vec2 u_mouse;
  uniform float u_time;
  uniform float u_mousex;
    varying float v_op;
  
  // otaviogood's noise from https://www.shadertoy.com/view/ld2SzK
  const float nudge = 0.739513;	// size of perpendicular vector
  float normalizer = 1.0 / sqrt(1.0 + nudge*nudge);	// pythagorean theorem on that perpendicular to maintain scale
  float SpiralNoiseC(vec3 p)
  {
      float n = 0.0;	// noise amount
      float iter = 1.0;
      for (int i = 0; i < 8; i++)
      {
          // add sin and cos scaled inverse with the frequency
          n += -abs(sin(p.y*iter) + cos(p.x*iter)) / iter;	// abs for a ridged look
          // rotate by adding perpendicular and scaling down
          p.xy += vec2(p.y, -p.x) * nudge;
          p.xy *= normalizer;
          // rotate on other axis
          p.xz += vec2(p.z, -p.x) * nudge;
          p.xz *= normalizer;
          // increase the frequency
          iter *= 1.733733;
      }
      return n;
  }

  void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    vec3 position = texture2D(v_samplerPosition, uv).xyz;
    vec3 velocity = texture2D(v_samplerVelocity, uv).xyz;
    vec3 acceleration = vec3(0.);
    
    float l = length(position);
    vec3 spherical = vec3(1./l, atan(position.y, position.x), acos(position.z / l));
    float n = SpiralNoiseC(spherical * 6. + u_time);
    n = SpiralNoiseC(vec3(l, spherical.y * 6. + u_time * 5., spherical.z));
    // n = fract(n) * 3.;
    
    // spherical *= 1. + n;
    spherical.z += (1. / n-.5)*length(velocity);
    spherical.y += n;
    
    float a =  n * .1 + smoothstep(5., 40., l) * 20.;
    a += smoothstep(20., 0., l) * .3;
    a -= smoothstep(30., 41., l) * 21.;
    
    // spherical.x +=;
    // spherical.x += smoothstep(20., 0., l) * .3;
    // spherical.x -= smoothstep(30., 41., l) * 21.;
    
    // spherical.xy += n;
    // spherical.z *= 1.5;
    // spherical.z += 1.;
    // spherical.x -= smoothstep(5., 1., l) * 1.;
    // spherical.yz += n*.5;
    
    acceleration.x = spherical.x * sin(spherical.z) * cos(spherical.y) * a;
    acceleration.y = spherical.x * sin(spherical.z) * sin(spherical.y) * a;
    acceleration.z = spherical.x * cos(spherical.z) * a;
    // if(acceleration.x == 0) { acceleration.x = .01 };
    
    // acceleration *= acceleration * acceleration * 200.;
    // acceleration = sin(acceleration) * .5 + .5;
    // acceleration *= 100.;
    
    vec3 vel = velocity * .98 + acceleration * .3;
    if(length(vel) > 5.) {
      vel = normalize(vel) * 5.;
    }
    
    gl_FragColor = vec4(vel, 1.0);
    // gl_FragColor = vec4(-.1);
  }
</script>
<script id="fragmentShaderPosition" type="x-shader/x-fragment">
  uniform float delta;
  uniform float u_time;
  uniform sampler2D v_samplerPosition_orig;
  uniform sampler2D u_noise;

  vec3 hash3(vec2 p)
  {
    vec3 o = texture2D( u_noise, (p+0.5)/256.0, -100.0 ).xyz;
    return o;
  }

  void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    vec3 position_original = texture2D(v_samplerPosition_orig, uv).xyz;
    vec3 position = texture2D(v_samplerPosition, uv).xyz;
    vec3 velocity = texture2D(v_samplerVelocity, uv).xyz;
    // velocity -= .5;
    // velocity *= 3.;
    // velocity = velocity * 2. - 1.;
    
    vec3 pos = position + velocity * delta;
    
    // This just adds a little touch more randomness to the motion.
    // This is incredibly subtle but has the effect of making the particles
    // look more "separate" in motion
    vec3 hash = hash3(position_original.xy * position_original.zx * 20.);
    // pos *= 1. + (hash - .5) * .0005;
    // pos += (hash - .5) * .001;
    
    
//     vec2 p = vec2(atan(pos.y, pos.x), length(pos.xy));
//     p.x -= velocity.x * .001 + .0001;
    
//     pos.x = cos(p.x) * p.y;
//     pos.y = sin(p.x) * p.y;
    
//     pos.z += .005;
    
    if(length(pos) > 40.) {
      pos = position_original;
    }

    gl_FragColor = vec4(pos, 1.0);
  }
</script>


<div id="container" touch-action="none"></div>
body {
  margin: 0;
  padding: 0;
}

#container {
  position: fixed;
  touch-action: none;
}
const texturesize = 1024;
const particles = texturesize * texturesize;

let container;
let camera, scene, renderer, controls;
let cloud_obj;
let uniforms;
let gpuComputationRenderer, dataPos, dataVel, textureArraySize = texturesize*texturesize*4.;

let textureVelocity, texturePosition;

const particleVert = document.getElementById( 'vertexShaderParticle' ).textContent;
const particleFrag = document.getElementById( 'fragmentShaderParticle' ).textContent;
const velocityFrag = document.getElementById( 'fragmentShaderVelocity' ).textContent;
const positionFrag = document.getElementById( 'fragmentShaderPosition' ).textContent;

let loader=new THREE.TextureLoader();
let texture;
loader.setCrossOrigin("anonymous");
loader.load(
  'https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/noise.png',
  function do_something_with_texture(tex) {
    texture = tex;
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.minFilter = THREE.LinearFilter;
    init();
    animate();
  }
);

function init() {
  container = document.getElementById( 'container' );

  camera = new THREE.PerspectiveCamera(65, 1, 0.001, Math.pow(2, 16));
  camera.position.x = 0;
  camera.position.y = 0;
  camera.position.z = 50.;

  scene = new THREE.Scene();
  scene.background = new THREE.Color( 0xffffff );

  // create out particles
  // ----------------------------
	let vertices = new Float32Array(particles * 3).fill(0);
	let references = new Float32Array(particles * 2);
	
	for (let i = 0; i < references.length; i += 2) {
		let index = i / 2;
		
		references[i] = (index % texturesize) / texturesize;
		references[i + 1] = Math.floor(index / texturesize) / texturesize;
	}
	
	let geometry = new THREE.BufferGeometry();
	geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
	geometry.addAttribute('reference', new THREE.BufferAttribute(references, 2));
	
  // Create our particle material
  // ----------------------------
  uniforms = {
    u_time: { type: "f", value: 1.0 },
    u_resolution: { type: "v2", value: new THREE.Vector2() },
    u_noise: { type: "t", value: texture },
    u_mouse: { type: "v2", value: new THREE.Vector2() },
		u_texturePosition: { value: null },
    u_clicked: { type: 'b', value: true }
  };
  let particleMaterial = new THREE.ShaderMaterial( {
    uniforms: uniforms,
    vertexShader: particleVert,
    fragmentShader: particleFrag,
		side: THREE.DoubleSide,
		transparent: true
  } );
  particleMaterial.transparent = true;
  particleMaterial.blending = THREE.MultiplyBlending;
  particleMaterial.depthTest = false;
  particleMaterial.extensions.derivatives = true;
	
  // Create the particle cloud object
  // ----------------------------
	cloud_obj = new THREE.Points(geometry, particleMaterial);
    scene.background = new THREE.Color( 0x111111 );
  cloud_obj.material.blending = THREE.AdditiveBlending;
  //   scene.background = new THREE.Color( 0xFFFFFF );
  // cloud_obj.material.blending = THREE.SubtractiveBlending;

  // Create the renderer and controls and add them to the scene
  // ----------------------------
  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio( 1 );
  
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  window.controls = controls;

  container.appendChild( renderer.domElement );
  
  // Finally, add everything to stage
  // ----------------------------
  scene.add( cloud_obj );
  
  // Add the computational renderer and populate it with data
  // ----------------------------
  gpuComputationRenderer = new GPUComputationRenderer(texturesize, texturesize, renderer);
  dataPos_orig = gpuComputationRenderer.createTexture();
  dataPos = gpuComputationRenderer.createTexture();
  dataVel = gpuComputationRenderer.createTexture();

  for (let i = 0; i < textureArraySize; i += 4) {
    let radius = 2.;
    let phi = Math.random() * Math.PI * 2.;
    let costheta = Math.random() * 2. - 1.;
    let u = Math.random();

    let theta = Math.acos( costheta );
    let r = radius * Math.cbrt( u );
    
    let x = r * Math.sin( theta) * Math.cos( phi );
    let y = r * Math.sin( theta) * Math.sin( phi );
    let z = r * Math.cos( theta );

    dataPos.image.data[i] = x;
    dataPos.image.data[i + 1] = y;
    dataPos.image.data[i + 2] = z;
    dataPos.image.data[i + 3] = 1;
    
    dataPos_orig.image.data[i] = x;
    dataPos_orig.image.data[i + 1] = y;
    dataPos_orig.image.data[i + 2] = z;
    dataPos_orig.image.data[i + 3] = 1;

    dataVel.image.data[i] = x * 3.;
    dataVel.image.data[i + 1] = y * 3.;
    dataVel.image.data[i + 2] = z * 3.;
    dataVel.image.data[i + 3] = 1;
  }
  
  textureVelocity = gpuComputationRenderer.addVariable('v_samplerVelocity', velocityFrag, dataVel);
  texturePosition = gpuComputationRenderer.addVariable('v_samplerPosition', positionFrag, dataPos);

  texturePosition.material.uniforms.delta = { value: 0 };
  texturePosition.material.uniforms.v_samplerPosition_orig = { type: "t", value: dataPos_orig };
  textureVelocity.material.uniforms.u_time = { value: -1000 };
  textureVelocity.material.uniforms.u_mousex = { value: 0 };
  texturePosition.material.uniforms.u_time = { value: 0 };

  gpuComputationRenderer
    .setVariableDependencies(textureVelocity, [ textureVelocity, texturePosition ]);
  gpuComputationRenderer
	.setVariableDependencies(texturePosition, [ textureVelocity, texturePosition ]);

  texturePosition.wrapS = THREE.RepeatWrapping;
  texturePosition.wrapT = THREE.RepeatWrapping;
  textureVelocity.wrapS = THREE.RepeatWrapping;
  textureVelocity.wrapT = THREE.RepeatWrapping;

  const gpuComputationRendererError = gpuComputationRenderer.init();
  if (gpuComputationRendererError) {
    console.error('ERROR', gpuComputationRendererError);
  }

  // Add event listeners for resize and mouse move
  // ----------------------------
  onWindowResize();
  window.addEventListener( 'resize', onWindowResize, false );
  document.addEventListener('pointermove', pointerMove);
  // document.addEventListener('click', onClick);
  
  // initialise the video renderer
}

function onWindowResize( event ) {
  let w = window.innerWidth;
  let h = window.innerHeight;
	camera.aspect = w / h;
	camera.updateProjectionMatrix();
  renderer.setSize( w, h );
  uniforms.u_resolution.value.x = renderer.domElement.width;
  uniforms.u_resolution.value.y = renderer.domElement.height;
}

function pointerMove( event ) {
  let ratio = window.innerHeight / window.innerWidth;
  textureVelocity.material.uniforms.u_mousex.value = event.pageX;
  uniforms.u_mouse.value.x = (event.pageX - window.innerWidth / 2) / window.innerWidth / ratio;
  uniforms.u_mouse.value.y = (event.pageY - window.innerHeight / 2) / window.innerHeight * -1;

  event.preventDefault();
}

function onClick() {
  // return;
  let newval = !uniforms.u_clicked.value;
  uniforms.u_clicked.value = newval;
  console.log(cloud_obj.material.blending);
  if(newval === false) {
    scene.background = new THREE.Color( 0xffffff );
    cloud_obj.material.blending = THREE.MultiplyBlending;
  } else {
    scene.background = new THREE.Color( 0x000000 );
    cloud_obj.material.blending = THREE.AdditiveBlending;
  }
}

function animate(delta) {
  requestAnimationFrame( animate );
  render(delta);
}



let capturer = new CCapture( { 
  verbose: true, 
  framerate: 60,
  // motionBlurFrames: 4,
  quality: 90,
  format: 'webm',
  workersPath: 'js/'
 } );
let capturing = false;

isCapturing = function(val) {
  if(val === false && window.capturing === true) {
    capturer.stop();
    capturer.save();
    renderer.setPixelRatio( window.devicePixelRatio );
  } else if(val === true && window.capturing === false) {
    capturer.start();
    controls.enabled = false;
    renderer.setPixelRatio( 1 );
  }
  capturing = val;
}
toggleCapture = function() {
  isCapturing(!capturing);
}

window.addEventListener('keyup', function(e) { if(e.keyCode == 68) toggleCapture(); });

let then = 0;
function render(delta) {
  
	let now = Date.now() / 1000;
	let _delta = now - then;
	then = now;
  
	gpuComputationRenderer.compute();
  
	texturePosition.material.uniforms.delta.value = Math.min(_delta, 0.5);
  textureVelocity.material.uniforms.u_time.value += .0005;
  texturePosition.material.uniforms.u_time.value += _delta;
  
  uniforms.u_time.value += _delta;
  uniforms.u_texturePosition.value = gpuComputationRenderer.getCurrentRenderTarget(texturePosition).texture;
  
  window.pos = gpuComputationRenderer.getCurrentRenderTarget(texturePosition);
  
  renderer.render( scene, camera );

  if(capturing) {
    capturer.capture( renderer.domElement );
  }
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://threejs.org/examples/js/GPUComputationRenderer.js
  2. https://threejs.org/examples/js/controls/OrbitControls.js
  3. https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/ccapture.js