안동민 개발노트

본문 시작

렌더링 최적화

오버드로우·LOD·그림자·드로우 콜·머티리얼 비용을 진단해 GPU와 렌더 스레드 부하를 낮춥니다.

이전 절에서 우리는 메모리 관리 및 최적화 기법에 대해 살펴보았습니다.

이제 성능 최적화의 또 다른 핵심 영역인 렌더링(Rendering) 최적화에 대해 다뤄보겠습니다.

렌더링은 게임의 시각적 품질을 결정하는 동시에, 가장 많은 CPU와 GPU 자원을 소모하는 부분 중 하나입니다.

프레임 저하의 원인은 렌더링 외에도 게임 로직, 로딩, 동기화 대기 등에 있을 수 있습니다. 측정으로 렌더링 비용이 주요 후보인지 먼저 확인합니다.

이번 절에서는 언리얼 엔진에서 렌더링 성능을 저하시키는 주요 원인을 이해하고, GPU 및 렌더링 스레드의 부하를 줄여 프레임 속도를 향상시키는 다양한 최적화 기법들을 구체적으로 알아보겠습니다.


렌더링 병목 현상 이해

렌더링 성능 문제는 크게 GPU 바운드(GPU Bound)와 CPU 바운드(CPU Bound) (렌더 스레드)로 나눌 수 있습니다.

stat unit 명령을 통해 이 둘 중 어느 쪽에 병목이 있는지 빠르게 파악할 수 있습니다.

  • GPU 바운드: GPU가 렌더링 작업을 처리하는 데 너무 많은 시간이 걸리는 경우입니다.
    • 주요 원인
      • 픽셀 복잡도 (Pixel Complexity): 셰이더 명령 수, 텍스처 샘플링 수, 블렌딩 오버헤드 (오버드로우).
      • 폴리곤 수 (Polygon Count): 화면에 보이는 총 삼각형(Triangles) 수가 너무 많을 때.
      • 해상도: 높은 해상도는 픽셀 수를 증가시켜 GPU 부하를 높입니다.
      • 후처리 효과 (Post-Process Effects): 블룸, DOF, 모션 블러, 안티앨리어싱 등 복잡한 후처리.
      • 그림자: 그림자 맵(Shadow Map) 생성 및 샘플링 비용.
      • 레이 트레이싱: 레이 트레이싱 기능 활성화 시 높은 GPU 자원 소모.
  • CPU 바운드 (렌더 스레드 / RHI 스레드): CPU가 렌더링 명령을 준비하거나 그래픽 API(RHI)로 전송하는 데 너무 많은 시간이 걸리는 경우입니다.
    • 주요 원인
      • 드로우 콜 (Draw Calls): GPU에 보내는 렌더링 명령의 수가 너무 많을 때. 각 드로우 콜은 CPU에 상당한 오버헤드를 유발합니다.
      • 스테이트 변경 (State Changes): 머티리얼, 셰이더, 텍스처 등 렌더링 상태가 자주 변경될 때.
      • 복잡한 씬 그래프: 액터나 컴포넌트의 계층 구조가 너무 복잡하여 렌더링 데이터 준비에 시간이 오래 걸릴 때.
      • 렌더링 데이터 업데이트: 동적인 메시, 텍스처 업데이트 등 CPU가 GPU에 데이터를 보내기 위해 준비하는 과정.

GPU 바운드 최적화 기법

