안동민 개발노트 아이콘

안동민 개발노트

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와 렌더 스레드 작업을 나누고 작은 변경 단위로 다시 측정하는 루프가 중요합니다.


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


렌더링 최적화는 GPU 시간, 드로우 콜, 머티리얼 복잡도, 컬링 상태를 분리해 읽으면 수정 순서가 분명해집니다.

렌더링 비용은 화면 품질과 성능 목표를 함께 놓고 조정해야 하므로, 어떤 값을 낮출지보다 어떤 경험을 유지할지가 먼저입니다.

렌더링 최적화는 호출 경계, 소유권, 성능 영향, 재측정 기준으로 점검합니다.

렌더링 최적화에서 놓치기 쉬운 기준은 보충 점검 항목으로 다시 확인합니다.