0%

Unity

전시 패널을 움직이는 것만으로 3D 인체 모델을 자유롭게 볼 수 있다면 어떨까요

전*원··수정됨 2026.07.22

전시 패널을 움직이는 것만으로 3D 인체 모델을 자유롭게 볼 수 있다면 어떨까요

마우스도 터치도 없이, 관람객이 다가가면 콘텐츠가 자동으로 시작되고 떠나면 조용히 홈 화면으로 돌아가는 비접촉 인터랙션 키오스크라면요. 이번 포스팅에서는 LIDAR 스캐너의 점군 데이터를 Unity 카메라의 X축 위치로 매핑하여 구현한 인천 학생과학관 '밀어서 보는 인체구조'의 기술적인 구현 과정을 공유합니다.

아키텍처_01_전체구조.png

1. 시스템 아키텍처 개요

본 프로젝트는 크게 세 가지 단계로 구성됩니다.

비접촉 입력: LIDAR 장치가 UDP로 송신하는 JSON 점군 데이터{"points": [{"angle":..., "dist":...}]})를 백그라운드 스레드에서 수신하여 유효 거리의 평균값을 산출

카메라 매핑: 평균 거리를 InverseLerp로 정규화한 뒤 링버퍼 평균 + Lerp 보간을 거쳐 카메라 X 좌표로 변환, 인체 모델이 좌우로 미끄러지듯 움직이도록 처리

장기 선택: 터치스크린의 Physics.RaycastAll로 걸린 모든 장기 중 selectionPriority와 거리 기준으로 1개를 선택해 CSV 기반 상세 설명 팝업을 표시

이 모든 과정은 Unity 2022 LTS의 단일 씬 구조에서 LIDARManager 싱글톤이 3D 카메라 위치를, OrganRaycastController가 터치 레이캐스트를 각각 독립적으로 담당하는 계층 분리 구조로 동작합니다.

2. 주요 기술적 구현

2.1 멀티스레드 UDP 점군 수신과 2단계 스무딩 (LIDARManager)

LIDAR는 초당 수십~수백 패킷을 실시간으로 쏟아내므로, 이를 메인 스레드에서 처리하면 프레임이 크게 흔들립니다. LIDARManager는 별도 스레드의 RecvLoop()가 UDP 수신과 JSON 파싱, 그리고 유효 거리minDistance~maxDistance) 필터링까지 수행한 뒤 lock(dataLock)으로 보호된 _latestAverageDistance만 메인 스레드에 노출합니다. 메인 스레드의 UpdateCameraPosition()은 이 값을 링버퍼 5개 평균으로 1차 스무딩하고, 다시 Lerp(current, target, dt smoothSpeed)*2차 보간**하여 LIDAR 특유의 미세 떨림을 두 단계에 걸쳐 제거합니다.

```csharp

t = 1f - InverseLerp(lidarMin=160, lidarMax=190, avgDistance);

targetX = Lerp(cameraLeftX=0, cameraRightX=1.4, t);

smoothedX = ringBuffer.Average();

camera.x = Lerp(camera.x, smoothedX, dt * smoothSpeed);

```

2.2 우선순위 기반 장기 레이캐스트 (OrganRaycastController + OrganSelector)

인체 모델의 장기들은 서로 겹쳐 있거나 포함 관계인 경우가 많아, 단순한 Physics.Raycast로는 어떤 장기를 먼저 고를지 모호합니다. OrganRaycastController.PerformRaycast()RaycastAll()로 광선 위의 모든 콜라이더를 수집한 뒤 각 OrganSelector.selectionPriority를 비교하여 우선순위가 가장 높은 장기를 선택하고, 동률이면 카메라에서 가장 가까운 것을 고릅니다. 각 장기는 Awake()에서 텍스처를 유지한 채 하이라이트 셰이더 머티리얼을 자동 생성하고 Start()에서 반짝임 효과를 켜 두어, 관람객이 "어디를 터치해야 하는지" 시각적으로 알 수 있도록 유도합니다.

2.3 거리 변화 기반 자동 진입/이탈 판정 (CheckMovement + idleTimeout)

관람객이 다가와서 LIDAR 거리값이 일정 이상 변하면 자동으로 홈 패널에서 신체계통 선택 화면으로 진입하고, 60초간 움직임이 없으면 다시 홈으로 복귀하는 암묵적 입장/퇴장 판정 로직을 CheckMovement()CheckIdleTimeout()에 구현했습니다. baselineDistance를 기준점으로 두고 |현재 거리 - 기준선| > idleThreshold(10cm)일 때만 사람의 움직임으로 인식하여 idleTimer를 리셋하므로, 센서 노이즈에 의한 오탐은 방지하되 실제 관람객의 위치 변화는 민감하게 감지합니다.

3. 사용자 경험(UX) 최적화

버튼 없는 콘텐츠 진입 : 홈 화면에서 LIDAR가 관람객을 감지하는 순간 자동으로 다음 패널로 넘어가, "시작하기 버튼을 터치하세요" 같은 추가 설명 없이 자연스러운 체험 시작을 제공합니다.

장기별 커스텀 콜라이더 : OrganSelector.autoAddColliderType 옵션으로 큰 장기는 BoxCollider로 빠르게, 복잡한 장기는 MeshCollider로 정밀하게 터치 영역을 설정해 정확도와 성능의 균형을 맞췄습니다.

이중 유휴 타이머 안전망 : LIDAR 기반 유휴 감지LIDARManager.idleTimeout)와 터치 기반 유휴 감지GameQuitManager)를 독립적으로 운영하여, 어느 한 입력 경로에서 신호가 끊어져도 나머지가 자동 복귀를 책임지도록 설계했습니다.

CSV 기반 다국어 확장 준비 : 모든 장기 설명 텍스트가 StringTable.csvSTR_MSOTHB_* 키로 외부화되어 있어, 코드 수정 없이 CSV만 교체하면 다른 언어 버전의 콘텐츠 배포가 가능하도록 했습니다.

4. 마치며

이 프로젝트는 "마우스도 버튼도 없이 손짓만으로 3D 콘텐츠를 제어한다"는 체험을 LIDAR 한 대와 Unity 카메라 하나로 구현한 작업이었습니다. UDP 멀티스레드 수신, 링버퍼 + Lerp 2단 스무딩, 우선순위 기반 레이캐스트 같은 요소는 기술적으로도 흥미로웠지만, 관람객이 전시 패널에 가까이 서면 화면이 스르르 움직이고, 궁금한 장기에 손을 뻗으면 설명이 뜨는 "마법 같은 자연스러움"을 만들었다는 점에서 큰 의미가 있었습니다.

향후에는 LIDAR의 1차원 거리값을 넘어 Depth Camera나 Kinect 기반의 2차원 손 추적으로 확장하여 좌우 이동뿐 아니라 제스처 기반 확대·회전까지 지원하고, 장기별 설명을 음성 TTS로 병행하여 시각적 읽기 부담을 덜어낼 예정입니다.

읽기 도구

6분 읽기

이 글이 도움이 되었나요?

다음으로 읽기