<script id="vertexShader" type="x-shader/x-vertex">
    attribute vec3 color;
    attribute float size;
    varying vec3 vColor;
    void main() {
      vColor = color;
      vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
      gl_PointSize = size * ( 600.0 / -mvPosition.z );
      gl_Position = projectionMatrix * mvPosition;
    }
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
    varying vec3 vColor;
    void main() {
      float distanceToCenter = distance(gl_PointCoord, vec2(0.5)) * 2.0;
      float distanceToCenterAlpha = 1.0 - clamp(distanceToCenter, 0.0, 1.0);
      distanceToCenterAlpha = pow(distanceToCenterAlpha, 2.0);
      gl_FragColor = vec4( vColor, distanceToCenterAlpha );
    }
</script>

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

<span id="cursor">
  <span class="caret"></span>
  <span class="caption"></span>
</span>
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed')
html,
body
  padding: 0
  margin: 0
  height: 100%
  overflow: hidden
  font-family: 'Roboto Condensed', sans-serif
canvas
  display: block
$size: 24px
#cursor
  position: absolute
  top: 50%
  left: 50%
  margin-left: - $size * .5
  margin-top: - $size * .5
  width: $size
  height: $size
  opacity: 1
  will-change: transform
  .caret
    display: block
    border: 2px solid white
    width: $size
    height: $size
    box-sizing: border-box
    transform: rotate(45deg)
    opacity: .625
  .caption
    color: #ffffff
    font-size: 12px
    position: absolute
    bottom: 100%
    left: 100%
    margin-left: 10px
    margin-right: 10px
    white-space: nowrap
    letter-spacing: .15em
    
