如何正确将鼠标坐标传递给WebGL?

3

我希望将canvas鼠标坐标传递给一个函数,该函数与鼠标坐标交互生成以鼠标坐标为中心的圆。因此,我正在使用以下函数进行归一化:

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

然而,这只适用于屏幕中心。当鼠标移动时,光标不再位于圆形的中心: 在这里查看图片 鼠标光标距离屏幕中心越远,它就越偏离圆心。 以下是相关代码: 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(...);
    }
}

我该如何解决这个问题?

3
你可能需要发布一些代码。你提到了一个视图矩阵。如果在某个评论中你有一个视图矩阵,那么你不是在WebGL的剪裁空间内工作。相反,你正在使用另一个你定义的坐标系,我们无法帮助你将鼠标坐标转换到该空间,除非你展示代码。 - gman
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Muad
更新了我的答案。 - gman
3个回答

13
这实际上是一个比听起来更加复杂的问题。你的画布显示大小和绘图缓冲区大小一样吗?你的画布有边框吗?
以下是一些代码,假设你的画布没有边框或任何填充,它将为您提供相对于画布的像素坐标。
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 值是怎么回事呢?我的圆是二维的,当将 z 传递为 0 时也可以工作。此外,这行代码 const view = m4.inverse(camera); 似乎对输出没有任何影响。您能否再次简单地解释一下呢? :) - Muad
选择一个Z是为了3D。您需要决定物体在世界中距离相机有多远,以便您可以计算出正确的世界X和Y,使得您的对象将出现在鼠标下方。const view = m4.inverse(camera);创建一个视图矩阵,因为我的m4.lookAt创建了一个相机矩阵。您的数学库的lookAt函数可能返回一个视图矩阵,而不是相机矩阵。我发现我的lookAt函数更有用。请参见此 - gman

1

只需将mousemove事件绑定到画布本身,然后使用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表示。我只想让鼠标光标始终位于圆形中心。 - Muad
在我看来,你的图像看起来像是像素着色器的东西,如果不发布实际使用坐标的像素着色器,很难确定问题出在哪里。 - LJᛃ

0
假设这是在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坐标... - Muad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接