Three.js - 如何使用up向量与lookAt()函数一起工作?

17

我正在尝试理解在three.js中如何使用up向量和lookAt()函数。我设置了axisHelper的up向量,使得Y轴始终指向目标geo的位置,即标记up向量的位置。对于X和Y轴,它按照预期围绕Z轴旋转轴线;当我尝试调整up向量的Z值时,我期望轴线会围绕X轴旋转,但是却没有任何反应。

http://jsfiddle.net/68p5r/4/ [编辑:我添加了geo以展示up向量的位置。]

我有一个dat.gui界面来演示up向量的操作,但是即使我手动设置向量,问题仍然存在。

我怀疑问题出现在第74行附近:

zControl.onChange(function(value) {
  axes.up.set(this.object.x, this.object.y, value);
  axes.lookAt(new THREE.Vector3(0, 0, 1));
});
当我更新上向量时,我指示axisHelper通过沿着其Z轴重新执行lookAt()来更新其在屏幕上的方向。更改X和Y的工作效果符合预期,为什么Z不行?
(如果我使用geo而不是axisHelper,情况也是如此:http://jsfiddle.net/68p5r/5/rotated axisHelper

  1. 在three.js中,轴向量应该始终具有单位长度;请确保调用axis.normalize()
  2. 为什么要更改“up”向量?通常情况下不需要这样做。请修改您的帖子并解释原因。
  3. 您想实现什么目标?请在您的帖子中进行解释。
- WestLangley
我只是想了解上向量和lookAt()如何一起工作。我会澄清问题。 - meetar
4个回答

15
当你调用 Object.lookAt( vector ) 方法时,物体会被旋转以使其内部 z 轴指向目标 vector
但这还不足以确定物体的方向,因为物体本身仍然可以绕其z轴“旋转”。
因此,该物体随后会“旋转”,以使其内部y轴在其内部z轴和up向量的平面上。
目标向量和up向量在一起足以唯一确定物体的方向。
three.js r.63
提示:在three.js中,轴应始终具有单位长度;请确保在代码中调用axis.normalize()。

1
谢谢,我理解了向上矢量和lookAt()的工作原理,这就是为什么我对我在问题描述中所描述的问题感到困惑,正如链接的fiddles所示。 - meetar
在你的Fiddle中,目标向量从未改变,因此z轴始终指向同一方向。 - WestLangley
我理解这句话的两个部分,但不知道它们是如何相互关联的 - 我可能在看问题上有困难。我理解“目标”是一个对象空间向量,“上方”是一个世界空间向量,正如你所说:这两个向量决定了对象的方向。如果上方向量的Z值发生变化,那么对象的方向会改变以跟随它吗? - meetar
又喝了一杯咖啡,但多亏了您的评论,我找到了假设中的缺陷——目标向量在世界空间中,而我的心理模型是错误的。 - meetar
请明确一下:up和lookat向量都在世界空间中吗?在这个评论中,@mrdoob似乎在说up向量在对象空间中,但那是两年前的事了... https://github.com/mrdoob/three.js/issues/1752#issuecomment-5206241 - meetar

2
我假设你的标题意味着在Z轴上旋转而不是X轴?无论如何,罪魁祸首似乎是axes.lookAt(new THREE.Vector3(0, 0, 1));,如果你将其改为axes.lookAt(new THREE.Vector3(0, 1, 0));,则所有方法都不会像预期的那样旋转Y。你告诉轴辅助器朝特定轴(在你的情况下是Z)看。因此,Z值无法正常工作。有没有你想要实现的示例可能会对我们有所帮助?也许其他人可以更深入地解释发生了什么事情。希望我的答案能帮助你朝正确的方向前进。

在链接的示例中,您可以看到调整X和Y值会使轴在Z轴上旋转,因此Y轴始终指向目标。调整Z值应该会将轴在X轴上旋转。我稍微澄清一下问题。 - meetar
@meetar 啊,你说得对,我混淆了值和轴的措辞。现在这样更有意义了。你的例子总是指向 Z 并绕 Z 旋转,这就是为什么调整 Z 不会旋转它。你是想让轴助手旋转以指向由 UI 控制的球体吗? - Grant
我不确定我理解你的推理 - 我正在沿着Z轴向下看,因为这是让Y轴“向上”的方法。如果您检查示例fiddle,您会发现调整目标的Z值不会改变轴的X旋转,尽管我认为它应该这样做。 - meetar
所以你并没有错,但是直到@WestLangley的评论之后我才明白——至少我会给你点赞。 - meetar
没关系。我想@WestLangley最终会回复的。我应该稍微澄清一下,但我很高兴你能解决它! - Grant

1

以下是我理解问题的过程:

lookAt和up向量确定物体的方向如下:

  1. 首先应用lookAt向量,设置X和Y旋转,锁定物体Z轴指向的方向。
  2. 然后up向量决定物体绕Z轴旋转的方向,以设置物体Y轴指向的方向——它不会影响X和Y旋转。

在我的例子中,axisHelper沿着其蓝色Z轴朝向lookAt向量所在的空间点(0, 0, -1)——因此X和Y旋转已经被设置。唯一剩下的事情就是找出如何围绕其Z轴旋转axisHelper,这意味着设置up向量的X和Y点——沿着Z轴前后移动up向量不会改变任何东西。

这里有一个演示这种关系的演示:蓝色箭头是lookAt轴,绿色箭头是up轴。

https://jsfiddle.net/hrjfgo4b/3

lookat and up vector demo

 Links to jsfiddle.net must be accompanied by code

0

.up、.lookAt和.position都是在世界坐标系中定义的点。向量的物理定义是具有大小和方向,但.up、.lookAt和.position采用的是点作为输入,而不是向量。重要的是要区分这一点,因为在Three JS中,您正在定义向量(new THREE.Vector3()),但实际上您正在使用世界空间中的点。

这是我的解释。在内部,Three JS将对象的本地坐标系的+z轴从.position放置到.lookAt。Three JS将本地坐标系的+y轴定义为从.position到.up。

以下是一些代码以帮助理解。下面的代码将对象的本地坐标系围绕世界的+z轴旋转90度。对于测试用例1和2,将世界点转换为本地坐标系会产生正确的结果。对于测试用例3,结果不同,这是由于先执行了.lookAt,然后是.up的顺序造成的。

  1. 使用 .up,然后使用 .lookAt
  2. 在定义 .position、.up 和 .lookAt 后,确保使用 updateMatrixWorld()
  3. 请注意,虽然您可能期望得到零,但有时您会得到一个非常小的值,例如 -2.220446049250313e-16。这样的小值等效于零。

// let THREE = require('three');
// import * as THREE from 'THREE';

let worldPointA = new THREE.Vector3(0, 0, 0);
let worldPointB = new THREE.Vector3(0, 1, 0);
let worldPointC = new THREE.Vector3(-1, 0, 0);
let worldPointD = new THREE.Vector3(0, 0, 1);

// Test Case 1
let newObject = new THREE.Object3D();
newObject.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObject.rotateZ(Math.PI / 2);
newObject.updateMatrixWorld();
console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObject.worldToLocal(worldPointA.clone()));
console.log(newObject.worldToLocal(worldPointB.clone()));
console.log(newObject.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 1, y: 2.220446049250313e-16, z: 0 }
// Vector3 { x: -2.220446049250313e-16, y: 1, z: 0 }

// Test Case 2
let newObjectB = new THREE.Object3D();
newObjectB.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObjectB.up = worldPointC.clone();
newObjectB.lookAt(worldPointD.clone()); // lookAt should always come after .up
newObjectB.updateMatrixWorld();
console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObjectB.worldToLocal(worldPointA.clone()));
console.log(newObjectB.worldToLocal(worldPointB.clone()));
console.log(newObjectB.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 1, y: 2.220446049250313e-16, z: 0 }
// Vector3 { x: -2.220446049250313e-16, y: 1, z: 0 }

// Test Case 3
let newObjectC = new THREE.Object3D();
newObjectC.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObjectC.lookAt(worldPointD.clone());
newObjectC.up = worldPointC.clone();
newObjectC.updateMatrixWorld();

console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObjectC.worldToLocal(worldPointA.clone()));
console.log(newObjectC.worldToLocal(worldPointB.clone()));
console.log(newObjectC.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 0, y: 1, z: 0 }
// Vector3 { x: -1, y: 0, z: 0 }
// ^^^^^^ Not correct
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.145.0/three.min.js"></script>


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