'전체 글'에 해당되는 글 1801건

  1. 2024.01.18 XRI Slicing Object (1월 4째주 방송예정)
  2. 2024.01.15 Move, Turn, Teleport, Climb (1월 3주차 방송 예정)
  3. 2024.01.15 XR Interaction Toolkit 2.4.3 환경 설정 OpenXR
  4. 2024.01.14 가상 현실 환경을 위한 인터페이스 설계
  5. 2024.01.14 UI Interaction (01-14방송)
  6. 2024.01.10 UI Interaction (Slider with Handle) Ray, Poke Interaction

XRI Slicing Object (1월 4째주 방송예정)

VR/XR Interaction Toolkit 2024. 1. 18. 10:24
반응형

오늘은 VR Fruit Ninja라는 게임에서 오브젝트를 잘라내는 부분만 구현 해보겠습니다.

Fruit Ninja

 
Unity3D 게임 엔진용 오픈 소스 슬라이서 프레임워크인 Ezy - Slice를 소개 합니다.
 
평면을 사용하여 볼록한 메시를 슬라이스하는 기능
원활한 절단을 위한 UV/일반/접선 공간 보간 유연하고 문서화된 API
외부 플러그인 종속성이 없으며 완전히 C#으로 작성되었습니다. 
 
사용 중인 알고리즘
볼록 슬라이스의 단면 삼각측량을 위한 범용 모노톤 체인
UV/Normal/Tangent 공간 보간을 위한 무게 중심 좌표
슬라이싱에 대한 모든 일반적인 경우를 포괄하기 위해 특수 목적으로 만들어진 삼각형-평면 교차점
성능을 고려한 설계
 
자세한 내용은 다음 깃허브 페이지를 참고 하시면 될듯 합니다.

https://github.com/DavidArayan/ezy-slice

 
 


 
먼저 이지 슬라이스를 테스트 해보기 위해 새로운 씬을 생성한뒤 
 
큐브하나를 만들어 줍니다.

이어서 Plane하나를 만들고 큐브를 관통하게 위치 시킵니다. 
크기도 조절해주세요

 
 Plane의 이름은 Slicer로 변경합니다.

 
Slicer 스크립트를 생성한뒤 다음과 같이 작성 합니다.

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

public class Slicer : MonoBehaviour
{
    public Material afterSliceMaterial;
    public LayerMask sliceMask;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Slice();
        }
    }

    public void Slice()
    {
        Collider[] objectsToSlice = Physics.OverlapBox(transform.position, new Vector3(0.3f, 0.3f, 0.3f),
            transform.rotation, sliceMask);

        Debug.LogFormat("objectsToSlice.Length: {0}", objectsToSlice.Length);
        
        foreach (Collider col in objectsToSlice)
        {
            SlicedHull slicedObject = SliceObject(col.gameObject);

            Debug.LogFormat("slicedObject: {0}", slicedObject);
            
            var upperHullGo = slicedObject.CreateUpperHull(col.gameObject, afterSliceMaterial);
            var lowerHullGo = slicedObject.CreateLowerHull(col.gameObject, afterSliceMaterial);

            Debug.LogFormat("upperHullGo: {0}", upperHullGo);
            Debug.LogFormat("lowerHullGo: {0}", lowerHullGo);

            upperHullGo.transform.position = col.transform.position;
            lowerHullGo.transform.position = col.transform.position;

            ApplyPhysical(upperHullGo);
            ApplyPhysical(lowerHullGo);
            Destroy(col.gameObject);
        }
    }

    private void ApplyPhysical(GameObject go)
    {
        go.AddComponent<MeshCollider>().convex = true;
        go.AddComponent<Rigidbody>();
        //go.AddComponent<DestroyAfterSeconds>();
    }

    private SlicedHull SliceObject(GameObject go)
    {
        return go.Slice(transform.position, transform.up, afterSliceMaterial);
    }
}

 
 
Slicer오브젝트를 선택하고 스크립트를 부착 합니다.

 
After Slice Material은 잘려진 단면을 채울 메터리얼입니다.
Slice Mask는 슬라이드 대상 레이어 마스크입니다.
 
새로운 레이어 Sliceable을 추가 하고 다음과 같이 설정 합니다.

 
큐브를 선택후 레이어를 Sliceable로 변경 합니다.

 
실행후 스페이스바를 눌러봅니다.


 
어떻게 하면 칼로 베는 듯한 연출을 할수 있을까?

 
아이디어 

  • 칼에 Slice Plane을 붙이고 물체와 충돌했을때 단면을 자르면 되겟구나?

 
 
다음과 같이 칼 에셋을 가져와 설치 하고 

어떻게 테스트 할지 손으로 해본다 

 
 
