Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Curl Noise Particles</title>
</head>

<body>

</body>

</html>
              
            
!

CSS

              
                *, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  overflow: hidden;
  background-color: #000;
}

canvas {
  background-color: #fff;
}
              
            
!

JS

              
                // move the particles with mouse

const computePosShader = `
  uniform vec3 mouse3d;
  uniform float time;
  uniform sampler2D textureDefaultPosition;

  void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    vec4 texturePos = texture2D(texturePosition, uv);
    vec4 textureVel = texture2D(textureVelocity, uv);
    vec4 defaultPos = texture2D(textureDefaultPosition, uv);
    vec3 pos = texturePos.xyz;
    vec3 vel = textureVel.xyz;


    float life = texturePos.w - 0.02;
    vec3 followPos = mouse3d;

    if (life < 0.0) {
      texturePos = texture2D(textureDefaultPosition, uv);
      pos = texturePos.xyz * (sin(time * 30.0 + life) + 0.1) + followPos;
      life = 0.5 + fract(texturePos.w * 21.4131 + 0.1);
    } else {
      vec3 delta = followPos - pos;
      pos += delta * 0.001;
      pos += vel;
    }

    gl_FragColor = vec4(pos, life);
  }
`
const computeVelShader = `
  vec4 mod289(vec4 x) {
    return x - floor(x * (1.0 / 289.0)) * 289.0;
  }

  float mod289(float x) {
    return x - floor(x * (1.0 / 289.0)) * 289.0;
  }

  vec4 permute(vec4 x) {
    return mod289(((x*34.0)+1.0)*x);
  }

  float permute(float x) {
    return mod289(((x*34.0)+1.0)*x);
  }

  vec4 taylorInvSqrt(vec4 r) {
    return 1.79284291400159 - 0.85373472095314 * r;
  }

  float taylorInvSqrt(float r) {
    return 1.79284291400159 - 0.85373472095314 * r;
  }

  vec4 grad4(float j, vec4 ip) {
    const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
    vec4 p,s;

    p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
    p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
    s = vec4(lessThan(p, vec4(0.0)));
    p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;

    return p;
  }

  #define F4 0.309016994374947451

  vec4 snoise4 (vec4 v) {
    const vec4  C = vec4( 0.138196601125011,0.276393202250021,0.414589803375032,-0.447213595499958);

    vec4 i  = floor(v + dot(v, vec4(F4)) );
    vec4 x0 = v -   i + dot(i, C.xxxx);

    vec4 i0;
    vec3 isX = step( x0.yzw, x0.xxx );
    vec3 isYZ = step( x0.zww, x0.yyz );
    i0.x = isX.x + isX.y + isX.z;
    i0.yzw = 1.0 - isX;
    i0.y += isYZ.x + isYZ.y;
    i0.zw += 1.0 - isYZ.xy;
    i0.z += isYZ.z;
    i0.w += 1.0 - isYZ.z;

    vec4 i3 = clamp( i0, 0.0, 1.0 );
    vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
    vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );

    vec4 x1 = x0 - i1 + C.xxxx;
    vec4 x2 = x0 - i2 + C.yyyy;
    vec4 x3 = x0 - i3 + C.zzzz;
    vec4 x4 = x0 + C.wwww;

    i = mod289(i);
    float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
    vec4 j1 = permute( permute( permute( permute (
            i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
          + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
          + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
          + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));


    vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;

    vec4 p0 = grad4(j0,   ip);
    vec4 p1 = grad4(j1.x, ip);
    vec4 p2 = grad4(j1.y, ip);
    vec4 p3 = grad4(j1.z, ip);
    vec4 p4 = grad4(j1.w, ip);

    vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
    p0 *= norm.x;
    p1 *= norm.y;
    p2 *= norm.z;
    p3 *= norm.w;
    p4 *= taylorInvSqrt(dot(p4,p4));

    vec3 values0 = vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2)); //value of contributions from each corner at point
    vec2 values1 = vec2(dot(p3, x3), dot(p4, x4));

    vec3 m0 = max(0.5 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); //(0.5 - x^2) where x is the distance
    vec2 m1 = max(0.5 - vec2(dot(x3,x3), dot(x4,x4)), 0.0);

    vec3 temp0 = -6.0 * m0 * m0 * values0;
    vec2 temp1 = -6.0 * m1 * m1 * values1;

    vec3 mmm0 = m0 * m0 * m0;
    vec2 mmm1 = m1 * m1 * m1;

    float dx = temp0[0] * x0.x + temp0[1] * x1.x + temp0[2] * x2.x + temp1[0] * x3.x + temp1[1] * x4.x + mmm0[0] * p0.x + mmm0[1] * p1.x + mmm0[2] * p2.x + mmm1[0] * p3.x + mmm1[1] * p4.x;
    float dy = temp0[0] * x0.y + temp0[1] * x1.y + temp0[2] * x2.y + temp1[0] * x3.y + temp1[1] * x4.y + mmm0[0] * p0.y + mmm0[1] * p1.y + mmm0[2] * p2.y + mmm1[0] * p3.y + mmm1[1] * p4.y;
    float dz = temp0[0] * x0.z + temp0[1] * x1.z + temp0[2] * x2.z + temp1[0] * x3.z + temp1[1] * x4.z + mmm0[0] * p0.z + mmm0[1] * p1.z + mmm0[2] * p2.z + mmm1[0] * p3.z + mmm1[1] * p4.z;
    float dw = temp0[0] * x0.w + temp0[1] * x1.w + temp0[2] * x2.w + temp1[0] * x3.w + temp1[1] * x4.w + mmm0[0] * p0.w + mmm0[1] * p1.w + mmm0[2] * p2.w + mmm1[0] * p3.w + mmm1[1] * p4.w;

    return vec4(dx, dy, dz, dw) * 49.0;
  }
  vec3 curl( in vec3 p, in float noiseTime, in float persistence ) {

    vec4 xNoisePotentialDerivatives = vec4(0.0);
    vec4 yNoisePotentialDerivatives = vec4(0.0);
    vec4 zNoisePotentialDerivatives = vec4(0.0);

    for (int i = 0; i < 3; ++i) {

        float twoPowI = pow(2.0, float(i));
        float scale = 0.5 * twoPowI * pow(persistence, float(i));

        xNoisePotentialDerivatives += snoise4(vec4(p * twoPowI, noiseTime)) * scale;
        yNoisePotentialDerivatives += snoise4(vec4((p + vec3(123.4, 129845.6, -1239.1)) * twoPowI, noiseTime)) * scale;
        zNoisePotentialDerivatives += snoise4(vec4((p + vec3(-9519.0, 9051.0, -123.0)) * twoPowI, noiseTime)) * scale;
    }

    return vec3(
        zNoisePotentialDerivatives[1] - yNoisePotentialDerivatives[2],
        xNoisePotentialDerivatives[2] - zNoisePotentialDerivatives[0],
        yNoisePotentialDerivatives[0] - xNoisePotentialDerivatives[1]
    );

  }

  uniform vec3 mouse3d;
  uniform sampler2D textureDefaultPosition;

  void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    vec4 texturePos = texture2D(texturePosition, uv);
    vec4 textureVel = texture2D(textureVelocity, uv);


    vec3 vel = textureVel.xyz;
    vec3 pos = texturePos.xyz;
    vel += curl(pos * 0.02, 0.0, 0.05);
    vel *= 0.6;

    gl_FragColor = vec4(vel, 1.0);
  }
`
const particleVertShader = `
  uniform sampler2D texturePosition;
  uniform sampler2D textureVelocity;
  varying float vLife;

  #include <common>
  #include <fog_pars_vertex>
  #include <shadowmap_pars_vertex>


  void main() {
    vec4 pos = texture2D(texturePosition, uv);
    vec4 vel = texture2D(textureVelocity, uv);

    vec4 worldPosition = modelMatrix * vec4(pos.xyz, 1.0);
    vec4 mvPosition = viewMatrix * worldPosition;

    vLife = pos.w;

    gl_PointSize = 1300.0 / length(mvPosition.xyz) * smoothstep(0.0, 0.2, pos.w);

    gl_Position = projectionMatrix * mvPosition;

    #include <fog_vertex>
    #include <shadowmap_vertex>
  }
`

