게임 버전별 렌더링 결과의 시각적 회귀를 AI로 자동 비교하는 방법

Unity와 Unreal Engine 기반 게임의 버전별 렌더링 캡처를 정규화하고 픽셀 차이와 AI 기반 유사도 모델로 비교해 그래픽 회귀를 CI에서 자동 탐지하는 실전 가이드입니다.

✓ 복사 완료!
챕터자동화 테스트 & 회귀 검증
난이도중급
상태● 검토 완료
대상 엔진
UnityUnreal Engine
최종 업데이트
게임 버전별 렌더링 결과의 시각적 회귀를 AI로 자동 비교하는 방법

핵심 요약

게임 버전별 렌더링 결과의 시각적 회귀 테스트는 동일한 씬과 카메라 조건에서 캡처한 이미지를 기준 이미지와 비교해 의도하지 않은 그래픽 변화를 찾는 방법입니다. 실무에서는 픽셀 차이와 SSIM 같은 결정적 지표에 AI 기반 이미지 임베딩과 영역 분류를 결합합니다. 조명·파티클·안티앨리어싱처럼 변동성이 큰 영역은 마스크로 제외하거나 별도 정책으로 다뤄야 오탐을 줄일 수 있습니다.

CI 파이프라인은 캡처 정규화 → 차이 계산 → AI 의미 비교 → 사람 검토 및 승인 순서로 구성합니다. 배포 여부는 AI 점수 하나가 아니라 각 단계의 검증 결과를 바탕으로 판단합니다.

게임 렌더링 시각적 회귀 테스트란 무엇인가?

시각적 회귀 테스트는 코드나 엔진 버전을 변경한 뒤 화면이 이전 기준과 달라졌는지 자동으로 확인하는 테스트입니다. 기능 테스트가 플레이어 입력과 상태 전이를 검증한다면 시각적 회귀 테스트는 다음과 같은 렌더링 결과를 검증합니다.

비교 대상탐지 가능한 회귀 사례
머티리얼셰이더 컴파일 오류, 노멀맵 반전, 텍스처 누락
조명노출값 변경, 그림자 누락, 라이트맵 재생성 오류
카메라FOV 변경, 클리핑, 위치 오프셋
후처리톤 매핑, 블룸, 색수차, 안티앨리어싱 변화
UI앵커 오프셋, 폰트 폴백, 해상도별 레이아웃 오류
이펙트파티클 위치, VFX 타이밍, 투명도 변화
플랫폼 출력PC·콘솔·모바일 GPU별 셰이더 및 색상 차이

두 이미지를 단순히 픽셀 단위로 비교하면 아주 작은 색상 차이도 오류로 판단할 수 있습니다. 반대로 AI 기반 의미 비교만 사용하면 UI 한 칸의 이동이나 작은 텍스처 누락을 정상적인 장면 변화로 오인할 수 있습니다. 따라서 게임 QA에서는 두 방식을 함께 사용하는 편이 안전합니다.

왜 게임 버전별 이미지 비교에서 오탐이 발생하는가?

렌더링 이미지는 소스 코드만으로 결정되지 않습니다. GPU 드라이버, 해상도, 렌더 스케일, 셰이더 캐시, 프레임 타이밍, 난수 시드, 환경광, 후처리 설정이 결과에 영향을 줍니다.

특히 다음 요소는 같은 버전에서도 프레임마다 달라질 수 있습니다.

  • 파티클과 천 시뮬레이션의 시간 진행
  • TAA와 TSR의 이전 프레임 히스토리
  • 동적 그림자와 화면 공간 반사
  • 네트워크 상태와 플레이어 위치
  • 폰트 래스터라이저와 운영체제별 글꼴 폴백
  • GPU별 부동소수점 연산 및 드라이버 동작

이 문제를 해결하려면 비교 알고리즘보다 먼저 캡처 조건을 고정해야 합니다. 기준 이미지와 테스트 이미지를 같은 해상도에서 만들고 렌더 스케일, 카메라 변환, 밝기, 색 공간, 캡처 프레임을 통제해야 합니다.