다음과 같은 구조로 만들어 주고

콜라이더를 추가 합니다.

 
 
SliceListener스크립트를 만들고 다음과 같이 작성후 

using System;
using System.Collections;
using System.Collections.Generic;
using EzySlice;
using UnityEngine;
using UnityEngine.UIElements;

public class SliceListener : MonoBehaviour
{
    public Slicer slicer;
    
    private void OnTriggerEnter(Collider other)
    {
        Debug.LogFormat("OnTriggerEnter: {0}", other);
    }
}

 
Collider에 붙여 줍니다.

 
다음과 같이 수정해줍니다.

 
물체에 닿았을때 다음과 같이 나오면 됩니다.

 
SliceListener를 선택후 Slice Listener컴포넌트의 Slicer속성에 Slicer오브젝트를 넣어 줍니다.

 
스크립트를 수정후 
 
Slicer

public bool isTouch = false;
private void Update()
{
    if (isTouch)
    {
        Slice();
    }
}

 
 
SliceListener 

using System;
using System.Collections;
using System.Collections.Generic;
using EzySlice;
using UnityEngine;
using UnityEngine.UIElements;

public class SliceListener : MonoBehaviour
{
    public Slicer slicer;
    
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "SliceObject")
        {
            Debug.LogFormat("OnTriggerEnter: {0}", other);
            slicer.isTouch = true;
        }
    }
}

 
태그 (SliceObject)를 추가 후 Cube에 적용합니다.

 
HalfExtentsVisualizer 스크립트를 작성후 

using UnityEngine;

public class HalfExtentsVisualizer : MonoBehaviour
{
    public Vector3 halfExtents = new Vector3(1, 1, 1);
    public Color boxColor = Color.blue;  // 상자의 색깔을 설정합니다.
    public bool useWireframe = false;    // 상자를 와이어프레임으로 그릴지 여부를 결정합니다.

    void OnDrawGizmos()
    {
        // Gizmo 색상을 설정합니다.
        Gizmos.color = boxColor;

        // 오브젝트의 Collider로부터 중심 위치를 얻습니다.
        Collider collider = GetComponent<Collider>();
        Vector3 center = collider != null ? collider.bounds.center : transform.position;

        // Gizmo의 변환 행렬을 설정합니다. 오브젝트의 회전은 반영하지만, 스케일은 1로 고정합니다.
        Gizmos.matrix = Matrix4x4.TRS(center, transform.rotation, Vector3.one);

        // 상자를 그립니다. useWireframe 값에 따라 와이어프레임 또는 솔리드 상자를 그립니다.
        if (useWireframe)
        {
            Gizmos.DrawWireCube(Vector3.zero, 2.0f * halfExtents);  // 와이어프레임 상자를 그립니다.
        }
        else
        {
            Gizmos.DrawCube(Vector3.zero, 2.0f * halfExtents);  // 솔리드 상자를 그립니다.
        }
    }
}

 
Slicer를 선택하고 

부착 합니다.

 
 
Slicer의 다음 부분을 수정 합니다.

 
 
테스트를 위해 카타나를 회전 시키고 

실행후 결과를 확인 합니다.

 


 
이제 잘려진 단면들이 흩어졌으면 좋겠네요 
 
Cube를 선택후 Rigidbody를 추가하고

 
Slicer스크립트를 다음과 같이 수정 합니다.

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

public class Slicer : MonoBehaviour
{
    public Material afterSliceMaterial;
    public LayerMask sliceMask;

    public bool isTouch = false;
    private void Update()
    {
        if (isTouch)
        {
            Slice();
        }
    }

    public void Slice()
    {
        Collider[] objectsToSlice = Physics.OverlapBox(transform.position, new Vector3(1.8f, 1.2f, 1.3f),
            transform.rotation, sliceMask);

        //Debug.LogFormat("objectsToSlice.Length: {0}", objectsToSlice.Length);
        
        foreach (Collider col in objectsToSlice)
        {
            SlicedHull slicedObject = SliceObject(col.gameObject);

            Debug.LogFormat("slicedObject: {0}", slicedObject);
            
            var upperHullGo = slicedObject.CreateUpperHull(col.gameObject, afterSliceMaterial);
            var lowerHullGo = slicedObject.CreateLowerHull(col.gameObject, afterSliceMaterial);

            Debug.LogFormat("upperHullGo: {0}", upperHullGo);
            Debug.LogFormat("lowerHullGo: {0}", lowerHullGo);

            upperHullGo.transform.position = col.transform.position;
            lowerHullGo.transform.position = col.transform.position;

            var velocity = col.GetComponent<Rigidbody>().velocity;
                
            ApplyPhysical(upperHullGo, velocity);
            ApplyPhysical(lowerHullGo, velocity);
            
            Destroy(col.gameObject);
        }
    }