const particleFragShader = `
  #include <common>
  #include <packing>
  #include <bsdfs>
  #include <fog_pars_fragment>
  #include <lights_pars_begin>
  #include <shadowmap_pars_fragment>
  #include <shadowmask_pars_fragment>


  varying float vLife;

  void main() {
     vec3 color1 = vec3(134.0/255.0, 37.0/255.0, 25.0/255.0);
    vec3 color2 = vec3(174.0/255.0, 95.0/255.0, 57.0/255.0);

    vec3 outgoingLight = mix(color1, color2, smoothstep(0.9, 0.1, vLife));

    float shadowMask = max(getShadowMask(), 0.75);
    outgoingLight *= shadowMask;

    gl_FragColor = vec4(outgoingLight, 1.0);
    #include <fog_fragment>
  }

`
const particleDistanceVertShader = `
  uniform sampler2D texturePosition;

  varying vec4 vWorldPosition;

  void main() {

      vec4 texturePos = texture2D(texturePosition, uv.xy);

      vec4 worldPosition = modelMatrix * vec4(texturePos.xyz, 1.0);
      vec4 mvPosition = viewMatrix * worldPosition;

      //gl_PointSize = 50.0 / length(mvPosition.xyz);
      gl_PointSize = 2.0;

      vWorldPosition = worldPosition;

      gl_Position = projectionMatrix * mvPosition;

  }
`
const particleDistanceFragShader = `
  uniform vec3 lightPos;
  varying vec4 vWorldPosition;

  #include <common>

  vec4 pack1K (float depth) {

    depth /= 1000.0;
    const vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
    const vec4 bitMsk = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
    vec4 res = fract(depth * bitSh);
    res -= res.xxyz * bitMsk;
    return res;

  }

  float unpack1K (vec4 color) {

    const vec4 bitSh = vec4(1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0);
    return dot(color, bitSh) * 1000.0;

  }

  void main () {
    gl_FragColor = pack1K(length(vWorldPosition.xyz - lightPos.xyz));
}

`