AI 시각적 회귀 비교는 어떻게 구성하는가?

권장 흐름은 캡처, 정규화, 비교, 판정의 네 단계입니다.

flowchart LR
    A[동일한 테스트 씬 실행] --> B[고정 프레임 캡처]
    B --> C[해상도·색 공간 정규화]
    C --> D[픽셀 차이와 SSIM 계산]
    C --> E[AI 임베딩 유사도 계산]
    D --> F[영역별 점수 결합]
    E --> F
    F --> G{판정}
    G -->|통과| H[자동 통과]
    G -->|검토| I[리포트와 사람 검토]
    G -->|실패| J[CI 차단]

1. 캡처 조건을 고정한다

Unity에서는 테스트 전용 씬과 -batchmode 실행 옵션을 사용하고 카메라 위치와 회전값을 테스트 데이터로 관리합니다. Unreal Engine에서는 Functional Test 또는 Automation Test에서 동일한 맵과 카메라를 로드한 뒤 캡처 시점을 고정합니다.

권장하는 캡처 메타데이터는 다음과 같습니다.

메타데이터예시
게임 빌드1.8.0+shader-fix.142
엔진 버전Unity 2022.3 LTS 또는 Unreal Engine 5.3
플랫폼Windows-DX12, PS5, Android-Vulkan
해상도1920x1080
렌더 스케일100%
색 공간Linear rendering, sRGB output
캡처 프레임시뮬레이션 시작 후 120프레임
난수 시드20240824
테스트 씬VisualRegression/LightingRoom

캡처 프레임은 씬의 상태가 안정되는 시점으로 정해야 합니다. 로딩 직후 첫 프레임을 캡처하면 셰이더 워밍업이나 텍스처 스트리밍이 끝나지 않아 정상적인 결과도 실패로 판정될 수 있습니다.

2. 이미지와 메타데이터를 정규화한다

비교 전에 다음 작업을 수행합니다.

  1. 기준 이미지와 테스트 이미지를 같은 너비와 높이로 변환합니다.
  2. 알파 채널을 제거하거나 동일한 방식으로 합성합니다.
  3. 색 공간을 통일하고 sRGB 이미지를 동일한 색상 형식으로 읽습니다.
  4. 동적 영역 마스크를 적용합니다.
  5. 필요하면 UI와 월드 렌더링을 별도 레이어로 비교합니다.

마스크는 오류를 숨기는 용도로 사용하면 안 됩니다. 파티클처럼 본질적으로 변하는 영역을 비교에서 제외했다면 마스크를 적용한 이유와 범위를 리포트에 남겨야 합니다. 캐릭터의 얼굴, 무기, UI 버튼처럼 반드시 안정되어야 하는 영역은 마스크로 가리지 않는 편이 좋습니다.

게임 버전별 기준 이미지와 테스트 이미지의 차이 영역을 보여주는 시각적 회귀 비교 리포트

픽셀 비교와 AI 비교는 어떻게 결합하는가?

픽셀 차이와 SSIM

가장 단순한 픽셀 차이는 기준 이미지 I_base와 테스트 이미지 I_test의 각 채널에 대한 평균 절대 차이로 계산할 수 있습니다.

Dpixel=mean⁡(∣Ibase−Itest∣)D_{pixel} = \operatorname{mean}(|I_{base} - I_{test}|)

구조적 유사도인 SSIM은 밝기, 대비, 구조의 유사도를 함께 평가합니다. 일반적인 회귀 점수는 다음처럼 표현할 수 있습니다.

Dstructural=1−SSIM(Ibase,Itest)D_{structural} = 1 - SSIM(I_{base}, I_{test})

픽셀 차이는 국소적인 큰 오류에 민감하고 SSIM은 작은 이동이나 전체적인 대비 변화에 상대적으로 관대합니다. 두 점수는 전체 이미지 하나로 계산하기보다 UI, 캐릭터, 배경, 이펙트처럼 의미 있는 영역별로 계산하는 편이 유용합니다.

AI 이미지 임베딩 유사도