    private void ApplyPhysical(GameObject go, Vector3 velocity)
    {
        go.AddComponent<MeshCollider>().convex = true;
        var rbody = go.AddComponent<Rigidbody>();
        rbody.velocity = -velocity;

        int randX = UnityEngine.Random.Range(0, 3);
        int randY = UnityEngine.Random.Range(0, 3);
        int randZ = UnityEngine.Random.Range(0, 3);

        rbody.AddForce(1.5f * new Vector3(randX, randY, randZ), ForceMode.Impulse);
        
        go.AddComponent<DestroyAfterSeconds>();
    }

    private SlicedHull SliceObject(GameObject go)
    {
        return go.Slice(transform.position, transform.up, afterSliceMaterial);
    }
}

 
실행후 결과를 확인 합니다.

 
이제 모든 연구가 끝났습니다.


메인 카메라를 제거하고 
XR Origin을 생성후 Left, Right Controller를 선택해 XR Controller를 제외한 나머지 컴포넌트들을 지웁니다.

 
XR Controller의 Model Prefab을 None으로 설정후 

 
Right Controller에 카타나를 넣어줍니다.

 
Left Controller에는 핸드 모델을 넣어 줍시다 
 
Slicer를 선택하고 

Extents값을 수정후 

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

[RequireComponent(typeof(HalfExtentsVisualizer))]
public class Slicer : MonoBehaviour
{
    public Material afterSliceMaterial;
    public LayerMask sliceMask;
    private HalfExtentsVisualizer visualizer;
    public bool isTouch = false;

    private void Awake()
    {
        visualizer = GetComponent<HalfExtentsVisualizer>();
    }

    private void Update()
    {
        if (isTouch)
        {
            Slice();
        }
    }

    public void Slice()
    {
        Collider[] objectsToSlice = Physics.OverlapBox(transform.position, visualizer.halfExtents,
            transform.rotation, sliceMask);

        //Debug.LogFormat("objectsToSlice.Length: {0}", objectsToSlice.Length);
        
        foreach (Collider col in objectsToSlice)
        {
            SlicedHull slicedObject = SliceObject(col.gameObject);

            Debug.LogFormat("slicedObject: {0}", slicedObject);
            
            var upperHullGo = slicedObject.CreateUpperHull(col.gameObject, afterSliceMaterial);
            var lowerHullGo = slicedObject.CreateLowerHull(col.gameObject, afterSliceMaterial);

            Debug.LogFormat("upperHullGo: {0}", upperHullGo);
            Debug.LogFormat("lowerHullGo: {0}", lowerHullGo);

            upperHullGo.transform.position = col.transform.position;
            lowerHullGo.transform.position = col.transform.position;

            var velocity = col.GetComponent<Rigidbody>().velocity;
                
            ApplyPhysical(upperHullGo, velocity);
            ApplyPhysical(lowerHullGo, velocity);
            
            Destroy(col.gameObject);
        }
    }

    private void ApplyPhysical(GameObject go, Vector3 velocity)
    {
        go.AddComponent<MeshCollider>().convex = true;
        var rbody = go.AddComponent<Rigidbody>();
        rbody.velocity = -velocity;

        int randX = UnityEngine.Random.Range(0, 3);
        int randY = UnityEngine.Random.Range(0, 3);
        int randZ = UnityEngine.Random.Range(0, 3);

        rbody.AddForce(1.5f * new Vector3(randX, randY, randZ), ForceMode.Impulse);
        
        go.AddComponent<DestroyAfterSeconds>();
    }

    private SlicedHull SliceObject(GameObject go)
    {
        return go.Slice(transform.position, transform.up, afterSliceMaterial);
    }
}

실행후 결과를 확인 합니다.

 
 
이제 큐브를 활성화 후 앞으로 가져옵니다.
실행후 결과를 확인 합니다.

 
이제 왼손 X 버튼을 누르면 해당 위치에 다시 큐브가 생겨나도록 하고 칼에 붙어 있는 SlicerPlane의 메쉬 랜더러를 비활성화 합니다.
 
PrimaryButtonWatcher스크립트를 다음과 같이 작성후 

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.XR;

[System.Serializable]
public class PrimaryButtonEvent : UnityEvent<bool> { }

public class PrimaryButtonWatcher : MonoBehaviour
{
    public PrimaryButtonEvent primaryButtonPress;

    private bool lastButtonState = false;
    private List<InputDevice> devicesWithPrimaryButton;

    private void Awake()
    {
        if (primaryButtonPress == null)
        {
            primaryButtonPress = new PrimaryButtonEvent();
        }

        devicesWithPrimaryButton = new List<InputDevice>();
    }

