如何使用 LookRotation 根据手的位置旋转对象?
How to use LookRotation to rotate an object based on hand positions?
我正在为 VR 游戏创建一个交互系统,但我正在为双手交互而苦苦挣扎。我正在使用 Quaternion.LookRotation()
方法根据手的位置和旋转生成抓取对象的旋转。前向部分非常简单:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
“向上”部分是我遇到的困难。最初我尝试使用双手的平均向上方向:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
这种方法存在一个问题:手的向上向量可能与 fwd
向量对齐,这会导致对象在越过它时翻转。这是问题的简单说明:
浅绿色箭头表示手的向上方向,而深绿色是用作 LookRotation()
方法参数的计算方向。
显而易见的解决方案似乎是选择一个不同的固定向量而不是“向上”,这样一个向量不会很容易与 fwd
向量对齐。在示例中,它可以是与手指对齐的矢量。但请记住,手的初始旋转没有限制,因此无论您选择哪个向量,手总是会发生旋转,以便向量对齐。即使您动态地选择一个最佳矢量(一个垂直于 fwd
的矢量),它仍然最多与 fwd
.
对齐 90 度
为了解决这个问题,我尝试将方向限制为不会导致问题的值,但这导致了另一个问题(我很难确定哪些值是正确的,哪些应该被丢弃)。我觉得我在这里做错了,有没有更好的解决方案?
您可以计算手的旋转增量并将其应用于对象的“基础向上”(如果我们只考虑手的位置变化,则新的向上......当然是正交于物体的轴)。然后确定导致每只手向上旋转的角度变化。平均这些角度,然后使用 Quaternion.AngleAxis 将这些角度与 hand-hand 轴应用到之前的“基础向上”。然后你有你的前进和向上 Quaternion.LookRotation).
下面是如何使用它的示例,包括 VR 手部噪音模拟。要查看测试,请统一创建一个新场景并将其附加到相机,它将在播放开始时构建场景。有一个 grip/release gui 将出现在播放视图中。您可以在场景视图中调整手部旋转
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}
我正在为 VR 游戏创建一个交互系统,但我正在为双手交互而苦苦挣扎。我正在使用 Quaternion.LookRotation()
方法根据手的位置和旋转生成抓取对象的旋转。前向部分非常简单:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
“向上”部分是我遇到的困难。最初我尝试使用双手的平均向上方向:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
这种方法存在一个问题:手的向上向量可能与 fwd
向量对齐,这会导致对象在越过它时翻转。这是问题的简单说明:
LookRotation()
方法参数的计算方向。
显而易见的解决方案似乎是选择一个不同的固定向量而不是“向上”,这样一个向量不会很容易与 fwd
向量对齐。在示例中,它可以是与手指对齐的矢量。但请记住,手的初始旋转没有限制,因此无论您选择哪个向量,手总是会发生旋转,以便向量对齐。即使您动态地选择一个最佳矢量(一个垂直于 fwd
的矢量),它仍然最多与 fwd
.
为了解决这个问题,我尝试将方向限制为不会导致问题的值,但这导致了另一个问题(我很难确定哪些值是正确的,哪些应该被丢弃)。我觉得我在这里做错了,有没有更好的解决方案?
您可以计算手的旋转增量并将其应用于对象的“基础向上”(如果我们只考虑手的位置变化,则新的向上......当然是正交于物体的轴)。然后确定导致每只手向上旋转的角度变化。平均这些角度,然后使用 Quaternion.AngleAxis 将这些角度与 hand-hand 轴应用到之前的“基础向上”。然后你有你的前进和向上 Quaternion.LookRotation).
下面是如何使用它的示例,包括 VR 手部噪音模拟。要查看测试,请统一创建一个新场景并将其附加到相机,它将在播放开始时构建场景。有一个 grip/release gui 将出现在播放视图中。您可以在场景视图中调整手部旋转
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}