픽셀 복잡도 및 오버드로우 감소

  • 머티리얼 최적화
    • 셰이더 명령어 수 줄이기: 복잡한 수학 연산, 불필요한 텍스처 샘플링을 줄입니다. 머티리얼 에디터의 Stats 탭을 활용하여 셰이더 명령어 수를 확인합니다.
    • 텍스처 압축 및 밉맵(Mipmap): 텍스처를 올바르게 압축하고 밉맵을 사용하여 거리에 따라 낮은 해상도의 텍스처가 사용되도록 합니다.
    • 불투명(Opaque) 머티리얼 우선: 가능한 한 투명(Transparent) 또는 마스크드(Masked) 머티리얼 대신 불투명 머티리얼을 사용합니다. 투명 머티리얼은 오버드로우(Overdraw)를 유발하여 GPU 부하를 크게 증가시킵니다.
    • 블렌딩 모드: 필요한 경우에만 복잡한 블렌딩 모드를 사용하고, 가능한 경우 단순한 모드를 선택합니다.
    • 머티리얼 인스턴스: 동일한 머티리얼을 여러 개 만드는 대신, 머티리얼 인스턴스를 사용하여 파라미터만 변경하고 셰이더 컴파일 비용을 줄입니다.
  • 복잡도 시각화: 뷰포트 View Mode의 Shader Complexity(Alt + 8)로 셰이더 명령량과 겹쳐 그리는 영역을 살펴봅니다. 색은 실제 GPU 시간 측정값이 아니므로 profilegpu 등과 대조합니다. 지원 플랫폼에서는 Quad Overdraw도 함께 확인합니다.

폴리곤 수 감소 및 LOD 활용

  • LOD (Level Of Detail): 스태틱 메시와 스켈레탈 메시 모두에 LOD를 설정하여 카메라 거리에 따라 자동으로 폴리곤 수가 적은 모델을 사용하도록 합니다. 이는 GPU의 정점 처리 및 픽셀 처리 부하를 줄입니다.
  • 최적화된 모델: 모델링 단계에서 불필요한 폴리곤을 제거하고, 리토폴로지(Retopology)를 통해 메시를 최적화합니다.
  • 노멀 맵 사용: 디테일을 위해 높은 폴리곤을 사용하는 대신, 노멀 맵을 사용하여 저폴리곤 모델에 고품질의 시각적 디테일을 표현합니다.

그림자 최적화

그림자는 렌더링 비용이 매우 높은 기능입니다.

  • 불필요한 그림자 끄기: 작은 오브젝트나 멀리 있는 오브젝트는 그림자를 드리우지 않도록 Cast Shadows 옵션을 끕니다.
  • 그림자 캐스케이드(Cascaded Shadow Maps): Directional Light의 그림자 캐스케이드 수를 줄이거나 거리를 조정하여 비용을 절감합니다.
  • 그림자 해상도: 그림자 맵의 해상도를 적절히 낮춥니다.
  • 캡슐 그림자(Capsule Shadows): 스켈레탈 메시의 경우, 복잡한 그림자 대신 캡슐 그림자를 사용하여 성능을 향상시킬 수 있습니다.
  • 사전 계산된 그림자 (Baked Shadows): 정적인 오브젝트의 그림자는 라이트맵(Lightmap)으로 미리 계산하여 런타임 비용을 줄입니다.

후처리 효과 관리

  • 불필요한 효과 비활성화: 프로젝트 설정에서 사용하지 않는 후처리 효과를 비활성화합니다.
  • 낮은 품질 설정: 후처리 볼륨(Post Process Volume)에서 각 효과의 품질 설정을 조정하여 성능과 시각적 품질 사이의 균형을 맞춥니다.
  • 해상도 스케일: Screen Percentage를 낮춰 렌더링 해상도를 줄이면 GPU 부하를 크게 줄일 수 있습니다. (단, 이미지 품질 저하)

CPU 바운드 (렌더 스레드) 최적화 기법

드로우 콜 감소 (Draw Call Reduction)

드로우 콜은 CPU가 GPU에 렌더링 명령을 보내는 비용이 큰 작업입니다.

