미디어 콘텐츠 스터디

12. 얼굴 메이커 만들기 본문

증강현실(Augmented Reality)/ARCore 사용하기

12. 얼굴 메이커 만들기

danmujicat 2023. 11. 11. 12:20

AR 개발을 위한 프로젝트를 준비하기 위해 Window | Package Manangement 메뉴를 선택하여  Package Manangement 창을 열고 AR 장치 플러그인을 설치합니다.

1장의  ARCore 개발을 위한 환경 구축하기 참고하세요

더보기
  • AR 장치용 XR plugins 설치하기
  • ARCore XR Plugin 설치하기
  • AR Foundation 패키지 설치하기
  • Input System 설치하기
  • Androd 플랫폼의 ARcore 설정하기
  • Player 설정하기

1. 기본 AR 장면 만들기

① File | Save As 메뉴를 선택하여  Assets/Scenes/ 폴더에 이름을 ARFaceMaker로  저장합니다.

② Hierarchy 창에서 Main Camera를 삭제합니다.

③ GameObject |  XR | AR Session 메뉴를 선택하여 Hierarchy 창에 AR Session을 추가합니다.

④ GameObject |  XR | XR Origin  메뉴를 선택하여 Hierarchy 창에 XR Origin 을 추가합니다.

 

1.1  AR Face Manager  설정하기

 

① Hierarchy 창에서 XR Origin오브젝트를 선택하고 Inspector 창에 Add Component 버튼을 클릭하여 AR Face Manager 컴포넌트를 추가합니다. 

② Hierarchy 창에서 GameObject | XR | AR Default Face을 선택하여 오브젝트를 추가하고 Inspector 창에서 Trasform 초기화하고 Mesh Collider 컴포넌트 삭제합니다.

-  AR Default Face를 선택하고  Inspector 창에서  Add Componet 버튼을 클릭하여 ChangeableFace 스립트를추가합니다. 

ChangeableFace.cs

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

public class ChangeableFace : MonoBehaviour
{
    GameObject currentPosePrefab;
    GameObject poseObj;

    Dictionary<GameObject, GameObject> accessories = new Dictionary<GameObject, GameObject>();

    ARFaceMeshVisualizer meshVisualizer;
    MeshRenderer renderer;

    private void Start()
    {
        meshVisualizer = GetComponent<ARFaceMeshVisualizer>();
        meshVisualizer.enabled = false;
        renderer = GetComponent<MeshRenderer>();
        renderer.enabled = false;
    }

    public void SetPosePrefab(GameObject prefab)
    {
        if (prefab == currentPosePrefab)
            return;

        if (poseObj != null) 
            Destroy(poseObj);

       currentPosePrefab = prefab;
       if (prefab != null)
            poseObj = Instantiate(prefab, transform, false);
    }

    public void AddAccessory(GameObject prefab)
    {
        GameObject obj;
        if (accessories.TryGetValue(prefab, out obj) && obj.activeInHierarchy)
        {
            obj.SetActive(false);
            return;
        }
        else if (obj != null)
        {
            obj.SetActive(true);
        }
        else
        {
            obj = Instantiate(prefab, transform, false);
            accessories.Add(prefab, obj);
        }
    }

    public void ResetAccessories()
    {
        foreach (GameObject prefab in accessories.Keys)
        {
            accessories[prefab].SetActive(false);
        }
    }

    public void SetMeshMaterial(Material mat)
    {
        if (mat == null)
        {
            meshVisualizer.enabled = false;
            renderer.enabled = false;
            return;
        }

        renderer.material = mat;
        meshVisualizer.enabled = true;
        renderer.enabled = true;
    }
}

-  AR Default Face를 선택하고  Inspector 창에서  Add Componet 버튼을 클릭하여 FaceRegionAttachments스립트를추가합니다. 

FaceRegionAttachments.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
using UnityEngine.XR.ARFoundation;
#if UNITY_ANDROID
using UnityEngine.XR.ARCore;
#endif

public class FaceRegionAttachments : MonoBehaviour
{
    ARFaceManager faceManager;
    ARFace face;

    Dictionary<ARCoreFaceRegion, GameObject> prefabs = new Dictionary<ARCoreFaceRegion, GameObject>();
    Dictionary<ARCoreFaceRegion, GameObject> objs = new Dictionary<ARCoreFaceRegion, GameObject>();

#if UNITY_ANDROID && !UNITY_EDITOR
    NativeArray<ARCoreFaceRegionData> faceRegions;
#endif

    private void Start()
    {
        faceManager = FindObjectOfType<ARFaceManager>();
        face = GetComponent<ARFace>();
    }

