将 3D 位置转换为 2d 屏幕位置 [r69!]
Posted
技术标签:
【中文标题】将 3D 位置转换为 2d 屏幕位置 [r69!]【英文标题】:Converting 3D position to 2d screen position [r69!] 【发布时间】:2015-02-09 02:58:05 【问题描述】:我需要 Three.js 代码将 3D 对象坐标转换为 'div' 元素中的 2d 坐标,以便我可以将文本标签放置在需要的位置(这些标签不会随着 3D 移动而缩放/移动/旋转) .不幸的是,到目前为止我看到和尝试过的所有例子似乎都在使用过时的功能/技术。就我而言,我相信我使用的是 Three.js 的 r69。
这是一个“旧”技术的示例,它只会为我产生错误:
Three.js: converting 3d position to 2d screen position
这是一些较新的代码 (?) 的 sn-p,它没有提供足够的上下文让我开始工作,但看起来更干净:
https://github.com/mrdoob/three.js/issues/5533
【问题讨论】:
感谢我在第一次发帖时收到的帮助!在我看来,下面的 both 解决方案可以适用于我的最终解决方案。但是,就我而言,我需要能够动态地(以及分配给多个对象)分配文本标签。我知道的唯一技术是创建一个'div',并为其分配文本/位置,然后'document.body.appendChild' div。但是,当我在循环中执行此操作时,这些文本标签会累积——因为它们没有从 div 中“删除”。我应该开一个新案子吗? 【参考方案1】:我为我的项目编写了以下函数;它接收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';
Here a fiddle with a demo.
【讨论】:
感谢您提供帮助,特别是因为这是我向这个社区提出的第一个问题 :) 但是,我在您的函数的某些语法/构造方面遇到了问题。您能否澄清需要分配或传递哪些参数才能使其正常工作?看起来它应该需要:(1)div(2)相机(3)对象。我是一个相当新的 javascript 作家,所以任何混乱都会让我误入歧途。我目前正在为 X 和 Y 获得“未定义”,这告诉我我没有正确调用您的函数。 好例子!但是,为了使它更真实,我将对象更改为位于原点之外,即“sphereMesh.position.set(100, 100, 100);”,并且由于某种原因,文本标签没有跟随这个重定位(也没有当我平移/缩放/等时标签“移动”)。由于我是 Three.js 的新手,问题很可能是我做错了。 你能给我一个 jsFiddle 的链接吗?无论如何,在我的示例中,更新 div 位置的函数绑定到相机更改事件,如果您通过其他方式(而不是 OrbitControl)更改位置,您还必须调用此函数来对 div 进行此更改。 如果我错了,请纠正我,但我认为如果vector.z < 1
,相机正在看向物体,如果vector.z > 1
,它正在远离它。这可以用于当您想让一些 DOM 元素出现在 three.js 3D 对象旁边的情况下,并且您不希望该元素出现在相机的“另一侧”(因为投影“穿透”相机平面)。这与演示中的小提琴无关,因为物体总是在我们面前,但如果我们使用不同的凸轮控件将视线从它身上移开,小提琴中的文本将恰好出现在我们身后。
请记住,canvas.width
和 canvas.height
可能无法反映画布的真实宽度和高度,尤其是在全屏应用中。然后改用getBoundingClientRect
。【参考方案2】:
您可以使用如下模式将 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
【讨论】:
这看起来比以前的建议简单得多!但是,我很困惑:引用的对象在哪里——我们希望将其 3D 坐标转换为 2D 坐标? 对不起。错字已修复。问题是关于将 3d 位置转换为屏幕空间。 每个对象都有一个位置属性,它是一个vector3。如果将此解决方案应用于您的对象位置,您将移动它。但我没有看到任何实用程序... 是的,通过提取我的对象 XYZ 坐标,并在您的示例中使用这些值设置“矢量”变量中的坐标,您的代码可以很好地(最终)确定 2D 屏幕位置:)谢谢!但是,除了我提出的问题之外,我看不到这些标签使用 OrbitControls 来“跟随”运动的任何简单方法(就像下面提出的解决方案 #2 一样)。不过,您确实使用最少的代码回答了我的问题 :) 既然我是新来的,我想我只能“接受”一个答案?太糟糕了:(【参考方案3】:对我来说这个功能有效(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;
【讨论】:
很高兴有另一个建议 :) 看起来与上面的 WestLangley 非常相似(语法更短)。不过,正如我上面提到的,所有这些技术都不会将文本立即放在我要标记的对象旁边。不知道我需要调整什么来实现这一点。以上是关于将 3D 位置转换为 2d 屏幕位置 [r69!]的主要内容,如果未能解决你的问题,请参考以下文章
如何在opengl中计算给定3D点及其2D屏幕位置的投影/模型视图矩阵