Unity 모바일 배터리 최적화: On-Demand Rendering과 동적 타겟 프레임률 구현 가이드

모바일 기기의 발열과 배터리 소모를 줄이기 위해 Unity OnDemandRendering API와 targetFrameRate를 동적으로 제어하는 실무 최적화 기법을 완벽 해설합니다.

✓ 복사 완료!
챕터렌더링 파이프라인 & 배칭
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
Unity 모바일 배터리 최적화: On-Demand Rendering과 동적 타겟 프레임률 구현 가이드

핵심 요약

모바일 기기에서 불필요하게 높은 60fps 또는 120fps 유지는 GPU/CPU 연산량을 급증시켜 발열로 인한 쓰로틀링(Thermal Throttling)과 급격한 배터리 방전을 초래합니다. Unity의 UnityEngine.Rendering.OnDemandRendering API와 Application.targetFrameRate를 연동하면 인게임 전투 중에는 60fps를 유지하다가 인벤토리나 로비 화면, 터치 입력이 없는 유휴(Idle) 상태에서는 렌더링 주기를 15~30fps로 낮추어 GPU 전력 소모를 40~60% 이상 절감할 수 있습니다.


모바일 환경에서 프레임 레이트와 배터리 소모는 어떤 관계가 있을까?

모바일 기기의 SoC(System on Chip)에서 전력 소비량은 동작 전압과 클럭 주파수에 직접적으로 비례합니다. 디지털 회로의 동적 전력 소모 공식은 다음과 같습니다.

Pdynamic=C⋅V2⋅fP_{\text{dynamic}} = C \cdot V^2 \cdot f

여기서 CC는 유효 정전용량, VV는 공급 전압, ff는 클럭 주파수입니다. 프레임 레이트가 높아질수록 GPU와 CPU는 초당 더 많은 드로우 콜(Draw Call), 버텍스 변환, 픽셀 셰이딩을 처리하기 위해 높은 주파수 ff와 이에 필요한 고전압 VV를 유지해야 하므로 전력 소모가 기하급수적으로 증가합니다.

정적인 대화창, 인벤토리 정리 화면, 턴제 게임의 대기 시간처럼 화면 변화가 거의 없는 상태에서도 매초 60번씩 전체 씬을 다시 그리는 것은 심각한 배터리 낭비입니다.

화면 상태권장 타겟 프레임률렌더링 간격전력 소모 수준주요 적용 대상
액션/전투60 fps매 프레임 (1)높음 (High)빠른 카메라 이동, 물리 연산 씬
일반 로비/UI30 fps매 프레임 (1)중간 (Medium)퀘스트 창, 상점, 캐릭터 장비창
대화/정적 메뉴30 fps2 프레임당 1회 (2)낮음 (Low)텍스트 대화, 설정 메뉴, 카드 덱 빌딩
방치/유휴 (Idle)30 fps3~4 프레임당 1회 (3~4)최소 (Minimal)유저 조작이 5초 이상 없을 때

Application.targetFrameRate vs OnDemandRendering: 무엇이 다를까?

많은 개발자가 프레임 제어를 위해 Application.targetFrameRate만을 사용하지만 Unity 2019.3부터 도입된 OnDemandRendering을 함께 이해해야 정밀한 제어가 가능합니다.

Unity 라이프사이클에서 Application.targetFrameRate와 OnDemandRendering의 동작 주기 차이

1. Application.targetFrameRate

  • 동작 방식: 엔진 루프 전체(Update, Coroutine, Physics, Render)의 실행 주기를 직접 제한합니다.
  • 특징: 프레임률을 20으로 낮추면 Update() 실행 주기 자체가 초당 20회로 줄어들어 터치 입력 반응이나 백그라운드 타이머 계산의 분해능이 떨어집니다.

2. OnDemandRendering.renderFrameInterval

  • 동작 방식: Update(), FixedUpdate(), 터치 입력 처리 등 CPU 게임 로직은 기존 프레임률(예: 60Hz)로 부드럽게 유지하면서 GPU 렌더링 파이프라인(Draw Call 제출 및 래스터라이제이션)만 N프레임마다 1번씩 실행하도록 건너뜁니다.
  • 장점: 터치 입력을 감지하는 순간 즉시 다음 프레임에서 렌더링 간격을 정상화할 수 있어 조작 반응성이 저하되지 않습니다.