드로우 콜 수를 줄이는 것이 중요합니다.

  • 액터 머지(Actor Merging): 여러 개의 스태틱 메시 액터를 하나의 큰 메시로 병합하여 드로우 콜을 줄입니다. (에디터 메뉴: Developer Tools -> Merge Actors)
  • 인스턴싱(Instancing): UInstancedStaticMeshComponent 등으로 같은 메시를 반복 배치합니다. UFoliageType은 식생 배치 설정이며 메시 컴포넌트 자체가 아닙니다. 머티리얼 섹션·렌더 패스·플랫폼에 따라 여러 제출이 필요하므로 항상 하나의 드로우 콜이 된다고 보지는 않습니다.
  • 병합 단위: 인접 메시 병합은 제출 비용을 줄일 수 있지만 컬링 단위가 커집니다. LOD를 대신하는 보편적 해법은 아니므로 원거리 품질과 가려진 영역의 비용도 비교합니다.
  • 컬링(Culling): 카메라 시야 밖에 있거나 너무 작아서 보이지 않는 오브젝트를 렌더링 파이프라인에서 제외합니다. 언리얼 엔진은 기본적으로 프러스텀 컬링(Frustum Culling)과 오클루전 컬링(Occlusion Culling)을 수행합니다.
    • Hierarchical LOD (HLOD): 대규모 오픈 월드에서 멀리 있는 여러 액터를 하나의 최적화된 메시로 자동 병합하여 드로우 콜과 메시 복잡도를 줄입니다.
    • Manual Occlusion Culling: 커스텀 오클루전 볼륨이나 Precomputed Visibility를 사용하여 수동으로 컬링을 최적화할 수 있습니다.

머티리얼 및 텍스처 변경 최소화

  • 동일한 머티리얼 사용: 인접한 오브젝트에 가능한 한 동일한 머티리얼을 사용하면 렌더링 상태 변경을 줄여 드로우 콜 비용을 절감합니다.
  • 아틀라스 텍스처(Texture Atlases): 여러 작은 텍스처를 하나의 큰 텍스처 시트(아틀라스)에 묶어 사용하여 텍스처 바인딩 비용을 줄입니다.

동적 광원 및 그림자 최적화

동적 광원은 렌더 스레드에 부담을 줍니다.

  • 조명 경로 확인: 베이크 조명을 사용하는 프로젝트에서는 Static·Stationary를 검토합니다. Lumen GI에서는 사전 계산 정적 조명 기여가 비활성화되므로 Static으로 바꾸는 조언을 그대로 적용할 수 없습니다.
  • 동적 광원 수 제한: 씬의 동적 광원 수를 최소화합니다.
  • 광원 이동성(Mobility): 그림자 방식·겹침·영향 범위·렌더 경로를 함께 비교합니다. 이름만으로 모든 장면의 비용 순서를 확정하지 않습니다.

기타 렌더링 최적화 팁

  • 엔진 스케일러빌리티 설정: 엔진은 scalability 명령어를 통해 전역적인 그래픽 품질 설정을 제공합니다. r.ScreenPercentage, r.PostProcessAAQuality 등 개별 CVAR(콘솔 변수)을 조절하여 세부적인 렌더링 설정을 변경할 수 있습니다.
  • 플랫폼별 최적화: 모바일, 콘솔, PC 등 타겟 플랫폼의 하드웨어 특성에 맞춰 렌더링 파이프라인을 조절합니다. 예를 들어 모바일에서는 모바일 렌더링 경로를 사용하고, 데스크탑에서는 더 진보된 기능을 활용할 수 있습니다.
  • 리플렉션 캡처 (Reflection Captures): 반사(Reflection)는 성능에 큰 영향을 미칩니다. Reflection Capture 액터를 적절히 배치하고 필요한 경우에만 SSR (Screen Space Reflections)을 사용합니다.
  • 시간적 안티앨리어싱(Temporal AA): Temporal AA는 효과적이지만 일부 시나리오에서 고스팅(Ghosting)이나 블러(Blur)를 유발할 수 있습니다. FXAA나 다른 안티앨리어싱 방법을 고려할 수도 있습니다.
  • 측정 빌드 구분: 비최적화 DebugGame의 시간으로 배포 성능을 판단하지 않습니다. 측정 목적에 맞는 Development·Test 등 구성과 동일한 콘텐츠·설정을 사용하고, 소스 추적용 디버깅 결과와 구별합니다.

한 번에 한 비용 항목을 바꾸고 동일한 장면의 GPU·Draw 시간과 화면 품질을 함께 비교합니다. Nanite, Lumen, Virtual Shadow Maps 등 사용 중인 렌더 경로에 해당하는 설정인지 먼저 확인합니다.