Three methods of VR wall penetration (line of sight occlusion, capsule physical extrusion and head physical extrusion)

There are several solutions for wall penetration of VR games that I have used so far, and I recommend the first and third.

Here are code snippets and some ideas, for reference only.

First, we pull XR Origin into the scene, hang a capsule and a rigid body (or use CharacterController), and I like to write my own movement control.
We use direction control to move the capsule of XR Origin to avoid passing through the wall, but the movement in the real world is the movement of the Camera, so it can pass through the wall.
insert image description here

First of all, let's look at the first wall-penetrating solution, which is similar to the way of blocking the camera in Half-Life-Alex. When the head touches the object, the occlusion is displayed in front of the eyes to prevent the eyes from seeing it.

first line of sight

First, hang a node on the camera, put a patch in front of this node, which can completely block the camera, and put a script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.XR;
using UnityEngine.XR;
using UnityEngine.XR.Management;

public class HeadCheckWall : MonoBehaviour
{
    
    
    [SerializeField] float fadeSpeed = 5f;
    [SerializeField] float sphereCheckSize = .15f;
    [SerializeField] Transform vrOrgin;
    [SerializeField] Renderer render;
    Material fadeMat;
    bool isCameraFadeOut;
    int layer;

    void Start()
    {
    
    
        fadeMat = render.material;
        render.enabled = false;
        layer = 1 << 0 | 1 << 1;
    }

    private void Update()
    {
    
    
        if (transform.position.y - vrOrgin.position.y < 0.1f)
            return;

        bool throwwall = false;
        if (HeroMe.inst != null)
        {
    
    
            if (HeroMe.inst.firstChar != null)
            {
    
    
                throwwall = HeroMe.inst.firstChar.throwwall;
            }
        }
        
        bool check = Physics.CheckSphere(transform.position, sphereCheckSize, layer, QueryTriggerInteraction.Ignore) || throwwall;
        if (check)
        {
    
    
            CameraFade(1f);
            isCameraFadeOut = true;
        }
        else
        {
    
    
            if (!isCameraFadeOut)
                return;
            CameraFade(0f);
        }
    }

    void CameraFade(float a)
    {
    
    
        float fadevalue = Mathf.MoveTowards(fadeMat.GetFloat("_AlphaColor"),a,Time.deltaTime * fadeSpeed);
        fadeMat.SetFloat("_AlphaColor", fadevalue);
        if (fadevalue < 0.01f)
        {
    
    
            isCameraFadeOut = false;
            render.enabled = false;
        }
        else
        {
    
    
            if (!render.enabled)
                render.enabled = true;
        }
    }

    private void OnDrawGizmos()
    {
    
    
        Gizmos.color = new Color(0f, 1f, 0f, 0.75f);
        Gizmos.DrawSphere(transform.position, sphereCheckSize);
    }
}

For the Shader here, you can make a solid color with Alpha gradient by yourself. This is used to detect that if the head hits the wall, the occluder patch will be displayed.

There will be a problem here: the wall has a thickness, if it passes through, it will lose its effect. The way to make up for it is to launch a ray from XROrgin's capsule collider to the camera. If it hits a wall, it means there is a wall blocking it.

        //穿墙检测
        
        Vector3 capsultAt = xrOriginTrans.TransformPoint(capsule.center);
        //Debug.DrawLine(capsultAt, capsultAt+Vector3.up*3f, Color.blue, 0.1f);
        float dis = Vector3.Distance(vrCamera.transform.position, capsultAt);
        rayThrowWall.origin = capsultAt;// vrCamera.transform.position;
        rayThrowWall.direction = (vrCamera.transform.position - capsultAt).normalized;
        //Debug.DrawLine(vrCamera.transform.position, capsultAt, Color.red, 0.1f);
        throwwall = Physics.Raycast(rayThrowWall, out throwHit, dis, wallLayer,QueryTriggerInteraction.Ignore);
        if (throwwall)
        {
    
    
            Debug.Log("穿墙了" + Time.time);
        }

This method is slightly flawed. It may appear that the character is at the corner of the wall and performs the turning movement in the real world, which may cause false positives.

We make certain optimizations when the player is moving, allowing the player to instantly switch the capsule to the position of the camera when the player controls it through the handle, so as to perform some error repairs. The initial move code below is the fix.

