본문 바로가기
Project

[유니티/MR] 현실 벽 파괴하고 3D 공간 등장 시키기

by 배애앰이 좋아 2024. 3. 10.
반응형

 

목표 : MR 상 현실세계에서 공을 던져서 벽을 부수고 3D 공간이 나올 수 있도록 프로젝트 구성

 

https://github.com/oculus-samples/Unity-TheWorldBeyond

유니티에서 공식적으로 제공해주는 프로젝트 기반으로 작성된 글입니다.

사용한 유니티 버전 : 2020.3.18f
사용한 VR 기기 : meta quest 3

 

먼저 위에서 제공되는 링크에서 Unity-TheWorldBeyond 프로젝트를 받아줍니다. oculus 및 유니티 버전 별 제공하는 패키지가 다를 수 있기 때문에 사용한 유니티 버전 및 해당 프로젝트를 받아서 수정하는 식으로 진행하는 것을 추천드립니다. 

 

 

1. MR 실행시 현실 세계만 먼저 비추도록 하기

 

기본적인 세팅 설정입니다.

 

여기서 중요하게 봐야하는 부분은 아래 부분으로 해당 부분 클릭해서

 

안으로 들어가면 아래처럼 quad 형 mesh 가 있는 것을 확인할 수 있습니다. 

먼저, MR 경계 원리에 대해 간단하게 설명드리면, 사용자가 벽이나 경계선을 인식했다면 그에 따라서 벽을 만들고 현실 세계 이미지를 덮어씌워주는 식입니다. 

해당 mesh 가 위에서 벽 역할에 해당되며 이에 대한 모양을 변형하게 되면 MR 상에서 현실 세계를 보여주는 벽 모양이 바뀌게 됩니다. 

 

 

본래 가지고 있는 컴포넌트는 collider 까지이며 아래 3가지 스크립트는 프로젝트를 위해 추가한 것입니다. 

저희 프로젝트에서는 벽이 파괴되면서 그 뒤로 3D 공간이 나오길 바랬기 때문에 특정 상호작용을 통해 파괴되는 듯한 효과를 주기 위해 아래 에셋을 받아서 사용했습니다.

https://assetstore.unity.com/packages/tools/particles-effects/mesh-explosion-5471

해당 에셋은 mesh 모양에 대해 파괴해주는 에셋입니다. 

마지막 스크립트 경우 다른 테스트 스크립트로 무시하셔도 됩니다.

이후 공과 상호작용을 위해 태그로 변경해줍니다. 

참고로 설정상 해당 사이즈가 한 벽면에 맞춰줘 있기 때문에 줄이거나 하면 현실 벽이 세워질 때 뚫려서 세워지게 됩니다. (현실세계 방 크기가 정사각형이 아니기 때문에 인식한 직선에 따라 위에 면이 한개씩 생깁니다)  

 

무엇보다 중요한 것은 쉐이더로 해당 쉐이더를 넣어야 현실 세계 이미지가 해당 오브젝트에 씌워집니다. 만약에 해당 쉐이더가 흰색, 유니티 기본 쉐이더로 설정하고 실행한다면 사방이 흰색이 공간을 볼 수 있습니다.

 

 

저는 벽 한 면이 통으로 파괴되지 않고 그 중 일부만 변경되길 바랬기 때문에 위의 mesh 를 아래처럼 만들어놓았습니다. (각 큐브 동일한 컴포넌트 + 쉐이더 + 태그 설정 필요)

 

 

 

2. 현실세계를 뚫지 않게 3D 공간 처리해주기

 

프로젝트에서 벽을 파괴하고 3D 공간을 띄우기 위해서는 처음부터 3D 공간 상에서 벽을 만들어서 현실세계를 띄워주는 방식을 사용합니다. 이때 방 크기는 유동적이며 잘못하다가는 원래 존재했던 3D 오브젝트가 벽을 뚫고 들어올 수도 있습니다 이를 막아주는 부분이 아래 부분입니다.

 

