[TIL] 20240102 TIL : 동기화 in 메타버스

Jaeyoung Ko·2025년 1월 2일
0

부트캠프에서 협업 수단의 일종으로 활용하는 네이버 zep이란 메타버스 플랫폼이 있다.


이러한 메타버스 가상 공간 안에서의 동기화와 관련하여 다음과 같은 이슈들을 생각해 볼 수 있을 것이다.


가상 공간의 맵

(1) 맵의 분할

맵의 크기가 커짐에 따라 존재하는 오브젝트의 데이터들의 양이 많아진다면 한 번의 API 호출로 랜더링이 어려워질 수 있다.

따라서, 전체 맵의 여러 개의 부분으로 분할하고, 유저가 분할된 특정 영역에 접근 시 해당 영역에 대해서만 load를 처리하는 방식으로 최적화를 시킬 수 있다.


(2) LOD (level of detail)

그래픽 랜더링에 있어서 빠질 수 없는 개념이다. 화면 해상도, 확대/축소와 같은 줌 정도에 따라 적절한 수준의 LOD에 맞춰 데이터를 로딩함으로 랜더링 성능을 향상시킬 수 있다.





동기화 시야 (Synchronized Views)

동기화 시야란 멀티 플레이 환경에서 서버의 성능 최적화를 위해 설정되는 동기화 제한 범위로 오브젝트의 상태에 대해 동기화되어야할 클라이언트들을 결정하는 기준이 된다.

1. 왜 동기화 시야를 사용하는가?

MMORPG 환경에서, 모든 플레이어가 서로의 상태를 실시간으로 관찰해야 한다면, 각각에게 변경점 전파를 하는 데에만 O(n^2)의 복잡도를 가지게 된다. 이러한 방식은 매우 효율적이지 못하므로 불필요한 데이터 전파를 줄이는 과정이 필요하다.


2. 동기화 시야 설정 기준

보통의 동기화 시야에 대해 설정하는 기준은 예상되는 인구 밀도이다.

인구 밀도가 낮다면 꽤 넓은 범위를 설정가능하지만, 인구 밀도가 높다면 서버 부하가 가중되기에 보다 좁은 범위를 설정하는 편이다.

그럼에도 불구하고, 일부 오브젝트(ex.맵에 상징적인 표시 역할을 하는 오브젝트 등)는 넓은 범위에 걸쳐 보여야할 수 있다. 이 경우, local의 기본 동기화 시야가 아닌, 고유한 동기화 시야를 요구하기도 한다.


3. 동기화 시야 시나리오 예시

가장 기본적이고 주요한 부분으로 이동 동기화 시나리오를 다음과 같이 볼 수 있다.

// 시나리오

/*
	player P1이 location A에서 location B로 이동을 한다.
*/

위 시나리오에서 이동이란 이벤트에 대해서는 다음과 같은 흐름을 가지게 된다.

  1. P1이 A에서 B로 이동 시도한다.
  2. A의 로컬 유저들은 P1에 대해 인식하고 P1의 상태 동기화를 받는다.
    반면, B의 로컬 유저들은 P1에 대해 인식되지 않아 P1의 상태 동기화를 받지 않는다.
  3. P1이 From A to B로 이동하게 되면서, A의 로컬 유저들에게 멀어져 영역에 벗어나게 되어 더 이상 P1의 상태 동기화를 받지 않는다.
    반면, B의 로컬 유저들은 영역에 P1을 발견하고 인지하게 되어 P1의 상태 동기화를 시작하게 된다.

4. 동기화 시야에 사용되는 기법: Spatial Hashing

위에서 언급한 바와 같이, 동기화 받아야할 오브젝트의 목록을 결정하는 동기화 시야의 설정을 위해 Spatial Hashing(공간 해싱)의 최적화 기법을 적용할 수 있다.

간단하게 공간 해싱의 핵심을 요약하자면 다음과 같다.

Spatial Hashing

Space를 cell grid 형식 격자 형태로 나누어,

Object를 특정 cell에 mapping함으로 빠르게 검색/연산을 처리한다.


앞서 언급했듯이,

이러한 공간을 cell로 나눌 때에 인구 밀도를 기준으로 분할하는 것이 좋다. 물론 환경에 따라 정해진 경계가 존재한다면 경계 기반 분할을 하는 것과 같이 다른 policy로 분할하는 것도 가능하다.


Spatial Hashing을 간단히 적용한 예시


// 맵의 분할 : 30개 셀
const NUM_OF_AREA = 30;

// 맵 선언 빛 초기화: 아무도 없다는 가정의 빈 배열
const areas = new Map();
for (let i = 1; i <= NUM_OF_AREA; i += 1) {
  areas.set(i, []);
}

// 유저의 영역내 진입
const areaNum = getAreaNum(user.x, user.y);
const users = areas.get(areaNum);
users.push(user);

// 영역내 유저의 이동
const adjacentAreaNums = getAdjacentAreaNums(user.x, user.y);
const syncUsers = [];
for (const adjacentAreaNum of adjacentAreaNums) {
  // 인접한 노드의 유저들 목록 인출하여 동기화 대상에 포함
  syncUsers.push(...areas.get(adjacentAreaNum)); 
}

// 동기화 작업
for (const syncUser of SyncUsers) {
  syncObjects(syncUser, user);
}
profile
안녕하세요, 고재영입니다. 언제나 즐겁게 살려고 노력합니다.

0개의 댓글