Dimitris Karagiannis
Dimitris Karagiannis

Reputation: 9376

three.js shader to keep moving vertices of points cloud inside sphere

I am trying to achieve the effect of having a points cloud with moving points while also keeping them confined within a sphere of radius X.

I have managed to do it without using shaders but I want to try and do it with shaders as well.

What I have so far is this

const uniforms = {
  u_time: { value: 0 },
  u_radius: { value: 1500 },
};

const vShader = `
uniform float u_time;
uniform int u_radius;

attribute vec3 velocity;

varying vec3 v_position;

void main() {
  v_position = position;
  vec3 vel = velocity * u_time;
  
  if(length(position) > float(u_radius)) {
    vel = vel * -1.0;
  }
  
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position + vel, 1.0);
  gl_PointSize = 10.0;
}`;

const fShader = `
varying vec3 v_position;

void main() {
  vec3 color = vec3(1.0);
  gl_FragColor = vec4(color, 0.7);
}`;

The logic is that for each vertex, I am comparing its length based on its current position to the sphere radius and if it falls outside of it its velocity vector should be negated ,thus keeping it within the sphere.

Sadly what I have above does not seem to work, as the particles spread out to infinity over time.

I am new to three.js and GLSL so I must be missing something obvious and doing this wrong.

EDIT:

Shown in the gif: The effect as I have currently managed to implement it using Points, and an array of positions and an array of velocities (these are seeded once during initialisation with random values)

(it may be hard to see in the gif due to compression, but each particle is moving randomly but is always contained within the sphere)

On every tick I iterate over the array of particles and add their velocity vector to their position vector. If their position vector length is > than the sphere radius I negate the velocity vector and update the velocities array to the indices that correspond to that particle. I then mark the Points geometry position attribute as needing an update and that's it.

particles with random velocity contained within a sphere

Upvotes: 1

Views: 223

Answers (1)

prisoner849
prisoner849

Reputation: 17596

If I got you correctly, you want kind of pingpong animation.

Here is an example with modified PointsMaterial:

body{
  overflow: hidden;
  margin: 0;
}
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js",
      "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
console.clear();

let scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(innerWidth, innerHeight);
//renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", (event) => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let light = new THREE.DirectionalLight(0xffffff, 0.8);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xff8888, 0.2));

//scene.add(new THREE.GridHelper());

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

let amount = 10000;
let inits = new Array(amount).fill().map(() => {
  let v =  new THREE.Vector3().randomDirection();
  return [v.x, v.y, v.z, Math.random() * 2 - 1]
}).flat();

let g = new THREE.BufferGeometry().setFromPoints(new Array(amount).fill().map(() => {return new THREE.Vector3}));
g.setAttribute("inits", new THREE.Float32BufferAttribute(inits, 4));
let u = {
  radius: {value: 5},
  speed: {value: 0.25}
}
let m = new THREE.PointsMaterial({
  color: 0xff8800,
  size: 0.1,
  onBeforeCompile: shader => {
    shader.uniforms.time = gu.time;
    shader.uniforms.radius = u.radius;
    shader.uniforms.speed = u.speed;
    shader.vertexShader = `
      uniform float time;
      uniform float radius;
      uniform float speed;
      attribute vec4 inits;
      float euclideanModulo( float n, float m ) {
        return mod( mod( n, m ) + m , m);
      }
      float pingpong(float x, float l){
        return l - abs( euclideanModulo( x, l * 2. ) - l );
      }
      ${shader.vertexShader}
    `.replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
        float t = time * speed;
        float startRadius = inits.w * radius;
        float currentDist = -radius + (startRadius + t + radius);
        float ppVal = pingpong(currentDist, radius * 2.);
        transformed = (-radius + ppVal) * inits.xyz;
      
      `
    );
    console.log(shader.vertexShader);
  }
});
let p = new THREE.Points(g, m);
scene.add(p)

let gui = new GUI();
gui.add(u.radius, "value", 2, 5).name("radius");
gui.add(u.speed, "value", 0.1, 1).name("speed");

let clock = new THREE.Clock();

renderer.setAnimationLoop((_) => {
  let t = clock.getElapsedTime();
  gu.time.value = t;
  controls.update();
  renderer.render(scene, camera);
});
</script>

Upvotes: 1

Related Questions