해당 스크립트를 보면 먼저 바닥끼리 충돌하지 않게 z 축으로 3D 공간을 옮겨주고 각 오브젝트의 위치에서 오른쪽으로 광선을 쏴서 몇 개의 벽에 충돌하는지 확인합니다. 이렇게 확인했을 때, 충돌 수가 홀수면 방 안에 있다고 판단하고 삭제를 해주는 역할을 해줍니다.

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class SamplePassthroughRoom : MonoBehaviour
{
    public OVRSceneManager _sceneManager;

    // all virtual content is a child of this Transform
    public Transform _envRoot;

    // the corners of the room; for checking if a position is in the room's boundaries
    List<Vector3> _cornerPoints = new List<Vector3>();

    // drop the virtual world this far below the floor anchor
    const float _groundDelta = 0.02f;

    void Awake()
    {
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_ANDROID
        OVRManager.eyeFovPremultipliedAlphaModeEnabled = false;
#endif
        _sceneManager.SceneModelLoadedSuccessfully += InitializeRoom;
    }

    void InitializeRoom()
    {
        OVRSceneAnchor[] sceneAnchors = FindObjectsOfType<OVRSceneAnchor>();
        OVRSceneAnchor floorAnchor = null;
        if (sceneAnchors != null)
        {
            for (int i = 0; i < sceneAnchors.Length; i++)
            {
                OVRSceneAnchor instance = sceneAnchors[i];
                OVRSemanticClassification classification = instance.GetComponent<OVRSemanticClassification>();

                if (classification.Contains(OVRSceneManager.Classification.WallFace) ||
                    classification.Contains(OVRSceneManager.Classification.Ceiling) ||
                    classification.Contains(OVRSceneManager.Classification.DoorFrame) ||
                    classification.Contains(OVRSceneManager.Classification.WindowFrame))
                {
                    //Destroy(instance.gameObject);
                }
                else if (classification.Contains(OVRSceneManager.Classification.Floor))
                {
                    floorAnchor = instance;
                    // move the world slightly below the ground floor, so the virtual floor doesn't Z-fight
                    // 가상 바닥이 Z 싸움을 일으키지 않도록 세계를 지상 바닥보다 약간 아래로 이동시킵니다
                    if (_envRoot)
                    {
                        Vector3 envPos = _envRoot.transform.position;
                        float groundHeight = instance.transform.position.y - _groundDelta;
                        _envRoot.transform.position = new Vector3(envPos.x, groundHeight, envPos.z);
                        if (OVRPlugin.GetSpaceBoundary2D(instance.Space, out Vector2[] boundary))
                        {
                            // Use the Scence API and floor scene anchor to get the corner of the floor, and convert Vector2 to Vector3
                            // Scence API 및 Floor Scene 앵커를 사용하여 Floor의 모서리를 가져오고 Vector2를 Vector3으로 변환합니다
                            _cornerPoints = boundary.ToList()
                                .ConvertAll<Vector3>(corner => new Vector3(-corner.x, corner.y, 0.0f));

                            // GetSpaceBoundary2D is in anchor-space
                            // GetSpaceBoundary2D가 앵커 공간에 있습니다
                            _cornerPoints.Reverse();
                            for (int j = 0; j < _cornerPoints.Count; j++)
                            {
                                _cornerPoints[j] = instance.transform.TransformPoint(_cornerPoints[j]);
                            }
                        }
                    }
                }
            }
        }

        CullForegroundObjects();
    }

    /// <summary>
    /// If an object contains the ForegroundObject component and is inside the room, destroy it.
    /// // 개체에 ForegroundObject 구성요소가 포함되어 있고 룸 내부에 있으면 개체를 파괴합니다
    /// </summary>
    void CullForegroundObjects()
    {
        ForegroundObject[] foregroundObjects = _envRoot.GetComponentsInChildren<ForegroundObject>();
        foreach (ForegroundObject obj in foregroundObjects)
        {
            if (_cornerPoints != null && IsPositionInRoom(obj.transform.position))
            {
                Destroy(obj.gameObject);
            }
        }
    }

    /// <summary>
    /// Given a world position, test if it is within the floor outline (along horizontal dimensions)
    /// 월드 위치가 주어진 경우 바닥 윤곽선 내에 있는지 테스트합니다(수평 치수를 따라)
    /// </summary>
    public bool IsPositionInRoom(Vector3 pos)
    {
        Vector3 floorPos = new Vector3(pos.x, _cornerPoints[0].y, pos.z);
        // Shooting a ray from point to the right (X+), count how many walls it intersects.
        // If the count is odd, the point is in the room
        // Unfortunately we can't use Physics.RaycastAll, because the collision may not match the mesh, resulting in wrong counts
        // 점에서 오른쪽으로 광선을 쏘고(X+), 몇 개의 벽이 교차하는지 세어봅니다.
        // 카운트가 홀수이면 포인트는 방에 있습니다
        // 안타깝게도 Physics.RaycastAll을 사용할 수 없습니다. 충돌이 메쉬와 일치하지 않아 잘못된 카운트가 발생할 수 있기 때문입니다
        int lineCrosses = 0;
        for (int i = 0; i < _cornerPoints.Count; i++)
        {
            Vector3 startPos = _cornerPoints[i];
            Vector3 endPos = (i == _cornerPoints.Count - 1) ? _cornerPoints[0] : _cornerPoints[i + 1];

            // get bounding box of line segment
            float xMin = startPos.x < endPos.x ? startPos.x : endPos.x;
            float xMax = startPos.x > endPos.x ? startPos.x : endPos.x;
            float zMin = startPos.z < endPos.z ? startPos.z : endPos.z;
            float zMax = startPos.z > endPos.z ? startPos.z : endPos.z;
            Vector3 lowestPoint = startPos.z < endPos.z ? startPos : endPos;
            Vector3 highestPoint = startPos.z > endPos.z ? startPos : endPos;

            // it's vertically within the bounds, so it might cross
            // 수직으로 경계 내에 있기 때문에 교차할 수 있습니다
            if (floorPos.z <= zMax &&
                floorPos.z >= zMin)
            {
                if (floorPos.x <= xMin)
                {
                    // it's completely to the left of this line segment's bounds, so must intersect
                    // 이 선분의 경계에서 완전히 왼쪽이므로 교차해야 합니다
                    lineCrosses++;
                }
                else if (floorPos.x < xMax)
                {
                    // it's within the bounds, so further calculation is needed
                    // 범위 내에 있으므로 추가 계산이 필요합니다
                    Vector3 lineVec = (highestPoint - lowestPoint).normalized;
                    Vector3 camVec = (floorPos - lowestPoint).normalized;
                    // polarity of cross product defines which side the point is on
                    // 교차 제품의 극성은 점이 어느 쪽에 있는지를 정의합니다
                    if (Vector3.Cross(lineVec, camVec).y < 0)
                    {
                        lineCrosses++;
                    }
                }
                // else it's completely to the right of the bounds, so it definitely doesn't cross
                // 그렇지 않으면 완전히 경계의 오른쪽에 있기 때문에 절대 교차하지 않습니다
            }
        }
        return (lineCrosses % 2) == 1;
    }
}

 

