首页 > 解决方案 > WebGL 2D Canvas Point 转换为 3D 世界点

问题描述

我的代码有问题,或者我期望错误的值。我想要实现的是将 2D 点从画布转换为 3D 世界空间点。

如果我理解正确,无论相机旋转如何,我都应该始终获得相同的点,因为我不想要一个 VIEW SPACE 而是一个 WORLD SPACE 点。所以想象一下,我在 X 轴和 Z 轴之间点击立方体的面对墙,然后我认为我应该得到恒定的 Y 值并且它工作正常,直到我对我的相机进行一些旋转更改。如果我们让相机以某个角度看向那面墙上,那么每次点击都会获得不同的 Y 轴值,因为该面墙上的每个点都在相同的 Y 位置上,所以这个值应该是恒定的。

在此处输入图像描述

var r = canvas.getBoundingClientRect();
var x = clientX - r.left;
var y = height - (clientY - r.top);

var projectionMatrix = matrix4.perspective(fov , ratio, near, far);
// convert to clip space
var xClipSpace = x / width * 2.0 - 1.0;
var yClipSpace = y / height * -2.0 + 1.0;
var zClipSpace = 1;
// convert back from clip space to world space
var xyzVec3 = vector3.create(xClipSpace ,yClipSpace ,zClipSpace);
var transfrom = matrix4.multiply(projectionMatrix,viewMatrix);
var inverse = matrix4.invert(transform);
var result = vector3.transformMat4(xyzVec3,inverse);

我做错了什么?

标签: javascriptmatrix3dwebgl

解决方案


我不是 100% 确定我理解你的图表,但否则你的代码看起来不错。

在大多数 WebGL 数学中,平截头体在远处是 -Z。当然,您可以根据视图旋转它。但是在任何情况下,如果您通过 (projection * view) 矩阵的逆矩阵传递剪辑空间 [x, y, -1],那么您将获得视锥体远平面的某个点。随着视图的旋转,该点随平截头体移动。

如果我们让相机以某个角度看向那面墙上,那么每次点击都会获得不同的 Y 轴值,因为该面墙上的每个点都在相同的 Y 位置上,所以这个值应该是恒定的。

否:如果您旋转相机,整个墙都会旋转,因此上面的点也会旋转。

这是一张俯视世界空间视锥体顶部的图表。视图在旋转。如果 clipX 和 clipY 为 0,则计算的点位于截锥体的远平面的中心(剪辑空间中的 Z = 1)。即使它停留在平面上,您也可以看到该点在旋转。它在视图空间中的位置不会改变,但它在世界空间中的位置会发生变化,因为整个视锥体正在有效地旋转。

当然,如果你旋转相机,你会得到不同的 Y 值。

const v3 = twgl.v3;
const m4 = twgl.m4;
const ctx = document.querySelector('canvas').getContext('2d');

const boxTop = [
  [-1,  1, -1],
  [-1,  1,  1],
  [ 1,  1,  1],
  [ 1,  1, -1],
];

function render(time) {
  time *= 0.001;
  
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  const fov = 60 * Math.PI / 180;
  const ratio = ctx.canvas.clientWidth / ctx.canvas.clientHeight;
  const near = 10;
  const far = 40;
  const projectionMatrix = m4.perspective(fov , ratio, near, far);
  const viewMatrix = m4.rotationY(time);
  // convert to clip space
  const xClipSpace = 0;
  const yClipSpace = 0;
  const zClipSpace = 1;
  // convert back from clip space to world space
  const xyzVec3 = v3.create(xClipSpace ,yClipSpace ,zClipSpace);
  const transform = m4.multiply(projectionMatrix, viewMatrix);
  const inverse = m4.inverse(transform);
  const result = m4.transformPoint(inverse, xyzVec3);
  
  // -------------

  ctx.setTransform(1, 0, 0, 1, 150.5, 75.5);
  
  // draw origin
  ctx.beginPath();
  for (let i = -200; i <= 200; i += 20) { 
    ctx.moveTo(-400, i);
    ctx.lineTo( 400, i);
    ctx.moveTo(i, -400);
    ctx.lineTo(i,  400);
  }
  ctx.strokeStyle = '#DDD';
  ctx.stroke();
  
  ctx.beginPath();
  ctx.moveTo(-400, 0);
  ctx.lineTo( 400, 0);
  ctx.moveTo(0, -400);
  ctx.lineTo(0,  400);
  ctx.strokeStyle = '#444';
  ctx.stroke();
  
  ctx.fillStyle = '#888';
  ctx.fillText('x', 140, 10);
  ctx.fillText('z', 5, -65);
  
  // draw frustum
  ctx.beginPath();
  for (let i = 0; i < 4; ++i) {
    const v0 = m4.transformPoint(inverse, boxTop[i]);
    const v1 = m4.transformPoint(inverse, boxTop[(i + 1) % 4]);
    drawLine(ctx, v0, v1);
  }
  ctx.strokeStyle = 'black';
  ctx.stroke();
  
  {
    ctx.beginPath();
    ctx.arc(result[0], result[2], 3, 0, Math.PI * 2);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.fillText(`${result[0].toFixed(2)}, ${result[2].toFixed(2)}`, result[0] + 5, result[2] + 3);
  }
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);


function drawLine(ctx, v0, v1) {
  ctx.moveTo(v0[0], v0[2]);
  ctx.lineTo(v1[0], v1[2]);
}

render();
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.js"></script>


推荐阅读