    public void SetRegionAttachment(ARCoreFaceRegion region, GameObject prefab)
    {
        GameObject obj;
        if (objs.TryGetValue(region, out obj))
        {
            GameObject currentPrefab = prefabs[region];
            Destroy(obj);
            prefabs.Remove(region);
            objs.Remove(region);
            if (prefab == currentPrefab)
                return;
        }

        obj = Instantiate(prefab);
        prefabs.Add(region, prefab);
        objs.Add(region, obj);
    }

    public void Reset()
    {
        foreach (ARCoreFaceRegion region in objs.Keys)
        {
            Destroy(objs[region]);
        }
        objs.Clear();
        prefabs.Clear();
    }

    private void Update()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        var subsystem = (ARCoreFaceSubsystem)faceManager.subsystem;
        if (subsystem == null)
            return;

        subsystem.GetRegionPoses(face.trackableId, Allocator.Persistent, ref faceRegions);
        for (int i = 0; i < faceRegions.Length; ++i)
        {
            GameObject obj;
            if (objs.TryGetValue(faceRegions[i].region, out obj))
            {
                obj.transform.localPosition = faceRegions[i].pose.position;
            }
        }

#endif
    }

    void OnDestroy()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (faceRegions.IsCreated)
            faceRegions.Dispose();
#endif
    }
}

 

③ Hierarchy 창에서 Project 창의 Prefabs 폴더로 AR Default Face  오브젝트를 드래그하여 프리팹으로 만듭니다.

 Hierarchy 창에서  AR Default Face   오브젝틀르 삭제합니다.

⑤ Hierarchy 창에서 XR Origin 오브젝트를 선택하고 Inspector 창에서  AR Face Manager 의 Face Prefab에 AR Default Face  프리팹을 설정합나다.

 

2. UI 만들기

2.1  기본 프레임 UI 만들기

  메인 메뉴에서  GameObject | UI | Canvas를 선택하여 Canvas를 추가하고 이름을 UI Canvas로 변경하고 Inspector 창에 Canvas Scaler의 UI Scale Mode를 Scale With Screen Size로 설정합니다. 

 메인 메뉴에서  GameObject | UI | Panel 를 선택하여 UI Canvas  하위에 Panel를 추가하고 이름을 App Title Panel로 변경합니다. 

메인 메뉴에서   GameObject | UI | Button를 선택하여  App Title Panel 하위에 Button을 추가하고  이름을 Home Button로 변경합니다.

- Image 컴포넌트의 Source Image  : home.png을 sprite로 변경하여 설정

home.png
0.01MB

메인 메뉴에서  GameObject | UI | Text를 선택하여   App Title Panel 하위에 Text를 추가하고 이름을 Title Text로 변경합니다.

    - Text Input : Face Maker

 

   메인 메뉴에서  GameObject | Create Empty를 선택하여 UI Canvas   하위에 GameObject를 추가하고 이름을 Startup UI로 변경하고  Inspector 창에 Add Component 버튼을 클릭하여 Canvas Renderer와 Canvas Group 컴포넌트를 추가합니다. 메인 메뉴에서  GameObject | UI | Text를 선택하여  Startup UI  하위에 Text를 추가합니다.

 

  메인 메뉴에서  GameObject | Create Empty를 선택하여 UI Canvas   하위에 GameObject을 추가하고 이름을 Main UI로 변경하고  Inspector 창에 Add Component 버튼을 클릭하여 Canvas Renderer와 Canvas Group 컴포넌트를 추가합니다. 

- Main UI 하위에  메인 메뉴에서  GameObject | UI | Panel을 선택하여  Panel  추가한후 이름을 MainMenu Panel로 변경합니다.

- Info Panel 하위에 메인 메뉴에서  GameObject | UI |  Scroll View 을 선택하여 Scroll View를 추가한 후  Scroll View를 선택하고 Inspector 창에  Image 컴포넌트를 삭제합니다. 

- Scroll View 하위에 Viewport를 선택하고 Inspector 창에  Image 컴포넌트를 삭제합니다.

- Scroll View하위에 Scrollbar Horizontal와 Scrollbar Vertical 오브젝트를 삭제합니다.

 

Scroll View=> Viewport 하위에 Content를 선택하고 Inspector 창에  Horizontal Layout Group와 Content Size Filter 컴포넌트를 추가합니다.

 

Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 Reset Button으로 변경하고 Inspector창에서 Image 컴포넌트를 삭제하고 Reset Button하위의 Text에 Reset을 입력합니다.

 