// 60Hz 루프 기준, renderFrameInterval = 2로 설정 시 렌더링은 30fps로 동작
OnDemandRendering.renderFrameInterval = 2;

렌더링 빈도 계산식은 아래와 같습니다.

Effective Render FPS=Application.targetFrameRateOnDemandRendering.renderFrameInterval\text{Effective Render FPS} = \frac{\text{Application.targetFrameRate}}{\text{OnDemandRendering.renderFrameInterval}}

On-Demand Rendering을 활용한 동적 프레임 제어 아키텍처는 어떻게 구현할까?

사용자 입력 여부와 현재 게임 상태(인게임, UI, 유휴 상태)를 감지하여 자동으로 렌더링 빈도를 조절하는 프레임 매니저 아키텍처를 설계합니다.

flowchart TD
    A["사용자 터치 입력 감지 / UI 전환"] --> B{"현재 상태 판단"}
    B -->|"터치 발생"| C["renderFrameInterval = 1<br/>즉시 60fps 복원"]
    B -->|"인게임 플레이"| C
    B -->|"정적 UI 열림"| D["renderFrameInterval = 2<br/>30fps로 절전"]
    B -->|"유휴 시간 5초 초과"| E["renderFrameInterval = 4<br/>15fps 초절전 모드"]
    C --> F["Idle 타이머 리셋"]
    D --> G["GPU 렌더 패스 스킵 & 배터리 절감"]
    E --> G

Step-by-Step 구현 가이드

  1. DynamicFrameManager 작성: 싱글톤 컴포넌트로 전역 입력을 감지하고 유휴 타이머를 갱신합니다.
  2. 입력 발생 시 즉각 복구: Input.anyKey 또는 Input.touchCount > 0 발생 시 renderFrameInterval을 즉시 1로 변경합니다.
  3. UI 전환 연동: 무거운 UI 창이 열리면 기본 인터벌을 높여 불필요한 GPU 렌더링을 방지합니다.

아래는 모바일 프로젝트에 바로 적용할 수 있는 C# 완성형 스크립트 예제입니다.

using UnityEngine;
using UnityEngine.Rendering;

public class DynamicFrameManager : MonoBehaviour
{
    public static DynamicFrameManager Instance { get; private set; }

    [Header("기본 프레임 설정")]
    [SerializeField] private int baseTargetFrameRate = 60;
    [SerializeField] private float idleThresholdSeconds = 5.0f;

    [Header("인터벌 설정 (60Hz 기준)")]
    [SerializeField] private int activeInterval = 1;     // 60 fps 렌더링
    [SerializeField] private int staticUiInterval = 2;   // 30 fps 렌더링
    [SerializeField] private int idleInterval = 4;       // 15 fps 렌더링

    private float lastInputTime;
    private bool isStaticUiOpen = false;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }

        QualitySettings.vSyncCount = 0; // 모바일에서는 vSync를 끄고 타겟 프레임을 제어
        Application.targetFrameRate = baseTargetFrameRate;
        OnDemandRendering.renderFrameInterval = activeInterval;
        lastInputTime = Time.unscaledTime;
    }

    private void Update()
    {
        // 1. 유저 터치 또는 키 입력 감지
        if (HasUserInteraction())
        {
            lastInputTime = Time.unscaledTime;
            if (OnDemandRendering.renderFrameInterval != activeInterval)
            {
                OnDemandRendering.renderFrameInterval = activeInterval;
            }
            return;
        }

        // 2. 유휴 시간 경과에 따른 단계적 프레임 인터벌 조정
        float idleDuration = Time.unscaledTime - lastInputTime;

        if (idleDuration >= idleThresholdSeconds)
        {
            if (OnDemandRendering.renderFrameInterval != idleInterval)
            {
                OnDemandRendering.renderFrameInterval = idleInterval;
            }
        }
        else if (isStaticUiOpen)
        {
            if (OnDemandRendering.renderFrameInterval != staticUiInterval)
            {
                OnDemandRendering.renderFrameInterval = staticUiInterval;
            }
        }
    }

    private bool HasUserInteraction()
    {
        if (Input.touchCount > 0)
        {
            TouchPhase phase = Input.GetTouch(0).phase;
            return phase == TouchPhase.Began || phase == TouchPhase.Moved || phase == TouchPhase.Stationary;
        }
        return Input.anyKey;
    }

    public void SetStaticUiState(bool isOpen)
    {
        isStaticUiOpen = isOpen;
        lastInputTime = Time.unscaledTime;
        OnDemandRendering.renderFrameInterval = isOpen ? staticUiInterval : activeInterval;
    }
}

