<canvas id="canvas"></canvas>

<script id="vertex" type="x-shader/x-vertex">
#version 300 es
in vec3 position;
out float noise;
out vec3 vMVPos;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;

uniform float uTime;
uniform vec2 uMouse;
uniform vec2 uResolution;
uniform float uMouseDistortDist;

// Simplex 2D noise
// https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
vec3 permute(vec3 x) {
  return mod(((x * 34.0) + 1.0) * x, 289.0);
}

float snoise(vec2 v) {
  const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
  vec2 i = floor(v + dot(v, C.yy));
  vec2 x0 = v - i + dot(i, C.xx);
  vec2 i1;
  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  vec4 x12 = x0.xyxy + C.xxzz;
  x12.xy -= i1;
  i = mod(i, 289.0);
  vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
  vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
  m = m * m;
  m = m * m;
  vec3 x = 2.0 * fract(p * C.www) - 1.0;
  vec3 h = abs(x) - 0.5;
  vec3 ox = floor(x + 0.5);
  vec3 a0 = x - ox;
  m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
  vec3 g;
  g.x = a0.x * x0.x + h.x * x0.y;
  g.yz = a0.yz * x12.xz + h.yz * x12.yw;
  return 130.0 * dot(m, g);
}

vec2 getScreenPosition(vec3 pos) {
  vec4 p = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
  vec2 sp = p.xy / p.w * vec2(1.0, -1.0);
  return (sp * 0.5 + 0.5) * uResolution;
}
  
void main() {
  noise = snoise(position.xy + position.zx + uTime * 0.1) * 0.5 + 0.5;
  float n = mix(0.9, 1.3, noise * 0.5);
  
  vec2 pointScreenPosition = getScreenPosition(position * n);
  float pointMouseFact = 1.0 - min(1.0, distance(pointScreenPosition, uMouse) / uMouseDistortDist);
  
  vec4 newPosition = modelViewMatrix * vec4(position * (n + pointMouseFact * -0.25), 1.0);
  vMVPos = newPosition.xyz;
  newPosition = projectionMatrix * newPosition;

  gl_Position = newPosition;
  gl_PointSize = 3.0;
}
</script>

<script id="fragment" type="x-shader/x-fragment">
#version 300 es
precision highp float;
uniform float uTime;
uniform vec3 cameraPosition;

in float noise;
in vec3 vMVPos;

out vec4 pc_FragColor;

float circle(vec2 uv, float r) {
  vec2 dist = uv - vec2(0.5);
  return 1. - step(r, dot(dist, dist) * 4.0);
}
  
void main() {
  vec2 uv = gl_PointCoord;

  vec3 fogColor = vec3(0.04, 0.05, 0.05);
  vec3 color1 = vec3(0.96, 0.26, 0.21);
  vec3 color2 = vec3(1, 0.92, 0.23);
  vec3 color = mix(color1, color2, noise);
  
  float circ = circle(uv, 1.0);
  if (circ < 0.001) discard;
  
  float fogDist = length(vMVPos) - length(cameraPosition);
  float fogFact = smoothstep(-0.5, 1.0, fogDist);

  color = mix(color, fogColor, fogFact);

  pc_FragColor.rgb = color;
  pc_FragColor.a = circ;
}
</script>
body {
  margin: 0;
  height: 100vh;
  background-color: #111;
}

#canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
import {
  Renderer,
  Camera,
  Transform,
  Geometry,
  Box,
  Sphere,
  Orbit,
  Program,
  Mesh,
  Vec2,
  Vec3
} from "https://cdn.skypack.dev/ogl@0.0.97";

const vertex = document.getElementById("vertex").textContent.trim();
const fragment = document.getElementById("fragment").textContent.trim();

// from: https://stackoverflow.com/a/60579136
function fibonacciSphere(total, index) {
  const offset = 2 / total;
  const increment = 2.399963229728653; // Math.PI * (3 - Math.sqrt(5));

  const y = index * offset - 1 + offset / 2;
  const r = Math.sqrt(1 - Math.pow(y, 2));

  const phi = (index % total) * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return [x, y, z];
}

const createSpring2D = (from, springCoef = 0.01, frictionCoef = 0.7) => {
  from = from.clone();
  const velocity = new Vec2();
  const tmp = new Vec2();

  return (to) => {
    tmp.sub(to, from).scale(springCoef);
    velocity.add(tmp).scale(frictionCoef);
    from.add(velocity);
    return from;
  };
};

const mouse = new Vec2();
const mouseEased = new Vec2();
const resolution = new Vec2();

const pixelRatio = window.devicePixelRatio;
const canvas = document.querySelector("#canvas");
const renderer = new Renderer({ canvas, dpr: pixelRatio, antialias: true });
const gl = renderer.gl;
gl.clearColor(0.04, 0.05, 0.05, 1.0);

const camera = new Camera(gl, { fov: 35 });
camera.position.set(0, 0, 5);
camera.lookAt([0, 0, 0]);

const scene = new Transform(gl);
const controls = new Orbit(camera, { enableRotate: false });

const totalPoints = 4096;
const position = new Float32Array(totalPoints * 3);

for (let i = 0; i < totalPoints; i++) {
  const p = fibonacciSphere(totalPoints, i);
  position.set(p, i * 3);
}

const mesh = new Mesh(gl, {
  mode: gl.POINTS,
  geometry: new Geometry(gl, {
    position: { size: 3, data: position }
  }),
  program: new Program(gl, {
    vertex: vertex,
    fragment: fragment,
    uniforms: {
      uTime: { value: 0 },
      uResolution: { value: resolution },
      uMouse: { value: mouseEased },
      uMouseDistortDist: { value: 250 * pixelRatio }
    }
  })
});

// mesh.geometry.isInstanced = true;
// mesh.geometry.instancedCount = 1024;
mesh.setParent(scene);

function onResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  resolution.set(width * pixelRatio, height * pixelRatio);
  renderer.setSize(width, height);
  const aspect = width / height;
  camera.perspective({ aspect });
}

window.addEventListener("resize", onResize);
onResize();

window.addEventListener("pointermove", (event) => {
  const x = event.clientX * pixelRatio;
  const y = event.clientY * pixelRatio;
  mouse.set(x, y);
});

const mouseSpring = createSpring2D(mouse, 0.02, 0.85);

const mouseWorld = new Vec3();
const meshMouseVec = new Vec3();

function screenToWorld(position, targetZ = 0, out = new Vec3()) {
  out.copy(position).divide(resolution).scale(2);
  out.set(out.x - 1, 1 - out.y, 0);

  camera.unproject(out);
  out.sub(camera.position).normalize();
  out.scale((targetZ - camera.position.z) / out.z);
  out.add(camera.position);
  return out;
}

requestAnimationFrame(function update(now) {
  controls.update();

  const newMouse = mouseSpring(mouse);
  mouseEased.copy(newMouse);

  screenToWorld(newMouse, mesh.position.z, mouseWorld);

  meshMouseVec.sub(mesh.position, mouseWorld);
  const meshMouseLen = meshMouseVec.len();

  const meshMouseFactor = 1 - Math.min(2, meshMouseLen) / 2;
  meshMouseVec.scale((1 / meshMouseLen) * meshMouseFactor * 0.02);
  mesh.position.add(meshMouseVec);

  mesh.program.uniforms.uTime.value = now / 1000;

  renderer.render({ scene, camera });

  requestAnimationFrame(update);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.