Using mouse movements to adjust position in three.js with a fragment shader

After reading through this post, I'm attempting to modify a Voronoi shader from the Book of Shaders based on mouse cursor movements.

However, I seem to be encountering issues with the offsets. Currently, my mouse cursor appears slightly off to the right in relation to my code (see below).

I've tried adjusting the offset using the bounding rectangle of my renderer.domElement, but setting the top/left values to 0 had no effect. When simply trying

e.pageX / window.innerWidth; e.pageY / window.innerHeight;
, the y-position appeared flipped and significantly misaligned.

Additionally, I experimented with the standard mapping to [-1,1] using offsetX and other methods (commented out) but found them to be even less accurate. I also explored utilizing unproject from another Stack Overflow post, yet that didn't yield any noticeable changes either.

You can view a jsfiddle demonstrating the same issues here: https://jsfiddle.net/9y8hge3t/1/

Below is the complete code for reference:

<!--
  * Based on Book of Shaders 12:
  https://thebookofshaders.com/12/
-->

<!DOCTYPE HTML>
<html>

<head>
  <title>WebGL Demo - Voronoi (Mouse Move)</title>
  <meta charset="utf-8">
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
  </style>

  <script src="./libraries/threejs/three.min.js"></script>
  <!-- shaders -->
  <script type="x-shader/x-vertex" id="vertexShader">
    void main() {
      //gl_Position = vec4(position, 1.0);
      vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * modelViewPosition;
    }
  </script>
  <script type="x-shader/x-fragment" id="fragmentShader">
    uniform vec2 u_resolution;
    uniform vec2 u_mouse;
    uniform float u_time;

    void main() {
      vec2 st = gl_FragCoord.xy/u_resolution.xy;
      st.x *= u_resolution.x/u_resolution.y;

      vec3 color = vec3(.0);

      // Cell positions
      vec2 point[5];
      point[0] = vec2(0.83,0.75);
      point[1] = vec2(0.60,0.07);
      point[2] = vec2(0.28,0.64);
      point[3] =  vec2(0.31,0.26);
      point[4] = u_mouse;

      float m_dist = 1.;  // minimum distance

      // Iterate through the points positions
      for (int i = 0; i < 5; i++) {
          float dist = distance(st, point[i]);

          // Keep the closer distance
          m_dist = min(m_dist, dist);
      }

      // Draw the min distance (distance field)
      color += m_dist;

      // Show isolines
      // color -= step(.7,abs(sin(50.0*m_dist)))*.3;

      gl_FragColor = vec4(color,1.0);
    }
  </script>
</head>

<body></body>

<script>
  let camera, scene, renderer;
  let uniforms, mesh;

  init();
  animate();

  function init() {
    scene = new THREE.Scene();

    // 2D perspective camera -- see linked article on top for full explanation of params
    camera = new THREE.Camera();
    camera.position.z = 1;
    /*
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100);
    camera.position.set(0, 0, 1);
    */
    camera.lookAt(scene.position);
    scene.add(camera);

    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setClearColor(0x000000, 1);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.body.appendChild(renderer.domElement);

    uniforms = {
      u_resolution: { type: 'vec2', value: new THREE.Vector2() },
      u_mouse: { type: 'vec2', value: new THREE.Vector2() },
      u_time: { type: 'float', value: 1.0 }
    };

    let vShader = document.getElementById("vertexShader").textContent;
    let fShader = document.getElementById("fragmentShader").textContent;

    let geometry = new THREE.PlaneGeometry(2, 2);

    // give it a material
    let material = new THREE.ShaderMaterial({
      uniforms: uniforms,
      fragmentShader: fShader,
      vertexShader: vShader,
    });

    // and now create the mesh (geom+mat)
    mesh = new THREE.Mesh(geometry, material);
    // mesh.position.set(0, 0, 0);//-1.5, 0.0, 4.0);
    scene.add(mesh);

    onWindowResize();
    window.addEventListener('resize', onWindowResize, false);
    renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
  }

  function animate() {
    requestAnimationFrame(animate);
    render();
  }

  function render() {
    uniforms.u_time.value += 0.05;
    renderer.render(scene, camera);
  }

  function onWindowResize(e) {
    renderer.setSize(window.innerWidth, window.innerHeight);
    uniforms.u_resolution.value.x = renderer.domElement.width;
    uniforms.u_resolution.value.y = renderer.domElement.height;
  }

  function onDocumentMouseMove(e) {
    // uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth) * 2 - 1;//e.pageX / window.innerWidth;
    // uniforms.u_mouse.valye.y = -(e.offsetY / window.innerHeight) * 2 + 1;//e.pageY / window.innerHeight;

    uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth)*2;
    uniforms.u_mouse.value.y = -(e.offsetY / window.innerHeight)*2+1;
  }

