'How can I pass the mouse positions from JS to shader through a uniform?

I want to pass the mouse position to shader through uniform so that the color changes interactively when the mouse is moved.

Please see the comments with "★" where I suppose are the key codes to my issue.

I'm very new to shader and I'm creating this just for an exercising purpose. What I want to know is how to pass the mouse position from js(I'm using three.js) to shader and how to use it in shader. (I believe it needs to be normalized in order to use the value in r,g and b?)

var container;
var camera, scene, renderer;
var uniforms;

init();
animate();

function init() {
    container = document.getElementById('container');

    camera = new THREE.Camera();
    camera.position.z = 1;

    scene = new THREE.Scene();

    var geometry = new THREE.PlaneBufferGeometry(2, 2);

    uniforms = {

        u_time: { type: "f", value: 1.0 },
        u_resolution: { type: "v2", value: new THREE.Vector2() },
        u_mouse: { type: "v2", value: new THREE.Vector2() }
    };

    var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent
    });

    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio);

    container.appendChild(renderer.domElement);

    onWindowResize();
    window.addEventListener('resize', onWindowResize, false);


}

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 animate() {
    requestAnimationFrame(animate);
    render();
}

//★set the mouse positions to shader
function render() {
    uniforms.u_time.value += 0.05;
    document.onmousemove = function (e) {
        uniforms.u_mouse.x = e.pageX;
        uniforms.u_mouse.y = e.pageY;
    }
    renderer.render(scene, camera);
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script       src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js">     </script>
    <title>Document</title>
  </head>
  <body>
    <div id="container"></div>
    <script id="vertexShader" type="x-shader/x-vertex">
      void main(){
        gl_Position = vec4(position, 1.0);
      }
    </script>
    <script id="fragmentShader" type="x-shader/x-fragment">
      uniform vec2 u_resolution;
      uniform float u_time;
      uniform vec2 u_mouse;


      //★set the mouse positions to the colors    
      void main(){
        vec2 st = gl_FragCoord.xy / u_resolution.xy;
        vec2 um = u_mouse.xy;

        gl_FragColor = vec4(um.x, um.y, 0.0, 0.1);
      } 
    </script>
  </body>
</html>


Solution 1:[1]

The answer to your question is in your question. Can you see the difference between

uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;

and this?

uniforms.u_mouse.x = e.pageX;
uniforms.u_mouse.y = e.pageY;

Three.js uniforms are in the form uniforms.<name>.value.property. You didn't put the 'value' part.

Other issues though, Colors in three.js go from 0 to 1 so just using e.pageX and e.pageY is not going to give you any colors except black in the top left corner of the page and white everywhere else since the values will be 1 or greater everywhere

You could try

uniforms.u_mouse.x = e.pageX / window.innerWidth;
uniforms.u_mouse.y = e.pageY / window.innerHeight;

To maybe give you a more appropriate value. I say maybe because it's not clear in which context you want to change color. Over the window, over the page, over canvas, over some other element, all of those can be different.

One other thing, you don't generally want to assign an event inside your render loop. You assign it once at init time instead.

Here's the code working in a snippet

var container;
var camera, scene, renderer;
var uniforms;

init();
animate();

function init() {
  container = document.getElementById('container');

  camera = new THREE.Camera();
  camera.position.z = 1;

  scene = new THREE.Scene();

  var geometry = new THREE.PlaneBufferGeometry(2, 2);

  uniforms = {

    u_time: {
      type: "f",
      value: 1.0
    },
    u_resolution: {
      type: "v2",
      value: new THREE.Vector2()
    },
    u_mouse: {
      type: "v2",
      value: new THREE.Vector2()
    }
  };

  var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: document.getElementById('vertexShader').textContent,
    fragmentShader: document.getElementById('fragmentShader').textContent
  });

  var mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio(window.devicePixelRatio);

  container.appendChild(renderer.domElement);

  onWindowResize();
  window.addEventListener('resize', onWindowResize, false);


}

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 animate() {
  requestAnimationFrame(animate);
  render();
}

document.onmousemove = function(e) {
  uniforms.u_mouse.value.x = e.pageX / window.innerWidth;
  uniforms.u_mouse.value.y = e.pageY / window.innerHeight;
}


//?set the mouse positions to shader
function render() {
  uniforms.u_time.value += 0.05;
    renderer.render(scene, camera);
  }
body { margin: 0; }
canvas { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js"></script>

<div id="container"></div>

<script id="vertexShader" type="x-shader/x-vertex">
  void main(){
    gl_Position = vec4(position, 1.0);
  }
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
  uniform vec2 u_resolution;
  uniform float u_time;
  uniform vec2 u_mouse;
  //set the mouse positions to the colors
  void main(){
    vec2 st = gl_FragCoord.xy / u_resolution.xy;
    vec2 um = u_mouse.xy;
    gl_FragColor = vec4(um.x, um.y, 0.0, 0.1);
  }
</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Amery