그 다음은 바닥 경계선을 확인해서 그 주변으로 오브젝트를 생성해주는 스크립트 본 프로젝트에서는 크게 중요하지 않아서 따로 사용하지 않았습니다.

using System.Collections.Generic;
using UnityEngine;

public class SampleBoundaryDebris : MonoBehaviour
{
    public OVRSceneManager _sceneManager;
    public GameObject[] _debrisPrefabs;

    // roughly how far apart the debris are from each other
    // 대략 파편들이 서로 얼마나 떨어져 있는지
    public float _averageSpacing = 0.7f;

    // debris objects are scattered in a noisy-grid pattern
    // this is the percent chance of a cell getting an object
    // 잔해물들이 시끄러운 격자무늬로 흩어져 있습니다
    // 이것은 세포가 물체를 얻을 확률의 백분율입니다
    public float _debrisDensity = 0.5f;

    // add noise to positions so debris objects aren't perfectly aligned
    // 위치에 소음을 추가하여 잔해물이 완벽하게 정렬되지 않도록 합니다
    public float _boundaryNoiseDistance = 0.1f;
    int _cellCount = 20;

    List<Vector3> _cornerPoints = new List<Vector3>();

    void Awake()
    {
        _sceneManager.SceneModelLoadedSuccessfully += CreateDebris;
    }