let scene, camera, renderer
let posTexture, velTexture
let comPosition, comVelocity
let computeRenderer, particle

let particleUniforms, posUniforms, velUniforms


let ray = new THREE.Ray()
let mouse3d = ray.origin
let mouse = new THREE.Vector2()


const TEXTURE_WIDTH = 256
const TEXTURE_HEIGHT = 256
const AMOUNT = TEXTURE_WIDTH * TEXTURE_HEIGHT
const touch = !!('ontouchstart' in window)

let touched = false
let winSize = {
  w: window.innerWidth,
  h: window.innerHeight
}

const clock = new THREE.Clock()
clock.start()

const lightPos = new THREE.Vector3(0, 500, 0)


function init() {
  initScene()
  bindEvents()
  initComputeRenderer()
  initLights()
  initFloor()
  initParticles()
  render()
}

function initFloor() {
  const geometry = new THREE.PlaneBufferGeometry(4000, 4000, 10, 10)
  const material = new THREE.MeshStandardMaterial({
    roughness: 0.7,
    metalness: 1.0,
    color: 0xc4dcba,
    emissive: 0x000000
  })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.y = -100
  mesh.rotation.x = -1.57
  mesh.receiveShadow = true
  scene.add(mesh)
}

function initLights() {
  const group = new THREE.Object3D()
  group.position.set(lightPos.x, lightPos.y, lightPos.z)

  const ambient = new THREE.AmbientLight(0x333333)
  group.add(ambient)

  const pointLight = new THREE.PointLight(0xffffff, 1, 700)

  pointLight.castShadow = true
  pointLight.shadow.camera.near = 10
  pointLight.shadow.camera.far = 1000
  pointLight.shadow.bias = 0.001
  pointLight.shadow.radius = 30
  pointLight.shadow.mapSize.width = 4096
  pointLight.shadow.mapSize.height = 2048
  group.add(pointLight)


  const directionalLight = new THREE.DirectionalLight(0xba8b8b, 0.5)
  directionalLight.position.set(1, 1, 1)
  group.add(directionalLight)

  const directionalLight2 = new THREE.DirectionalLight(0x8bbab4, 0.3)
  directionalLight2.position.set(1, 1, -1)
  group.add(directionalLight2)

  scene.add(group)
}

function initParticles() {
  const particleGeometry = new THREE.BufferGeometry()
  const position = new Float32Array(AMOUNT * 3)
  const uv = new Float32Array(AMOUNT * 2)
  let index = 0
  for (let i = 0; i < AMOUNT; i++) {
    index = i * 2
    uv[index + 0] = (i % TEXTURE_WIDTH) / TEXTURE_WIDTH
    uv[index + 1] = ~~(i / TEXTURE_WIDTH) / TEXTURE_HEIGHT
  }


  particleGeometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
  particleGeometry.setAttribute('uv', new THREE.BufferAttribute(uv, 2))

  particleUniforms = THREE.UniformsUtils.merge([
    {
      texturePosition: { value: null },
      textureVelocity: { value: null }
    },
    THREE.UniformsLib.lights
  ])

  const particleMaterial = new THREE.ShaderMaterial({
    uniforms: particleUniforms,
    vertexShader: particleVertShader,
    fragmentShader: particleFragShader,
    lights: true,
    blending: THREE.NoBlending
  })


  particle = new THREE.Points(particleGeometry, particleMaterial)
  particle.customDistanceMaterial = new THREE.ShaderMaterial({
    uniforms: {
      lightPos: { type: 'v3', value: lightPos },
      texturePosition: { type: 't' }
    },
    vertexShader: particleDistanceVertShader,
    fragmentShader: particleDistanceFragShader,
    depthTest: true,
    depthWrite: true,
    side: THREE.BackSide,
    blending: THREE.NoBlending
  })


  particle.castShadow = true
  particle.receiveShadow = true
  scene.add(particle)

}