Icons.zip
0.45MB

Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 PlasticHead Button으로 변경하고 PlasticHead Button 하위의 Text를 삭제합니다。  Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 FacetHead Button으로 변경하고 FacetHead Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 HatAccessory Button으로 변경하고 HatAccessory Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 SunglassesAccessory Button으로 변경하고 SunglassesAccessory Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 DefaultFace Button으로 변경하고 DefaultFace Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 PhotoFace Button으로 변경하고 PhotoFace Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 PopFace Button으로 변경하고 PopFace Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 RobotFace Button으로 변경하고 RobotFace Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 Eyebrows Buttonn으로 변경하고 Eyebrows Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 Mustache Button으로 변경하고 Mustache Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 Scroll View=> Viewport=>  Content 하위에 GameObject | UI | Button를 선택하여 Button를 추가한후 이름을 Lips Button으로 변경하고 Lips Button 하위의 Text를 삭제합니다。 Inspector 창에 Image 컴포넌트에 Source Image를 설정합니다.

 

 Hierarchy 창에서 UI Canvas를 선택하고 인스펙터 창에서 Add Component 버튼을 클릭하여 UIController.cs 스크립트를 추가합니다.

UIController.cs

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

public class UIController : MonoBehaviour
{
  
    private Dictionary<string, CanvasGroup> uiPanels = new Dictionary<string, CanvasGroup>();
    CanvasGroup currentPanel;
    private static UIController Instance = null;

    public CanvasGroup[] canvasGroups;
    void Awake()
    {
       

        uiPanels.Add("Startup", canvasGroups[0]);       
        uiPanels.Add("Main", canvasGroups[1]);
        

        if (Instance == null)
        {
            Instance = this;
        }
        ResetAllUI();
    }

    public static void ShowUI(string name)
    {    
        Instance._ShowUI(name);
    }

    void _ShowUI(string name)
    {
        CanvasGroup panel;
        if (uiPanels.TryGetValue(name, out panel))
        {
            ChangeUI(uiPanels[name]);
        }
        else
        {
            Debug.LogError("Undefined ui panel " + name);
        }
    }

    void ResetAllUI()
    {
        foreach (CanvasGroup panel in uiPanels.Values)
        {
            panel.gameObject.SetActive(false);
        }
    }

    void ChangeUI(CanvasGroup panel)
    {
        if (panel == currentPanel)
            return;
        if (currentPanel)          
            currentPanel.gameObject.SetActive(false);
        currentPanel = panel;
        if (panel)         
            panel.gameObject.SetActive(true);
    }    

}

 - Hierarchy 창에서 UI Canvas를 선택하고 Inspector 창에서 UIController.cs 스크립트의 UI Panels에서 +버튼을 눌러서 Hierarchy 창의 UI Canvas  하위에 Startup UI와 Main UI를  설정합니다.

 

3. AR 오브젝트와 상호작용하기

 Hierarchy 창에서 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을Interaction Controller로 설정합니다. Transform은 초기화합니다.

  Hierarchy 창에서 Interaction Controller  오브젝트 하위에 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 Startup Mode로 설정합니다. Transform은 초기화합니다.  Inspector 창에 Add Component 버튼을 클릭하여 StartupMode.cs 컴포넌트를 추가합니다. 

StartupMode.cs

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

public class StartupMode : MonoBehaviour
{
    
    void OnEnable()
    {
        UIController.ShowUI("Startup");
    }

    void Update()
    {
        if (ARSession.state == ARSessionState.Unsupported)
        {
           
            InteractionController.EnableMode("Startup");
        }
        else if (ARSession.state >= ARSessionState.Ready)
        {          
                InteractionController.EnableMode("Main");
          
        }
    }
}

  Hierarchy 창에서 Interaction Controller  오브젝트 하위에 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 Main Mode로 설정합니다. Transform은 초기화합니다.  Inspector 창에 Add Component 버튼을 클릭하여 MainMode.cs 컴포넌트를 추가합니다

MainMode.cs 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
#if UNITY_ANDROID 
using UnityEngine.XR.ARCore;
#endif
public class FaceMainMode : MonoBehaviour
{
    [SerializeField] ARFaceManager faceManager;

    void OnEnable()
    {
        UIController.ShowUI("Main");
    }

    public void ChangePosePrefab(GameObject prefab)
    {
        foreach (ARFace face in faceManager.trackables)
        {
            ChangeableFace changeable = face.GetComponent<ChangeableFace>();
            if (changeable != null)
            {
                changeable.SetPosePrefab(prefab);
            }
        }
    }

    public void AddAccessory(GameObject prefab)
    {
        foreach (ARFace face in faceManager.trackables)
        {
            ChangeableFace changeable = face.GetComponent<ChangeableFace>();
            if (changeable != null)
            {
                changeable.AddAccessory(prefab);
            }
        }
    }

    public void ChangeMaterial(Material mat)
    {
        foreach (ARFace face in faceManager.trackables)
        {
            ChangeableFace changeable = face.GetComponent<ChangeableFace>();
            if (changeable != null)
            {
                changeable.SetMeshMaterial(mat);
            }
        }
    }

