사용법

1. 빈 오브젝트 추가후 Video Capture 2 Local Example 스크립트를 Component로 준다.

2. 위 이지에서 보이듯 Previewer라는 프리팹을 생성한 엠티 오브젝트의 자식으로 만든다.

 

3. 아래 보이는 text는 휴대폰 어느곳에 동영상이 저장되는지 확인하기 위함이다. 

 

아래 코드는 경로 확인을 위한 코드를 기존 코드에 추가한 내용이다.

using NRKernal.Record;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

namespace NRKernal.NRExamples
{
    [HelpURL("https://developer.nreal.ai/develop/unity/video-capture")]
    public class VideoCapture2LocalExample : MonoBehaviour
    {
        public NRPreviewer Previewer;

        [SerializeField]
        private Text text;

        // Save the video to Application.persistentDataPath
        public string VideoSavePath
        {
            get
            {
                string timeStamp = Time.time.ToString().Replace(".", "").Replace(":", "");
                string filename = string.Format("Nreal_Record_{0}.mp4", timeStamp);
                string filepath = Path.Combine(Application.persistentDataPath, filename);
                return filepath;
            }
        }

        NRVideoCapture m_VideoCapture = null;

        void Start()
        {
            CreateVideoCaptureTest();
        }

        void CreateVideoCaptureTest()
        {
            NRVideoCapture.CreateAsync(false, delegate (NRVideoCapture videoCapture)
            {
                if (videoCapture != null)
                {
                    m_VideoCapture = videoCapture;
                }
                else
                {
                    Debug.LogError("Failed to create VideoCapture Instance!");
                }
            });
        }

        public void StartVideoCapture()
        {
            Resolution cameraResolution = NRVideoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
            Debug.Log(cameraResolution);

            int cameraFramerate = NRVideoCapture.GetSupportedFrameRatesForResolution(cameraResolution).OrderByDescending((fps) => fps).First();
            Debug.Log(cameraFramerate);

            if (m_VideoCapture != null)
            {
                Debug.Log("Created VideoCapture Instance!");
                CameraParameters cameraParameters = new CameraParameters();
                cameraParameters.hologramOpacity = 0.0f;
                cameraParameters.frameRate = cameraFramerate;
                cameraParameters.cameraResolutionWidth = cameraResolution.width;
                cameraParameters.cameraResolutionHeight = cameraResolution.height;
                cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;
                cameraParameters.blendMode = BlendMode.Blend;

                m_VideoCapture.StartVideoModeAsync(cameraParameters, OnStartedVideoCaptureMode);

                Previewer.SetData(m_VideoCapture.PreviewTexture, true);
            }
        }

        public void StopVideoCapture()
        {
            if (m_VideoCapture == null)
            {
                return;
            }
            Debug.Log("Stop Video Capture!");
            m_VideoCapture.StopRecordingAsync(OnStoppedRecordingVideo);
            Previewer.SetData(m_VideoCapture.PreviewTexture, false);
        }

        void OnStartedVideoCaptureMode(NRVideoCapture.VideoCaptureResult result)
        {
            Debug.Log("Started Video Capture Mode!");
            // 경로 확인을 위해 추가한 코드
            text.text = VideoSavePath;
            m_VideoCapture.StartRecordingAsync(VideoSavePath, OnStartedRecordingVideo);
        }

        void OnStartedRecordingVideo(NRVideoCapture.VideoCaptureResult result)
        {
            Debug.Log("Started Recording Video!");
        }

        void OnStoppedRecordingVideo(NRVideoCapture.VideoCaptureResult result)
        {
            Debug.Log("Stopped Recording Video!");
            m_VideoCapture.StopVideoModeAsync(OnStoppedVideoCaptureMode);
        }

        void OnStoppedVideoCaptureMode(NRVideoCapture.VideoCaptureResult result)
        {
            Debug.Log("Stopped Video Capture Mode!");
        }
    }
}

tip)동영상 파일 경로를 찾는데 많은 시간을 사용하였기에 추가적으로 경로에 관한 이야기를 하겠다.

