【问题标题】:How to correctly pass mouse coordinates to WebGL?如何正确地将鼠标坐标传递给 WebGL?
【发布时间】:2017-02-18 00:38:02
【问题描述】:

我想将画布鼠标坐标传递给一个以鼠标坐标为中心以交互方式生成圆的函数。因此,我使用以下函数进行归一化:

var mousePositionX = (2*ev.clientX/canvas.width) - 1;
var mousePositionY = (2*ev.clientY/(canvas.height*-1)) + 1;

但是,这仅适用于屏幕中心。当围绕光标移动鼠标时,不再位于圆的中心: see the picture here

鼠标光标离屏幕中心越远,它离圆心的位置就越大。 以下是一些相关代码:

HTML

  body {
    border: 0;
    margin: 0;
  }
  /* make the canvas the size of the viewport */
  canvas {
    width: 100vw;
    height: 100vh;
    display: block;
  }
  ...
  <body onLoad="main()">
        <canvas id="glContext"></canvas>
  </body>

着色器

<script id="vShaderCircle" type="notjs">
    attribute vec4 a_position;
    uniform mat4 u_viewMatrix;

    void main(){
        gl_Position = u_viewMatrix * a_position;
    }
</script>

JS

function main(){

    // PREPARING CANVAS AND WEBGL-CONTEXT
    var canvas = document.getElementById("glContext");
    var gl_Original = initWebGL(canvas);
    var gl = WebGLDebugUtils.makeDebugContext(gl_Original);

    resize(canvas);
    gl.viewport(0, 0, canvas.width, canvas.height);
    // ----------------------------------
    ...
    // MATRIX SETUP
    var viewMatrix = new Matrix4();
      viewMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
      viewMatrix.lookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
    // ----------------------------------
    canvas.addEventListener("mousemove", function(){stencilTest(event)});

    function stencilTest(ev){
        var mousePositionX = (2*ev.clientX/canvas.width) - 1;
        var mousePositionY = (2*ev.clientY/(canvas.height*(-1))) + 1;
        ...
        ...
        drawCircle(..., mousePositionX, mousePositionY, viewMatrix);
        ...
        drawCube(...);
    }
}

我该如何解决这个问题?

【问题讨论】:

  • 您可能需要发布一些代码。你提到你有一个视图矩阵。如果您在某些评论中有一个视图矩阵,那么您就没有在 WebGL 的坐标系(即剪辑空间)中工作。相反,您正在使用您定义的其他坐标系,我们无法帮助您将鼠标坐标转换到该空间,除非您向我们展示 codez
  • @gman 所以我现在提供了更多代码。你能从中找出问题所在吗?各个函数(例如矩阵函数)做他们应该做的事情。我也尝试了您的getRelativeMousePosition() 功能,但没有成功...
  • 更新了我的答案

标签: javascript webgl coordinate-systems


【解决方案1】:

这实际上是一个far more complicated issue than it sounds。您的画布的显示大小是否与其绘图缓冲区相同?你的画布上有边框吗?

假设您的画布上没有边框或任何填充,这里有一些代码将为您提供画布相对像素坐标。

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  var rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  var pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / target.clientWidth;
  pos.y = pos.y * target.height / target.clientHeight;

  return pos;  
}

将其转换为 WebGL 坐标

  var pos = getRelativeMousePosition(event, target);
  const x = pos.x / gl.canvas.width  *  2 - 1;
  const y = pos.y / gl.canvas.height * -2 + 1;

工作示例:

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  var rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  var pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / target.clientWidth;
  pos.y = pos.y * target.height / target.clientHeight;

  return pos;  
}

const vs = `
attribute vec4 position;
void main() {
  gl_Position = position;
  gl_PointSize = 20.;
}
`;
const fs = `
void main() {
  gl_FragColor = vec4(1,0,1,1);
}
`;
const gl = document.querySelector("canvas").getContext("webgl");
// compiles and links shaders and assigns position to location 
const program = twgl.createProgramFromSources(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, "position");

window.addEventListener('mousemove', e => {

  const pos = getNoPaddingNoBorderCanvasRelativeMousePosition(e, gl.canvas);

  // pos is in pixel coordinates for the canvas.
  // so convert to WebGL clip space coordinates
  const x = pos.x / gl.canvas.width  *  2 - 1;
  const y = pos.y / gl.canvas.height * -2 + 1;

  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.useProgram(program);
  // only drawing a single point so no need to use a buffer
  gl.vertexAttrib2f(positionLoc, x, y);
  gl.drawArrays(gl.POINTS, 0, 1);
});
canvas { 
  display: block;
  width: 400px;
  height: 100px;
}
div {
  display: inline-block;
  border: 1px solid black;
}
<div><canvas></canvas></div>
<p>move the mouse over the canvas</p>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

请注意,不涉及任何矩阵。如果您使用的是矩阵,那么您已经定义了自己的空间,而不是 WebGL 的空间,它始终是剪辑空间。在这种情况下,您要么需要乘以矩阵的倒数,然后在 -1 和 +1 之间选择所需的 Z 值。这样,当您的位置乘以着色器中使用的矩阵时,它会将位置反转回正确的 webgl 剪辑空间坐标。或者,您需要摆脱矩阵或将它们设置为恒等式。