</script>
</html>

Answer №1

It's important to keep in mind that your fragment shader coordinates should fall within the range of [0, 1]. This is achieved with the following line:

vec2 st = gl_FragCoord.xy/u_resolution.xy;

However, multiplying everything by 2 introduces an unwanted offset as it extends the range to [0, 2].

// Using incorrect calculation
uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth)*2;
uniforms.u_mouse.value.y = -(e.offsetY / window.innerHeight)*2+1;

To rectify this issue, simply remove the multiplication by 2. Additionally, since y-coordinates are inverted in texture coordinates, you need to adjust using 1 - y:

// Using correct calculation
uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth);
uniforms.u_mouse.value.y = 1-(e.offsetY / window.innerHeight);

Furthermore, considering varying browser window sizes, you also need to account for potential changes in x-coordinate values by using:

st.x *= u_resolution.x/u_resolution.y;

This adjusts the x-range based on the aspect ratio of the screen. Accordingly, update the mouse coordinate calculations with the viewport ratio:

const vpRatio = window.innerWidth / window.innerHeight;

uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth) * vpRatio;
uniforms.u_mouse.value.y = 1-(e.offsetY / window.innerHeight);

By making these adjustments, you can ensure proper alignment between mouse and fragment coordinates. Check out the demo with the necessary modifications

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

Developing a search feature using Ajax in the MVC 6 framework

Embarking on a new project, I have chosen c# .net 6 MVC in VS2022... In my previous projects, this code has run flawlessly. @section Scripts { <script type="text/javascript"> $("#Klijent_Name").autocomplete({ ...

I'm having trouble with emailjs in HTML. Why am I not receiving any emails from emailjs? And how can I successfully send emails using emailjs

How can I successfully send emails using emailjs in an HTML project? Previously, I had no issues sending emails with emailjs in Reactjs, but now in my HTML CSS JavaScript project, it doesn't seem to be working. Could someone assist me with implementin ...

Exploring the capabilities of ECMAScript generators within the Intel XDK Simulator

I am attempting to utilize a generator that has been declared using function* in Intel XDK. The simulate function within XDK is said to be based on Chromium, though it's difficult to determine the specific version ('about' box and similar fe ...

Issue encountered while creating a token in a JavaScript Chrome extension and attempting to validate it on a backend Node.js server

Trying to create a token within a chrome extension and utilizing it to authenticate requests to the backend server has proven challenging. While successfully generating a token on the front end, encountering an error when verifying it with the google-auth- ...

Performing addition in Angular 2 with the help of TypeScript

Here is a code snippet of a component I have written: export class AppComponent { public num1: number = 2; public num2: number = 3; public sum: number = 0; public add() { this.sum = this.num1 + this.num2; } } However, when I r ...

Take away the CSS class from an element after reCAPTCHA verification is complete in Next.js

I'm struggling with removing the CSS class btn-disabled from the input button element upon successful verification of a form entry. I have implemented a function called enableForm to remove the btn-disabled CSS class when reCAPTCHA is verified. Howe ...

