|
(探索中)
实际上这只是利用相关网络传输框架对SteamVR进行连线的一次技术探索,unity商店里面应该已经有不少可以商用的插件吧,但这不是我的兴趣点。我希望是通过阅读和理解SteamVR示例代码来实现一套自己的多人连线方案。
目前一直没有时间完成这个SteamVR多人连线的试验项目。本来在2021年6-7月份的时候,通过阅读SteamVR示例项目代码,已经差不多可以实现了,但是因为时间安排问题就搁置下来。前几天因为有人问起,但是目前手上有其它任务,因此只能把以前的一点发现先写下来,与有需求的同学们分享一下(时间一长基本已经忘光光了,请容我慢慢更新)。
Unity MLAPI
unity MLAPI究竟是怎样一种思路,这个目前不得而知,总体觉得这个框架的目的就是减少应用层开发的工作量,但是这样就隐藏了技术细节,在需要进行拓展的时候就不知道从何入手。初步感觉是,是基于unity GameObject远程克隆的思想,由此获得一个与本地端同步的gameobject克隆体,包含了transform等主要对象属性参数。因此,VR头戴设备和手柄在操作时,位置、朝向的改变就能反映在远端克隆体上面了。
但是MLAPI似乎并不能完全将SteamVR Player对象的所有细节都进行序列化和传输同步。SteamVR手柄所表现出来的手势动作,主要是通过SteamVR_Behaviour_Skeleton进行处理的。而骨骼数据的传入,是通过SteamVR_Input.onSkeletonsUpdated这个事件来触发的。
SteamVR手势骨骼数据
要实现完整的SteamVR Player的远程克隆,相对来说手柄、头盔的位置和朝向同步比较容易,而手柄的骨骼数据,如何获取、传输、远端渲染,这些问题比较烧脑。而SteamVR在这方面似乎并没有提供完整易用的方案(不知道2022最新有没有进展,如果知道的小伙伴请告知一下)。我之前只实现了本地的动作克隆(一只手,同步复制SteamVR手部的动作)。
而远程克隆的大概思路是,从steamVR.Player继承一个新的Player比如叫FrankPlayer用于替换掉原来的SteamVR.Player,在这个player中判断一下自己是连接本地还是远程,如果是远程就不从SteamVR插件获取动作数据。
private void OnEnable()
{
//断开VR设备输入对模型的pose干扰
poseBehaviour.poseAction[SteamVR_Input_Sources.RightHand].onUpdate += this.SteamVR_Behaviour_Pose_OnUpdate;
poseBehaviour.poseAction[SteamVR_Input_Sources.RightHand].onUpdate -= poseBehaviour.SteamVR_Behaviour_Pose_OnUpdate;
rightHand.nglSteamVRHandInitializedEvent += this.OnHandInitialized;
SteamVR_Behaviour_Skeleton.SkeletonRecord = new SteamVR_Behaviour_Skeleton.SteamVRSkeletonFrame();
teleportBehaviour.booleanAction[SteamVR_Input_Sources.RightHand].onStateUp += this.onRightHandPressUp;
}
private void onRightHandPressUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
Debug.Log($"{fromSource} PressUp: {teleportBehaviour.booleanAction[fromSource].state}");
}
//此方法接受来自Hand加载模型完成的消息
void OnHandInitialized(RenderModel handPrefab)
{
var skeletonBehaviour = handPrefab.handPrefab.GetComponent<SteamVR_Behaviour_Skeleton>();
//断开VR设备输入对模型的skeleton干扰
//skeletonBehaviour = rightHandGo.GetComponentInChildren<SteamVR_Behaviour_Skeleton>();
skeletonBehaviour.isRemoteControl = true;
SteamVR_Input.onSkeletonsUpdated -= skeletonBehaviour.SteamVR_Input_OnSkeletonsUpdated;
}
private void SteamVR_Behaviour_Pose_OnUpdate(SteamVR_Action_Pose fromAction, SteamVR_Input_Sources fromSource)
{
poseBehaviour.SteamVR_Behaviour_Pose_OnUpdate(fromAction, fromSource);
//Debug.Log($&#34;{fromSource} UPDATED: {poseBehaviour.poseAction[fromSource].localPosition}&#34;);
}
//此方法替代了SteamVR_Behaviour_Skeleton.UpdateSkeletonTransforms()
public void UpdateSkeletion(SteamVR_Behaviour_Skeleton.SteamVRSkeletonFrame frame)
{
var length = frame.bonePositions.Length;
//Debug.Log(frame.bonePositions[4].x + &#34; &#34; + frame.bonePositions[4].y + &#34; &#34; + frame.bonePositions[4].z);
for (int boneIndex = 0; boneIndex < length; boneIndex++)
{
skeletonBehaviour.SetBonePosition(boneIndex, frame.bonePositions[boneIndex]);
skeletonBehaviour.SetBoneRotation(boneIndex, frame.boneRotations[boneIndex]);
}
}
public void Update()
{
var record = SteamVR_Behaviour_Skeleton.SkeletonRecord;
if (record != null && record.bonePositions != null) { }
//UpdateSkeletion(record);
}然后还得改掉SteamVR_Behaviour_Skeleton脚本:
protected virtual void OnEnable()
{
CheckSkeletonAction();
// 2021-08-26 ===== Frank魔改 =====
// 如果是remoteControl属性就不接受硬件数据
if (!isRemoteControl)
// 2021-08-26 ===== Frank魔改 =====
SteamVR_Input.onSkeletonsUpdated += SteamVR_Input_OnSkeletonsUpdated;
if (skeletonAction != null)
{
skeletonAction.onDeviceConnectedChanged += OnDeviceConnectedChanged;
skeletonAction.onTrackingChanged += OnTrackingChanged;
}
}
上述代码将VR手柄硬件的数据传入断开了。要实现整体多人连线,特别是要看见对方的手势骨骼动作,还要做以下工作:
- 一个本地方法,响应SteamVR_Input.onSkeletonsUpdated事件,使本地手柄对象进行更新。
- 上述方法中将骨骼数据记录下来,并传输到远端。
- 远端的Player发现自己是远程克隆体,因此不去寻找VR硬件,而是接受网络传入的数据,并进行手势骨骼动作的渲染。
SteamVR手部骨骼动作的渲染
SteamVR的示例是在SteamVR_Behaviour_Skeleton.SteamVR_Input_OnSkeletonsUpdated方法中完成手部骨骼的渲染的,因此这个方法也要魔改掉:
public virtual void UpdateSkeletonTransforms()
{
Vector3[] bonePositions;
Quaternion[] boneRotations ;
if (!isRemoteControl)
{
bonePositions = GetBonePositions();
boneRotations = GetBoneRotations();
// 2021-08-26 ===== Frank魔改 =====
Debug.Log(bonePositions[4].x + &#34; &#34; + bonePositions[4].y + &#34; &#34; + bonePositions[4].z);
//SkeletonRecords.Add(new SteamVRSkeletonFrame()
//{
// bonePositions = bonePositions,
// boneRotations = boneRotations
//});
SkeletonRecord.bonePositions = bonePositions;//录下来
SkeletonRecord.boneRotations = boneRotations;//录下来
// 2021-08-26 ===== Frank魔改 =====
}
else
{
bonePositions = SkeletonRecord.bonePositions;
boneRotations = SkeletonRecord.boneRotations;
}
if (skeletonBlend <= 0)
{
if (blendPoser != null)
{
SteamVR_Skeleton_Pose_Hand mainPose = blendPoser.skeletonMainPose.GetHand(inputSource);
for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++)
{
if (bones[boneIndex] == null)
continue;
if ((boneIndex == SteamVR_Skeleton_JointIndexes.wrist && mainPose.ignoreWristPoseData) ||
(boneIndex == SteamVR_Skeleton_JointIndexes.root && mainPose.ignoreRootPoseData))
{
SetBonePosition(boneIndex, bonePositions[boneIndex]);
SetBoneRotation(boneIndex, boneRotations[boneIndex]);
}
else
{
Quaternion poseRotation = GetBlendPoseForBone(boneIndex, boneRotations[boneIndex]);
SetBonePosition(boneIndex, blendSnapshot.bonePositions[boneIndex]);
SetBoneRotation(boneIndex, poseRotation);
}
}
}
else
{
for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++)
{
Quaternion poseRotation = GetBlendPoseForBone(boneIndex, boneRotations[boneIndex]);
SetBonePosition(boneIndex, blendSnapshot.bonePositions[boneIndex]);
SetBoneRotation(boneIndex, poseRotation);
}
}
}
else if (skeletonBlend >= 1)
{
for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++)
{
if (bones[boneIndex] == null)
continue;
SetBonePosition(boneIndex, bonePositions[boneIndex]);
SetBoneRotation(boneIndex, boneRotations[boneIndex]);
}
}
else
{
for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++)
{
if (bones[boneIndex] == null)
continue;
if (blendPoser != null)
{
SteamVR_Skeleton_Pose_Hand mainPose = blendPoser.skeletonMainPose.GetHand(inputSource);
if ((boneIndex == SteamVR_Skeleton_JointIndexes.wrist && mainPose.ignoreWristPoseData) ||
(boneIndex == SteamVR_Skeleton_JointIndexes.root && mainPose.ignoreRootPoseData))
{
SetBonePosition(boneIndex, bonePositions[boneIndex]);
SetBoneRotation(boneIndex, boneRotations[boneIndex]);
}
else
{
//Quaternion poseRotation = GetBlendPoseForBone(boneIndex, boneRotations[boneIndex]);
SetBonePosition(boneIndex, Vector3.Lerp(blendSnapshot.bonePositions[boneIndex], bonePositions[boneIndex], skeletonBlend));
SetBoneRotation(boneIndex, Quaternion.Lerp(blendSnapshot.boneRotations[boneIndex], boneRotations[boneIndex], skeletonBlend));
//SetBoneRotation(boneIndex, GetBlendPoseForBone(boneIndex, boneRotations[boneIndex]));
}
}
else
{
if (blendSnapshot == null)
{
SetBonePosition(boneIndex, Vector3.Lerp(bones[boneIndex].localPosition, bonePositions[boneIndex], skeletonBlend));
SetBoneRotation(boneIndex, Quaternion.Lerp(bones[boneIndex].localRotation, boneRotations[boneIndex], skeletonBlend));
}
else
{
SetBonePosition(boneIndex, Vector3.Lerp(blendSnapshot.bonePositions[boneIndex], bonePositions[boneIndex], skeletonBlend));
SetBoneRotation(boneIndex, Quaternion.Lerp(blendSnapshot.boneRotations[boneIndex], boneRotations[boneIndex], skeletonBlend));
}
}
}
}
if (onBoneTransformsUpdated != null)
onBoneTransformsUpdated.Invoke(this, inputSource);
if (onBoneTransformsUpdatedEvent != null)
onBoneTransformsUpdatedEvent.Invoke(this, inputSource);
}
一只假手克隆SteamVR骨骼动作
https://www.zhihu.com/video/1469293386000199680
手势骨骼数据的传输(MLAPI)
(待更新) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|