본문 바로가기

렌더링 최적화

오버드로우·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 부하를 크게 줄일 수 있습니다. (단, 이미지 품질 저하)

예시: 해상도 배율과 픽셀 수 구별하기

출력은 1920×1080이고, 계산 연습에서는 주 렌더링 해상도의 폭·높이만 같은 배율로 줄인다고 가정합니다. 50%면 960×540, 518,400픽셀로 원래 2,073,600픽셀의 25%입니다. 50%라는 숫자를 픽셀 수가 절반이라는 뜻으로 읽지 않습니다. 업스케일 뒤 출력과 UI 해상도, 패스별 해상도는 렌더 경로에 따라 따로 확인합니다.

같은 단순 모델에서 80%의 폭·높이·픽셀 수와 원래 대비 비율을 계산하세요. 픽셀 수가 줄어든 비율만큼 GPU ms와 전체 FPS도 반드시 좋아진다고 결론 내릴 수 있을까요? Game Thread가 Frame과 함께 길어진 장면에서도 먼저 해상도만 낮춰야 하는지 설명하세요.

두 축을 곱한 뒤 비용의 범위를 확인하기

1536×864=1,327,104픽셀이고 원래의 64%입니다. 렌더 패스에는 해상도와 다르게 변하는 비용도 있으며 업스케일링·다른 스레드·대기까지 있어 GPU ms나 FPS의 같은 비율 개선을 보장하지 않습니다. Game 축이 주된 신호라면 해당 게임 작업의 타임라인을 먼저 조사합니다. 같은 장면의 품질과 전후 시간을 함께 비교합니다.

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 등 사용 중인 렌더 경로에 해당하는 설정인지 먼저 확인합니다.