    void OnEnable()
    {
        List<InputDevice> allDevices = new List<InputDevice>();
        InputDevices.GetDevices(allDevices);
        foreach(InputDevice device in allDevices)
            InputDevices_deviceConnected(device);

        InputDevices.deviceConnected += InputDevices_deviceConnected;
        InputDevices.deviceDisconnected += InputDevices_deviceDisconnected;
    }

    private void OnDisable()
    {
        InputDevices.deviceConnected -= InputDevices_deviceConnected;
        InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected;
        devicesWithPrimaryButton.Clear();
    }

    private void InputDevices_deviceConnected(InputDevice device)
    {
        bool discardedValue;
        if (device.TryGetFeatureValue(CommonUsages.primaryButton, out discardedValue))
        {
            devicesWithPrimaryButton.Add(device); // Add any devices that have a primary button.
        }
    }

    private void InputDevices_deviceDisconnected(InputDevice device)
    {
        if (devicesWithPrimaryButton.Contains(device))
            devicesWithPrimaryButton.Remove(device);
    }

    void Update()
    {
        bool tempState = false;
        foreach (var device in devicesWithPrimaryButton)
        {
            bool primaryButtonState = false;
            tempState = device.TryGetFeatureValue(CommonUsages.primaryButton, out primaryButtonState) // did get a value
                        && primaryButtonState // the value we got
                        || tempState; // cumulative result from other controllers
        }

        if (tempState != lastButtonState) // Button state changed since last frame
        {
            primaryButtonPress.Invoke(tempState);
            lastButtonState = tempState;
        }
    }
}

 
Left Controller를 선택후 

 
부착 합니다.

 
Main오브젝트를 생성하고 

 
Main 스크립트를 생성후 다음과 같이 작성후 부착 합니다.

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

public class Main : MonoBehaviour
{
    [SerializeField] private GameObject cubePrefab;
    [SerializeField] private Transform initPoint;

    public void CreateCube(bool press)
    {
        if (press)
        {
            var go = Instantiate(cubePrefab);
            go.transform.position = initPoint.position;
        }
    }
}

 
 
큐브를 프리팹화 시키고 Main에 넣어 줍니다.

 
 
씬에 있던 큐브는 제거 하고 원래 큐브가 있던 자리에 빈 오브젝트를 만들어 준후 메인에 InitPoint에 넣어줍니다.

 
이제 Left Controller를 선택후 

 
다음과 같이 Primary Button Watcher컴포넌트의 Button Press이벤트를 추가 후 Main을 넣고 Function을 선택 합니다.

Slicer를 선택후 Mesh Renderer를 비활성화 합니다.

 

using UnityEngine;

public class DestroyAfterSeconds : MonoBehaviour
{
    // duration 속성을 public으로 설정하여 Inspector에서 수정 가능하게 함
    public float duration = 2.0f;

    // Start is called before the first frame update
    void Start()
    {
        // duration 시간 후에 현재 게임 오브젝트를 파괴
        Destroy(gameObject, duration);
    }
}

 
모든 작업이 완료 되었습니다.
실행후 결과를 확인 합니다.
 

 
 

반응형
:

Move, Turn, Teleport, Climb (1월 3주차 방송 예정)

VR/XR Interaction Toolkit 2024. 1. 15. 17:09
반응형

 

 

 

Move

컨트롤러 기본형을 만들어 줍니다.

 

 


 

스탭 2. 컨트롤러 추가 

 

환경을 만들어 주고 

 

XR Origin을 선택후 Character Controller, Character Controller Driver컴포넌트를 추가 합니다.

 

손과 컨트롤러가 추적되는지 여부에 따라 런타임시 손과 컨트롤러 간의 교체가 필요 하다면 XR Input Modality Manager컴포넌트도 추가 합니다.

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-input-modality-manager.html


 

스텝 3. 로코모션 시스템 만들기 

 

XR Origin자식으로 빈오브젝트 (Locomotion System)을 생성 하고 Locomotion System컴포넌트를 부착 합니다.

 

 

 

로코모션 시스템 자식으로 빈오브젝트 (Move)를 생성한뒤 

Dynamic Move Provider를 부착 합니다.

 

각 손에 대한 사용자 선호도에 따라 이동의 전진 방향을 결정하는 기준 프레임을 자동으로 제어하는 동작 기반 연속 이동 버전입니다. 예를 들어 왼손에는 머리 상대 이동을 사용하고 오른손에는 컨트롤러 상대 이동을 사용하도록 구성할 수 있습니다.

 

참고