这是一个例子,注意我没有/不知道你的数学库,所以你必须翻译成你的

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  var rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  var pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / target.clientWidth;
  pos.y = pos.y * target.height / target.clientHeight;

  return pos;  
}

const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = matrix * position;
}
`;
const fs = `
void main() {
  gl_FragColor = vec4(1,0,0,1);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createSphereBufferInfo(gl, .5, 12, 8);

window.addEventListener('mousemove', e => {

  const pos = getNoPaddingNoBorderCanvasRelativeMousePosition(e, gl.canvas);

  // pos is in pixel coordinates for the canvas.
  // so convert to WebGL clip space coordinates
  const x = pos.x / gl.canvas.width  *  2 - 1;
  const y = pos.y / gl.canvas.height * -2 + 1;
  
  // use a projection and view matrix
  const projection = m4.perspective(
     30 * Math.PI / 180, 
     gl.canvas.clientWidth / gl.canvas.clientHeight, 
     1, 
     100);
  const camera = m4.lookAt([0, 0, 15], [0, 0, 0], [0, 1, 0]);
  const view = m4.inverse(camera);
  const viewProjection = m4.multiply(projection, view);
  
  // pick a clipsace Z value between -1 and 1 
  // we'll zNear to zFar and convert back to clip space
  const viewZ = -5;  // 5 units back from the camera
  const clip = m4.transformPoint(projection, [0, 0, viewZ]);
  const z = clip[2];
  
  // compute the world space position needed to put the sphere
  // under the cursor at this clipspace position
  const inverseViewProjection = m4.inverse(viewProjection);
  const worldPos = m4.transformPoint(inverseViewProjection, [x, y, z]);

  // add that world position to our matrix
  const mat = m4.translate(viewProjection, worldPos);

  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.useProgram(programInfo.program);
  
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, {
    matrix: mat,
  });
  gl.drawElements(gl.LINES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
});
canvas { 
  display: block;
  width: 400px;
  height: 100px;
}
div {
  display: inline-block;
  border: 1px solid black;
}
<div><canvas></canvas></div>
<p>move the mouse over the canvas</p>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

另请注意,我故意使画布的显示大小与其绘图缓冲区大小不匹配以显示数学作品。

【讨论】:

  • 就是这样,谢谢!与您的代码一起使用,尽管我仍然不明白为什么这样做(我对所有矩阵数学的东西还是陌生的......)。据我了解,我的鼠标坐标是在剪辑空间坐标中传递的,但它们必须在世界空间中,对吗?所以我们将通过反转投影和视图矩阵将它们转换回来。但是那个 z-Value 是怎么回事呢?我的圈子是二维的,当将 0 作为z 传递时它也可以工作。此外,这一行const view = m4.inverse(camera); 似乎对输出没有任何影响。你能再解释一下吗? :)
  • 选择 Z 用于 3d。您需要确定您希望对象在世界中的距离相机多远,以便您可以计算正确的世界 X 和 Y,以便您的对象出现在鼠标下方。 const view = m4.inverse(camera); 制作了一个视图矩阵,因为我的 m4.lookAt 制作了一个相机矩阵。您的数学库的 lookAt 函数可能返回视图矩阵,而不是相机矩阵。我发现我的 lookAt 函数更有用。 See this
【解决方案2】:

只需将鼠标移动事件绑定到画布本身并使用offsetXoffsetY

var mouseX = (e.offsetX / canvas.clientWidth)*2-1;
var mouseY = ((canvas.clientHeight - e.offsetY) / canvas.clientHeight)*2-1;

请注意,这一切都取决于您在着色器中执行的转换。

【讨论】:

  • 不幸的是,这与我的版本具有相同的效果(至少在 Chrome 中;无法尝试其他浏览器)。在着色器中,我只是将顶点乘以视图矩阵(眼睛位置:0、0、5 | 看:0、0、0),因此它不应该影响 x/y 表示。我只想让鼠标光标始终位于圆圈的中心。
  • 对我来说,您的图像看起来像是像素着色器的东西,如果没有在实际使用坐标的地方发布像素着色器,很难分辨出什么问题。
【解决方案3】:

假设这是在 mousemove 回调事件中调用的,并且 canvas 被定义为对 HTML CANVAS 元素的正确引用,那么鼠标指针到画布空间的相对位置应该是:

var rect = gl.canvas.getBoundingClientRect();
var mousePositionX = ev.clientX - rect.left;
var mousePositionY = ev.clientY - rect.top;

从像素坐标转换为 WebGL 坐标系:

var rect = gl.canvas.getBoundingClientRect();
var x = (ev.clientX - rect.left) / canvas.width *  2 - 1;
var y = (ev.clientY - rect.top) / canvas.height * -2 + 1;

【讨论】:

  • 我试过了,但它不返回 WebGL 坐标系 [-1 -- 1] 中的坐标,而只返回像素坐标。我必须将鼠标的像素屏幕坐标转换为 WebGL 坐标...
猜你喜欢
  • 2019-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-11
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
相关资源
最近更新 更多