Following up from this question, I'm trying to get my mouse cursor to update a Voronoi shader from the Book of Shaders.
However, I think I might be misunderstanding something with the offsets. As it stands, my mouse is slightly offset to the right with my current code (see below).
I've attempted offsetting from the bounding rectangle of my renderer.domElement
, but the top
/left
values were 0 and had no impact. Simply trying e.pageX / window.innerWidth; e.pageY / window.innerHeight;
left the y-position mirrored and significantly off.
I also tried the fairly standard mapping to [-1,1] with offsetX
and whatnot (commented) and that was actually worse off. Also attempted using unproject
from another SO post I found, but that didn't appear to have any effect either.
Here's a link to a jsfiddle with the same issues I'm seeing: https://jsfiddle.net/9y8hge3t/1/
And the full code for historical purposes:
<!--
* 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>
Something worth understanding is that your fragment shader coordinates want to be in the [0, 1]
range. That's what you're doing with this line:
vec2 st = gl_FragCoord.xy/u_resolution.xy;
Now, I'm not sure why you're multiplying everything by 2. This gives you [0, 2]
range, which is the source of your unwanted offset.
// Wrong approach
uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth)*2;
uniforms.u_mouse.value.y = -(e.offsetY / window.innerHeight)*2+1;
Instead, just remove the *2. Since y is inverted in texture coordinates, you'll have to do 1 - y
:
// Correct approach
uniforms.u_mouse.value.x = (e.offsetX / window.innerWidth);
uniforms.u_mouse.value.y = 1-(e.offsetY / window.innerHeight);
Additionally, since the browser window can narrow or widen upon resize, you're adjusting the x-coordinate in the shader with:
st.x *= u_resolution.x/u_resolution.y;
This could give you greater than 1 x-range if it's a wide landscape ratio, or less than 1 if it's a narrow portrait ratio. So you'll have to account for this also in your mouse coordinates values:
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);
And there you go! Matching mouse / fragment coordinates! Here's your demo with the necessary adjustments