파일 경로는 Android 폴더에 data 폴더 안에 com.회사이름.product_name 폴더안에 파일에 동영상이 저장된다.

https://developer.nreal.ai/develop/unity/android-quickstart

 

Home

Nreal strives to build mixed reality experiences for everyone and empower developers to create apps that can propel a new era of entertainment and productivity.

www.nreal.ai

 

일단 프로젝트를 생성한 후  NRSDK를 다운로드 한 후 임포트한다.

https://developer.nreal.ai/download

 

Home

Nreal strives to build mixed reality experiences for everyone and empower developers to create apps that can propel a new era of entertainment and productivity.

www.nreal.ai

 

다음으로 빌드 세팅을 클릭한 후 안드로이드 플랫폼을 선택한다.

1. Player Settings..에서 Player -> Other Settings -> Api Compatibility Level*을 .NET 4.x로 설정한다.

 

2. Resolution and Presentation -> Default Orientation -> Portrait 

휴대폰 모드를 세로 모드로 고정한다는 의미이다.

3. Auto Graphics API가 체크 해제되어있는지 확인한다.

4. Vulkan을 제거한다. Vulkan은 프로그램을 계발할 때마다. 항상 제거하고있는 느낌이 든다.

 

5. Company Name을 default이름에서 다른 이름으로 변경한다.

6. Minimum API Level과 Target API Level을 Android 8.0으로 맞춘다.

7. API Level 밑에보면 Write Permission 설정하는 부분이 있다 이 부분을 External (SDCard)로 변경한다.

8. 이번엔 프로젝트 세팅에서 Quality를 선택한 후 V Sync Count를 Don't Sync로 변경하여준다.

 

이번 글은 2개의 다른 마커 위에 각각 다른 가상화 객체를 만드는 방법을 다루었다. 기본적으로 nreal glasses에 대한 이해가 조금 필요하다.

 

1. NRCameraRig, NRInput 을 Hierarchy 창에 드래그&드랍

2. NRInput에서 Override Camera Center 부분을 NRCameraRig의 CenterCamera 드래그&드랍

 

3. 캔버스 추가 후 이벤트 시스템 제거

그리고 캔버스에서 Graphic Raycaster를 제거하고 Canvas Raycast Target을 추가한다.

캔버스에 버튼 등 인터렉션이 필요없으면 할필요 없다.

캔버스는 월드스페이스로 하고 nreal에서 제공하는 move with camera 스크립트를 컴포넌트로 주면 사용자 화면에 고정되게 된다. 거리 조정은 유니티 내에 실행하면서 해주어야 할 것이다. 

 

 

4. empty gameobject 추가후 Tracking Image Visualizer 이름의 속성으로 추가

 

Tracking Image Example Controller를 컴포넌트로 가져온다.

Text : 이미지의 인덱스를 화면에서 확인하기 위한 객체

tracking Image Visualize에서 위에는 엔진 아래는 자동차

Fit To Scan Overlay는 이미지를 탐색할 때 나오는 텍스트이다.

 

위 컨트롤러는 글쓴이가 수정한 코드이다. 월래라면 등록된 이미지가 탐지되면 그위에 가상화할 객체를 하나만 보여주는 것이지만 글쓴이는  2개의 이미지에 각각 서로 다른 객체를 가상화 하고 싶었다. 

아래는 수정된 코드이다. 

