Creating a three.js shader that continuously moves the vertices of a point cloud within a sphere

My current project involves creating a points cloud with moving points that are confined within a sphere of a specified radius, X.

Initially, I was able to achieve this without using shaders, but now I am experimenting with shaders to enhance the effect.

What I have implemented so far is as follows:

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);
}`;

My approach involves comparing the length of each vertex's current position to the sphere radius. If the position falls outside the sphere, the velocity vector is negated to keep it within the sphere.

Unfortunately, my current implementation does not yield the desired result, as the particles spread out over time.

As a beginner in three.js and GLSL, I suspect I must be overlooking something obvious.

EDIT:

The gif showcases the effect I have achieved so far using Points, an array of positions, and an array of velocities (initially randomized during initialization).

(The particles move randomly but stay within the sphere, although this may not be clear in the gif due to compression.)

Upon each tick, I iterate over the particles array, updating their positions based on velocity vectors. If a particle's position exceeds the sphere radius, I reverse the velocity vector and update the corresponding indices in the velocities array. Finally, I mark the Points geometry position attribute for update.

https://i.sstatic.net/8bqzG.gif

Answer №1

If I understand correctly, you are looking for a type of pingpong animation.

Below is an example featuring a modified PointsMaterial:

body{
  overflow: hidden;
  margin: 0;
}
<script async src="https://ga.jspm.io/npm:<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="9affe9b7f7f5feeff6ffb7e9f2f3f7e9daabb4acb4a9">[email protected]</a>/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="483c203a2d2d087866797d7b6678">[email protected]</a>/build/three.module.js",
      "three/addons/": "https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="81f5e9f3e4e4c1b1afb0b4b2afb1">[email protected]</a>/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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

javascript hide rows without data

I have a knack for tweaking existing code to suit my needs, but I'm not very proficient in writing my own from scratch. That being said, here's my current dilemma. My pool league uses a Google sheet to manage a variety of statistics, and with so ...

Issues encountered with jQuery's $.ajax function when communicating with PHP

I am having trouble creating a simple app that displays data from a MySQL database using PHP and jQuery. The issue I am facing is with retrieving the data using jQuery. While my PHP script successfully returns the data without any problems, I am not receiv ...

The initial line is the only place where JSON headers are displayed

Currently diving into the world of JavaScript and tackling a project head-on. However, I've hit a roadblock as the JSON file I'm working with looks different from what I'm used to. Can anyone assist me in figuring out how to display this dat ...

Unique ActionBar design for my NativeScript-Vue application

I'm currently working on customizing the ActionBar for my nativescript-vue app. I have implemented FlexBoxLayout within the ActionBar, but I am facing an issue where the icon and title of the bar are not aligning to the left as intended; instead, they ...

The function Amplify.configure does not exist

Currently attempting to utilize AWS Amplify with S3 Storage, following the steps outlined in this tutorial for manual setup. I have created a file named amplify-test.js, and here is its content: // import Amplify from 'aws-amplify'; var Amplify ...

How can we guide the user to a different page when a particular result is retrieved by AJAX?

Whenever my PHP function makes a database call, I receive multiple results. The ajax then displays these results in a div element. My question is: How can I redirect the user to the next page once I obtain a specific result from the PHP function? Current ...

Guide on displaying content in two different tabbable ID's when one tab is selected in a Rails 3.2 App using Twitter Bootstrap's Tabbable Tabs

My implementation using Twitter Bootstrap's bootstrap-tab.js includes: <ul class="tabnavcenter" id="myTab"> <li class="active"><a href="#home" data-toggle="tab">about</a></li> <li><a href="#tab2" data-togg ...

What steps should be followed to construct a window identical to the one depicted in the attached image using HTML and CSS?

Check out this link to see the window style I'm trying to recreate using HTML, CSS, and Javascript. No Jquery needed. Thank you in advance. ...

What are the steps to successfully deploy a static website created with Next.js on Vercel?

Using the Next.js static site generator, I created a simple static site that I now want to deploy on Vercel. However, I keep encountering an error during the build process. While I have successfully deployed this site on other static hosting platforms befo ...

Transferring scope between pages without the need for an angular service definition

Looking to open a new JSP page while passing the $scope in order to utilize an array response generated in the initial page. Example from test.js file: (function() { 'use strict'; angular .module('test', []) .control ...

displaying and activating element using jQuery

I'm currently working on setting up a notification system for my website but seem to be encountering some issues that I can't quite pinpoint. Essentially, I have a link that triggers a JavaScript function upon being clicked. This function is mean ...

How can a producer know when it's time to send a message in NodeJS using ZeroMQ?

After conducting some research on patterns supported by zeromq, I have encountered an issue with the PUB/SUB pattern in my recent project as well as the PUSH/PULL pattern. I am using NodeJS for the zeromq implementation. In my examples (server.js & client ...

Tips for integrating execute_script and WebDriverWait in Selenium automation

Is there a way to combine execute_script() and WebdriverWait? In my current code: network_list = driver.find_element_by_xpath('//*[@id="folder_box"]/div[1]/div/div[2]/div[1]') wait = WebDriverWait(driver, 4) try: wait_network_list = wait.unt ...

I am looking to optimize my JavaScript function so that the console.log structure is functioning correctly. What changes can I make to

I've been trying out this method to tackle the issue, however, my console.log isn't providing the expected output. What adjustments should I make? const executeCalculator = ({ x, y, operation }) => { let calculator = { x: this.x, ...

One login for accessing multiple forms

I am trying to figure out a way to use one login for two different forms that serve different functions. How can I pass the login details between these two functions? Just to clarify, I only have knowledge of JavaScript and VBScript, not jQuery. For inst ...

Utilize the datepicker function in jQuery version 1.6.3 to select a range of dates

I need help adding a jQuery datepicker to my JSP page for selecting a date range. Below is the current code I am working with. $(function() { $( "#createdAtFrom" ).datepicker({ defaultDate: "+1w", changeMonth: true, ...

Automating user login with node.js and passport.js: A step-by-step guide

My login application is built using node.js and passport.js, with the session being maintained using express-session and connect-mongo. I need to ensure that users are redirected to the home page upon accessing the URL, only sending them to the login page ...

Tips for Sending Props While Utilizing CSS Modules

I've been working with a button component that utilizes the Tailwindcss framework and css modules for some extra styling. It currently looks like this, incorporating template literal to integrate the red background styling. CSS Module: .red { back ...

Modify the background's color and incorporate a pointlight using three.js

Recently, I have been exploring the capabilities of three.js. One interesting challenge I encountered was creating a cube within a wireframe cube. However, I found myself struggling to alter the background color in my project. I believe that incorporating ...

Ways to merge values across multiple arrays

There is a method to retrieve all unique properties from an array, demonstrated by the following code: var people = [{ "name": "John", "age": 30 }, { "name": "Anna", "job": true }, { "name": "Peter", "age": 35 }]; var result = []; people. ...