    void CreateDebris()
    {
        OVRSceneAnchor[] sceneAnchors = FindObjectsOfType<OVRSceneAnchor>();
        if (sceneAnchors!= null)
        {
            for (int i = 0; i < sceneAnchors.Length; i++)
            {
                OVRSceneAnchor instance = sceneAnchors[i];
                OVRSemanticClassification classification = instance.GetComponent<OVRSemanticClassification>();

                if (classification.Contains(OVRSceneManager.Classification.Floor))
                {
                    if (OVRPlugin.GetSpaceBoundary2D(instance.Space, out Vector2[] boundaryVertices))
                    {
                        _cornerPoints.Clear();
                        for (int j = 0; j < boundaryVertices.Length; j++)
                        {
                            Vector3 vertPos = new Vector3(-boundaryVertices[j].x, boundaryVertices[j].y, 0.0f);
                            // use world position
                            _cornerPoints.Add(instance.transform.TransformPoint(vertPos));
                        }
                        //CreateBoundaryDebris(instance.transform, _cornerPoints.ToArray());
                        //CreateExteriorDebris(instance.transform);
                    }
                }
            }
        }
    }

    /// <summary>
    /// Scatter debris along the floor perimeter.
    /// 바닥 둘레를 따라 파편을 흩뿌립니다.
    /// </summary>
    void CreateBoundaryDebris(Transform floorTransform, Vector3[] boundaryVertices)
    {
        // "walk" around the room perimeter, creating debris along the way
        // 방 주변을 "걸어서" 돌아다니면서 잔해를 만듭니다
        float accumulatedLength = 0.0f;

        GameObject boundarydebris = new GameObject("BoundaryDebris");
        boundarydebris.transform.SetParent(floorTransform, false);
        for (int i = 0; i < boundaryVertices.Length; i++)
        {
            int nextId = (i == boundaryVertices.Length - 1) ? 0 : i + 1;
            Vector3 vecToNext = boundaryVertices[nextId] - boundaryVertices[i];

            while (accumulatedLength < vecToNext.magnitude)
            {
                Vector3 debrisPos = boundaryVertices[i] + vecToNext.normalized * accumulatedLength;

                // add noise
                boundaryVertices[i] += (new Vector3(Random.Range(-1.0f, 1.0f), 0, Random.Range(-1.0f, 1.0f)).normalized * _boundaryNoiseDistance);

                CreateDebris(debrisPos, floorTransform, Random.Range(0.5f, 1.0f));

                accumulatedLength += (_averageSpacing + _averageSpacing * Random.Range(-0.5f, 0.5f));
                if (accumulatedLength >= vecToNext.magnitude)
                {
                    accumulatedLength = 0.0f;
                    break;
                }
            }
        }
    }