https://fistfullofshrimp.com/unity-vr-basics-2023-continuous-movement/

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/continuous-move-provider-action-based.html

 

 

XR Origin을 선택 하고 Character Controller Driver의 Locomotion Provider에 Move를 넣어 줍니다.


 

실행후 결과를 확인 합니다.

 

 


 

Turn

 

Locomotion System을 선택후 빈 오브젝트 (Turn)을 생성 합니다.

 

Snap Turn Provider를 부착 합니다.

(또는 Continuous Turn Provider)

 

 

인터렉터가 여러개 있다면 Right Controller를 선택하고 XR Interaction Group을 추가후 Action Based Controller Manager도 부착 합니다

 

왼손 컨트롤러는 이동, 오른손 컨트롤러는 회전을 하고 싶다면 

 

Locomotion System을 선택후 Dynamic Move Provider의 Right Hand Move Action속성의 Use Reference를 언체크 합니다.

 

Turn을 선택후 Snap Turn Provider의 Left Hand Snap turn Action의 Use Reference를 언체크 합니다.

실행후 결과를 확인 합니다.

 


 

텔레포트 

 

Locomotion System을 선택후 빈 오브젝트 (Teleportation)을 추가 합니다.

이어서 Teleportation Provider를 추가 합니다.

 

프로젝트 창에서 Teleport Interactor를 찾아 Right Controller에 넣어 줍니다.

Teleport Interactor 오브젝트를 선택후 XR Controller를 추가 합니다.

 

Right Controller를 선택후 

 

XR interaction Group컴포넌트를 부착 하고 

 

 

Action Based Controller Manager컴포넌트를 부착 합니다.

 

실행후 결과를 확인 합니다.

 


 

프로젝트 창에서 Teleport Anchor를 가져옵니다.

 


 

Locomotion System을 선택후 빈오브젝트 Climb를 생성하고 Climb Provider컴포넌트를 부착 합니다.

 

 

 

프로젝트 창에서 Direct Interactor를 검색해 Left / Right Controller에 넣어 줍니다.

 

 

Right Controller를 선택하고 XR Interaction Group의 Memebers를 다음과 같이 수정 합니다.

 

 

 

 


 

Move를 선택하고 

 

Dynamic Move Provider의 Gravity Application Mode를 Immediately를 선택 합니다.

 

실행후 결과를 확인 합니다.

이제 Climb 인터렉션을 하다가 그랩을 놓으면 중력의 영향을 바로 받아 떨어지게 됩니다.

 

 

반응형
:

XR Interaction Toolkit 2.4.3 환경 설정 OpenXR

VR/XR Interaction Toolkit 2024. 1. 15. 15:46
반응형

 

윈도우즈 11

유니티 2022.3.5f1

메타쿼스트 2 


https://youtu.be/_YC4nTnCY2Y?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

 

URP 프로젝트를 생성 합니다.

 

 

 

XR Plugin Management를 설치 합니다.

 

 

Provider는 Open XR을 선택 합니다.

Yes를 눌러 줍니다.

 

 

패키지 매니저를 열어 XR 을 검색후 XR Interaction Toolkit, XR Hands를 설치 합니다.

 

XR Interaction Toolkit의 Starter Assets 샘플과 Hands Interaction Demo 샘플을 설치 합니다.

 

XR Hands의 HandVisualizer 샘플도 설치 합니다.

 

 

Player Settings 를 열어 Project Validation에서 Fix All을 선택 합니다.

 

 

Features Menu 선택하라는 경고가 남으면 Edit를 눌러줍니다.

 

 

Interaction Profiles를 선택한뒤 다시 Project Validation으로 돌아갑니다.

 

다음과 같이 경고메시지가 없으면 모든 셋팅이 끝났습니다.

 


 

프리셋을 선택하고 Add 버튼을 눌러줍니다.

 

 

Project Settings를 열어 Preset Manager를 선택한뒤 

다음과 같이 Left, Right를 작성 합니다.

 

 

이제 Demo Scene을 열어 줍니다.

 

기기를 오큘러스 링크에 연결하고 실행 합니다.

 


HandVisualizer씬을 열어 줍니다.

 

 

다음과 같이 손이 핑크색으로 보이면 

 

 

메터리얼들을 선택 하고 

 

Edit > Rendering > Materials > Convert Selected Bulit-In Materials to URP를 선택 합니다.

 

Proceed를 눌러줍니다.

 

 

실행후 손이 안나오면 

https://docs.unity3d.com/Packages/com.unity.xr.hands@1.4/manual/index.html

 

 

 

프로젝트 셋팅즈를 열어 Hand Tracking Subsystem을 체크 합니다.


 

다음은 HandsDemoScene을 열어주고 

 

다음과 같이 핑크색이 보이면 

 

 

