尽管提供的答案在某些情况下可能很有用,但我几乎无法想象那些情况(可能是游戏或动画),因为它们根本不精确(猜测目标的NDC z?)。如果知道目标z平面,则不能使用这些方法将屏幕坐标投影到世界坐标。但是对于大多数情况,您应该知道这架飞机。
例如,如果您按中心(模型空间中的已知点)和半径绘制球体-您需要将半径作为未投影的鼠标坐标的增量-但是您不能!出于所有应有的尊重,使用TargetZ的@WestLangley方法不起作用,它给出了错误的结果(如果需要,我可以提供jsfiddle)。另一个示例-您需要通过双击鼠标来设置轨道控制目标,但不能对场景对象进行“真实的”光线投射(当您没有其他选择时)。
对我来说,解决方案是仅在沿z轴的目标点中创建虚拟平面,然后在该平面上使用光线投射。目标点可以是当前轨道控制的目标,也可以是您需要在现有模型空间等中逐步绘制的对象的顶点。这非常有效,而且很简单(打字稿中的示例):
screenToWorld(v2D: THREE.Vector2, camera: THREE.PerspectiveCamera = null, target: THREE.Vector3 = null): THREE.Vector3 {
const self = this;
const vNdc = self.toNdc(v2D);
return self.ndcToWorld(vNdc, camera, target);
}
//get normalized device cartesian coordinates (NDC) with center (0, 0) and ranging from (-1, -1) to (1, 1)
toNdc(v: THREE.Vector2): THREE.Vector2 {
const self = this;
const canvasEl = self.renderers.WebGL.domElement;
const bounds = canvasEl.getBoundingClientRect();
let x = v.x - bounds.left;
let y = v.y - bounds.top;
x = (x / bounds.width) * 2 - 1;
y = - (y / bounds.height) * 2 + 1;
return new THREE.Vector2(x, y);
}
ndcToWorld(vNdc: THREE.Vector2, camera: THREE.PerspectiveCamera = null, target: THREE.Vector3 = null): THREE.Vector3 {
const self = this;
if (!camera) {
camera = self.camera;
}
if (!target) {
target = self.getTarget();
}
const position = camera.position.clone();
const origin = self.scene.position.clone();
const v3D = target.clone();
self.raycaster.setFromCamera(vNdc, camera);
const normal = new THREE.Vector3(0, 0, 1);
const distance = normal.dot(origin.sub(v3D));
const plane = new THREE.Plane(normal, distance);
self.raycaster.ray.intersectPlane(plane, v3D);
return v3D;
}