<div id="stats"></div>
html, body {
	background: black;
	overflow: hidden;
}
#stats{
  z-index: 1;
  position: absolute;
  color: #fff;
}
import * as THREE from "https://cdn.skypack.dev/three@0.150.1";
let scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 3);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
})


let gu = {
  time: {value: 0}
}

let sizes = [];
let shift = [];
let pushShift = () => {
  shift.push(
    Math.random() * Math.PI, 
    Math.random() * Math.PI, 
    Math.random() * Math.PI * 0.1,
    Math.random() * 0.4 + 0.1
  );
}
let pts = new Array(50000).fill().map(p => {
  sizes.push(Math.random() * 0.05 + 0.1);
  pushShift();
  return new THREE.Vector3().randomDirection().multiplyScalar(Math.random() * 0.05 + 0.1);
})

let g = new THREE.BufferGeometry().setFromPoints(pts);
g.setAttribute("sizes", new THREE.Float32BufferAttribute(sizes, 1));
g.setAttribute("shift", new THREE.Float32BufferAttribute(shift, 4));
let m = new THREE.PointsMaterial({
  size: 0.1,
  transparent: true,
  depthTest: false,
  blending: THREE.AdditiveBlending,
  onBeforeCompile: shader => {
    shader.uniforms.time = gu.time;
    shader.vertexShader = `
      uniform float time;
      attribute float sizes;
      attribute vec4 shift;
      varying vec3 vColor;
      ${shader.vertexShader}
    `.replace(
      `gl_PointSize = size;`,
      `gl_PointSize = size * sizes;`
    ).replace(
      `#include <color_vertex>`,
      `#include <color_vertex>
        float d = length(abs(position) / vec3(0., 0.5, 1.));
        d = clamp(d, 0., 1.);
        vColor = mix(vec3(227., 155., 0.), vec3(100., 50., 255.), d) / 255.;
      `
    ).replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
        float t = time;
        float moveT = mod(shift.x + shift.z * t, PI2);
        float moveS = mod(shift.y + shift.z * t, PI2);
        transformed += vec3(cos(moveS) * sin(moveT), cos(moveT), sin(moveS) * sin(moveT)) * shift.w;
      `
    );
    //console.log(shader.vertexShader);
    shader.fragmentShader = `
      varying vec3 vColor;
      ${shader.fragmentShader}
    `.replace(
      `#include <clipping_planes_fragment>`,
      `#include <clipping_planes_fragment>
       float d = length(gl_PointCoord.xy - 0.5);
      `
    ).replace(
      `vec4 diffuseColor = vec4( diffuse, opacity );`,
      `vec4 diffuseColor = vec4( vColor, smoothstep(0.5, 0.1, d) );`
    );
    //console.log(shader.fragmentShader);
  }
});
let p = new THREE.Points(g, m);
scene.add(p)

let clock = new THREE.Clock();

let prevTime = performance.now();
let frames = 0;
renderer.info.autoReset = true;
renderer.setAnimationLoop(() => {
  let t = clock.getElapsedTime() * 0.5;
  gu.time.value = t * Math.PI;
  p.rotation.y = t * 0.05;
  renderer.render(scene, camera);

  frames++;
  const time = performance.now();
  if (time >= prevTime + 1000) {
    window._fps = (frames * 1000) / (time - prevTime);
    prevTime = time;
    frames = 0;
  }
});


const updateStats = () => {
  setTimeout(updateStats, 500);
  const info = renderer.info;
  stats.innerText = `
Frame rate: ${Math.round(window._fps||0)}fps
[Memory]
Geometries: ${info.memory.geometries}
Textures: ${info.memory.textures}
[Render]
Frame: ${info.render.frame}
Calls: ${info.render.calls}
Triangles: ${info.render.triangles}
Points: ${info.render.points}
Lines: ${info.render.lines}
`;
};
updateStats();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.min.js
  2. https://cdn.jsdelivr.net/npm/three-nebula@10.0.3/build/three-nebula.js