유니티 VR Full Body IK Setup with Hand Animation

저의 천사 발렘 유튜버님의 영상을 후루룩 해왔습니다!!!

 

필요한 재료

첫 번째로 Animation Rigging이 필요합니다!

더보기

IK Foot Solver

using UnityEngine;

public class IKFootSolver : MonoBehaviour
{
    public bool isMovingForward;

    [SerializeField] LayerMask terrainLayer = default;
    [SerializeField] Transform body = default;
    [SerializeField] IKFootSolver otherFoot = default;
    [SerializeField] float speed = 4;
    [SerializeField] float stepDistance = .2f;
    [SerializeField] float stepLength = .2f;
    [SerializeField] float sideStepLength = .1f;

    [SerializeField] float stepHeight = .3f;
    [SerializeField] Vector3 footOffset = default;

    public Vector3 footRotOffset;
    public float footYPosOffset = 0.1f;

    public float rayStartYOffset = 0;
    public float rayLength = 1.5f;
    
    float footSpacing;
    Vector3 oldPosition, currentPosition, newPosition;
    Vector3 oldNormal, currentNormal, newNormal;
    float lerp;

    private void Start()
    {
        footSpacing = transform.localPosition.x;
        currentPosition = newPosition = oldPosition = transform.position;
        currentNormal = newNormal = oldNormal = transform.up;
        lerp = 1;
    }
    
    void Update()
    {
        transform.position = currentPosition + Vector3.up * footYPosOffset;
        transform.localRotation = Quaternion.Euler(footRotOffset);

        Ray ray = new Ray(body.position + (body.right * footSpacing) + Vector3.up * rayStartYOffset, Vector3.down);

        Debug.DrawRay(body.position + (body.right * footSpacing) + Vector3.up * rayStartYOffset, Vector3.down);
            
        if (Physics.Raycast(ray, out RaycastHit info, rayLength, terrainLayer.value))
        {
            if (Vector3.Distance(newPosition, info.point) > stepDistance && !otherFoot.IsMoving() && lerp >= 1)
            {
                lerp = 0;
                Vector3 direction = Vector3.ProjectOnPlane(info.point - currentPosition,Vector3.up).normalized;

                float angle = Vector3.Angle(body.forward, body.InverseTransformDirection(direction));

                isMovingForward = angle < 50 || angle > 130;

                if(isMovingForward)
                {
                    newPosition = info.point + direction * stepLength + footOffset;
                    newNormal = info.normal;
                }
                else
                {
                    newPosition = info.point + direction * sideStepLength + footOffset;
                    newNormal = info.normal;
                }

            }
        }

        if (lerp < 1)
        {
            Vector3 tempPosition = Vector3.Lerp(oldPosition, newPosition, lerp);
            tempPosition.y += Mathf.Sin(lerp * Mathf.PI) * stepHeight;

            currentPosition = tempPosition;
            currentNormal = Vector3.Lerp(oldNormal, newNormal, lerp);
            lerp += Time.deltaTime * speed;
        }
        else
        {
            oldPosition = newPosition;
            oldNormal = newNormal;
        }
    }

    private void OnDrawGizmos()
    {

        Gizmos.color = Color.red;
        Gizmos.DrawSphere(newPosition, 0.1f);
    }

    public bool IsMoving()
    {
        return lerp < 1;
    }
}

 

더보기

IKTargetFollowVRRig

using UnityEngine;

[System.Serializable]
public class VRMap
{
    public Transform vrTarget;
    public Transform ikTarget;
    public Vector3 trackingPositionOffset;
    public Vector3 trackingRotationOffset;
    public void Map()
    {
        ikTarget.position = vrTarget.TransformPoint(trackingPositionOffset);
        ikTarget.rotation = vrTarget.rotation * Quaternion.Euler(trackingRotationOffset);
    }
}

public class IKTargetFollowVRRig : MonoBehaviour
{
    [Range(0,1)]
    public float turnSmoothness = 0.1f;
    public VRMap head;
    public VRMap leftHand;
    public VRMap rightHand;

    public Vector3 headBodyPositionOffset;

    void LateUpdate()
    {
        transform.position = head.ikTarget.position + headBodyPositionOffset;
        float yaw = head.vrTarget.eulerAngles.y;
        transform.rotation = Quaternion.Lerp(transform.rotation,Quaternion.Euler(transform.eulerAngles.x, yaw, transform.eulerAngles.z),turnSmoothness);

        head.Map();
        leftHand.Map();
        rightHand.Map();
    }
}

 

더보기

AnimateOnInput

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

[System.Serializable]
public class AnimationInput
{
    public string animationPropertyName;
    public InputActionProperty action;
}

public class AnimateOnInput : MonoBehaviour
{
    public List<AnimationInput> animationInputs;
    public Animator animator;

    void Update()
    {
        foreach (var item in animationInputs)
        {
            float actionValue = item.action.action.ReadValue<float>();
            animator.SetFloat(item.animationPropertyName, actionValue);
        }
    }
}