AI 비교에서는 이미지 또는 이미지 타일을 임베딩 벡터로 변환한 뒤 코사인 유사도를 계산할 수 있습니다. CLIP 계열 모델이나 사내 학습 모델을 사용할 수 있지만 모델이 모든 그래픽 오류를 이해한다고 가정해서는 안 됩니다. 특히 게임 UI의 1~2픽셀 이동이나 셰이더 오류까지 안정적으로 식별한다고 기대하기는 어렵습니다.

두 임베딩 벡터 e_base와 e_test의 코사인 거리는 다음과 같습니다.

Dembedding=1−ebase⋅etest∥ebase∥×∥etest∥D_{embedding} = 1 - \frac{e_{base} \cdot e_{test}}{\lVert e_{base} \rVert \times \lVert e_{test} \rVert}

임베딩 비교는 색조가 조금 바뀌었지만 장면의 의미가 같은 경우를 유연하게 처리합니다. 반면 버튼 위치가 8픽셀 이동하거나 작은 아이콘이 사라지는 문제는 놓칠 수 있습니다. 그러므로 다음과 같은 혼합 점수를 사용할 수 있습니다.

Dtotal=wpDpixel+wsDstructural+weDembeddingD_{total} = w_p D_{pixel} + w_s D_{structural} + w_e D_{embedding}

여기서 w_p, w_s, w_e는 프로젝트의 오류 유형에 따라 조정하는 가중치입니다. 세 점수의 범위가 서로 다를 수 있으므로 합산하기 전에 점수를 정규화해야 하며 가중치의 합을 1로 맞추면 결과를 해석하기 쉽습니다. UI 회귀가 중요하면 픽셀 및 구조 점수의 비중을 높이고 예술적 스타일 변화가 잦은 배경은 임베딩의 비중을 높일 수 있습니다.

OpenCV 기반 결정적 비교 예제

아래 예제는 AI 임베딩을 호출하기 전에 기본 차이 이미지를 만드는 코드입니다. structural_similarity의 data_range와 반환값은 scikit-image API 문서를 따릅니다. 유사도 맵이 uint8 변환 범위를 벗어나지 않도록 차이값을 클리핑합니다. 실제 CI에서는 여기에 임베딩 모델 점수와 영역별 정책을 추가합니다. 설명을 단순화하기 위해 입력 이미지의 크기와 색 공간이 이미 정규화되어 있다고 가정합니다.

from pathlib import Path
import cv2
import numpy as np
from skimage.metrics import structural_similarity

base_path = Path('baseline/lighting-room.png')
test_path = Path('artifacts/lighting-room.png')
diff_path = Path('artifacts/lighting-room.diff.png')

base = cv2.imread(str(base_path), cv2.IMREAD_COLOR)
test = cv2.imread(str(test_path), cv2.IMREAD_COLOR)

if base is None or test is None:
    raise FileNotFoundError('기준 이미지 또는 테스트 이미지가 없습니다.')

if base.shape != test.shape:
    raise ValueError(f'해상도가 다릅니다: {base.shape} != {test.shape}')

gray_base = cv2.cvtColor(base, cv2.COLOR_BGR2GRAY)
gray_test = cv2.cvtColor(test, cv2.COLOR_BGR2GRAY)

score, diff = structural_similarity(gray_base, gray_test, data_range=255, full=True)
diff_u8 = np.clip((1.0 - diff) * 255, 0, 255).astype(np.uint8)
_, mask = cv2.threshold(diff_u8, 32, 255, cv2.THRESH_BINARY)

mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
heatmap = cv2.applyColorMap(diff_u8, cv2.COLORMAP_JET)
diff_path.parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(str(diff_path), heatmap)

print({'ssim': round(float(score), 5), 'changed_pixels': int(np.count_nonzero(mask))})

임계값은 모든 프로젝트에 공통으로 적용하지 말고 테스트 케이스별로 저장해야 합니다. 예를 들어 정적 UI 테스트에는 SSIM 기준을 엄격하게 적용하고 안개와 파티클이 포함된 장면에는 마스크와 영역별 기준을 함께 사용합니다.