void InputCameraMoveUpdate()
    {
    
    
        if (HeroMe.inst == null || HeroMe.inst.player == null)
            return;
        bool needrot = false;

        
        stand = inputMove.magnitude < 0.01f ;
        staticBody = rig.velocity.magnitude < 0.01f;

        //穿墙检测
        
        Vector3 capsultAt = xrOriginTrans.TransformPoint(capsule.center);
        //Debug.DrawLine(capsultAt, capsultAt+Vector3.up*3f, Color.blue, 0.1f);
        float dis = Vector3.Distance(vrCamera.transform.position, capsultAt);
        rayThrowWall.origin = capsultAt;// vrCamera.transform.position;
        rayThrowWall.direction = (vrCamera.transform.position - capsultAt).normalized;
        //Debug.DrawLine(vrCamera.transform.position, capsultAt, Color.red, 0.1f);
        throwwall = Physics.Raycast(rayThrowWall, out throwHit, dis, wallLayer,QueryTriggerInteraction.Ignore);
        //if (throwwall)
        //{
    
    
        //    Debug.Log("穿墙了" + Time.time);
        //}

        capsule.height = vrCamera.transform.position.y - xrOriginTrans.position.y;
        if (capsule.height <= 0.01f)
            capsule.height = 0.2f;
        if (!throwwall)
        {
    
    
            if (!stand && laststand != stand)
            {
    
    
                //刚开始移动
                originalCenter = xrOriginTrans.InverseTransformPoint(ToPos);//  - xrOrigin.transform.position;
            }
            
            if (Mathf.Abs(inputRot.x) > 0f)
            {
    
    
                needrot = true;
                originalCenter = xrOriginTrans.InverseTransformPoint(vrCamera.transform.position);//  - xrOrigin.transform.position;
            }
        }
        originalCenter.y = capsule.height * 0.5f;
        capsule.center = originalCenter;
        laststand = stand;

        smoothInput = Vector3.SmoothDamp(smoothInput, inputMove, ref smoothInputV, 0.1f);

        // Rotate input to avatar space
        Vector3 forward = vrCamera.transform.forward;// ctrlMe.transform.forward;// transform.forward;
        forward.y = 0f;
        forward = forward.normalized;
        Quaternion avatarSpace = Quaternion.LookRotation(forward);
        if (!stand)
        {
    
    
            transform.rotation = Quaternion.Slerp(transform.rotation, avatarSpace, Time.deltaTime * 5f);
        }
        //Debug.Log(avatarSpace * smoothInput);

        nowVelocity = avatarSpace * smoothInput * Time.deltaTime * vSpeed;
        if (onGround)
        {
    
    
            rig.velocity = nowVelocity;
        }
        else
        {
    
    
            nowVelocity.y += rig.velocity.y;
            rig.velocity = nowVelocity;
        }

        if (needrot)
        {
    
    
            xrOrigin.RotateAroundCameraUsingOriginUp(inputRot.x * 60f);
            inputRot.x = 0f;
        }
        
        //刚体摩擦力是0的时候,刚体速度就是1秒移动距离
        ToPos = vrCamera.transform.position - vrCamera.transform.forward * thirdOffset;
        ToPos.y = xrOriginTrans.position.y;

        ToRot = xrOriginTrans.localEulerAngles;// forward.normalized;

        ThirdMove();
    }

    float thirdOffset = 0.1f;

    public float movespd = 5f;

    float moveOver = 0.2f;  //超过这个距离才开始动
    public bool standmoving;
    Vector3 zeroV3;
    

    void ThirdMove()
    {
    
    
        if (staticBody)
        {
    
    
            //float nowtime = Time.time;
            float dis = Vector3.Distance(ToPos, transform.position);
            if (dis > moveOver)
            {
    
    
                standmoving = true;
            }
            else
            {
    
    
                if (dis < 0.05f)
                    standmoving = false;
            }
            standToMove = (ToPos - transform.position).normalized;
            if (standmoving)
            {
    
    
                transform.position = Vector3.Lerp(transform.position, ToPos, Time.deltaTime * movespd);
            }
        }
        else
        {
    
    
            transform.position = ToPos;
        }
    }

