找回密码
 立即注册
查看: 440|回复: 0

如何用Unity MLAPI实现SteamVR多人连线(二)

[复制链接]
发表于 2022-1-25 14:21 | 显示全部楼层 |阅读模式
(探索中)
实际上这只是利用相关网络传输框架对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($"{fromSource} UPDATED: {poseBehaviour.poseAction[fromSource].localPosition}");
    }

    //此方法替代了SteamVR_Behaviour_Skeleton.UpdateSkeletonTransforms()
    public void UpdateSkeletion(SteamVR_Behaviour_Skeleton.SteamVRSkeletonFrame frame)
    {
        var length = frame.bonePositions.Length;
        //Debug.Log(frame.bonePositions[4].x + " " + frame.bonePositions[4].y + " " + 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 + " " + bonePositions[4].y + " " + 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)

(待更新)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-23 07:32 , Processed in 0.090614 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表