TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역] SLG 게임에서의 나무 컬링 최적화 - GPU 기반 접근법

jplee 2025. 10. 25. 13:44

저자: 燕生

1. 서론

SLG 게임 개발 과정에서 자주 마주하게 되는 문제가 있습니다. 바로 장면 내에서 수많은 나무와 건물이 서로 겹치면서 발생하는 시각적 충돌입니다.

위 이미지에서 보시는 것처럼, 빨간색 구체는 건물을 나타내고 녹색 큐브는 나무를 나타냅니다. 이때 나무와 빨간색 건물이 서로 겹치면서 시각적 오류가 발생하게 됩니다. 이 문제를 해결하기 위해 CPU를 활용한 바운딩 박스 컬링 방식을 고려할 수 있습니다. 각 나무와 건물 사이의 거리를 순회하며 반경을 기준으로 검사한 후, 범위 내에 있으면 비활성화하는 방식입니다.

하지만 모바일 환경에서는 "배경 나무"(모든 정적 장식용 식물을 포함)를 바이너리 파일로 압축하여 처리합니다. 이는 단순히 몇 바이트를 절약하기 위함이 아니라, 초대형 샌드박스 맵 + 천 명 동시 접속이라는 극한 조건에서 메모리, IO, GPU, 핫픽스 네 가지 핵심 요구사항을 동시에 만족시키기 위함입니다. 한마디로 정리하면:

바이너리 = 극한의 "읽기 전용-로드 전용-렌더링 전용" 최적화

건물과 나무가 겹치는 상황에서, 녹색 나무들은 이미 바이너리로 패킹되어 있습니다.

이 경우 CPU 컬링은 더 이상 적용할 수 없으므로, GPU 컬링을 사용해야 합니다. 즉, 각 나무의 셰이더에서 직접 처리를 수행하는 방식입니다.

2. 구현 방식

현재 건물을 배치한 후, 반경 2m 이내의 모든 객체를 제거해야 하는 상황입니다. 어떻게 구현해야 할까요?

나무가 독립적이라면 Update 함수를 사용할 수 있지만, 현재는 나무를 개별적으로 추출할 수 없으므로 셰이더를 통해서만 처리할 수 있습니다.

그렇다면 버텍스 셰이더(VS)와 프래그먼트 셰이더(FS) 중 어느 것을 사용해야 할까요?

처음에는 FS를 사용하여 RT 텍스처를 업다운 샘플링하고 오프셋을 적용하여 범위를 확장한 후, UV 샘플링 결과가 검은색이 아니면 discard하는 방식을 고려했습니다.

하지만 ShadowCaster 패스에서는 텍스처 샘플링이 불가능하고, 텍스처 샘플링 자체도 성능을 많이 소모합니다. 따라서 VS에서 처리하는 것이 최선입니다.

다음 문제들을 해결하면 답을 얻을 수 있습니다:

1. VS에서 건물이 컬링된 것처럼 보이게 하려면 어떻게 해야 할까요?

2. 빨간 건물의 데이터(좌표 + 반경)를 셰이더에 전달하고 업데이트하려면?

3. 나무의 원점과 빨간 건물의 월드 좌표를 어떻게 비교할까요?

4. 나무의 ShadowCaster 그림자를 어떻게 컬링할까요?

5. 반경을 제어하고 시각화하려면 어떻게 해야 할까요?

각 문제를 하나씩 해결해 나가면서 답을 찾아보겠습니다.

3. 상세 구현 단계

1. VS에서 건물이 컬링된 것처럼 보이게 하는 방법

해답은 다음과 같습니다: 클립 공간으로 변환하기 전인 positionCS 단계에서, positionOS 로컬 위치를 모두 원점으로 변경하면 됩니다.

즉, 최상단에 다음 코드를 추가합니다: v.positionOS = float4(0,0,0,1) 이렇게 하면 나무가 사라지며, 컬링과 동일한 효과를 얻을 수 있습니다.

2. 빨간 건물의 데이터(좌표 + 반경)를 셰이더에 전달하고 업데이트하는 방법

해답은 다음과 같습니다:

부모 빈 오브젝트(좌표 0,0,0)에 아래 여러 구체의 월드 좌표를 배치한 후, 글로벌 셰이더로 설정하면 됩니다.

Rider에서 브레이크포인트를 설정하면 데이터를 시각화할 수 있습니다.

Rider에서 브레이크포인트를 찍어보면 데이터가 다음과 같이 표시됩니다. 0123은 각각 xyz 좌표(-116, 0, 119 근처)를 나타내고, 반경은 1.38입니다.