    public void ResetFace()
    {
        foreach (ARFace face in faceManager.trackables)
        {
            ChangeableFace changeable = face.GetComponent<ChangeableFace>();
            FaceRegionAttachments regionAttachments = face.GetComponent<FaceRegionAttachments>();
            if (changeable != null)
            {
                changeable.SetPosePrefab(null);
                changeable.ResetAccessories();
                changeable.SetMeshMaterial(null);
                regionAttachments.Reset();
            }
        }
    }


    public void SetNoseAttachment(GameObject prefab)
    {
        SetRegionAttachment(ARCoreFaceRegion.NoseTip, prefab);
    }

    public void SetForeheadLeftAttachment(GameObject prefab)
    {
        SetRegionAttachment(ARCoreFaceRegion.ForeheadLeft, prefab);
    }

    public void SetForeheadRightAttachment(GameObject prefab)
    {
        SetRegionAttachment(ARCoreFaceRegion.ForeheadRight, prefab);
    }

    private void SetRegionAttachment(ARCoreFaceRegion region, GameObject prefab)
    {
        foreach (ARFace face in faceManager.trackables)
        {
            FaceRegionAttachments regionAttachments = face.GetComponent<FaceRegionAttachments>();
            if (regionAttachments != null)
            {
                regionAttachments.SetRegionAttachment(region, prefab);
            }
        }
    }
}

 

 Hierarchy 창에서 Interaction Controller  오브젝트를 선택하고 Inspector 창에 Add Component 버튼을 클릭하여 InteractionController.cs 컴포넌트를 추가합니다. 

InteractionController.cs

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

public class InteractionController : MonoBehaviour
{
    

    private Dictionary<string, GameObject> interactionModes = new Dictionary<string, GameObject>();
    [SerializeField] private GameObject[] gameObjects;
    GameObject currentMode;
    private static InteractionController Instance = null;
   
    void Awake()
    {
      

        interactionModes.Add("Startup", gameObjects[0]);      
        interactionModes.Add("Main", gameObjects[1]);

        if (null == Instance)
        {
            Instance = this;
        }

        ResetAllModes();
    }

    void Start()
    {
        _EnableMode("Startup");
    }

    public static void EnableMode(string name)
    {
      
        Instance._EnableMode(name);
    }

    void _EnableMode(string name)
    {
        GameObject modeObject;
        if (interactionModes.TryGetValue(name, out modeObject))
        {
            StartCoroutine(ChangeMode(modeObject));
        }
        else
        {
            Debug.LogError("undefined mode named " + name);
        }
    }

    void ResetAllModes()
    {
        foreach (GameObject mode in interactionModes.Values)
        {
            mode.SetActive(false);
        }
    }

    IEnumerator ChangeMode(GameObject mode)
    {
        if (mode == currentMode)
            yield break;

        if (currentMode)
        {
            currentMode.SetActive(false);
            yield return null;
        }

        currentMode = mode;
        mode.SetActive(true);
    }

}

 

 Hierarchy 창에서 Interaction Controller  오브젝트를 선택하고 Inspector 창에서 Game Objects 항목에 +버튼을 클릭하여 항목을 추가합니다.

ARCoreOnly.cs

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

public class ARCoreOnly : MonoBehaviour
{
    private void Awake()
    {
#if !UNITY_ANDROID
        gameObject.SetActive(false);
#endif
#if UNITY_EDITOR
        Button button = GetComponent<Button>();
        button.interactable = false;
#endif
    }
}

4. UI의 Button에 Onclick() 이벤트 설정하기 

Stickers.zip
1.42MB

   Hierarchy 창에서 Main UI=>...=> Reset Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 - Hierarchy 창에서 Main UI=>...=> FacetHead Button 를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다.  

 - Hierarchy 창에서 Main UI=>...=> HatAccessory Button 를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 

 -Hierarchy 창에서 Main UI=>...=> HatAccessory Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 - Hierarchy 창에서 Main UI=>...=> SunglassesAccessory Button 를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다.  

 - Hierarchy 창에서 Main UI=>...=> DefaultFace Button 를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 Hierarchy 창에서 Main UI=>...=> PhotoFace Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 - Hierarchy 창에서 Main UI=>...=>PopFace Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다.  

 - Hierarchy 창에서 Main UI=>...=> RobotFace Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 Hierarchy 창에서 Main UI=>...=> Eyebrows Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

 - Hierarchy 창에서 Main UI=>...=>Mustache Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다.  

 - Hierarchy 창에서 Main UI=>...=> Lips Button를 선택하고 Inspector 창에서 On Click() 이벤트에 다음과 같이 설정합니다. 

Comments