    /// <summary>
    /// Scatter debris on the floor in a noisy grid pattern, outside of the room.
    /// 방 밖에서 시끄러운 격자 무늬로 바닥에 파편을 흩뿌립니다.
    /// </summary>
    void CreateExteriorDebris(Transform floorTransform)
    {
        GameObject exteriorDebris = new GameObject("ExteriorDebris");
        exteriorDebris.transform.SetParent(floorTransform, false);

        GameObject[,] _debrisObjects = new GameObject[_cellCount, _cellCount];
        float cellSize = _averageSpacing;
        float mapSize = _cellCount * cellSize;
        float cHalf = cellSize * 0.5f;
        Vector3 roomCenter = floorTransform.position;
        Vector3 mapOffset = new Vector3(-mapSize * 0.5f, 0, -mapSize * 0.5f);
        Vector3 cellOffset = new Vector3(cellSize * 0.5f, 0, cellSize * 0.5f);
        Vector3 centerOffset = roomCenter + mapOffset + cellOffset;
        for (int x = 0; x < _cellCount; x++)
        {
            for (int y = 0; y < _cellCount; y++)
            {
                // % chance of this cell having an object
                // % 이 세포가 물체를 가지고 있을 가능성
                bool spawnDebris = Random.Range(0.0f, 1.0f) <= _debrisDensity;

                // offset the grid point with random noise
                Vector3 cellCenter = centerOffset + new Vector3(x * cellSize, 0, y * cellSize);
                Vector3 randomOffset = new Vector3(Random.Range(-cHalf, cHalf), 0, Random.Range(-cHalf, cHalf));
                Vector3 desiredPosition = cellCenter + randomOffset;

                // only spawn an object if the position is outside of the room
                // 위치가 룸 외부에 있는 경우에만 개체를 생성합니다
                if (spawnDebris && !IsPositionInRoom(desiredPosition))
                {
                    // shrink object, based on distance from grid center
                    // 축소 개체, 그리드 중심으로부터의 거리를 기준으로
                    float distanceSize = Mathf.Abs(Vector3.Distance(roomCenter, desiredPosition));
                    distanceSize /= (mapSize * 0.5f);
                    distanceSize = Mathf.Clamp01(distanceSize);
                    // remap 0...1 to 1.5...1 재매핑
                    distanceSize = (1 - distanceSize) * 0.5f + 1;

                    _debrisObjects[x, y] = CreateDebris(desiredPosition, floorTransform, Random.Range(0.5f, 1.0f) * distanceSize);
                }
                else
                {
                    _debrisObjects[x, y] = null;
                }
            }
        }
    }

    /// <summary>
    /// Instantiate a random debris object with a random rotation and provided scale.
    /// 무작위 회전 및 제공된 스케일로 무작위 잔해 개체를 인스턴스화합니다.
    /// </summary>
    GameObject CreateDebris(Vector3 worldPosition, Transform parent, float uniformScale)
    {
        GameObject newObj = Instantiate(_debrisPrefabs[Random.Range(0, _debrisPrefabs.Length)], parent);
        newObj.transform.position = worldPosition;
        newObj.transform.rotation = Quaternion.Euler(0, Random.Range(0, 360.0f), 0);
        newObj.transform.localScale = Vector3.one * uniformScale;
        return newObj;
    }

