Skip to content

Instantly share code, notes, and snippets.

@listenlight
Created January 31, 2019 09:18
Show Gist options
  • Save listenlight/33e363744d3649a5acfd332d4cab9ec4 to your computer and use it in GitHub Desktop.
Save listenlight/33e363744d3649a5acfd332d4cab9ec4 to your computer and use it in GitHub Desktop.
Coalesce 17

Coalesce 17

GPGPU particles using texture caches for position and velocity.

  • Click and drag to rotate.
  • Scroll wheel to zoom.
  • Right-click and drag to pan.

A Pen by Liam Egan on CodePen.

License.

<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;
varying vec2 v_ref;
float random(vec2 st) {
return fract(sin(dot(st,
vec2(12.9898,78.233)))*
43758.5453123);
}
vec2 hash2(vec2 p)
{
vec2 o = texture2D( u_noise, (p+0.5)/256.0).xy;
return o;
}
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.);
float r = random(reference);
r *= r * r;
gl_PointSize = r * 500. * (1. / length(mvpos.xyz) * 5.51);
v_op = r;
// gl_PointSize = 2.;
gl_Position = projectionMatrix * mvpos;
v_ref = sin(reference.xy * 100.) * .3;
}
</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;
varying vec2 v_ref;
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(.8, .8 + v_ref.y, 1. + v_ref.x);
// float shade = 1. - abs(uv.x + uv.y);
// shade = min(shade, 1. - abs(uv.x - uv.y));
// shade = clamp(shade, 0., 1.);
// shade *= min(1. - abs(uv.x), 1. - abs(uv.y));
// shade = clamp(shade, 0., 1.);
float shade = 1. - abs(uv.x);
shade = max(shade, 1. - abs(uv.y));
shade = max(shade, 1. - abs(uv.y + uv.x));
shade = max(shade, 1. - abs(uv.y - uv.x));
// shade -= length(uv) * -shade;
shade -= clamp(length(uv * .1), 0., 1.);
shade = clamp(shade, 0., 1.);
// vec3 colour = particlecolour * smoothstep(.3, 1.1, shade);
vec3 colour = particlecolour * vec3(smoothstep(.95, 1., shade));
gl_FragColor = vec4(colour,1.0);
}
</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;
}
mat4 rotationMatrix(vec3 axis, float angle)
{
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0);
}
vec3 hash33(vec3 p) {
return fract(vec3(
sin(p.x) * 43543.454354,
sin(p.y) * 7531.154354,
sin(p.z) * 10053.75315
));
}
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);
position = ( vec4(position, 1.) * rotationMatrix(vec3(sin(u_time * 5.), cos(u_time * 10.), sin(u_time) * cos(u_time * 5.)), .5 + 10. / l) ).xyz;
// float s = sin(5./l);
// float c = cos(5./l);
// position.zy *= mat2(c, -s, s, c);
// position.xy *= mat2(c, -s, s, c);
vec3 spherical = vec3(1./l, atan(position.y, position.x), acos(position.z / l));
float n = SpiralNoiseC(spherical * 6. + u_time * 20.);
// 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(8. + sin(u_time*5.) * 3., 60., l) * 20.;
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;
vec3 vel = velocity * .99 + acceleration * (1.+n);
vel += (hash33(position) - .5) * 0.1;
if(length(vel) > 10.) {
vel = normalize(vel) * 10.;
}
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;
vec3 pos = position + velocity * delta;
if(length(pos) > 40.) {
pos = pos / length(pos);
}
gl_FragColor = vec4(pos, 1.0);
}
</script>
<div id="container" touch-action="none"></div>
const texturesize = 256;
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 = -25;
camera.position.y = -100;
camera.position.z = 100.;
camera.position.setLength(10);
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: false }
};
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.MultiplyBlending;
// cloud_obj.material.blending = THREE.SubtractiveBlending;
// Create the renderer and controls and add them to the scene
// ----------------------------
renderer = new THREE.WebGLRenderer();
// renderer.setPixelRatio( 2 );
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 = 5.;
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: 30,
// 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.toggleCapture = toggleCapture;
// setTimeout(()=> {
// toggleCapture()}, 0)
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 = -10000 + delta;
uniforms.u_texturePosition.value = gpuComputationRenderer.getCurrentRenderTarget(texturePosition).texture;
window.pos = gpuComputationRenderer.getCurrentRenderTarget(texturePosition);
renderer.render( scene, camera );
if(capturing) {
capturer.capture( renderer.domElement );
}
}
<script src="https://threejs.org/examples/js/GPUComputationRenderer.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/ccapture.js"></script>
body {
margin: 0;
padding: 0;
}
#container {
position: fixed;
touch-action: none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment