본문 바로가기

렌더링 최적화

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

렌더링은 게임의 시각적 품질을 정하는 동시에 CPU와 GPU의 자원을 가장 많이 쓰는 부분 가운데 하나입니다. 그래서 프레임이 떨어지면 먼저 그래픽 옵션부터 낮추고 싶어지지만, 프레임 저하의 원인은 렌더링 말고도 게임 로직, 로딩, 동기화 대기에 있을 수 있습니다. 렌더링을 고치기 전에 측정으로 렌더링 비용이 정말 주요 원인인지부터 확인해야 합니다.

메모리 관리와 최적화 기법에 이어 이 글에서는 렌더링을 다룹니다. 렌더링 병목을 GPU 쪽과 렌더 스레드 쪽으로 나눠 이해하고, 각각의 원인과 줄이는 기법을 살펴봅니다. 해상도 배율처럼 숫자만 보고 효과를 잘못 짐작하기 쉬운 경우도 계산으로 확인합니다.


렌더링 병목은 어디에 있는가

렌더링의 성능 문제는 크게 GPU 바운드와, 렌더 스레드나 RHI 스레드의 CPU 바운드로 나뉩니다. 프로파일링 도구 사용법에서 본 stat unit으로 어느 쪽에 병목이 있는지 빠르게 파악할 수 있습니다.

GPU 바운드는 GPU가 렌더링 작업을 처리하는 데 너무 오래 걸리는 경우입니다. 원인은 여러 가지입니다. 셰이더 명령 수, 텍스처 샘플링 수, 같은 픽셀을 여러 번 그리는 오버드로(overdraw) 같은 픽셀 복잡도가 높을 수 있고, 화면에 보이는 삼각형이 너무 많을 수 있습니다. 해상도가 높으면 그릴 픽셀 수가 늘어나고, 블룸, 피사계 심도(DOF), 모션 블러, 안티에일리어싱 같은 후처리와 그림자 맵의 생성과 샘플링, 레이 트레이싱도 GPU를 많이 씁니다.

렌더 스레드나 RHI 스레드의 CPU 바운드는 CPU가 렌더링 명령을 준비하거나 그래픽 API로 보내는 데 너무 오래 걸리는 경우입니다. GPU에 보내는 렌더링 명령인 드로 콜(draw call)이 너무 많으면 각 드로 콜이 CPU에 부담을 주고, 머티리얼, 셰이더, 텍스처 같은 렌더링 상태가 자주 바뀌어도 비용이 커집니다. 액터와 컴포넌트의 계층이 너무 복잡해 렌더링 데이터를 준비하는 데 오래 걸리거나, 동적인 메시와 텍스처를 갱신하느라 CPU가 GPU로 보낼 데이터를 준비하는 데 시간을 쓰는 경우도 있습니다.


GPU 바운드 줄이기

픽셀 복잡도와 오버드로

머티리얼부터 살펴봅니다. 복잡한 수학 연산과 필요 없는 텍스처 샘플링을 줄여 셰이더 명령 수를 낮추고, 머티리얼 에디터의 Stats 탭으로 명령 수를 확인합니다. 텍스처는 올바르게 압축하고 밉맵을 써서 멀리 있을 때 낮은 해상도가 쓰이게 합니다.

가능하면 반투명(Translucent)이나 마스크드(Masked) 머티리얼 대신 불투명(Opaque) 머티리얼을 씁니다. 반투명 머티리얼은 뒤에 있는 것까지 겹쳐 그리므로 오버드로를 일으켜 GPU 부하를 크게 늘립니다. 복잡한 블렌딩 모드도 필요할 때만 씁니다. 같은 머티리얼을 여러 벌 만드는 대신 머티리얼 인스턴스(Material Instance)로 매개변수만 바꾸면 셰이더 컴파일 비용도 줄일 수 있습니다.

비용이 큰 곳을 눈으로 찾으려면 뷰포트 뷰 모드(View Mode)의 Shader Complexity(Alt+8)를 씁니다. 셰이더 명령량과 겹쳐 그리는 영역을 색으로 보여 주지만, 그 색이 실제 GPU 시간을 잰 값은 아니므로 profilegpu 같은 측정과 비교합니다. 지원하는 플랫폼에서는 Quad Overdraw 뷰도 함께 확인합니다.

폴리곤 수와 LOD

스태틱 메시와 스켈레탈 메시 모두에 LOD를 설정해, 카메라에서 멀어지면 폴리곤이 적은 모델이 쓰이게 합니다. GPU의 정점 처리와 픽셀 처리 부담이 함께 줄어듭니다. 모델링 단계에서 필요 없는 폴리곤을 지우고 리토폴로지(retopology)로 메시를 다듬으며, 디테일을 폴리곤으로 표현하는 대신 노멀 맵을 써서 적은 폴리곤으로도 세밀한 모습을 냅니다. 나나이트(Nanite)를 쓰는 메시는 엔진이 거리에 따라 세부를 자동으로 조절하므로 전통적인 LOD 설정과 다른 방식으로 비용을 봐야 합니다.

그림자

그림자는 렌더링 비용이 큰 기능입니다. 작은 물체나 멀리 있는 물체는 Cast Shadows를 꺼 그림자를 드리우지 않게 합니다. 캐스케이드 섀도 맵(Cascaded Shadow Maps)을 쓰는 디렉셔널 라이트라면 캐스케이드 수를 줄이거나 거리를 조절하고, 그림자 맵의 해상도도 알맞게 낮춥니다. 언리얼 엔진 5에서 기본으로 쓰는 가상 섀도 맵(Virtual Shadow Maps)은 설정 항목이 다르므로, 지금 쓰는 그림자 방식에 맞는 설정인지 먼저 확인합니다. 스켈레탈 메시는 복잡한 그림자 대신 캡슐 섀도(Capsule Shadows)로 비용을 줄일 수 있고, 움직이지 않는 물체의 그림자는 라이트맵으로 미리 계산해 두는 방법도 있습니다.

후처리와 해상도

쓰지 않는 후처리 효과는 프로젝트 설정에서 끄고, 포스트 프로세스 볼륨(Post Process Volume)에서 효과마다 품질을 조절해 성능과 화면 품질 사이의 균형을 맞춥니다. Screen Percentage를 낮춰 렌더링 해상도를 줄이면 GPU 부하를 크게 줄일 수 있지만, 그만큼 화면 품질이 떨어집니다.

해상도 배율의 효과는 숫자를 잘못 읽기 쉬우니 계산해 봅시다. 출력이 1920×1080이고, 계산을 위해 주 렌더링 해상도의 가로와 세로를 같은 배율로 줄인다고 가정합니다. 50%로 줄이면 960×540이 되어 518,400픽셀인데, 이는 원래 2,073,600픽셀의 25%입니다. 가로와 세로가 각각 절반이 되므로 픽셀 수는 4분의 1이 되는 것이지, 50%라는 숫자가 픽셀 수가 절반이라는 뜻은 아닙니다. 업스케일한 뒤의 출력, UI의 해상도, 패스마다의 해상도는 렌더 경로에 따라 따로 확인합니다.


렌더 스레드의 CPU 바운드 줄이기

드로 콜 줄이기

드로 콜은 CPU가 GPU에 렌더링 명령을 보내는 비용이 큰 작업이므로, 그 수를 줄이는 것이 중요합니다. 여러 스태틱 메시 액터를 하나의 큰 메시로 합치는 액터 병합(Merge Actors)은 에디터 상단 메뉴의 Actor > Merge Actors에서 할 수 있습니다. 같은 메시를 되풀이해 배치할 때는 UInstancedStaticMeshComponent 같은 인스턴싱을 씁니다. UFoliageType은 식생의 배치 설정이지 메시 컴포넌트 자체가 아니라는 점에 주의합니다. 인스턴싱을 써도 머티리얼 섹션, 렌더 패스, 플랫폼에 따라 여러 번 제출해야 하므로, 늘 드로 콜 하나가 된다고 볼 수는 없습니다.

가까운 메시를 합치면 제출 비용은 줄지만 컬링 단위가 커집니다. 일부만 보여도 합친 메시 전체를 그려야 하기 때문입니다. 그러니 병합이 LOD를 대신하는 만능 해법은 아니며, 먼 거리의 품질과 가려진 영역의 비용도 함께 비교합니다.

컬링(culling)은 카메라 시야 밖에 있거나 너무 작아 보이지 않는 물체를 렌더링에서 빼는 것으로, 언리얼은 기본적으로 시야 절두체 컬링(frustum culling)과 오클루전 컬링(occlusion culling)을 합니다. 큰 오픈 월드에서는 계층적 LOD(HLOD)가 멀리 있는 여러 액터를 하나의 최적화된 메시로 묶어 드로 콜과 메시 복잡도를 줄이고, 필요하면 오클루전 볼륨이나 Precomputed Visibility로 컬링을 직접 다듬을 수도 있습니다.

머티리얼과 텍스처 변경 줄이기

가까이 있는 물체에 가능하면 같은 머티리얼을 쓰면 렌더링 상태 변경이 줄어 드로 콜 비용이 내려갑니다. 작은 텍스처 여러 장을 큰 텍스처 한 장에 모은 텍스처 아틀라스(texture atlas)를 쓰면 텍스처를 바꿔 끼우는 비용도 줄어듭니다.

동적 광원

동적 광원은 렌더 스레드에도 부담을 줍니다. 먼저 프로젝트의 조명 방식을 확인합니다. 미리 계산한 조명을 쓰는 프로젝트라면 광원을 Static이나 Stationary로 바꾸는 것을 검토할 수 있지만, 루멘(Lumen) 전역 조명을 쓰면 미리 계산한 정적 조명의 기여가 꺼지므로 "Static으로 바꾸라"는 조언을 그대로 적용할 수 없습니다. 동적 광원의 수는 줄이고, 광원의 모빌리티는 그림자 방식, 겹침, 영향 범위, 렌더 경로를 함께 비교해 고릅니다. 이름만으로 모든 장면에서의 비용 순서를 정할 수는 없습니다.


그 밖의 렌더링 설정

엔진의 스케일러빌리티(Scalability) 설정은 그래픽 품질을 한꺼번에 조절하게 해 주고, r.ScreenPercentage나 r.PostProcessAAQuality 같은 개별 콘솔 변수(CVar)로 세부 설정을 바꿀 수도 있습니다. 모바일, 콘솔, PC처럼 대상 플랫폼의 하드웨어에 맞춰 렌더링 경로를 고르는데, 예를 들어 모바일에서는 모바일 렌더링 경로를 쓰고 데스크톱에서는 더 발전된 기능을 씁니다.

반사는 성능에 큰 영향을 주므로 Reflection Capture 액터를 알맞게 배치하고, 화면 공간 반사(SSR)는 필요할 때만 씁니다. 안티에일리어싱은 언리얼 엔진 5에서 기본으로 시간적 초해상도(TSR)를 쓰며, 시간 축으로 누적하는 방식은 상황에 따라 잔상(ghosting)이나 흐림을 만들 수 있으므로 필요하면 TAA나 FXAA 같은 다른 방법과 비교합니다.

측정에 쓰는 빌드도 구별합니다. 게임 코드를 최적화하지 않는 DebugGame에서 잰 시간으로 배포 성능을 판단하지 말고, 목적에 맞는 Development나 Test 같은 구성에서 같은 콘텐츠와 설정으로 측정합니다. 소스를 추적하기 위한 디버깅 결과와 성능 측정 결과는 따로 다룹니다.

한 번에 한 가지 비용 항목만 바꾸고, 같은 장면의 GPU 시간과 Draw 시간, 그리고 화면 품질을 함께 비교합니다. 나나이트, 루멘, 가상 섀도 맵처럼 지금 쓰는 렌더 경로에 해당하는 설정인지도 먼저 확인합니다.


정리

  • 렌더링을 고치기 전에 측정으로 렌더링이 주요 원인인지 확인하고, stat unit으로 GPU 바운드와 렌더 스레드의 CPU 바운드를 나눕니다.
  • GPU 바운드는 셰이더 명령, 오버드로, 폴리곤 수, 해상도, 후처리, 그림자, 레이 트레이싱이 원인이 되며, 머티리얼 단순화, 불투명 머티리얼, LOD, 그림자 설정, 해상도 배율로 줄입니다.
  • Shader Complexity 뷰의 색은 실제 GPU 시간이 아니므로 profilegpu로 확인합니다.
  • 해상도 배율은 가로와 세로에 함께 적용되므로, 50%는 픽셀 수의 25%입니다. 픽셀이 줄어든 비율만큼 시간이 줄어든다는 보장은 없습니다.
  • 렌더 스레드의 부담은 액터 병합(Actor > Merge Actors), 인스턴싱, 컬링, HLOD, 같은 머티리얼과 텍스처 아틀라스로 드로 콜과 상태 변경을 줄여 낮춥니다.
  • 조명과 그림자의 조언은 루멘, 가상 섀도 맵, 나나이트처럼 쓰고 있는 렌더 경로에 맞는지 먼저 확인하고, 한 번에 하나씩 바꿔 측정합니다.

연습 문제

1. 80%로 줄이면

같은 단순 모델에서 렌더링 해상도를 80%로 줄이면 가로, 세로, 픽셀 수는 각각 얼마이고, 원래 대비 비율은 얼마일까요? 픽셀 수가 줄어든 비율만큼 GPU 시간과 전체 FPS도 반드시 좋아질까요?

해설 보기

1920×0.8 = 1536, 1080×0.8 = 864이므로 1536×864 = 1,327,104픽셀이고, 원래의 0.8×0.8 = 0.64, 즉 64%입니다.

하지만 GPU 시간이 그만큼 줄어든다는 보장은 없습니다. 렌더 패스에는 해상도와 다르게 늘고 주는 비용도 있고, 업스케일링 비용이 더해지며, 다른 스레드의 시간과 대기도 프레임에 섞입니다. 같은 장면에서 화면 품질과 함께 전후의 시간을 측정해 비교해야 합니다.

2. Game이 길어진 장면

게임 스레드의 시간이 Frame과 함께 길어진 장면에서 렌더링 해상도부터 낮췄습니다. 알맞은 순서일까요?

해설 보기

알맞지 않습니다. Game 축이 주된 신호라면 병목은 게임 로직 쪽에 있으므로, 해상도를 낮춰 GPU 부담을 줄여도 프레임은 거의 나아지지 않습니다. 먼저 언리얼 인사이트의 타이밍 뷰에서 게임 스레드의 어떤 작업이 긴지 조사하고 그 작업을 고칩니다.

3. 병합했더니 오히려 느려졌다

넓은 마을의 집 수백 채를 한 덩어리로 병합해 드로 콜을 크게 줄였는데, 플레이어가 좁은 골목에 있을 때 GPU 시간이 오히려 늘었습니다. 왜 그럴 수 있을까요?

해설 보기

병합하면 컬링 단위가 커지기 때문입니다. 골목에서는 실제로 보이는 집이 몇 채뿐인데, 마을 전체가 한 메시라면 그 일부만 보여도 전체를 그려야 할 수 있습니다. 가려진 집까지 그리게 되어 GPU가 할 일이 늘어난 것입니다.

병합은 제출 비용을 줄이는 대신 컬링의 효과를 잃을 수 있으므로, 적당한 크기로 묶거나 HLOD처럼 먼 거리에서만 묶는 방법을 비교합니다. 먼 거리의 품질과 가려진 영역의 비용을 같은 장면에서 함께 측정합니다.

액터 병합의 방법과 옵션은 Epic의 Merging Actors 문서에서, 성능을 살펴보는 방법은 Introduction to Performance Profiling and Configuration 문서에서 확인할 수 있습니다.

다음 글 CPU, GPU 병목 현상 해결에서는 측정한 병목을 실제로 해결하는 과정을 CPU와 GPU의 경우로 나눠 따라가 봅니다.