    /// <summary>
    /// Given a world position, test if it is within the floor outline (along horizontal dimensions)
    /// 월드 위치가 주어진 경우 바닥 윤곽선 내에 있는지 테스트합니다(수평 치수를 따라)
    /// </summary>
    bool IsPositionInRoom(Vector3 worldPosition)
    {
        // Shooting a ray from worldPosition to the right (X+), count how many walls it intersects.
        // If the count is odd, the position is in the room
        // Unfortunately we can't use Physics.RaycastAll, because the collision may not match the mesh, resulting in wrong counts
        // worldPosition에서 오른쪽(X+)으로 광선을 쏘면 몇 개의 벽이 교차하는지 세어봅니다.
        // 카운트가 홀수인 경우 위치는 방에 있습니다
        // 안타깝게도 Physics.RaycastAll을 사용할 수 없습니다. 충돌이 메쉬와 일치하지 않아 잘못된 카운트가 발생할 수 있기 때문입니다
        int lineCrosses = 0;
        for (int i = 0; i < _cornerPoints.Count; i++)
        {
            Vector3 startPos = _cornerPoints[i];
            Vector3 endPos = (i == _cornerPoints.Count - 1) ? _cornerPoints[0] : _cornerPoints[i + 1];

            // get bounding box of line segment
            float xMin = startPos.x < endPos.x ? startPos.x : endPos.x;
            float xMax = startPos.x > endPos.x ? startPos.x : endPos.x;
            float zMin = startPos.z < endPos.z ? startPos.z : endPos.z;
            float zMax = startPos.z > endPos.z ? startPos.z : endPos.z;
            Vector3 lowestPoint = startPos.z < endPos.z ? startPos : endPos;
            Vector3 highestPoint = startPos.z > endPos.z ? startPos : endPos;

            // it's vertically within the bounds, so it might cross
            // 수직으로 경계 내에 있기 때문에 교차할 수 있습니다
            if (worldPosition.z <= zMax &&
                worldPosition.z >= zMin)
            {
                if (worldPosition.x <= xMin)
                {
                    // it's completely to the left of this line segment's bounds, so it must intersect
                    // 이 선분의 경계에서 완전히 왼쪽이므로 교차해야 합니다
                    lineCrosses++;
                }
                else if (worldPosition.x < xMax)
                {
                    // it's within the bounds, so further calculation is needed
                    // 범위 내에 있으므로 추가 계산이 필요합니다
                    Vector3 lineVec = (highestPoint - lowestPoint).normalized;
                    Vector3 camVec = (worldPosition - lowestPoint).normalized;
                    // polarity of cross product defines which side the point is on
                    // 교차 제품의 극성은 점이 어느 쪽에 있는지를 정의합니다
                    if (Vector3.Cross(lineVec, camVec).y < 0)
                    {
                        lineCrosses++;
                    }
                }
                // else it's completely to the right of the bounds, so it definitely doesn't cross
                // 그렇지 않으면 완전히 경계의 오른쪽에 있기 때문에 절대 교차하지 않습니다
            }
        }
        return (lineCrosses % 2) == 1;
    }
}

 

 

3. 공 생성

 

벽을 파괴하기 위해 필요한 공 생성에 대해서는 아래 부분과 같습니다.

 

아래 스크립트를 보시면 오른쪽 트리거를 눌렸을 때 공이 오른쪽 손 앞 쪽에 생성하게 해놓았습니다. (원래는 공을 날리는 총 스크립트지만 변형해놓았습니다)

using UnityEngine;

public class SampleShooter : MonoBehaviour
{
    public GameObject _gunPrefab;
    public GameObject _bulletPrefab;
    public Transform _leftHandAnchor;
    public Transform _rightHandAnchor;

    GameObject _leftGun;
    GameObject _rightGun;

    void Start()
    {
        _leftGun = Instantiate(_gunPrefab);
        _leftGun.transform.SetParent(_leftHandAnchor, false);
        _rightGun = Instantiate(_gunPrefab);
        _rightGun.transform.SetParent(_rightHandAnchor, false);
    }

    // Update is called once per frame
    void Update()
    {
        if (OVRInput.GetDown(OVRInput.RawButton.LIndexTrigger))
        {
            //ShootBall(_leftGun.transform.position, _leftGun.transform.forward);
            //PlayShootSound(_leftGun);
        }
        if (OVRInput.GetDown(OVRInput.RawButton.RIndexTrigger))
        {
            Vector3 ballPos = _rightGun.transform.position + _rightGun.transform.forward * 0.1f;
            Instantiate(_bulletPrefab, ballPos, Quaternion.identity);
            //ShootBall(_rightGun.transform.position, _rightGun.transform.forward);
            //PlayShootSound(_rightGun);
        }
    }

    public void ShootBall(Vector3 ballPosition, Vector3 ballDirection)
    {
        Vector3 ballPos = ballPosition + ballDirection * 0.1f;
        GameObject newBall = Instantiate(_bulletPrefab, ballPos, Quaternion.identity);
        Rigidbody _rigidBody = newBall.GetComponent<Rigidbody>();
        if (_rigidBody)
        {
            _rigidBody.AddForce(ballDirection * 3.0f);
        }
    }