메터리얼들을 선택하고 컨버트 해줍니다.

 

 

 

 

 

오브젝트가 안잡히면 다음과 같이 설정 합니다.

https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.8/manual/features/metaquest.html

 

Meta Quest Support | OpenXR Plugin | 1.8.2

In order to deploy to Meta Quest, enable the Meta Quest Support feature on the Android build target: Open the Project Settings window (menu: Edit > Project Settings). Select the XR Plug-in Management from the list of settings on the left. If necessary, ena

docs.unity3d.com

 

반응형
:

가상 현실 환경을 위한 인터페이스 설계

VR/XR Interaction Toolkit 2024. 1. 14. 22:35
반응형

고려해야 할 사항 :-

가상 현실 상호 작용 디자인과 관련하여 가장 어려운 작업 중 하나는 사용자 인터페이스를 디자인하고 구현하는 것입니다. VR 인터페이스를 디자인하려면 3D 환경에 적응해야 하고, 사용성 원칙(VR에서는 그다지 명확하지 않음)을 존중해야 하며, 물론 심미적이고 시각적으로 매력적이어야 한다는 점을 고려해야 합니다.

가상 환경 설계 원칙 -

  • 개인 공간: 사람들은 실제 환경과 가상 환경 모두에서 개인 공간을 중요하게 생각합니다. 개인 공간을 침해하는 것은 불편하며 사용자의 신뢰를 손상시킬 수 있습니다. 모든 UI 요소를 사용자로부터 약 0.75m~3.5m 정도의 편안한 거리에 유지하여 개인 공간을 확보해야 합니다 .
  • 사용자 이동: 콘텐츠와 사용자 사이에 허용되는 최소 거리를 설계하는 것을 고려하세요. 이는 사용자가 개체나 다른 사용자에게 너무 가까이 이동하는 것을 방지하는 데 도움이 될 수 있습니다. 이는 클리핑 평면을 사용하여 플레이어 근처에 개체가 보이지 않게 만드는 방법으로 수행할 수 있습니다. 또는 디자이너는 사용자가 탐색할 수 있도록 장면에서 특정 순간 이동 목적지를 지시할 수도 있습니다.
  • 작업 영역: 인체공학적 환경을 조성합니다. "작업 영역"은 사용자가 대부분의 시간이 소요되는 정보를 소비하고 콘텐츠와 상호 작용하는 영역입니다. 머리 움직임을 최소화하고 쉬운 동작을 사용하여 피로를 피하십시오.
  • 시야각: 사용자가 머리를 너무 많이 움직이게 하면 근육 피로를 유발하고 멀미 가능성이 높아집니다. 평균 편안한 시야각은 사용자가 현재 보고 있는 곳에서 약 15~20° 떨어져 있습니다. 사용자가 편안하게 사용할 수 있는 영역 밖에 중요한 콘텐츠를 배치하지 마세요.

 

가상 환경과의 상호작용 -

가상 환경에서 다양한 종류의 작업을 처리하려면 사용자에게 다양한 종류의 상호 작용이 필요합니다. 하드웨어 VR 장치와 컨트롤러가 다르기 때문에 웨어러블마다 상호 작용도 다릅니다. 상호 작용 방법에 따라 두 가지 주요 내장 메뉴 범주가 있습니다. 즉, 한 손을 사용하는 범주와 양손을 사용하는 범주입니다. 

  • 한 손 상호 작용: 한 손 상호 작용이란 Vive 명령 중 하나만을 사용하여 메뉴를 조작한다는 의미입니다. 한 손 상호 작용의 두 가지 방법, 즉 동적 메뉴와 정적 메뉴를 구분할 수 있습니다. 정적 메뉴는 선택 항목 수가 적고 동시에 표시될 수 있는 경우 가장 좋은 옵션입니다. 그 좋은 예가 Vive용 Space Pirate Trainer입니다. 
  • 양손 상호 작용: 양손 상호 작용이란 손 컨트롤 중 하나에 내장된 메뉴가 표시되고 다른 손 컨트롤은 입력 장치로 사용되는 경우를 의미합니다. 이러한 종류의 상호 작용은 더욱 현실적이고 자연스럽습니다. 특히 폴더 또는 태블릿 효과를 달성하는 경우, 즉 상호 작용이 태블릿 또는 유사한 터치 가능 장치와의 일반적인 상호 작용을 재현하는 경우 더욱 그렇습니다. 

 

학습 :-

