在Three.js中使用投影将世界坐标转换为屏幕坐标

46

有几个关于在Three.js中反投影的优秀堆栈问题(12),即如何将浏览器中的(x,y)鼠标坐标转换为Three.js画布空间中的(x,y,z)坐标。大多数情况下,它们遵循以下模式:

    var elem = renderer.domElement, 
        boundingRect = elem.getBoundingClientRect(),
        x = (event.clientX - boundingRect.left) * (elem.width / boundingRect.width),
        y = (event.clientY - boundingRect.top) * (elem.height / boundingRect.height);

    var vector = new THREE.Vector3( 
        ( x / WIDTH ) * 2 - 1, 
        - ( y / HEIGHT ) * 2 + 1, 
        0.5 
    );

    projector.unprojectVector( vector, camera );
    var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
    var intersects = ray.intersectObjects( scene.children );

我一直在尝试做相反的事情 - 而不是从“屏幕到世界”空间,而是从“世界到屏幕”空间。如果我知道Three.js中对象的位置,如何确定它在屏幕上的位置?
似乎没有任何已发布的解决此问题的方法。这里有一个关于此问题的另一个问题,但作者声称已经用一个对我无效的函数解决了这个问题。他们的解决方案不使用投影光线,并且我很确定,由于2D到3D使用了unprojectVector(),所以3D到2D的解决方案将需要projectVector()。
还有这个Github上的问题
感谢任何帮助。

非常感谢您提供的“屏幕到世界”翻译模式。这对我来说是一个漫长而痛苦的过程,但现在终于解决了。 - lephleg
4个回答

41

尝试使用以下代码:

var width = 640, height = 480;
var widthHalf = width / 2, heightHalf = height / 2;

var vector = new THREE.Vector3();
var projector = new THREE.Projector();
projector.projectVector( vector.setFromMatrixPosition( object.matrixWorld ), camera );

vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;

1
另一个Stack的操作在其问题中发布了此链接,其中也有解决方案。再次感谢!https://github.com/mrdoob/three.js/issues/78 - BishopZ
2
getPosition()已被弃用。请改用以下方法: var vector = projector.projectVector(new THREE.Vector3().getPositionFromMatrix(object.matrixWorld), camera); - imbrizi
看起来 vector.getPositionFromMatrix() 现在被称为 vector.setFromMatrixPosition()。 http://threejs.org/docs/index.html#Reference/Math/Vector3 - stephband
固定答案。谢谢! - mrdoob
"对象未定义" - João Vilaça
显示剩余4条评论

36

对于现代的Three.js(r75),可以使用以下方式将向量投影到屏幕上:

var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;

var pos = object.position.clone();
pos.project(camera);
pos.x = ( pos.x * widthHalf ) + widthHalf;
pos.y = - ( pos.y * heightHalf ) + heightHalf;

7
请注意,如果对象是组的一部分,则 object.position 可能不是您需要的内容,因为 object.position 是组内的本地位置。相反,使用 object.getWorldPosition()(或像其他答案中所示的 pos.setFromMatrixPosition())将考虑到在组内的相对定位。 - tangle
3
假设画布大小与窗口大小相同,因此应该使用画布宽度的一半和画布高度的一半... - duhaime
3
如果您想准确地获得位置,而不考虑包含画布的容器(可能不是整个窗口)的位置和大小,那么您需要基于 canvas.getBoundingClientRect() 进行这些计算,而不是基于 window - BallpointBen

17

对于所有收到deprecatedwarnings日志的人,被接受的答案适用于较旧的Three.js版本。现在使用以下方式更加容易:

let pos = new THREE.Vector3();
pos = pos.setFromMatrixPosition(object.matrixWorld);
pos.project(camera);

let widthHalf = canvasWidth / 2;
let heightHalf = canvasHeight / 2;

pos.x = (pos.x * widthHalf) + widthHalf;
pos.y = - (pos.y * heightHalf) + heightHalf;
pos.z = 0;

console.log(pos);

"object" 未定义。 - João Vilaça
1
@JoãoVilaça object 应该是你想要转换为屏幕坐标的 THREE.Object3D 实例(它会获取该对象的位置)。 - Ferrybig
1
这很不错,但是当物体在相机后面时,pos.project() 返回的值好像物体围绕相机旋转了180度。 - Jespertheend
@Jespertheend,你知道这个问题的解决方案吗? - Marcel Ennix
@MarcelEnnix 你可以检查 pos.z 是否在 01 之间,并在不在此范围内时假定对象已离屏。理想情况下,当物体在相机后面时,我希望2D位置趋近于无穷大,但我还没有想出如何实现这一点。 - Jespertheend
1
@Jespertheend 谢谢你的回答。我通过从相机到物体创建一个方向向量,并将其与相机的lookAt向量进行比较来解决了它。你可以在这里看到它的效果(浏览器 MMO):https://g13.ennix.io - Marcel Ennix

1

这些答案都没能解决我的问题,但它们非常接近,所以我进一步调查了一下,并结合了那些答案的一些代码和这篇文章,最终使用以下代码片段使其正常工作。

const vector = new THREE.Vector3();
const canvas = renderer.domElement; // `renderer` is a THREE.WebGLRenderer

obj.updateMatrixWorld();  // `obj´ is a THREE.Object3D
vector.setFromMatrixPosition(obj.matrixWorld);

vector.project(camera); // `camera` is a THREE.PerspectiveCamera

const x = Math.round((0.5 + vector.x / 2) * (canvas.width / window.devicePixelRatio));
const y = Math.round((0.5 - vector.y / 2) * (canvas.height / window.devicePixelRatio));

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