AI 자동 비교를 CI/CD에 어떻게 연결할까?

Step 1. 기준 이미지 저장소를 분리한다

기준 이미지는 일반 빌드 산출물과 섞지 말고 버전이 관리되는 저장소에 보관합니다. 파일 경로에는 씬, 플랫폼, 해상도, 그래픽 프리셋을 포함합니다.

visual-baselines/
  windows-dx12/
    1920x1080/
      high/
        lighting-room.png
        inventory-ui.png

기준을 갱신할 때는 자동 덮어쓰기 대신 Pull Request 승인 절차를 둡니다. 변경 이유, 담당자, 기준 엔진 버전, 관련 이슈 번호를 함께 기록해야 의도된 아트 변경과 회귀 버그를 구분할 수 있습니다.

Step 2. 빌드에서 테스트 씬을 자동 실행한다

CI 작업은 다음 순서로 동작합니다.

  1. 고정된 엔진 버전과 그래픽 API로 빌드를 만듭니다.
  2. 테스트 씬을 지정한 시드와 프레임에서 실행합니다.
  3. 원본 캡처와 정규화된 캡처를 저장합니다.
  4. 픽셀 차이, SSIM, AI 임베딩 점수를 계산합니다.
  5. 차이 이미지와 JSON 리포트를 CI 아티팩트로 업로드합니다.
  6. 정책 임계값을 넘으면 작업을 실패시키고 검토 링크를 남깁니다.

Step 3. 자동 판정과 사람 검토를 분리한다

모든 차이를 즉시 실패로 처리하면 팀이 알림을 무시하게 됩니다. 다음과 같이 위험도 구간을 두는 방식이 실용적입니다.

상태조건 예시처리
통과모든 보호 영역이 기준 이내자동 통과
검토 필요배경 임베딩 차이는 작지만 특정 영역 차이 존재리포트 생성 후 승인
실패UI 이동, 텍스처 누락, 셰이더 오류CI 차단
캡처 오류해상도 불일치, 씬 로드 실패, 이미지 누락테스트 인프라 실패

판정에 사용한 근거 데이터도 함께 출력해야 합니다. 최소한 전체 점수, 영역별 점수, 차이 마스크, 기준 이미지, 테스트 이미지, 모델 버전, 임계값을 저장합니다. 그래야 모델 업데이트로 판정이 바뀌었을 때 원인을 추적할 수 있습니다.

어떤 영역을 AI로 분석해야 하는가?

이미지 전체를 한 장으로만 비교하면 오류 위치를 설명하기 어렵습니다. 다음과 같은 타일 또는 의미 영역 단위의 분석을 권장합니다.

  • HUD와 메뉴: OCR 또는 UI 레이아웃 좌표 비교
  • 캐릭터와 무기: 실루엣, 머티리얼, 애니메이션 포즈 비교
  • 배경: 임베딩 유사도와 저주파 색상 차이 비교
  • 그림자와 반사: 픽셀 차이보다 설정 변경 로그와 함께 분석
  • 파티클: 여러 프레임을 비교해 시간에 따른 분포를 평가

영역 분할은 수동 좌표로 시작해도 됩니다. 테스트 케이스가 안정되면 세그멘테이션 모델이나 오브젝트 ID 패스(object ID pass)를 활용할 수 있습니다. 다만 세그멘테이션 모델 자체가 변경되면 판정 결과가 달라질 수 있으므로 모델 버전도 빌드 메타데이터에 기록해야 합니다.

시각적 회귀 테스트의 임계값은 어떻게 정하는가?

임계값은 모델의 기본값이 아니라 프로젝트 데이터로 정해야 합니다. 먼저 정상적인 빌드를 여러 번 캡처해 자연스럽게 발생하는 변동 범위를 측정합니다. 그런 다음 UI 위치를 8픽셀 이동하거나 텍스처를 누락하는 등 의도적인 대표 회귀를 주입해 정상 변동과 실제 오류를 분리할 수 있는 기준을 찾습니다.