사용자에게 시각적 단서와 피드백 제공: 시각적 단서와 촉각 단서를 결합하여 주의를 유도하고 개체가 실제처럼 느껴지도록 하여 존재감을 강화합니다. 시각적 또는 청각적 설명 없이 햅틱 피드백을 사용하면 컨트롤러가 진동하면서 사용자가 컨트롤러를 내려다보게 됩니다. 진동이 시각 또는 청각 신호와 결합되면 사용자는 일반적으로 관계를 직관적으로 이해할 것입니다.

친숙한 UI 구성 요소: VR 애플리케이션은 익숙하지 않은 가상 환경에서 사용자의 소외감을 최소화하기 위해 친숙한 2D 인터페이스를 준수해야 합니다. 인터페이스와 인터랙션을 만들 때 2D 기반의 모바일과 웹 UI의 요소를 활용하면 익숙한 액션과 인터페이스에 익숙해져 3D 인터랙션을 더 잘 이해하고 해석할 수 있습니다.

 


참고 

https://www.linkedin.com/pulse/designing-interfaces-virtual-reality-environments-suyash-ekre

반응형
:

UI Interaction (01-14방송)

VR/XR Interaction Toolkit 2024. 1. 14. 21:56
반응형

https://youtu.be/CDrSGeiA1a8?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

새 씬을 만들고 메인 카메라를 지운후 XR Origin을 생성합니다.

이어서 Left, Right Controller를 선택하고 XR Controller를 제외한 나머지 컴포넌트들을 제거 합니다.

 

 

Left, Right Controller를 선택후 Model Prefab을 None으로 설정 합니다.

 

Left, Right Controller에 컨트롤러 모델을 넣어주고 

 

XR Controller의 Model프로퍼티에 넣어줍니다.

 

실행후 컨트롤러가 잘 나오는지 확인 합니다,

 


 

 

 

XR Origin을 선택후 Tracking Origin Mode를 Floor로 설정하고 

XR Origin, Camera Offset의 위치를 0으로 초기화 합니다.

 

빈오브젝트 (Flat UI)를 생성후 자식으로 XR > UI Canvas를 생성 해줍니다.

 

 

Flat UI의 위치와 크기를 조절 합니다.

 

함께 생성된 EventSystem의 XR UI Input Module컴포넌트가 부착 되고 Input System UI Actions에 내용이 채워져 있는지 확인 합니다.

 

채워져 있지 않다면 물음표 옆에 버튼을 눌러 XRI Default XR UI Input Module프리셋을 선택 합니다.

 

 

Canvas를 선택하고 가로 세로 길이와 Image컴포넌트를 부착 합니다.

 

 

캔버스 자식으로 버튼을 만들고 

 

트랜지션을 Color Tint로 변경(Default) 후 색을 설정 하고 Navigation을 None으로 설정해주세요 

 

Ray Interactor 프리팹에서 찾아 Left, Right Controller에 넣어 줍니다.

 

실행후 결과를 확인합니다.

이때 거리가 너무 가까우면 Flat UI를 선택해 Z 값을 수정 합니다.

 

 


 

 

버튼을 선택하고 

 

Image 컴포넌트의 Source Image를 None을 설정 한뒤 Color의 알파를 0으로 만들어 줍니다.

Canvas Renderer컴포넌트의 Cull Transparent Mesh를 언체크 합니다.

 

 

Button을 선택해 Image를 추가 하고 

 

앵커 프리셋을 사용해 스트레치 합니다.

 

Image컴포넌트의 Raycast Target을 언체크 합니다.

적당한 이미지를 Source Image에 넣고 메터리얼도 넣어주세요 

 

텍스트를 Image자식으로 넣고 

 

Extra Settings의 Raycast Target을 언체크 합니다.

 

이제 버튼을 선택해 

 

Target Graphic에 Image를 넣어 줍니다.

 

 

 

Image를 선택하고 Pos Z의 값을 -10정도 설정 합니다.

 

 

Button을 선택하고 

 

XR poke Follow Affordance컴포넌트를 부착후 Clamp To Max Distance를 체크 한후 Max Distance의 값을 10으로 설정 합니다.

 

Poke Follow Transform에 Image를 넣어 줍니다.

 

 

Flat UI를 선택후 Z 값을 수정 합니다.(너무 가깝지도 너무 멀지도 않게)

 

프로젝트 창에서 Poke Interactor프리팹을 찾아 Left, Right Controller에 넣어 줍니다.

Left Controller를 선택하고 XR Interaction Group컴포넌트를 부착 합니다.

Starting Group Memebers에 + 버튼을 눌러 필드를 두개 만든후 Poke Interactor -> Ray Interactor순으로 넣어 줍니다.

 

오른쪽도 동일하게 해줍니다.

 

Poke Interactor의 Point Point 자식으로 있는 실린더는 제거 합니다.

 

Poke Point를 선택해 메터리얼을 빨강으로 변경해주고 

 