function initComputeRenderer() {
  computeRenderer = new THREE.GPUComputationRenderer(TEXTURE_WIDTH, TEXTURE_HEIGHT, renderer)

  posTexture = computeRenderer.createTexture()
  velTexture = computeRenderer.createTexture()

  initPosition(posTexture)
  initVelocity(velTexture)

  comPosition = computeRenderer.addVariable('texturePosition', computePosShader, posTexture)
  comVelocity = computeRenderer.addVariable('textureVelocity', computeVelShader, velTexture)

  computeRenderer.setVariableDependencies(comPosition, [comPosition, comVelocity])
  posUniforms = comPosition.material.uniforms
  posUniforms.time = { type: 'f' }
  posUniforms.mouse3d = { type: 'v3', value: new THREE.Vector3() }
  posUniforms.textureDefaultPosition = { value: posTexture.clone() }

  computeRenderer.setVariableDependencies(comVelocity, [comVelocity, comPosition])
  velUniforms = comVelocity.material.uniforms
  velUniforms.mouse3d = { type: 'v3', value: new THREE.Vector3() }
  velUniforms.textureDefaultPosition = { value: posTexture.clone() }


  computeRenderer.init()

}


function initPosition(texture) {
  const data = texture.image.data
  for (let i = 0, l = data.length; i < l; i += 4) {
    const radius = (0.5 + Math.random() * 0.5) * 50
    const phi = (Math.random() - 0.5) * Math.PI
    const theta = Math.random() * Math.PI * 2
    data[i + 0] = radius * Math.cos(theta) * Math.cos(phi)
    data[i + 1] = radius * Math.sin(phi)
    data[i + 2] = radius * Math.sin(theta) * Math.cos(phi)
    data[i + 3] = Math.random()
  }
}

function initVelocity(texture) {
  const data = texture.image.data
  for (let i = 0, l = data.length; i < l; i += 4) {
    data[i + 0] = Math.random() * 20 - 10
    data[i + 1] = Math.random() * 20 - 10
    data[i + 2] = Math.random() * 20 - 10
    data[i + 3] = 0
  }
}

function bindEvents() {
  const touchBegan = touch ? 'touchstart' : 'mousedown'
  const touchMoved = touch ? 'touchmove' : 'mousemove'
  const touchEnded = touch ? 'touchend' : 'mouseup'
  document.addEventListener(touchBegan, onTouchBegan)
  window.addEventListener(touchMoved, onTouchMoved)
  document.addEventListener(touchEnded, onTouchEnded)
  window.addEventListener('resize', setSize, false)
}

function onTouchBegan(e) { }

function onTouchMoved(e) {
  const x = touch ? e.changedTouches[0].pageX : e.pageX
  const y = touch ? e.changedTouches[0].pageY : e.pageY
  mouse.x = (x / winSize.w) * 2 - 1
  mouse.y = -(y / winSize.h) * 2 + 1
}

function onTouchEnded(e) { }

function setSize() {
  winSize.w = window.innerWidth
  winSize.h = window.innerHeight
  renderer.setSize(winSize.w, winSize.h)
  camera.aspect = winSize.w / winSize.h
  camera.updateProjectionMatrix()
}

function initScene() {
  renderer = new THREE.WebGLRenderer({
    alpha: false,
    antialias: true
  })
  renderer.setClearColor(0x4c7d79)
  renderer.shadowMap.type = THREE.PCFSoftShadowMap
  renderer.shadowMap.enabled = true
  scene = new THREE.Scene()
  scene.fog = new THREE.FogExp2(0x4c7d79, 0.001)
  camera = new THREE.PerspectiveCamera(45, winSize.w / winSize.h, 10, 3000)
  camera.position.set(0, 0, 400)
  document.body.appendChild(renderer.domElement)
  setSize()
}

function update() {

  const delta = clock.getDelta()
  const lightPos = new THREE.Vector3(0, 500, 0)

  camera.updateMatrixWorld()
  ray.origin.setFromMatrixPosition(camera.matrixWorld)
  ray.direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(ray.origin).normalize()
  const distance = ray.origin.length() / Math.cos(Math.PI - ray.direction.angleTo(ray.origin))
  ray.origin.add(ray.direction.multiplyScalar(distance * 1.0))

  posUniforms.mouse3d.value.copy(mouse3d)
  posUniforms.time.value = delta

  computeRenderer.compute()
  particleUniforms.texturePosition.value = computeRenderer.getCurrentRenderTarget(comPosition).texture
  particleUniforms.textureVelocity.value = computeRenderer.getCurrentRenderTarget(comVelocity).texture
  particle.customDistanceMaterial.uniforms.texturePosition.value = computeRenderer.getCurrentRenderTarget(comPosition).texture
}

function render() {
  update()
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

init()
              
            
!
999px

Console