최근 유니티를 시작했다.
취업 준비하기 바쁜데, 굳이굳이 유니티를 하는 이유는... 해커톤 예선에 통과했기 때문이다.
근데 프로젝트를 실현시키려면 유니티 밖에 방법이 없다.
확실히 AI가 코딩을 잘하게 된 뒤로는
새로운 프로젝트를 새로운 언어로 시작하기 위해 필요한 절차들이 많이 생략되는 거 같다.
개발 환경 세팅, 잘 모르는 라이브러리, 함수 검색 및 사용을 위한 코드 짜기 등등
다만, 짜여있는 코드를 빠르게 이해하고 적용하는 역량이 중요해진 것 같다.
AI라고 해서 만능도 아니고, 그 모든 복잡한 구성을 AI에게 먹일 수도 없을 뿐더러, AI는 내가 제시한 방법에 대해서 구현하기 때문에 전체적인 맥락에서 최적화된 방법을 제공해주지 않는다.
그래서 언어나 개발에 대한 기본적인 지식이 없다면, 정말 프롬프트를 잘짜지 않는 이상 막히는 순간인 오고야 만다.
AI 만능 궁시렁은 이만 줄이고 본격적으로 XR 고찰과 트러블 슈팅을 하려한다.
유니티는 '게임 엔진'이다. 다시 말해 게임을 위해 개발된 툴이라는 것이다.
따라서 게임이 아닌 분야에서 사용하기에는 굉장히 무겁다.
오버 테크놀러지아~
근데 우리팀에서 사용하는 기술들이 XR류의 기술들이고
관련 라이브러리들이 Unity에 매우 잘 준비되어 있다.
왜 Unity가 잘 돼 있냐고?
XR류 기술은 그래픽스, 트래킹 등과 밀접한 관련이 있는데
이런 기술들은 게임에서 주로 쓰이기 때문이다.
차라리 잘 됐다고 생각한다.
추후에 게임 팀 프로젝트도 있을 예정이고,
개인 프로젝트로 게임 만들고 싶다는 생각은 꾸준히 해 왔다.
이 참에 유니티 기본 사용 방법을 확실히 익히고 가려한다.
그리고 지금은 컴퓨터와 모바일 기기 기반의 웹/앱이 대세지만
아마 판도가 변하지 않을까 생각한다.
AR 글래스가 휴대폰 수준으로 많이 보급되면 그때부터는 게임 엔진을 사용해서 웹/앱이 구현되지 않을까? 개인적으로 생각한다.
아님 말고
각각을 증강/가상 현실이라고 부른다.
증강 현실은 현실의 화면에 컴퓨터 그래픽, 사운드를 덧입힌 것을 말한다.
대표적인 예로는 '포켓몬 고'가 있다.
물체를 비췄을 때 해당 물체의 정보를 떠오르게 한다거나 빈 공간에 뭔가를 띄우는 것 등 활용방식은 무궁무진하다.
가상 현실은 말 그대로 가상의 공간을 현실처럼 만드는 것을 말한다.
사실 넓은 의미에서는 우리가 늘 하는 게임들 역시 가상 현실이긴하다.
다만, VR기기 등을 통해 정말 새로운 공간에 온 것 같은 착각이 들게 만들고, 손으로 상호작용 가능하게 만든 것이 가상 현실에 더 가깝다.
이 둘을 섞은 MR. 혼합 현실도 있지만, 우리가 구현할 것과는 거리가 있으므로 패스하도록 한다.
virtual private server 아니다.
AWS에서 제공하는 그거 아니다.
Visual Positioning System
이라고 하는 GPS를 보완하는 기술이다.
GPS는 위성 항법 시스템으로 위성을 활용해서 지구상에서 나의 위치를 일종의 좌표로 나타내는 시스템이다.
사실 지구의 크기를 생각하면 굉장히 정확하지만, 그래도 미터 단위의 오차가 발생할 수 밖에 없다.
이 오차를 줄여주는 것이 VPS이다. VPS는 cm 단위까지 오차를 줄여준다.
원리는 Anchor라는 기준점을 두고 나의 위치를 시각적으로 계산하는 것이다.
다양한 위치에서 찍은 사진들, 특징점을 추출하여 지도랑 매칭시킨다.
다만, 시간적 한계에 부딪혀 우리는 이 기술을 쓰지 않기로 했다.
자료 조사 및 구현 중에 알게된 라이브러리/패키지 목록들이다.
꼭 유니티가 아니더라도 AR을 활용할 수 있긴하다.
다만 이제 안드로이드로 개발하기 위해 자바/코틀린을 알거나
IOS로 개발하기 위해 오브젝트C/스위프트를 알아야한다.
그리고 우리는 그럴 시간이 없었기 때문에 유니티를 선택했다.
포켓몬 그거다.
VPS를 활용하기 위해 찾은 패키지였으나, 시간 부족으로 인해 활용하지 않기로 했다.
Unity 가상 세계에서 물체의 좌표를 현실 세계의 GPS 좌표로 가져오기 위해, 혹은 현실 세계 GPS를 유니티 가상 세계로 변환하기 위해 필요한 패키지다.
코드는 AI가 대부분 짜서, 이해하는데 노력이 필요할 것 같다.
특정 함수를 비동기적으로 실행시키는 함수이다.
StartCoroutine(InitializeAndRunGps());
private IEnumerator InitializeAndRunGps()
{
if (!Input.location.isEnabledByUser) { yield break; }
Input.location.Start();
int maxWait = 20;
while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
{
yield return new WaitForSeconds(1);
maxWait--;
}
if (maxWait < 1 || Input.location.status == LocationServiceStatus.Failed) { yield break; }
IsInitialized = true;
while (true)
{
UpdateLocation();
yield return new WaitForSeconds(5.0f);
}
}
GPSService라는 싱글톤 인스턴스를 생성하고 이를 활용해서 유니티 앱에서 필요한 gps 정보를 가져온다.
마우스, 키와 같은 입력이나 gps, 가속도 같은 정보들도 가져올 수 있다.
핀토스에서 스레드를 다룰 때 봤던 yield가 떠오른다.
원리는 비슷한 것 같은데
break로 끝나는지, return으로 끝나는지에 따라 진행이 다르다.
break로 끝나면 함수를 호출 했을 때 처음부터 다시 실행하고
return으로 끝나면 함수를 호출 했을 때 return 다음부터 실행된다.
또한 만약에 while문 안에 yield가 있다면
while(true)
{
yield return new WaitForSeconds(1);
}
unity 한정으로
while문에서
yield return은 return 문 뒤의 객체를 Unity 엔진에 전달하고
unity 엔진은 그 객체를 해석해서 실행 시기를 결정한다.
사실 이 프로젝트에서 메시지 기능을 구현하는 것들이 가장 기술적인 챌린지가 되었는데,
이후 게시한 글에서 구현한 기능도 한 번 볼 만하다.
휴대폰 내장 기기는 생각보다 GPS를 정밀하게 잡지 못한다.
구글맵이나 카카오맵에 있는 나의 위치를 확인하면서, '어 GPS 생각보다 정밀한데?'라고 생각하면 그건 크나큰 착각이다.
정말 위치를 세밀하게 잡을 수 있도록 다양한 기술이 들어가있을 가능성이 농후하다.
아무튼 그래서 문제들은 다음과 같다.
1. 가만히 있는데도 불구하고 GPS 값이 갑자기 큰 폭으로 증가/감소하는 경우
2. GPS를 지속적으로 Update하면서 생기는 문제 (1번과 궤를 같이한다.)
(유니티에서 1프레임 당 Update라는 메소드를 한 번씩 돌리기 때문에 영어로 표현했다.)
3. GPS 기준으로 메시지를 렌더링할 때 생기는 메시지 깜빡임, 혹은 순간이동 문제
두 가지 문제는 해결 방법이 거의 비슷하기 때문에 같은 공간에 뒀다.
우선 AI의 힘을 빌려서 smoothingFactor(예: 0.1)로 보정 좌표를 업데이트하여 급격한 위치 변화 완화시켰다. 보정 좌표를 기준으로 가시성 판정, 스폰/언로드를 했다.
사실 이건 칼만 필터를 써도 아마 꽤나 괜찮은 결과가 나올 가능성이 높다.
그렇지만 그냥 안했다.
그리고 GPS 업데이트 주기를 10초로 변경했다.
해결 방법을 설명하기 전에 메시지 렌더링을 어떤 식으로 구현했는지 먼저 설명하는 것이 좋을 것 같다.
메시지 렌더링은 나의 GPS를 기준으로 반경 15m 안에 해당하는 메시지의 GPS가 있을 경우
해당 메시지를 오브젝트로 만드는 방식으로 구현했다.
물론, 이미 메시지가 있는지 확인하는 방법이 조금 정확하지 않았던 것 같다.
(ID값 기반의 Equal 메소드 오버라이드)
(활성화 메시지 딕셔너리의 ID 값 조회를 통한 불필요한 렌더링 빙지)
아직 생성되지 않은 메시지들에 대해서는 최신 GPS를 반영하여 스폰이 되도록 했다.
또한 메시지 오브젝트들이 ID 및 해시 테이블을 기반으로 렌더링되도록 변경하여, 만약 이미 렌더링된 메시지라면 급격스러운 GPS의 변화에도 렌더링 위치가 유지된다.
Unity에서 XR Origin의 Main Camera와 Cesium Georeference의 Dynamic Camera가 동시에 존재하면서, 카메라가 두 개의 좌표계를 기준으로 움직이려는 충돌이 발생했다.
결과적으로 다음과 같은 문제가 생겼다.
1. 카메라가 두 시스템(Cesium / XR)의 입력을 동시에 받아 위치가 불안정하게 튀거나 흔들림이 발생함
2. Main Camera는 여전히 실제 세상을 비추며 불필요한 에러 로그를 남김
3. Dynamic Camera는 가상의 세계를 렌더링하지만, 기기의 움직임(Gyroscope / ARPose)에 반응하지 않음
우선 문제의 핵심은 “어떤 카메라가 실제 기기의 움직임을 추적할 것인가”였다.
이를 명확히 하기 위해 다음 단계를 거쳤다.
XR Origin의 Main Camera는 실제 환경을 비추고 있어서 Cesium의 Dynamic Camera와 역할이 중복된다고 생각했다. 따라서 Main Camera를 비활성화하여 Cesium 전용 카메라(Dynamic Camera)만 남겼다.
Dynamic Camera를 XR Origin의 하위로 옮겨 AR 시스템의 위치 추적 계층(Origin Transform) 과 동기화시켰다. 이렇게 하면 CesiumGeoreference의 기준점 변경 시에도 Dynamic Camera가 XR Origin을 따라 움직이게 된다.
XR Origin 내 Main Camera가 가지고 있던 Tracked Pose Driver 컴포넌트를 Dynamic Camera로 옮겼다. 이 컴포넌트는 실제 기기의 IMU/GPS/ARPose 데이터를 기반으로 카메라의 위치와 회전을 갱신하는 역할을 담당한다. Dynamic Camera가 이제 휴대폰의 움직임을 정확히 반영하게 되었다.
요약하면,
“Main Camera를 비활성화하고, Dynamic Camera를 XR Origin 아래로 이동시킨 뒤 Tracked Pose Driver를 부착”
이 조합으로 Cesium과 XR 시스템의 카메라 충돌 문제를 해결했다.
오브젝트 관계 변경
Dynamic Camera에 카메라의 이동을 포착하는 Track Pose Driver 부착
GPS 갱신 주기(약 10초)마다 위치 값이 새로 갱신되면서,
사용자가 가만히 있어도 카메라 위치가 불규칙하게 튀는 현상이 발생했다.
Cesium Globe Anchor의 좌표 반영 과정에서 이 변동이 과도하게 적용된 것으로 추정되었다.
과도한 위치 반영
GPS 값이 갱신될 때마다 Anchor에 즉시 반영되어, 실제 기기가 거의 움직이지 않았음에도 카메라의 위치가 크게 이동함.
Anchor와 Transform 값 모두를 로그로 출력하여 변화를 추적한 결과, Cesium Globe Anchor가 지속적으로 위치를 재설정하는 것을 확인함.
AR 시스템과 VR(또는 Cesium)의 충돌
Unity의 AR Pose(기기 추적)과 Cesium의 지오레퍼런스 좌표 갱신이 동시에 적용되면서 서로 다른 좌표계의 이동 정보가 중첩되어 위치가 튀는 현상이 발생함.
Anchor가 매 프레임마다 Transform 변화를 감지하지 않도록 설정했고, 이로 인해 카메라의 Transform 변화에도 위치가 급격하게 튀지 않게 되었다. 단, 이 경우 Anchor의 GPS 좌표는 더 이상 자동 갱신되지 않았다.
CesiumOriginShift는 플레이어가 먼 거리로 이동할 때 발생하는 부동소수점 정밀도 문제를 완화하기 위해, Georeference 기준점을 주기적으로 플레이어 근처로 이동시키는 역할을 했다. 그러나 위 방식만으로는 Cesium Georeference가 플레이어의 이동을 실시간으로 따라가지 않아, 여전히 위치 동기화가 완전하지 않음. 따라서 GPS의 초기 위치를 CesiumGeoreference로 설정하였다.
Anchor/Transform 상호작용과 Origin Shift