vectorgame-physicsgpgpuparticlescompute-shader

Webgl: Maintaining particle positions with mouse offsets in GPGPU


I have a simple particle square that I want to move with the mouse movement, mapped to -1 to 1, in such a way that its bottom left point sticks to the cursor.

This is fairly easy in vertex shade where you can just add the mouse.xy to the pos.xy. But I want to use the GPGPU approach to save particle state and do some stuff with it.

I am unable to make the entire square move and stop with the mouse movements. How do I modify my shader code so that I get the entire square move in tandem with the mouse movements like this video below?

The expected behaviour achieved simply in vertex shader without gpgpu: enter image description here



But with gpgpu, this is what I managed: My gpgpu

This is my shader code:

    void main() {
        vec2 uv = vUv;
        vec4 pos = texture2D( tMap, vUv );
        
        vec2 mouse = uMouse;
        vec2 targetPos = mouse + pos.xy;
        float targetLen = length( mouse );
        
       float len = distance( mouse.xy, pos.xy ); // this needs to shrink for every particle together
    
        pos.xy += ( targetPos - pos.xy ) * .01 * len;
        
        gl_FragColor = pos;
    }

I know my approach is wrong because each frame, pos.xy get re-written in the texture and we keep adding the mouse.xy to it. But in what way can I actually fix the offset so that the distance function keeps shrinking the velocity for each particle together?

FIDDLE


Solution

  • I modified your fiddle code (https://jsfiddle.net/zn8sm5gx/), you can see if it meets your expectations now.


    The real thing you wanna do is to calculate the delta between the mouse position at frame n-1 and at frame n, and then add this delta to pos in the shader, like:

    precision highp float;
    uniform float uTime;
    
    uniform sampler2D tMap;
    uniform vec2 uMouse;
    uniform vec2 uLastMouse;
    
    varying vec2 vUv;
    
    
    void main() {
        vec4 pos = texture2D( tMap, vUv );
        
        vec2 moveDelta = uMouse - uLastMouse;
        pos.xy += moveDelta;
        
        gl_FragColor = pos;
    }
    

    Just record the mouse position at the last frame in a uniform variable, and update it on a pre-frame basis. You can achieve this by decoupling the handling of mousemove event and setting the render states:

      // EVENTS
      #events(){
      
        this.currentMouse = new Vec2();
        this.lastMouse = new Vec2();
        this.mouseMoved = false;
        
        document.addEventListener('mousemove', ( e ) => {
      
          this.progress = 0;
          this.mouseMoved = true;
          
          this.lastMouse = this.currentMouse.clone();
      
          this.currentMouse.x = ( e.pageX / this.gl.renderer.width ) * 2 - 1;
          this.currentMouse.y = ( e.pageY / this.gl.renderer.height ) * 2 - 1;
        });
      
        }
      
      // ANIMATE
      #animate(){
        
        // UPDATING VALUES
        // this.speed = (t * 0.000007) % 1;
        this.speed += .007;
    
        if (this.mouseMoved) {
          this.positionBuffer.passes[0].uniforms.uMouse.value = new Vec2( this.currentMouse.x, -this.currentMouse.y );
          this.positionBuffer.passes[0].uniforms.uLastMouse.value = new Vec2( this.lastMouse.x, -this.lastMouse.y );
          this.mouseMoved = false;
        } else {
          this.positionBuffer.passes[0].uniforms.uLastMouse.value = new Vec2( this.currentMouse.x, -this.currentMouse.y );
        }
    
        this.sourceMesh.program.uniforms.uTime.value = this.speed;
        this.positionBuffer.passes[0].uniforms.uTime.value = this.speed;
        this.positionBuffer.render();
    
        this.renderer.render({ scene: this.sourceMesh.scene, camera: this.camera });
    
        requestAnimationFrame( this.#animate.bind(this) );
    
      }
    

    Here #events focuses on updating the "last changed mouse position", and uses a boolean to notify the rendering routine about whether this change happened at the last frame.