namespace NRKernal.NRExamples
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using UnityEngine;
    using UnityEngine.UI;


    /// <summary>
    /// Controller for TrackingImage example.
    /// </summary>
    [HelpURL("https://developer.nreal.ai/develop/unity/image-tracking")]
    public class TrackingImageExampleController : MonoBehaviour
    {


        public Text text;

        // A prefab for visualizing an TrackingImage.
        public TrackingImageVisualizer TrackingImageVisualizerPrefab_1;
        public TrackingImageVisualizer TrackingImageVisualizerPrefab_2;

        // The overlay containing the fit to scan user guide.
        public GameObject FitToScanOverlay;

        private Dictionary<int, TrackingImageVisualizer> m_Visualizers
            = new Dictionary<int, TrackingImageVisualizer>();

        private List<NRTrackableImage> m_TempTrackingImages = new List<NRTrackableImage>();

        public void Update()
        {
#if !UNITY_EDITOR
            // Check that motion tracking is tracking.
            if (NRFrame.SessionStatus != SessionState.Running)
            {
                return;
            }
#endif
            // Get updated augmented images for this frame.
            NRFrame.GetTrackables<NRTrackableImage>(m_TempTrackingImages, NRTrackableQueryFilter.New);

            // Create visualizers and anchors for updated augmented images that are tracking and do not previously
            // have a visualizer. Remove visualizers for stopped images.
            foreach (var image in m_TempTrackingImages)
            {
/*                UnityEngine.Debug.Log("image.GetDataBaseIndex() : " + image.GetDataBaseIndex());
                UnityEngine.Debug.Log("image.GetTrackableType() : " + image.GetTrackableType());
                UnityEngine.Debug.Log("image.GetTrackingState() : " + image.GetTrackingState());*/

                TrackingImageVisualizer visualizer = null;
                m_Visualizers.TryGetValue(image.GetDataBaseIndex(), out visualizer);
                if (image.GetTrackingState() == TrackingState.Tracking && visualizer == null)
                {
                    NRDebugger.Log("Create new TrackingImageVisualizer!");
                    // Create an anchor to ensure that NRSDK keeps tracking this augmented image.
                    
                    try
                    {
                        text.text = "Image Index : " + image.GetDataBaseIndex();
                        if (image.GetDataBaseIndex() == 4)
                        {
                            visualizer = (TrackingImageVisualizer)Instantiate(TrackingImageVisualizerPrefab_1, image.GetCenterPose().position, image.GetCenterPose().rotation);
                        }
                        else 
                        {
                            visualizer = (TrackingImageVisualizer)Instantiate(TrackingImageVisualizerPrefab_2, image.GetCenterPose().position, image.GetCenterPose().rotation);
                        }
                    }
                    catch(NullReferenceException e) {
                     }
                    visualizer.Image = image;
                    visualizer.transform.parent = transform;
                    m_Visualizers.Add(image.GetDataBaseIndex(), visualizer);
                }
                else if (image.GetTrackingState() == TrackingState.Stopped && visualizer != null)
                {
                    m_Visualizers.Remove(image.GetDataBaseIndex());
                    Destroy(visualizer.gameObject);
                }

                FitToScanOverlay.SetActive(false);
            }

        }

        public void EnableImageTracking()
        {
            var config = NRSessionManager.Instance.NRSessionBehaviour.SessionConfig;
            config.ImageTrackingMode = TrackableImageFindingMode.ENABLE;
            NRSessionManager.Instance.SetConfiguration(config);
        }

        public void DisableImageTracking()
        {
            var config = NRSessionManager.Instance.NRSessionBehaviour.SessionConfig;
            config.ImageTrackingMode = TrackableImageFindingMode.DISABLE;
            NRSessionManager.Instance.SetConfiguration(config);
        }
    }
}

 

여기서 추가한 핵심 코드는 image.GetDataBaseIndex() 코드이다. 

GetDataBaseIndex() 코드는 데이터베이스에 등록된 이미지의 인덱스 번호를 반환하는 메소드이다.

여기서 주의할 점이 있다. 유니티 내부에서 실행했을 때 이미지의 인덱스 번호와 AR 안경을 착용하고 봤을 때 이미지의 인덱스 번호가 다르다는 점이다 . 따라서 ar 안경을 착용하고 이미지의 인덱스 번호를 확인하기 위해 유니티 캔버스에 Text를 이용하여 직접 인덱스 번호를 GetDataBaseIndex를 사용하여 확인하길 바란다.

 

5. 트래킹 이미지 벌추얼라이저 만들기

위 이미지 처럼 엠티 오브젝트에 추적된 이미지 위에 가상화할 객체를 자식으로 넣어준다. 그리고 엠티 오브젝트에 컴포넌트로 