Arm, Leg IK 세팅 방법

IK 부착하는 방법은 예전 포스팅에서 다룬 적이 있습니다!

2022.11.06 - [Unity/VR(OVR SDK)] - Unity VR Arm IK

 

Unity VR Arm IK

패키지 매니저에 들어가서 Animation Rigging 패키지를 다운받는다 준비해둔 휴머노이드 모델에다가 Rig Builder를 추가하고 Animation을 삭제해준다 Bone Renderer를 추가해주고 모델의 Root부분부터 맨 아래

wlsdn629.tistory.com

2022.11.06 - [Unity/VR(OVR SDK)] - Unity VR Leg IK

 

Unity VR Leg IK

이번 시간엔 Leg Ik 설정하는 방법에 대해 알아보겠다 먼저 모델의 Animation Type을 Humanoid로 바꿔준다 그다음 믹사모같은데에서 Walk관련 애니메이션을 하나 다운받아준다 그 애니메이션 또한 휴머

wlsdn629.tistory.com

복습할 겸 처음부터 다시 적어보겠습니다!


모델을 눌러준 후 상단 [Animation Rigging] 탭을 눌러준 후 [Bone Renderer Setup]을 눌러줍니다!

자동으로 Bone Renderer 세팅이 된 것을 볼 수 있습니다!

 

그 다음으로 , Rig Builder 컴포넌트를 붙여줍니다!


다음으로 모델에 빈 오브젝트를 하나 생성해준 후 Rig컴포넌트를 붙여줍니다!

아까 만든 Rig builder에 드래그해서 할당시켜줍니다!


VR Rig IK 자식으로 총 5가지 종류의 IK를 만들어줍니다!

 

그 다음으로 Head IK을 제외한 모든 IK에 Two Bone IK Constraint 컴포넌트를 부착시켜줍니다!

 

Tip에 RightHand Rig을 찾아 할당시켜준 후 

점 3개를 눌러준 다음 Auto Setup From Tip Transform을 눌러줍니다! 그러면 자동으로 세팅이됩니다!!!!!

 

다음으로

Right Arm IK Target과 RightHand을 동시에 눌러준 후 Align Transform을 눌러줍니다!

 

Hint같은 경우에는 팔꿈치 뒤에 위치하도록 세팅했습니다!(Hint Setting에 대해서는 아직 잘 모르겠네요!, 주로 관절쪽, 즉 굽혀지는 곳에 세팅한다는데 Valem은 이상한곳에 세팅했더라고요...이유는 잘 모르겠습니다!)


IK_Target같은 경우에는 손이 해당 지점으로 위치하게 끔 유도하는 친구이고,

IK_Hint같은 경우에는 캐릭터의 팔이 휘는 방향을 안내하는 도우미 점이라고 생각하시면 되겠습니다!

Two Bone IK Contstaint 컴포넌트 같은 경우에는 2개의 Bone을 정의할 때 사용된다고 합니다! (그럼 3개의 Bone일 때는 어떡하지..?)

 

오른 팔, 왼팔, 오른 발, 왼발 다 똑같이 세팅해줍시다!

 

마지막으로 Leg Target에는 포스팅 맨 위에 적어놨던 IK Foot Solver 스크립트를 부착해줍니다!


Head IK 세팅 방법

Head IK 에는 Multi Parent Constraint 컴포넌트를 붙여줍니다!

1. 자식 오브젝트로 Head Target을 만들어 준 다음 Head에 align시켜줍니다!

2. 그 후에 Source Object에 Head Target을 할당시켜주고 Constrained Object에는 Head을 할당해줍니다!

 


VR Rig에 IK 할당하기

포스팅 맨 위 재료 부분에서 작성한 IK Target Follow VR Rig와 Animate On Input 스크립트를 만든 후 컴포넌트로 부착시켜줍니다!


XR Rig에 Main Camera, LeftHand Controller, RightHand Controller 오브젝트에 각각 자식 오브젝트를 하나씩 만들어줍니다! 

IK Target Follow VR Rig 컴포넌트는 위와 같이 세팅해줍니다! 

VR Target은 XR Rig를 뜻하며 , IK Target은 모델 Rig를 뜻합니다!

 

위와 같이 세팅을 한 후에 플레이 해봤는데 XR Rig가 이상하면 본인 프로젝트에 맞게 세팅하셔야 합니다!


Animation Hand

마지막으로 Animate On Input 스크립트는 다음과 같이 세팅해줍니다!

Rig Player Animator같은 경우에는

VR Rig Animator.unitypackage
0.00MB

위 파일을 다운받아 사용하시면 됩니다!

 

본인의 Model Rig Hand가 XR Rig에 일치하지 않는 경우 위와 같이 Transform을 변경해보시길 바랍니다!

 


결과 영상

춤 추거랏!