위치를 컨트롤러 모델 앞쪽으로 이동 시켜 줍니다.

 

너무 멀다면 위치를 수정해주세요 

 

실행후 결과를 확인 합니다.

 

 

 


 

 

Flat UI를 선택하고 

 

XR Grab Interactable컴포넌트를 부착 합니다.

 

Throw On Detach는 언체크 합니다.

 

함께 부착되 Rigidbody의 Use Gravity는 언체크 하고 Is Kinematic은 체크 합니다.

 

Flat UI자식으로 빈오브젝트 Collider를 생성하고 

 

Box Collider를 부착 한후 size를 조절 합니다.

 

다시 Flat UI를 선택하고 XR Grab Interactable컴포넌트의 Colliders에 넣어 줍니다.

 

 

 

Flat UI를 선택후 자식으로 빈오브젝트 (Attach)를 생성한후 위치를 조절 합니다.

 

XR Grab Interactable의 Attach Transform에 넣어 줍니다.

 

 

Ray Interactor를 선택하고 Achor Controller프로퍼티의 Rotation Speed를 0으로 설정 합니다.

 

실행후 결과를 확인 합니다.

 


 

 

슬라이더 만들기 

버튼을 살짝 위로 올려주고 

 

크기를 정해 줍니다.

 

 

자식으로 Image(Fill Area Background)를 생성하고 앵커 프리셋으로 스트레치 합니다.

 

 

 

그 자식으로 Image( Fill)을 생성 합니다.

 

 

 

앵커 프리셋으로 알트만 눌러 왼쪽 으로 붙이고 위아래 스트레치 합니다.

 

 

Slider게임 오브젝트를 선택하고 Slider컴포넌트를 부착후 Fill Rect에 Fill오브젝트를 넣어 줍니다.

 

 

Value를 수정해 슬라이더가 정상동작 하는지 확인 합니다.

 

 

데코 하기 

슬라이더의 Transition의 컬러들을 변경후 Target Graphic 에 Fill을 넣어 주고 Navigation을 None으로 설정 합니다.

 

 

실행후 결과를 확인 합니다.

 

 

반응형
:

UI Interaction (Slider with Handle) Ray, Poke Interaction

VR/XR Interaction Toolkit 2024. 1. 10. 17:26
반응형

이전시간에 만들었던 부분부터 추가 하면서 만들겠습니다.

 

이번시간에 만들것은 Slider입니다.

보통의 슬라이더와 다른게 없기 때문에 편하게 만들어 주시면 됩니다.

 

 


 

 

빈 오브젝트를 만들고 

대략 정면에 오도록 위치 시키고 크기도 줄여 줍니다.

 

Flat UI를 선택후 우클릭하고 XR > UI Canvas를 생성 합니다.

 

이미지를 부착 합니다.

 

 

Canvas를 선택하고 빈오브젝트 (Slider)를 생성 합니다.

슬라이더 크기를 정해주고 

 

자식으로 빈 오브젝트 Fill Area Background를 생성 합니다.

 

 

Image컴포넌트를 부착하고 앵커 프리셋을 사용해 스트레치합니다.

 

 

Fill Area Background를 선택하고 자식으로 Image를 만들고 이름을 Fill로 변경 합니다.

앵커프리셋과 Pos X, Top, Pos Z, Width, Bottom을 0으로 설정 합니다.

 

 

 

이제 슬라이더를 선택하고 

 

Target Graphic과 Fill Rect에 Fill오브젝트를 넣어 줍니다.

 

슬라이더의 Value를 움직여 슬라이더가 잘 동작 하는지 확인 합니다.

 

테스트 해보기 

 

 

Slider를 선택해 Transition을 Color Tint로 설정후 

Normal Color 2096F3
Highlighted Color 1870B6
Pressed Color 58B0F6
Selected Color 2096F3

 

변경 합니다.

 

 

실행후 결과를 확인 합니다.

 

 


 

핸들 만들기 

 

Slider를 선택후 빈오브젝트 (Handle Slide Area)를 생성 합니다.

 

앵커 프리셋을 사용해 스트레치 합니다.

 

 

이미지를 하나 만들어 주고 

Handle이라는 이름으로 변경합니다.

Source Image를 넣어주고 

 

Set Native Size를 눌러줍니다.

 

앵커 프리셋을 오른쪽으로 옮겨주고 

 

핸들을 앵커에 반쯤 걸리게 합니다.

 

Slider를 선택하고 

 

Handle Rect에 넣어 줍니다.

 

 

 

Value를 조절해가며 핸들이 잘 위치 하는지 확인 합니다.

 

 

테스트 

 

 

테스트 

 

실행후 결과를 확인 합니다.

 

반응형
: