如何从 xyz 世界坐标获取 xy 屏幕坐标?
How to get xy screen coordinates from xyz world coordinates?
我正在构建一个简单的应用程序,它在屏幕上放置一个标记,位于现实世界中某些地标的顶部,并将标记覆盖在相机的视图上。
我有用于查看设备和世界地标的 latitude/longitude/altitude,我将它们转换为 ECEF 坐标。但是我在 3D 投影数学方面遇到了麻烦。该点似乎总是位于屏幕中间...也许我的缩放比例有误所以它看起来几乎没有从中心移动?
查看设备 GPS 坐标:
GPS:
lat: 45.492132
lon: -122.721062
alt: 124 (meters)
ECEF:
x: -2421034.078421273
y: -3768100.560012433
z: 4525944.676268726
地标 GPS 坐标:
GPS:
lat: 45.499278
lon: -122.708417
alt: 479 (meters)
ECEF:
x: -2420030.781624382
y: -3768367.5284123267
z: 4526754.604333807
我尝试按照 here 中的数学来构建一个函数,从 3D 点坐标获取屏幕坐标。
当我将这些 ECEF 点放入我的投影函数中时,视口为 1440x335,我得到:x: 721, y: 167
这是我的函数:
function projectionCoordinates(origin, destination) {
const relativeX = destination.x - origin.x;
const relativeY = destination.y - origin.y;
const relativeZ = destination.z - origin.z;
const xPerspective = relativeX / relativeZ;
const yPerspective = relativeY / relativeZ;
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
const yNormalized = (yPerspective + viewPort.height / 2) / viewPort.height;
const xRaster = Math.floor(xNormalized * viewPort.width);
const yRaster = Math.floor((1 - yNormalized) * viewPort.height);
return { x: xRaster, y: yRaster };
}
我认为该点应该放在屏幕上更高的位置。我链接的那篇文章提到了我无法遵循的 3x4 矩阵(不确定如何从 3D 点构建 3x4 矩阵)。也许这些很重要,尤其是因为我最终必须考虑设备的倾斜度(用 phone 向上或向下看)。
如果需要,这是我将 latitude/longitude/altitude 坐标转换为 ECEF 的函数(来自另一个 SO 答案的 copy/pasted):
function llaToCartesion({ lat, lon, alt }) {
const cosLat = Math.cos((lat * Math.PI) / 180.0);
const sinLat = Math.sin((lat * Math.PI) / 180.0);
const cosLon = Math.cos((lon * Math.PI) / 180.0);
const sinLon = Math.sin((lon * Math.PI) / 180.0);
const rad = 6378137.0;
const f = 1.0 / 298.257224;
const C =
1.0 / Math.sqrt(cosLat * cosLat + (1 - f) * (1 - f) * sinLat * sinLat);
const S = (1.0 - f) * (1.0 - f) * C;
const h = alt;
const x = (rad * C + h) * cosLat * cosLon;
const y = (rad * C + h) * cosLat * sinLon;
const z = (rad * S + h) * sinLat;
return { x, y, z };
}
您的规范化和光栅化步骤抵消了您需要的视口缩放。乘以这个:
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
给你:
const xNormalized = xPerspective / viewPort.width + 0.5;
并应用这一行:
const xRaster = Math.floor(xNormalized * viewPort.width);
给你:
const xRaster = Math.floor(xPerspective + viewPort.width * 0.5);
您对 xPerspective
的计算是正确的(但请参阅下面的评论)- 但是从您的数字来看,该值将在 1 左右。这就是为什么该点靠近屏幕中心的原因。
正确的做法是:
const xRaster = Math.floor(xPerspective * viewPort.width /2 + viewPort.width /2);
你可以简化它。这个想法是 xPerspective
是 xRelative
对着眼睛的角度的 tan
。将 tan
乘以屏幕宽度的一半可以得出距屏幕中心的 x 距离。然后添加屏幕中心的 x 位置以获得屏幕坐标。
你的数学使用与 x、y、z 轴对齐的隐式相机视图。要移动视图,您需要在执行透视划分步骤(除以 zRelative
之前)相对于相机计算 xRelative
等。一种简单的方法是将您的相机表示为 3 个向量,它们是相机视图的 X、Y、Z。然后,您通过对向量 [xRelative, yRelative, zRelative]
与 X、Y 和 Z 中的每一个进行点积来计算 3D 点在相机上的投影。这将为您提供一个新的 [xCamera, yCamera, zCamera]
,它会随着您的变化而变化移动你的相机。您也可以使用矩阵执行此操作。
我正在构建一个简单的应用程序,它在屏幕上放置一个标记,位于现实世界中某些地标的顶部,并将标记覆盖在相机的视图上。 我有用于查看设备和世界地标的 latitude/longitude/altitude,我将它们转换为 ECEF 坐标。但是我在 3D 投影数学方面遇到了麻烦。该点似乎总是位于屏幕中间...也许我的缩放比例有误所以它看起来几乎没有从中心移动?
查看设备 GPS 坐标:
GPS:
lat: 45.492132
lon: -122.721062
alt: 124 (meters)
ECEF:
x: -2421034.078421273
y: -3768100.560012433
z: 4525944.676268726
地标 GPS 坐标:
GPS:
lat: 45.499278
lon: -122.708417
alt: 479 (meters)
ECEF:
x: -2420030.781624382
y: -3768367.5284123267
z: 4526754.604333807
我尝试按照 here 中的数学来构建一个函数,从 3D 点坐标获取屏幕坐标。
当我将这些 ECEF 点放入我的投影函数中时,视口为 1440x335,我得到:x: 721, y: 167
这是我的函数:
function projectionCoordinates(origin, destination) {
const relativeX = destination.x - origin.x;
const relativeY = destination.y - origin.y;
const relativeZ = destination.z - origin.z;
const xPerspective = relativeX / relativeZ;
const yPerspective = relativeY / relativeZ;
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
const yNormalized = (yPerspective + viewPort.height / 2) / viewPort.height;
const xRaster = Math.floor(xNormalized * viewPort.width);
const yRaster = Math.floor((1 - yNormalized) * viewPort.height);
return { x: xRaster, y: yRaster };
}
我认为该点应该放在屏幕上更高的位置。我链接的那篇文章提到了我无法遵循的 3x4 矩阵(不确定如何从 3D 点构建 3x4 矩阵)。也许这些很重要,尤其是因为我最终必须考虑设备的倾斜度(用 phone 向上或向下看)。
如果需要,这是我将 latitude/longitude/altitude 坐标转换为 ECEF 的函数(来自另一个 SO 答案的 copy/pasted):
function llaToCartesion({ lat, lon, alt }) {
const cosLat = Math.cos((lat * Math.PI) / 180.0);
const sinLat = Math.sin((lat * Math.PI) / 180.0);
const cosLon = Math.cos((lon * Math.PI) / 180.0);
const sinLon = Math.sin((lon * Math.PI) / 180.0);
const rad = 6378137.0;
const f = 1.0 / 298.257224;
const C =
1.0 / Math.sqrt(cosLat * cosLat + (1 - f) * (1 - f) * sinLat * sinLat);
const S = (1.0 - f) * (1.0 - f) * C;
const h = alt;
const x = (rad * C + h) * cosLat * cosLon;
const y = (rad * C + h) * cosLat * sinLon;
const z = (rad * S + h) * sinLat;
return { x, y, z };
}
您的规范化和光栅化步骤抵消了您需要的视口缩放。乘以这个:
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
给你:
const xNormalized = xPerspective / viewPort.width + 0.5;
并应用这一行:
const xRaster = Math.floor(xNormalized * viewPort.width);
给你:
const xRaster = Math.floor(xPerspective + viewPort.width * 0.5);
您对 xPerspective
的计算是正确的(但请参阅下面的评论)- 但是从您的数字来看,该值将在 1 左右。这就是为什么该点靠近屏幕中心的原因。
正确的做法是:
const xRaster = Math.floor(xPerspective * viewPort.width /2 + viewPort.width /2);
你可以简化它。这个想法是 xPerspective
是 xRelative
对着眼睛的角度的 tan
。将 tan
乘以屏幕宽度的一半可以得出距屏幕中心的 x 距离。然后添加屏幕中心的 x 位置以获得屏幕坐标。
你的数学使用与 x、y、z 轴对齐的隐式相机视图。要移动视图,您需要在执行透视划分步骤(除以 zRelative
之前)相对于相机计算 xRelative
等。一种简单的方法是将您的相机表示为 3 个向量,它们是相机视图的 X、Y、Z。然后,您通过对向量 [xRelative, yRelative, zRelative]
与 X、Y 和 Z 中的每一个进行点积来计算 3D 点在相机上的投影。这将为您提供一个新的 [xCamera, yCamera, zCamera]
,它会随着您的变化而变化移动你的相机。您也可以使用矩阵执行此操作。