    void PlayShootSound(GameObject gun)
    {
        if (gun.GetComponent<AudioSource>())
        {
            gun.GetComponent<AudioSource>().time = 0.0f;
            gun.GetComponent<AudioSource>().Play();
        }
    }
}

 

 

4. 공 설정

 

다음과 같습니다. 이때 사용자가 컨트롤러로 grab해서 잡을 수 있을려면 collider와 ovr grabbable 컴포넌트가 필요합니다.

 

위 컴포넌트 중 마지막 컴포넌트는 직접 만든 컴포넌트는 내용은 아래와 같습니다.

충돌한 객체에 저희가 붙여준 스크립트가 붙어있다면 파괴를 실행해주는 식입니다. 

이 부분은 태그로 해도 되고 방법은 여러가지가 있습니다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjCS : MonoBehaviour
{
    private bool isFirst = true;

    void OnCollisionEnter(Collision collision)
    {
        if(isFirst){
            CanvasController.Instance.ChangeText1(collision.gameObject.name + " " + collision.gameObject.tag);
            if (collision.gameObject.GetComponent<ClickOrTapToExplode>() != null)
            {
                collision.gameObject.GetComponent<ClickOrTapToExplode>().StartBreak();
                collision.gameObject.GetComponent<PassthroughCS>().OnBoxes();
            }
            isFirst = false;
        }
    }
}

 

그리고 추가적으로 유용한 코드에 대해 설명드리면 위에서 한번 나왔던 코드지만 아래와 방법을 통해 각 벽에 천장인지 바닥인지 등 알 수 있습니다.

OVRSceneAnchor[] sceneAnchors = FindObjectsOfType<OVRSceneAnchor>();
OVRSceneAnchor floorAnchor = null;
if (sceneAnchors != null)
{
	for (int i = 0; i < sceneAnchors.Length; i++)
	{
		OVRSceneAnchor instance = sceneAnchors[i];
		OVRSemanticClassification classification = instance.GetComponent<OVRSemanticClassification>();

		if (classification.Contains(OVRSceneManager.Classification.WallFace) ||
            classification.Contains(OVRSceneManager.Classification.Ceiling) ||
            classification.Contains(OVRSceneManager.Classification.DoorFrame) ||
            classification.Contains(OVRSceneManager.Classification.WindowFrame)
			classification.Contains(OVRSceneManager.Classification.Floor)) {}
    }
 }

 

 

5. 사용자가 grab 할 수 있게 설정

 

아래 사진과 같이 LeftHandAnchor / RightHandAnchor 에 옆에와 같은 컴포넌트를 설정해줍니다. 이때 collider grab 할 수 있는 범위를 나타냅니다.

 

위 상태까지 설정을 한 후 실행하면 잘 되는 것을 확인하지만 문제가 있는 것을 확인할 수 있습니다.

 

위 사진과 같이 손이랑 컨트롤러가 이미지 뒤로 가서 가리는 문제가 있습니다. 해당 문제는 레이어가 실물 위로 올라가도록 기본적으로 설정되있기 때문입니다. 해당 문제를 방지하기 위해서는 기본적으로 손에 오브젝트를 붙여주게 됩니다.

 

 

7. 실제 사용자 손에 3D 오브젝트 손 달기

 

정확히는 손 오브젝트가 아닌 oculus에서 기본적으로 제공해주는 컨트롤러 3D 오브젝트를 달아보겠습니다 먼저 LeftHandAnchor 와 RightHandAnchor 아래에 기본적으로 제공하는 OVRHandPrefab 을 넣어주고 아래처럼 손 위치에 맞게 바꿔줍니다.

 

그 다음으로는 LeftControllerAnchor 와 RightControllerAnchor 아래에 OVRControllerPrefab 을 넣어주고 아래처럼 손 위치에 맞게 설정해주면 끝입니다!

 

 

결과 영상 :

 

 

긴 글 읽어주셔서 감사합니다.

반응형

댓글