将3D位置转换为2D屏幕位置 [r69!]

38
我需要Three.js代码将3D对象坐标转换为2D坐标,并在“div”元素中放置文本标签,以便我可以将它们放置在需要的位置(而不会随着3D移动而缩放/移动/旋转)。不幸的是,到目前为止,我看到和尝试过的所有示例似乎都在使用过时的函数/技术。在我的情况下,我相信我正在使用Three.js的r69版本。
这里是一个“旧”的技术示例,对我来说只产生错误: Three.js:将3D位置转换为2D屏幕位置 这里是一段新代码的片段(?),它没有提供足够的上下文让我工作,但看起来更加简洁:

https://github.com/mrdoob/three.js/issues/5533


非常感谢我在第一篇帖子中得到的帮助!对我来说,下面两个解决方案都可以用于我的最终解决方案。然而,在我的情况下,我需要能够动态地分配文本标签(并且分配给多个对象)。我所知道的唯一技术是创建一个“div”,并将文本/位置分配给它,然后将div“document.body.appendChild”。然而,当我在循环中这样做时,这些文本标签会累积--因为它们没有从div中“擦除”。我应该开一个新的案例吗? - Darren Enns
4个回答

49
我已经为我的项目编写了以下函数:它接收一个THREE.Object3D实例和一个相机作为参数,并返回屏幕上的位置。
function toScreenPosition(obj, camera)
{
    var vector = new THREE.Vector3();

    var widthHalf = 0.5*renderer.context.canvas.width;
    var heightHalf = 0.5*renderer.context.canvas.height;

    obj.updateMatrixWorld();
    vector.setFromMatrixPosition(obj.matrixWorld);
    vector.project(camera);

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

    return { 
        x: vector.x,
        y: vector.y
    };

};

然后我创建了一个THREE.Object3D,只是为了保存div的位置(它附着在场景中的一个网格上),当需要时可以使用toScreenPosition函数轻松地将其转换为屏幕位置,并更新div元素的坐标。

var proj = toScreenPosition(divObj, camera);

divElem.style.left = proj.x + 'px';
divElem.style.top = proj.y + 'px';

这里是一个演示的 JSFiddle


这是我第一次使用jsFiddle尝试模拟循环创建两个带有自己标签的对象。然而,只有第二个对象实际上被创建了:( http://jsfiddle.net/darethehair/kgxeuz24/12/ - Darren Enns
1
请纠正我,但我认为如果vector.z < 1,相机朝向物体,如果vector.z > 1,则相机背离物体。这可以在您想要使一些DOM元素出现在three.js 3D对象旁边的情况下使用,并且您不希望该元素出现在相机的“另一侧”(因为投影“穿透”相机平面)。对于演示中的小提琴而言,这是无关紧要的,因为对象始终在我们面前,但是如果我们使用不同的相机控件将其背离,来自小提琴的文本将恰好出现在我们身后。 - nh2
请注意,这里的JSFiddle似乎无法工作,看起来需要更新。 - 10000RubyPools
2
请记住,canvas.widthcanvas.height可能不反映画布的实际宽度和高度,特别是在全屏应用程序中。因此,请改用getBoundingClientRect - Krzysztof Grzybek
renderer.getSize() 对我返回了正确的尺寸。 - Keith Thomas
显示剩余11条评论

29

你可以使用以下模式将3D位置转换为屏幕坐标:

var vector = new THREE.Vector3();
var canvas = renderer.domElement;

vector.set( 1, 2, 3 );

// map to normalized device coordinate (NDC) space
vector.project( camera );

// map to 2D screen space
vector.x = Math.round( (   vector.x + 1 ) * canvas.width  / 2 );
vector.y = Math.round( ( - vector.y + 1 ) * canvas.height / 2 );
vector.z = 0;

three.js r.69


2
这看起来比之前的建议简单得多!但是,我有点困惑:我们要将哪个对象的3D坐标转换为2D坐标? - Darren Enns
3
抱歉,打错字了。问题是关于将3D位置转换为屏幕空间的。 - WestLangley
1
每个对象都有一个位置属性,它是一个Vector3。如果您将此解决方案应用于对象位置,那么您将移动它。但我不认为这有任何实用性... - vals
既然我是新来的,我猜我只能“接受”一个答案?太糟糕了 :( - Darren Enns
@WestLangley等,我在这里发布了:https://dev59.com/k6Xja4cB1Zd3GeqPXNbM ... 很抱歉在这个问题的评论区发问,但我非常需要帮助 >_< - dylnmc
显示剩余8条评论

8

对我而言,这个函数可行(Three.js 版本 69):

function createVector(x, y, z, camera, width, height) {
        var p = new THREE.Vector3(x, y, z);
        var vector = p.project(camera);

        vector.x = (vector.x + 1) / 2 * width;
        vector.y = -(vector.y - 1) / 2 * height;

        return vector;
    }


1
很高兴有另一个建议 :) 看起来与WestLangley上面的非常相似(语法更短)。正如我上面提到的,所有这些技术都没有将文本立即放在我要标记的对象旁边。不确定我需要调整什么才能实现这一点。 - Darren Enns

1
这是对 @meirm 回答的补充代码,其中使用了 .getContext() 在最近版本的 THREE.js 中,并且添加了画布的偏移量,因为有时候画布被嵌入到另一个 UI 中。
const toScreenPosition = function(obj, camera){
    let vector = new THREE.Vector3();
    let widthHalf = 0.5 * renderer.getContext().canvas.width;
    let heightHalf = 0.5 * renderer.getContext().canvas.height;
    obj.updateMatrixWorld();
    vector.setFromMatrixPosition(obj.matrixWorld);
    vector.project(camera);
    vector.x = ( vector.x * widthHalf ) + widthHalf + $(scenecanvas).offset().left;
    vector.y = - ( vector.y * heightHalf ) + heightHalf + $(scenecanvas).offset().top;
    return { 
        x: vector.x,
        y: vector.y
    };
};

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