를 넣어주어야 한다. Axis 값은 자식으로 주었던 객체를 넣어주면 된다.

글쓴이는 예제 코드를 조금 수정하였다.

 

namespace NRKernal.NRExamples
{
    using UnityEngine;

    /// <summary>
    /// Uses 4 frame corner objects to visualize an TrackingImage.
    /// </summary>
    public class TrackingImageVisualizer : MonoBehaviour
    {
        // The TrackingImage to visualize.
        public NRTrackableImage Image;

/*        // A model for the lower left corner of the frame to place when an image is detected.
        public GameObject FrameLowerLeft;

        // A model for the lower right corner of the frame to place when an image is detected.
        public GameObject FrameLowerRight;

        /// A model for the upper left corner of the frame to place when an image is detected.
        public GameObject FrameUpperLeft;

        // A model for the upper right corner of the frame to place when an image is detected.
        public GameObject FrameUpperRight;*/

        public GameObject Axis;

        public void Update()
        {
            if (Image == null || Image.GetTrackingState() != TrackingState.Tracking)
            {
/*                FrameLowerLeft.SetActive(false);
                FrameLowerRight.SetActive(false);
                FrameUpperLeft.SetActive(false);
                FrameUpperRight.SetActive(false);*/
                Axis.SetActive(false);
                return;
            }

/*            float halfWidth = Image.ExtentX / 2;
            float halfHeight = Image.ExtentZ / 2;*/
/*            FrameLowerLeft.transform.localPosition = (halfWidth * Vector3.left) + (halfHeight * Vector3.back);
            FrameLowerRight.transform.localPosition = (halfWidth * Vector3.right) + (halfHeight * Vector3.back);
            FrameUpperLeft.transform.localPosition = (halfWidth * Vector3.left) + (halfHeight * Vector3.forward);
            FrameUpperRight.transform.localPosition = (halfWidth * Vector3.right) + (halfHeight * Vector3.forward);*/

            var center = Image.GetCenterPose();
            transform.position = center.position;
            transform.rotation = center.rotation;

/*            FrameLowerLeft.SetActive(true);
            FrameLowerRight.SetActive(true);
            FrameUpperLeft.SetActive(true);
            FrameUpperRight.SetActive(true);*/
            Axis.SetActive(true);
        }
    }
}

 글쓴이는 모서리 부분을 알 필요없이 이미지 중앙에다가 객체만 띠우고 싶어서 위와 같이 소스코드를 수정하였다. 

그러면 원하는 객체만 이미지 위에 나타나게 된다. 

 

 

그외 글쓴이는 엔진을 가상화 하였는데 그림자 때문에 엔진이 잘 보이지 않았다 그래서 아래 블로그를 참조 하였다.

<그림자 지우기>

https://mingyu0403.tistory.com/56

 

[Unity] 그림자 지우기

ㅇ 그림자가 그려지지 않게 하기 Cast Shadows - 그림자가 그려질 것인가 Receive Shadows - 다른 물체의 그림자가 나한테 그려질 것인가

mingyu0403.tistory.com

 

그리고 엔진이 나타나면서 좀더 집중력을 올리는 요소를 넣고자 소리도 아래와 같은 컴포넌트를 엔진 객체에 넣어주어 봤다.

Play On Awake에 체크 되어있으면 객체가 생성될 때 오디오가 재생된다. Loop를 하게 되면 객체가 일단 지워저도 계속 실행되는 것을 관찰하여 일단 Loop는 선택하지 않았다.

 

기본적으로 이런 식으로 세팅하면 된다고 한다.

인터넷 검색 결과 Configuration에서 Scripting Runtime Version을 찾을 수 있다고 한다. 하지만 없었는데 상위 버전에선Api Compatibility Level로 변경되었다 한다. 

 

'NRealGlass' 카테고리의 다른 글

[NRealGlass] Video capture  (0) 2020.11.19
NrealGlass 개발환경 세팅  (0) 2020.10.30
2개의 다른 마커에 다른 가상화 객체  (0) 2020.09.04

+ Recent posts