Restricting the frequency at which a specific key value is allowed to appear in JSON documents

Within my JSON file, there is an array structured like this: { "commands": [ { "user": "Rusty", "user_id": "83738373", "command_name": "TestCommand", "command_reply": "TestReply" } ] } I have a requirement to restrict the num ...

In Nodejs, the value of req.headers['authorization'] is not defined when using JWT (JSON Web Token)

Below is the implementation of JWT in Node.js: const express = require("express"); const jwt = require("jsonwebtoken"); const app = express(); app.use(express.json()); const user = [ { name: "Rohan", id: 1, }, { name: "Sophie", id ...

Issue: Unable to locate 'path' in directory 'C:workdemo ode_modulesmime-types'

Encountering an error after updating from "react-scripts": "4.0.3" to "react-scripts": "5.0.1",. I need assistance with understanding why this error is occurring and how to resolve it... ERROR in ./node_modules/mime ...

Tips for updating a reactive form with multiple layers of nested JSON values

I am tasked with retrieving and working with the data from a deeply nested (potentially infinite levels) reactive form that includes formArrays, formGroups, and formControls. This data is stored in a database. Currently, my approach involves patching the ...

Maintain consistent dash lengths in LineDashedMaterial even when adjusting line lengths

I've managed to create a dynamic dashed line that changes its position and length over time by adjusting the vertices of its geometry object. Everything works perfectly, except when the line extends or retracts, the dashes also adjust accordingly even ...

Leveraging Variables within my .env Configuration

Does anyone have suggestions on how to set variables in my environment files? Website_Base_URL=https://${websiteId}.dev.net/api In the code, I have: websiteId = 55; and I would like to use config.get('Website_Base_URL'); to retrieve the compl ...

What is the best way to format specific text as bold within an input text field?

I am attempting to make certain text bold within an input text field. However, I'm uncertain about how to achieve this because HTML code is not recognized inside a text field. Therefore, using <b> will not be effective. Is there a way to bold sp ...

provide a promise that resolves to a boolean value

Below is the code I have: const executorFunction = (resolve, reject) => { <script> if ( 1==1){ resolve(true); } else{ resolve(false); } } const myFirstPromise = new Promise(executorFunction); console.log(myFirstPro ...

Adding a sign at the center of a map in React-Leaflet

One of the features I added to the map is a center indicator sign. <MapContainer fullscreenControl={true} center={center} zoom={18} maxNativeZoom = {22} maxZoom={22} classNa ...

Issues have been reported with the functionality of the backbone.js app when switching views, particularly regarding the inconsistent

I am in need of assistance with backbone.js. Whenever a 403 or 401 error occurs, I want to display the login view. Currently, I have attempted the following approach: $(document).on('ready', function(){ app.loadTemplates(['ShellView&apo ...

Tips for resolving CORS error in swagger-ui-express

I'm encountering a "Possible cross-origin (CORS) issue?" error with Spec2 while running this swagger-ui-express application: const express = require('express'); var cors = require('cors'); const app = express(); const swaggerUi = ...

Tips on maintaining the Parent menu in a hovered state when the mouse is over the child menu within a Dropdown Menu

I have been working on creating a dropdown menu that functions correctly. However, I am facing an issue where the top menu, when hovered, turns white, but as soon as I move down to the submenus, the top menu reverts back to its original color. Is there a ...

What is the process for sending a request to a static resource along with parameters?

I currently have a node.js restify server running, along with a folder containing static resources. const restify = require('restify') let server = restify.createServer() server.listen(8080, function () { console.log('%s listening at ...

I am looking to transfer the data stored in a variable within a JavaScript function to a different PHP page

Here is my form featuring the summernote editor: <form class="form-group" action="upload.php" style="width: 700px;" method="post" enctype="multipart/form-data"> <label> Title: </label> <input name="title" class="form-c ...