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:
But with gpgpu, this is what I managed:
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?
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.