View Compiled
const names=["ACAMAR","ACHERNAR","Achird","ACRUX","Acubens","ADARA","Adhafera","Adhil","AGENA","Ain al Rami","Ain","Al Anz","Marceline","Bubblegum","Jake","Finn the human","Alathfar","Albaldah","Albali","ALBIREO","Alchiba","ALCOR","ALCYONE","ALDEBARAN","ALDERAMIN","Aldhibah","Alfecca Meridiana","Alfirk","ALGENIB","ALGIEBA","ALGOL","Algorab","ALHENA","ALIOTH","ALKAID","Alkalurops","Alkes","Alkurhah","ALMAAK","ALNAIR","ALNATH","ALNILAM","ALNITAK","Alniyat","Alniyat","ALPHARD","ALPHEKKA","ALPHERATZ","Alrai","Alrisha","Alsafi","Alsciaukat","ALSHAIN","Alshat","Alsuhail","ALTAIR","Altarf","Alterf","Aludra","Alula Australis","Alula Borealis","Alya","Alzirr","Ancha","Angetenar","ANKAA","Anser","ANTARES","ARCTURUS","Arkab Posterior","Arkab Prior","ARNEB","Arrakis","Ascella","Asellus Australis","Asellus Borealis","Asellus Primus","Asellus Secondus","Asellus Tertius","Asterope","Atik","Atlas","Auva","Avior","Azelfafage","Azha","Azmidiske","Baham","Baten Kaitos","Becrux","Beid","BELLATRIX","BETELGEUSE","Botein","Brachium","CANOPUS","CAPELLA","Caph","Cake","Cebalrai","Celaeno","Chara","Chort","COR CAROLI","Cursa","Dabih","Deneb Algedi","Deneb Dulfim","Deneb el Okab","Deneb el Okab","Deneb Kaitos Shemali","DENEB","DENEBOLA","Dheneb","Diadem","DIPHDA","Double Double (7051)","Double Double (7052)","Double Double (7053)","Double Double (7054)","Dschubba","Dsiban","DUBHE","Ed Asich","Electra","ELNATH","ENIF","ETAMIN","FOMALHAUT","Fornacis","Fum al Samakah","Furud","Gacrux","Gianfar","Gienah Cygni","Gienah Ghurab","Gomeisa","Gorgonea Quarta","Gorgonea Secunda","Gorgonea Tertia","Graffias","Grafias","Grumium","HADAR","Haedi","HAMAL","Hassaleh","Head of Hydrus",'Herschel\'s "Garnet Star"',"Heze","Hoedus II","Homam","Hyadum I","Hyadum II","IZAR","Jabbah","Kaffaljidhma","Kajam","KAUS AUSTRALIS","Kaus Borealis","Kaus Meridionalis","Keid","Kitalpha","KOCAB","Kornephoros","Kraz","Kuma","Lesath","Maasym","Maia","Marfak","Marfak","Marfic","Marfik","MARKAB","Matar","Mebsuta","MEGREZ","Meissa","Mekbuda","Menkalinan","MENKAR","Menkar","Menkent","Menkib","MERAK","Merga","Merope","Mesarthim","Metallah","Miaplacidus","Minkar","MINTAKA","MIRA","MIRACH","Miram","MIRPHAK","MIZAR","Mufrid","Muliphen","Murzim","Muscida","Muscida","Muscida","Lady Unicorn","Naos","Nash","Nashira","Nekkar","NIHAL","Nodus Secundus","NUNKI","Nusakan","Peacock","PHAD","Phaet","Pherkad Minor","Pherkad","Pleione","Polaris Australis","POLARIS","POLLUX","Porrima","Praecipua","Prima Giedi","PROCYON","Propus","Propus","Propus","Rana","Lavie","Garancine","RASALGETHI","RASALHAGUE","Rastaban","REGULUS","Rigel Kentaurus","RIGEL","Rijl al Awwa","Rotanev","Ruchba","Ruchbah","Rukbat","Sabik","Sadalachbia","SADALMELIK","Sadalsuud","Sadr","SAIPH","Salm","Sargas","Sarin","Sceptrum","SCHEAT","Secunda Giedi","Segin","Seginus","Sham","Sharatan","SHAULA","SHEDIR","Sheliak","SIRIUS","Situla","Skat","SPICA","Sterope II","Sualocin","Subra","Fionna","Sulafat","Syrma","Tabit (1543)","Tabit (1544)","Tabit (1552)","Tabit (1570)","Talitha","Tania Australis","Tania Borealis","TARAZED","Taygeta","Tegmen","Tejat Posterior","Terebellum","Terebellum","Terebellum","Terebellum","Thabit","Theemim","THUBAN","Torcularis Septentrionalis","Turais","Tyl","UNUKALHAI","VEGA","VINDEMIATRIX","Wasat","Wezen","Wezn","Yed Posterior","Yed Prior","Yildun","Zaniah","Zaurak","Zavijah","Zibal","Zosma","Zuben Elakrab","Zuben Elakribi","Zuben Elgenubi","Zuben Elschemali"];

const API = {
  exposure: 1.208,
  bloomStrength: 2.563,
  bloomThreshold: 0,
  bloomRadius: 0.54
};
const resetVector = new THREE.Vector3(0, 0, 0);
var vertex = new THREE.Vector3();
let INTERSECTED = null;

var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );

