如何正确地将鼠标坐标传递给 WebGL?
How to correctly pass mouse coordinates to WebGL?
我想将 canvas 鼠标坐标传递给一个函数,该函数以交互方式生成以鼠标坐标为中心的圆。因此,我使用以下函数进行标准化:
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(...);
}
}
我该如何解决这个问题?
假设这在 mousemove
回调事件中被调用并且 canvas
被定义为对 HTML CANVAS
元素的正确引用,鼠标的相对位置指向 canvas space 的指针应该是:
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;
只需将您的 mousemove 事件绑定到 canvas 本身并使用 offsetX and offsetY
var mouseX = (e.offsetX / canvas.clientWidth)*2-1;
var mouseY = ((canvas.clientHeight - e.offsetY) / canvas.clientHeight)*2-1;
请注意,这完全取决于您在着色器中进行的变换。
这其实是一个far more complicated issue than it sounds。您的 canvas 的显示大小是否与其绘图缓冲区相同?你的 canvas 上有边框吗?
假设您的 canvas.
上没有边框或任何填充,这里有一些代码将为您提供 canvas 相对像素坐标
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>
注意这里没有涉及矩阵。如果您使用的是矩阵,那么您已经定义了自己的 space,而不是 WebGL 的 space,后者始终是剪辑 space。在这种情况下,您要么需要乘以矩阵的逆矩阵,然后在 -1 和 +1 之间选择您想要的任何 Z 值。这样,当您的位置乘以着色器中使用的矩阵时,它会将位置反转回正确的 webgl 剪辑 space 坐标。或者,您需要摆脱矩阵或将它们设置为恒等式。
这是一个例子,注意我没有 have/know 你的数学库,所以你必须翻译成你的
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>
另请注意,我故意使 canvas 的显示大小与其绘图缓冲区大小不匹配,以显示数学计算结果。
我想将 canvas 鼠标坐标传递给一个函数,该函数以交互方式生成以鼠标坐标为中心的圆。因此,我使用以下函数进行标准化:
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(...);
}
}
我该如何解决这个问题?
假设这在 mousemove
回调事件中被调用并且 canvas
被定义为对 HTML CANVAS
元素的正确引用,鼠标的相对位置指向 canvas space 的指针应该是:
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;
只需将您的 mousemove 事件绑定到 canvas 本身并使用 offsetX and offsetY
var mouseX = (e.offsetX / canvas.clientWidth)*2-1;
var mouseY = ((canvas.clientHeight - e.offsetY) / canvas.clientHeight)*2-1;
请注意,这完全取决于您在着色器中进行的变换。
这其实是一个far more complicated issue than it sounds。您的 canvas 的显示大小是否与其绘图缓冲区相同?你的 canvas 上有边框吗?
假设您的 canvas.
上没有边框或任何填充,这里有一些代码将为您提供 canvas 相对像素坐标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>
注意这里没有涉及矩阵。如果您使用的是矩阵,那么您已经定义了自己的 space,而不是 WebGL 的 space,后者始终是剪辑 space。在这种情况下,您要么需要乘以矩阵的逆矩阵,然后在 -1 和 +1 之间选择您想要的任何 Z 值。这样,当您的位置乘以着色器中使用的矩阵时,它会将位置反转回正确的 webgl 剪辑 space 坐标。或者,您需要摆脱矩阵或将它们设置为恒等式。
这是一个例子,注意我没有 have/know 你的数学库,所以你必须翻译成你的
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>
另请注意,我故意使 canvas 的显示大小与其绘图缓冲区大小不匹配,以显示数学计算结果。