Elastic ring glsl

Hi everyone, I am making my first project in GLSL and I am trying to make a ring which stretches like an elastic band according to the position of the mouse cursor, this is the code I have made so far:

or here’s the code:

import * as THREE from "three";

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.z = 5;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const vertexShader = `
  uniform vec2 mouse;
  uniform float stretch;
  uniform float area;
  varying vec2 vUv;
  void main() {
    vUv = uv;
    vec3 pos = position;
    vec2 direction = normalize(mouse - uv);
    float distance = length(mouse - uv);
    float ringRadius = 0.25; 
    if (distance > ringRadius) {
      float influence = exp(-distance * area); 
      pos.xy += direction * influence * stretch;
    }
    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
  }
`;

const fragmentShader = `
  uniform float thickness;
  varying vec2 vUv;
  void main() {
    vec2 uv = vUv - 0.5;
    float len = length(uv);
    float angle = atan(uv.y, uv.x);
    vec3 color = vec3(0.5 + 0.5 * cos(angle + 0.0), 0.5 + 0.5 * cos(angle + 2.0), 0.5 + 0.5 * cos(angle + 4.0));
    float ring = smoothstep(thickness * 2.0, thickness * 2.0 + 0.01, len) - smoothstep(thickness * 2.0 + 0.01, thickness * 2.0 + 0.02, len);
    gl_FragColor = vec4(color * ring, 1.0);
  }
`;

const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({
  vertexShader,
  fragmentShader,
  uniforms: {
    thickness: { value: 0.2 },
    mouse: { value: new THREE.Vector2(0, 0) },
    stretch: { value: 10.0 },
    area: { value: 10.0 },
  },
});
const ring = new THREE.Mesh(geometry, material);

scene.add(ring);

window.addEventListener("mousemove", (event) => {
  const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
  const mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
  material.uniforms.mouse.value.set(mouseX, mouseY);
});

function animate() {
  requestAnimationFrame(animate);

  renderer.render(scene, camera);
}

animate();

window.addEventListener("resize", () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

what I am trying to achieve is this:

1 Like