class App {
  constructor({ container, caption, cursor }) {
    this.caption = caption
    this.cursor = cursor
    this.sizes = {
      width: document.body.offsetWidth,
      height: document.body.offsetHeight,
      halfWidth: document.body.offsetWidth * 0.5,
      halfHeight: document.body.offsetHeight * 0.5
    };
    this.currentName = null
    this.pointer = new THREE.Vector2(.01,.01);
    this.movement = new THREE.Vector2(0,0);
    this.mouse = new THREE.Vector2(
      this.sizes.width * 0.5,
      this.sizes.height * 0.5
    );
    this.target = new THREE.Vector2(this.mouse.x, this.mouse.y);
    this.renderer = new THREE.WebGLRenderer({
      canvas: container,
      alpha: false,
      stencil: false,
      depth: false,
      powerPreference: "high-performance",
      antialias: true
    });
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      75,
      this.sizes.width / this.sizes.hegiht,
      0.1,
      2000
    );
    this.camera.rotation.order = 'YXZ';
    this.wagon = new THREE.Object3D();
    this.wagon.position.set(0, 0, 10);
    this.wagon.add(this.camera);
    this.camera.lookAt(0, 0, 0);
    this.scene.add(this.wagon);
    this.init();
    this.setPasses();
    this.setRaycaster();
    this.setMain();
    // this.setControls();
    this.render();
    window.addEventListener("resize", () => this.handleResize(), {
      passive: true
    });
    window.addEventListener("mousemove", e => this.handleMousemove(e), false);
    window.addEventListener("click", e => this.handleClick(e), false);
  }
  setPasses() {
    this.renderScene = new THREE.RenderPass(this.scene, this.camera);
    this.bloomPass = new THREE.UnrealBloomPass(
      new THREE.Vector2(this.sizes.width, this.sizes.hegiht),
      1.5,
      0.4,
      0.85
    );
    this.bloomPass.threshold = API.bloomThreshold;
    this.bloomPass.strength = API.bloomStrength;
    this.bloomPass.radius = API.bloomRadius;
    this.composer = new THREE.EffectComposer(this.renderer);
    this.composer.setSize(window.innerWidth, window.innerHeight);
    this.composer.addPass(this.renderScene);
    this.composer.addPass(this.bloomPass);
  }
  setRaycaster() {
    this.raycaster = new THREE.Raycaster();
    this.raycaster.params.Points.threshold = 5;
  }
  setControls() {
    var gui = new dat.GUI();
    gui.add(API, "exposure", 0.1, 2, 0.001).onChange(value => {
      this.renderer.toneMappingExposure = Math.pow(value, 4.0);
    });
    gui.add(API, "bloomThreshold", 0.0, 1.0, 0.001).onChange(value => {
      this.bloomPass.threshold = Number(value);
    });
    gui.add(API, "bloomStrength", 0.0, 3.0, 0.001).onChange(value => {
      this.bloomPass.strength = Number(value);
    });
    gui
      .add(API, "bloomRadius", 0.0, 1.0, 0.001)
      .step(0.01)
      .onChange(value => {
        this.bloomPass.radius = Number(value);
      });
  }
  init() {
    var particles = 100000;
    var geometry = new THREE.BufferGeometry();
    var positions = [];
    var colors = [];
    var sizes = [];
    var n = 3200,
      n2 = n / 2;
    for (var i = 0; i < particles; i++) {
      var x = Math.random() * n - n2;
      var y = Math.random() * n - n2;
      var z = Math.random() * n - n2;
      positions.push(x, y, z);
      var s = Math.pow(Math.random(), 12.0);
      sizes.push(s * 16 + 1);
      const c = new THREE.Color(d3.interpolateMagma(Math.random()));
      colors.push(c.r, c.g, c.b);
    }
    geometry.addAttribute(
      "position",
      new THREE.Float32BufferAttribute(positions, 3)
    );
    geometry.addAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
    geometry.addAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));
    var material = new THREE.ShaderMaterial({
      transparent: true,
      depthWrite: false,
      uniforms: {
        uColor: { type: "v3", value: new THREE.Color(0xffffff) },
        uAlpha: { value: 1.0 }
      },
      vertexShader: document.getElementById("vertexShader").textContent,
      fragmentShader: document.getElementById("fragmentShader").textContent
    });
    this.stars = new THREE.Points(geometry, material);
    this.scene.add(this.stars);
  }
  render() {
    requestAnimationFrame(() => this.render());
    // this.camera.rotation.x += (this.pointer.y) * 0.01;
    // this.camera.rotation.y += -(this.pointer.x) * 0.01;
    
		euler.setFromQuaternion( this.camera.quaternion );
 
		euler.x -= -(this.pointer.y) * 0.00625;
		euler.y -= (this.pointer.x) * 0.00625;

		this.camera.quaternion.setFromEuler( euler );
		this.camera.quaternion.normalize();
    
    this.raycaster.setFromCamera(this.pointer, this.camera);
    const intersects = this.raycaster.intersectObject(this.stars);
    if (intersects.length > 0) {
      if (intersects[0].distance < 1000.0) {
        var geometry = this.stars.geometry;
        var attributes = geometry.attributes;
        const idx = intersects[0].index
        INTERSECTED = vertex.fromBufferAttribute(
          attributes.position,
          idx
        );
        this.currentName = names[idx % names.length]
        this.caption.innerHTML = this.currentName
      }
      if (INTERSECTED) {
        let position = INTERSECTED.clone();
        let screenPos = this.getScreenXY(position);
        const cx = Math.round(screenPos.x * this.sizes.halfWidth)
        const cy = Math.round(screenPos.y * -this.sizes.halfHeight)
        TweenMax.to(this.cursor, 0.1, {
          x: cx,
          y: cy
        });
      }
    }
    this.composer.render();
  }
  setMain() {
    this.renderer.setSize(this.sizes.width, this.sizes.height);
    this.composer.setSize(this.sizes.width, this.sizes.height);
    this.camera.aspect = this.sizes.width / this.sizes.height;
    this.camera.updateProjectionMatrix();
  }
  handleClick(e) {
    e.preventDefault()
    if(INTERSECTED) {  
      const pos = INTERSECTED.clone()
      var camPos = this.camera.position.clone()
      const distance = camPos.distanceTo(pos)
      var direction = new THREE.Vector3()
      direction.subVectors( pos, camPos ).normalize();   
      var nearPos = new THREE.Vector3();
      nearPos.addVectors ( camPos, direction.multiplyScalar( distance - 20 ) );
      const {x,y,z} = nearPos
      TweenMax.to(camPos, .625, {
        x,
        y,
        z,
        onUpdate: () => { 
          this.camera.position.x = camPos.x
          this.camera.position.y = camPos.y
          this.camera.position.z = camPos.z
        }
      })
    }
    
  }
  handleMousemove(e) {
    e.preventDefault();

    this.pointer.x = e.clientX / this.sizes.width * 2 - 1;
    this.pointer.y = -(e.clientY / this.sizes.height) * 2 + 1;

    this.mouse.x = e.clientX - this.sizes.width * 0.5;
    this.mouse.y = e.clientY - this.sizes.height * 0.5;
    this.target.x = this.mouse.x;
    this.target.y = this.mouse.y;
  }
  getScreenXY(position) {
    const { halfWidth, halfHeight } = this.sizes;
    var vector = position.clone();
    this.camera.updateProjectionMatrix();
    vector.project(this.camera);
    return vector;
  }
  handleResize() {
    this.sizes.width = document.body.offsetWidth;
    this.sizes.height = document.body.offsetHeight;
    this.sizes.halfWidth = this.sizes.width * 0.5;
    this.sizes.halfHeight = this.sizes.height * 0.5;
    this.setMain();
  }
}
const app = new App({
  container: document.body.querySelector("#scene"),
  caption: document.body.querySelector(".caption"),
  cursor: document.body.querySelector("#cursor")
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
  2. https://threejs.org/examples/js/postprocessing/EffectComposer.js
  3. https://threejs.org/examples/js/postprocessing/RenderPass.js
  4. https://threejs.org/examples/js/postprocessing/ShaderPass.js
  5. https://threejs.org/examples/js/shaders/CopyShader.js
  6. https://threejs.org/examples/js/shaders/LuminosityHighPassShader.js
  7. https://threejs.org/examples/js/postprocessing/UnrealBloomPass.js
  8. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js
  9. https://d3js.org/d3-color.v1.min.js
  10. https://d3js.org/d3-interpolate.v1.min.js
  11. https://d3js.org/d3-scale-chromatic.v1.min.js
  12. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js