That's about it for line of sight occlusion.

The second physical extrusion method

The following is the second solution to prevent the camera from passing through the wall.
This method is relatively simple. We can control the capsule or CharacterController to force movement in FixeUpdate, which activates the physical calculation of the system, and if it touches something, it will be squeezed out.

		//胶囊随时跟随相机
		originalCenter = xrOriginTrans.InverseTransformPoint(vrCamera.transform.position);
		originalCenter.y = capsule.height * 0.5f;
		capsule.center = originalCenter;

		//重点在这里
        rig.MovePosition(rig.position);
		//或者CharacterController调用一次移动,触发胶囊挤压出来
		//CharacterController.Move(Vector3.Zero);
		//不行可以试试下面的
		//CharacterController.Move(new Vector3(0.001f, -0.001f, 0.001f))
		//CharacterController.Move(new Vector3(-0.001f, 0.001f, -0.001f))

This method has a disadvantage. When the player bends down to pick up something on the table, because the capsule follows the head, the person will be pushed back, making it difficult to pick up the item.

Let's talk about another way, similar to ghost phobia.

The third way of physical extrusion of the head

He prevents his head from going through, but the capsule can, but there is a disadvantage that he can walk through the table, because the head does not touch the table, you will see this person through the table. But it cannot be penetrated by directional remote sensing.
The specific implementation method:
First, add the rigid body and capsule vr to the camera
insert image description here
. There is this TrackedPoseDriver script on the camera. We create a new script to replace it and rewrite some of its functions.


using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.InputSystem.XR;

public class HeadRigMove : TrackedPoseDriver
{
    
    

    Vector3 cameraLocalPos;
    Vector3 cameraWorldPos;
    Rigidbody rig;
    float colliderRadius = 1f;
    Vector3 dir;
    const float wallThickness = 0.01f;   //设置让刚体强制移动这么多。可能顶墙,这个距离不要超过相机碰撞的半径
    int wallLayer;
    protected override void Awake()
    {
    
    
        base.Awake();
        rig = transform.gameObject.GetComponent<Rigidbody>();
        SphereCollider collider = transform.gameObject.GetComponent<SphereCollider>();
        colliderRadius = collider.radius;
        wallLayer = 1 << 0 | 1 << 1 | 1 << LayerMask.NameToLayer("item");
    }
    protected override void SetLocalTransform(Vector3 newPosition, Quaternion newRotation)
    {
    
    
        if (trackingType == TrackingType.RotationAndPosition ||
            trackingType == TrackingType.RotationOnly)
        {
    
    
            transform.localRotation = newRotation;
        }

        if (trackingType == TrackingType.RotationAndPosition ||
            trackingType == TrackingType.PositionOnly)
        {
    
    
            cameraLocalPos = newPosition;
        }
    }

    Ray rayThrowWall;
    RaycastHit throwHit;
    private void FixedUpdate()
    {
    
    
        
        cameraWorldPos = transform.parent.TransformPoint(cameraLocalPos);
        bool throwwall = false;
        if (cameraLocalPos.y > 0.01f)   //如果vr设备没连接,这里都是0f
        {
    
    
            float dis = Vector3.Distance(cameraWorldPos, rig.position) + colliderRadius;

            //检测头部到目标位置是否有墙体,如果有只能移动一部分
            rayThrowWall.origin = rig.position;// vrCamera.transform.position;
            rayThrowWall.direction = (cameraWorldPos - rig.position).normalized;
            //Debug.DrawLine(vrCamera.transform.position, capsultAt, Color.red, 0.1f);
            throwwall = Physics.Raycast(rayThrowWall, out throwHit, dis, wallLayer, QueryTriggerInteraction.Ignore);
        }
        if (!throwwall)
        {
    
    
            rig.MovePosition(cameraWorldPos);
        }
        else
        {
    
    
            rig.MovePosition(rig.position + rayThrowWall.direction * wallThickness);
        }
    }
}


In this way, the actual coordinates are calculated after the head-mounted display obtains the coordinate data, and the rigid body is moved over, adding a wall thickness, which is not allowed to exceed. That's it.

Guess you like

Origin blog.csdn.net/thinbug/article/details/129387762