예를 들어 다음 수치를 별도로 측정합니다.

  • 정상 재실행 사이의 평균 D_pixel
  • 정상 재실행 사이의 최대 D_structural
  • 머티리얼 누락이나 UI 이동 시의 점수 변화
  • 플랫폼별 점수 분포
  • 마스크 적용 전후의 오탐률

통계적으로는 정상 샘플의 평균과 표준편차를 이용해 경고 기준을 설정할 수 있습니다.

threshold=mean⁡(normal_scores)+k×std⁡(normal_scores)threshold = \operatorname{mean}(normal\_scores) + k \times \operatorname{std}(normal\_scores)

k는 실패 비용과 오탐 비용에 따라 조정합니다. 단일 숫자 하나보다 영역별 임계값과 오류 유형별 정책을 두는 편이 안전합니다.

운영 중인 게임에서는 무엇을 추가로 기록해야 하는가?

라이브 게임에서는 클라이언트와 서버 상태가 항상 일치하지 않을 수 있습니다. 패치 버전, 그래픽 옵션, 플랫폼, GPU 모델, 해상도, 언어, UI 스케일을 함께 기록하면 특정 환경에서만 발생하는 회귀를 찾기 쉽습니다.

권장 리포트 필드는 다음과 같습니다.

{
  "build": "1.8.0+142",
  "engine": "Unity 2022.3 LTS",
  "platform": "Windows-DX12",
  "resolution": "1920x1080",
  "scene": "LightingRoom",
  "seed": 20240824,
  "frame": 120,
  "model": "vision-embedding-v3",
  "scores": {
    "pixel": 0.018,
    "ssim_distance": 0.041,
    "embedding_distance": 0.067
  },
  "decision": "review"
}

실서비스에서 수집한 화면을 학습 데이터로 재사용할 때는 개인정보, 채팅 내용, 계정 정보가 포함되지 않는지 확인해야 합니다. UI 테스트용 캡처에는 계정명과 알림이 노출되지 않도록 테스트 계정을 사용하고 민감한 영역은 사전에 제거합니다.

자주 묻는 질문 (FAQ)

픽셀 비교만으로 충분한가?

정적 UI나 셰이더 출력처럼 픽셀이 같아야 하는 영역에는 효과적입니다. 그러나 파티클, 반사, TAA처럼 자연스러운 변동이 있는 영역에서는 오탐이 늘어나므로 SSIM과 AI 임베딩을 보조 수단으로 결합하는 것이 좋습니다.

AI 모델이 시각적 회귀 테스트를 완전히 대체할 수 있는가?

대체하기 어렵습니다. AI는 의미적으로 비슷한 두 장면을 높은 유사도로 판단할 수 있어 작은 UI 이동이나 아이콘 누락을 놓칠 수 있습니다. 배포 차단은 보호 영역에 대한 결정적 비교와 함께 운영해야 합니다.

기준 이미지는 언제 갱신해야 하는가?

기획 또는 아트 변경이 승인되고 해당 변경이 의도된 결과인지 확인된 경우에만 갱신합니다. 기준 이미지 자동 덮어쓰기는 회귀를 새로운 기준으로 굳힐 수 있으므로 Pull Request에서 변경 이미지와 사유를 검토한 뒤 갱신해야 합니다.

정리

게임 버전별 렌더링 결과를 비교할 때 가장 중요한 것은 고성능 AI 모델 자체가 아니라 재현 가능한 캡처 조건과 추적 가능한 판정 과정입니다. Unity와 Unreal Engine의 테스트 씬을 고정하고 해상도·색 공간·프레임·난수 시드를 통제한 뒤, 픽셀 차이와 SSIM으로 정확한 변화를 찾고 AI 임베딩으로 의미적 유사도를 보완하면 오탐과 미탐을 함께 줄일 수 있습니다.

최종적으로는 차이 점수만 저장하지 말고 차이 이미지, 영역별 점수, 마스크, 엔진과 플랫폼 정보, 모델 버전을 함께 CI 아티팩트로 남겨야 합니다. 그래야 그래픽 패치가 실제 회귀인지 승인된 비주얼 변경인지 빠르게 판단할 수 있습니다.