DynamicFrameManager의 유휴 시간 기반 프레임률 전환 흐름


실전 적용 시 주의해야 할 3가지 엣지 케이스와 해결책

1. UI 스크롤 및 드래그 반응성 저하 문제

  • 문제점: ScrollRect를 드래그할 때 renderFrameInterval이 2 이상으로 유지되면 스크롤이 버벅이는 현상이 발생합니다.
  • 해결책: IPointerDownHandler, IBeginDragHandler 이벤트 인터페이스를 구현한 공용 UI 감지기를 배치하여 드래그 중에는 무조건 renderFrameInterval = 1을 유지하도록 강제합니다.

2. 파티클 시스템 및 애니메이션 재생과의 충돌

  • 문제점: 화면에 지속적으로 루프되는 파티클 이펙트나 UI 셰이더 애니메이션이 있을 때 프레임 인터벌을 낮추면 시각적으로 뚝뚝 끊겨 보일 수 있습니다.
  • 해결책: 화면 내에 주요 시각 피드백이 활성화되어 있는 동안에는 인터벌 조절을 일시 중지하는 락(Lock) 카운터 시스템을 도입합니다.

3. URP(Universal Render Pipeline) 호환성

  • 확인 사항: Unity 2020.3 LTS 및 2022 LTS 이상의 URP에서는 OnDemandRendering이 공식 지원됩니다. 단, 커스텀 렌더 패스(Render Pass)를 작성할 때는 해당 패스가 매 프레임 실행될 필요가 없는지 검토해야 불필요한 GPU 대역폭 낭비를 막을 수 있습니다.

자주 묻는 질문 (FAQ)

Q1. OnDemandRendering을 적용하면 Unity UI 이벤트나 Update() 함수도 느리게 호출되나요?

아닙니다. Update(), LateUpdate(), FixedUpdate() 및 EventSystem의 터치 입력 감지는 Application.targetFrameRate에 지정된 원래 주기(예: 60fps)로 정상 실행됩니다. 오직 최종 씬 렌더링 패스(Render Loop)만 지정한 간격만큼 건너뛰어 GPU 연산을 생략합니다.

Q2. iOS와 Android 플랫폼 모두에서 동일하게 배터리 절감 효과가 나타나나요?

네, 두 플랫폼 모두에서 유의미한 전력 절감 효과가 확인됩니다. 특히 URP 환경의 Android(Vulkan/GLES3) 및 iOS(Metal) 기기에서 정적 메뉴 화면 시 GPU 동작 클럭이 즉각 최저 단계로 스케일 다운되어 기기 표면 온도와 배터리 소모율이 대폭 감소합니다.

Q3. targetFrameRate를 직접 바꾸는 것과 OnDemandRendering 중 어느 방식이 더 유리한가요?

유저와의 실시간 상호작용이 중요한 모바일 게임에서는 OnDemandRendering이 훨씬 유리합니다. targetFrameRate를 낮추면 터치 다운 감지 속도까지 지연되지만 OnDemandRendering은 터치 입력을 감지한 즉시 동일 프레임에서 인터벌을 1로 복원할 수 있어 부드러운 사용자 경험을 유지할 수 있습니다.


요약 및 모바일 라이브 빌드 최적화 체크리스트

  • QualitySettings.vSyncCount = 0으로 설정하고 Application.targetFrameRate를 60으로 고정했는가?
  • 정적 팝업창, 인벤토리, 대화창 진입 시 OnDemandRendering.renderFrameInterval을 2(30fps)로 조정하는가?
  • 유저 터치 입력이 5초 이상 없을 때 3~4(15~20fps) 인터벌로 전환되는 유휴 타이머가 작동하는가?
  • 터치 감지 즉시 renderFrameInterval = 1로 복귀하여 입력 딜레이가 발생하지 않는가?
  • 스크롤 뷰 드래그 및 주요 이펙트 재생 시 프레임 드랍이 없도록 예외 처리가 되어 있는가?