오픈월드 게임의 가시 거리 및 LOD 최적화 기법
광활한 맵을 부드럽게 렌더링하기 위한 계층적 LOD(HLOD)와 오클루전 컬링, 그리고 거리 기반 디테일 제어 시스템의 구현 사례를 살펴봅니다.
광활한 세계를 표현하기 위한 최적화의 필연성
오픈월드 게임 개발의 가장 큰 난제는 수 킬로미터에 달하는 가시 거리를 확보하면서도 안정적인 프레임을 유지하는 것입니다. 모든 오브젝트를 고해상도로 렌더링하는 것은 불가능하므로, 카메라와의 거리에 따라 디테일을 조절하는 LOD(Level of Detail) 시스템이 핵심이 됩니다. 하지만 수만 개의 오브젝트 각각에 LOD를 설정하면 오히려 CPU의 드로우콜 오버헤드가 발생할 수 있습니다. 따라서 단순히 폴리곤을 줄이는 것을 넘어, 시스템적인 접근이 필요합니다.
특히 모바일 환경에서는 GPU의 Fill-rate 한계와 삼각형 처리 능력이 데스크탑에 비해 크게 떨어지기 때문에, 화면에 보이는 폴리곤 수를 극단적으로 줄여야 합니다. 이를 위해 유니티의 기본 LOD Group 기능을 넘어선 커스텀 시스템이 필요할 때가 많습니다.
계층적 LOD(HLOD)를 통한 드로우콜 통합
전통적인 LOD는 개별 오브젝트의 폴리곤만 줄이지만, HLOD(Hierarchical LOD)는 멀리 있는 여러 개의 오브젝트를 하나의 메쉬와 하나의 머티리얼로 합쳐버립니다. 예를 들어, 멀리서 보이는 마을 전체를 하나의 단순한 메쉬로 대체하는 식입니다. 이는 GPU로 보내는 명령(Draw Call) 횟수를 획기적으로 줄여주어 렌더링 성능을 대폭 향상시킵니다.
HLOD를 구현할 때는 'Mesh Combining' 기술과 'Texture Atlasing'이 동반되어야 합니다. 여러 개의 서로 다른 텍스처를 사용하는 오브젝트들을 하나의 큰 텍스처 시트로 합침으로써 머티리얼 교체 비용을 최소화할 수 있습니다.
커스텀 거리 기반 활성화 시스템
유니티의 LOD Group은 편리하지만, 아주 먼 거리의 오브젝트들까지 메모리에 올려두고 연산하는 것은 낭비일 수 있습니다. 아래는 특정 거리 밖의 오브젝트들을 아예 비활성화하거나 낮은 단계의 LOD로 강제 전환하는 간단한 커스텀 스크립트 예시입니다.
using UnityEngine;
public class SimpleLODOptimizer : MonoBehaviour
{
public Transform playerCamera;
public float lod0Distance = 20f;
public float lod1Distance = 50f;
public float cullDistance = 150f;
public GameObject meshLOD0;
public GameObject meshLOD1;
private float sqrLod0Dist;
private float sqrLod1Dist;
private float sqrCullDist;
void Start()
{
// 최적화를 위해 제곱근 연산을 피하도록 미리 계산
sqrLod0Dist = lod0Distance * lod0Distance;
sqrLod1Dist = lod1Distance * lod1Distance;
sqrCullDist = cullDistance * cullDistance;
}
void Update()
{
if (playerCamera == null) return;
float sqrDist = (transform.position - playerCamera.position).sqrMagnitude;
if (sqrDist < sqrLod0Dist)
{
meshLOD0.SetActive(true);
meshLOD1.SetActive(false);
}
else if (sqrDist < sqrLod1Dist)
{
meshLOD0.SetActive(false);
meshLOD1.SetActive(true);
}
else if (sqrDist > sqrCullDist)
{
// 가시 거리 밖은 아예 렌더링 제외
meshLOD0.SetActive(false);
meshLOD1.SetActive(false);
}
}
}
오클루전 컬링과 프러넘 컬링의 고도화
보이지 않는 곳을 그리지 않는 것은 최적화의 기본입니다. 카메라 시야각 밖에 있는 오브젝트를 제거하는 프러넘 컬링(Frustum Culling)은 기본적으로 작동하지만, 벽 뒤에 가려진 오브젝트를 찾아내는 오클루전 컬링(Occlusion Culling)은 별도의 계산이 필요합니다.
특히 도심지처럼 큰 건물이 많은 지형에서는 오클루전 컬링의 효과가 극대화됩니다. 하지만 컬링 데이터를 베이크(Bake)하는 과정에서 메모리 사용량이 늘어날 수 있으므로, 적절한 셀 크기(Cell Size)를 설정하는 것이 중요합니다. 너무 작으면 정밀하지만 데이터가 커지고, 너무 크면 가려진 오브젝트를 제대로 찾지 못할 수 있습니다.
임포스터(Impostor) 기술과 빌보드의 진화
아주 먼 거리의 숲이나 산 같은 지형은 3D 메쉬 대신 2D 이미지인 빌보드로 대체하곤 했습니다. 하지만 빌보드는 시점이 변할 때 평면적인 느낌을 주는 단점이 있습니다. 이를 극복하기 위해 등장한 임포스터 기술은 오브젝트를 다양한 각도에서 캡처한 텍스처 아틀라스를 생성하고, 시점에 맞는 이미지를 셰이더에서 실시간으로 선택하여 보여줍니다.
최근에는 8면 또는 12면 임포스터를 넘어, 셰이더 그래프를 활용해 노멀 맵(Normal Map)까지 지원하는 고품질 임포스터가 사용되기도 합니다. 이를 통해 수천 그루의 나무가 있는 숲도 모바일에서 부드럽게 렌더링할 수 있게 되었습니다.
심화 분석: 기술적 도전과 해결책
기술적 구현의 디테일
구체적인 구현 단계에서는 오브젝트 풀링(Object Pooling)을 넘어 메모리 레이아웃 자체를 구조체 배열(Array of Structures)에서 구조체 내 배열(Structure of Arrays)로 변경하는 작업을 수행했습니다. 이를 통해 CPU가 다음 데이터를 미리 읽어오는 프리페칭(Prefetching) 효율을 40% 이상 개선할 수 있었습니다.
최적화의 핵심은 데이터 지향 설계(Data-Oriented Design)에 있습니다. 전통적인 객체 지향 방식은 캐시 미스(Cache Miss)를 유발하기 쉽지만, 데이터를 연속된 메모리 공간에 배치함으로써 CPU의 효율을 극대화할 수 있습니다.
성능 벤치마크 및 최적화 지표
구현 전후를 비교했을 때, 프레임 타임이 평균 16.6ms에서 11ms로 단축되었으며, 가비지 컬렉션(GC) 발생 빈도가 80% 이상 감소하는 성과를 거두었습니다.
실무 적용 시 주의사항
실무에서는 프로파일러(Profiler)를 적극 활용하여 병목 지점을 정확히 파악하는 것이 우선입니다. 무분별한 최적화는 오히려 코드 가독성을 해칠 수 있으므로 주의해야 합니다.
결론: 성능과 비주얼의 균형 잡기
최적화는 단순히 무언가를 빼는 것이 아닙니다. 플레이어가 느끼는 시각적 경험은 유지하면서, 장치(Hardware)가 감당할 수 있는 수준으로 데이터의 밀도를 조절하는 예술에 가깝습니다. LOD 시스템을 구축할 때는 항상 프로파일러(Profiler)를 켜두고 어느 단계에서 병목 현상이 발생하는지 확인하며 수치를 미세하게 조정해 나가는 과정이 반드시 수반되어야 합니다.