총 4개의 건물이 있으므로 이러한 데이터 세트가 4개 있으며, 이를 버퍼로 압축하여 글로벌 셰이더에 전달합니다.

글로벌 셰이더 변수 _ArchWorldCount의 값

글로벌 셰이더 변수 _ArchWorldDataArray의 값

archRadiusDebugVis는 디버그용 색상 조정에만 사용되므로 무시해도 됩니다. 데이터 문제도 해결되었습니다.

3. 나무의 원점과 빨간 건물의 월드 좌표를 비교하는 방법

VS 내에서 비교를 수행해야 합니다. 반경 거리가 버퍼에 설정된 거리보다 작으면 positionOS를 (0,0,0,1)로 설정합니다.

원점을 얻는 핵심 코드는 원점을 월드 좌표로 변환하는 것입니다. 이를 통해 원점의 월드 좌표를 얻을 수 있습니다. 원점의 월드 좌표와 건물의 월드 좌표 사이의 거리를 계산하고, 설정한 반경과 비교하면 됩니다.

4. 나무의 ShadowCaster 그림자를 컬링하는 방법

이 부분도 원래 VS와 동일하게 처리하면 됩니다. 메인 패스의 VS와 완전히 동일한 코드를 복사하면 됩니다.

5. 반경을 제어하고 시각화하는 방법

시각화는 색상을 통해 제어할 수 있습니다. 위의 버텍스 컬링 코드를 주석 처리하면 됩니다.

프래그먼트 셰이더 시각화 완료


이상으로 GPU 기반 나무 컬링 셰이더 구현이 완료되었습니다. 읽어주셔서 감사합니다.


컴퓨터 그래픽스 기술 용어 레퍼런스

  • SLG (Simulation Game): 시뮬레이션 게임. 전략이나 관리 요소가 중심이 되는 게임 장르입니다.
  • 컬링 (Culling): 렌더링 최적화 기법으로, 화면에 보이지 않거나 필요 없는 객체를 렌더링 파이프라인에서 제외하는 과정입니다.
  • 바운딩 박스 (Bounding Box): 3D 객체를 둘러싸는 가상의 직육면체로, 충돌 검사나 컬링에 사용됩니다.
  • 바이너리 파일 (Binary File): 컴퓨터가 직접 읽을 수 있는 형식으로 인코딩된 파일로, 텍스트 파일보다 공간 효율적이고 로딩이 빠릅니다.
  • CPU (Central Processing Unit): 중앙 처리 장치. 컴퓨터의 주요 연산을 담당하는 하드웨어입니다.
  • GPU (Graphics Processing Unit): 그래픽 처리 장치. 그래픽 렌더링과 병렬 연산에 특화된 프로세서입니다.
  • 핫픽스 (Hotfix): 게임이나 소프트웨어를 재설치하지 않고도 즉시 적용할 수 있는 긴급 패치입니다.
  • VS (Vertex Shader): 버텍스 셰이더. 3D 모델의 정점 데이터를 처리하는 셰이더 단계입니다.
  • FS (Fragment Shader): 프래그먼트 셰이더 또는 픽셀 셰이더. 화면의 각 픽셀 색상을 계산하는 셰이더 단계입니다.
  • RT 텍스처 (Render Target Texture): 렌더링 결과를 저장하는 텍스처로, 추가 처리나 재사용이 가능합니다.
  • UV 샘플링: 2D 텍스처 좌표(U, V)를 사용하여 텍스처에서 색상 값을 가져오는 과정입니다.
  • Discard: 셰이더에서 현재 픽셀의 렌더링을 중단하는 명령어입니다.
  • ShadowCaster 패스: 그림자를 생성하기 위해 깊이 정보를 렌더링하는 셰이더 패스입니다.
  • positionOS (Object Space Position): 객체의 로컬 좌표계에서의 위치입니다.
  • positionCS (Clip Space Position): 클립 공간 좌표. 화면에 투영되기 직전 단계의 좌표계입니다.
  • 월드 좌표 (World Space): 전체 씬의 절대 좌표계입니다.
  • 글로벌 셰이더 (Global Shader): 모든 머티리얼에서 접근 가능한 전역 셰이더 변수입니다.
  • 버퍼 (Buffer): GPU 메모리에 저장되는 데이터 배열로, 셰이더에서 효율적으로 접근할 수 있습니다.

원문

https://zhuanlan.zhihu.com/p/1947000131544129957