icon
9장 : 성능 최적화

렌더링 최적화


이전 절에서 우리는 메모리 관리 및 최적화 기법에 대해 살펴보았습니다. 이제 성능 최적화의 또 다른 핵심 영역인 렌더링(Rendering) 최적화에 대해 다뤄보겠습니다. 렌더링은 게임의 시각적 품질을 결정하는 동시에, 가장 많은 CPU와 GPU 자원을 소모하는 부분 중 하나입니다. 프레임 속도(FPS)가 낮거나, 끊김 현상이 발생한다면 대부분의 경우 렌더링 파이프라인에 병목 현상이 발생하고 있을 가능성이 큽니다.

이번 절에서는 언리얼 엔진에서 렌더링 성능을 저하시키는 주요 원인을 이해하고, 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 부하를 크게 증가시킵니다.
    • 블렌딩 모드: 필요한 경우에만 복잡한 블렌딩 모드를 사용하고, 가능한 경우 단순한 모드를 선택합니다.
    • 머티리얼 인스턴스: 동일한 머티리얼을 여러 개 만드는 대신, 머티리얼 인스턴스를 사용하여 파라미터만 변경하고 셰이더 컴파일 비용을 줄입니다.
  • 오버드로우 시각화: Alt + 8을 눌러 Shader Complexity 모드에서 오버드로우를 시각적으로 확인합니다. 붉은색 영역은 GPU 부담이 높은 곳을 나타냅니다.
    • Show -> Visualize -> Buffer Visualization -> Shader Complexity

폴리곤 수 감소 및 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로 설정하여 라이트맵에 굽고 런타임 비용을 줄입니다.
  • 동적 광원 수 제한: 씬의 동적 광원 수를 최소화합니다.
  • 광원 컴포넌트의 이동성(Mobility): Stationary 광원은 Movable 광원보다 성능이 좋고, Static 광원은 가장 성능이 좋습니다.

기타 렌더링 최적화 팁

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

렌더링 최적화는 복잡하고 지속적인 과정입니다. GPU와 렌더 스레드의 병목 현상을 정확히 진단하고, 머티리얼, 메시, 그림자, 후처리 효과를 최적화하며, 드로우 콜을 줄이기 위한 인스턴싱 및 컬링 기법을 적용하는 것이 중요합니다. 언리얼 엔진의 profilegpu, stat 명령, Shader Complexity 시각화 모드 등 강력한 프로파일링 도구를 적극적으로 활용하여 렌더링 파이프라인의 병목 현상을 해결하고, 모든 플레이어에게 부드럽고 시각적으로 만족스러운 게임 경험을 제공할 수 있을 것입니다.