AI 기반 노멀 맵 Invert Y 오류를 잡는 법: 탄젠트 스페이스 자동 검증 파이프라인

노멀 맵의 Invert Y 오류가 왜 생기는지부터 OpenGL·DirectX 탄젠트 스페이스 차이, TBN 기반 검증 수식과 기준 렌더 비교, AI를 활용한 자동 경고 파이프라인까지 실무 적용법을 정리합니다. Unity와 Unreal에 공통으로 적용할 체크 포인트도 함께 다룹니다.

✓ 복사 완료!
챕터자동화 테스트 & 회귀 검증
난이도중급
상태● 검토 완료
대상 엔진
UnityUnreal Engine
최종 업데이트
AI 기반 노멀 맵 Invert Y 오류를 잡는 법: 탄젠트 스페이스 자동 검증 파이프라인

핵심 요약

노멀 맵의 Invert Y 오류는 탄젠트 스페이스의 Y축 방향과 텍스처의 G 채널 규약이 프로젝트 설정과 맞지 않을 때 발생합니다. 일반적인 OpenGL 계열은 +Y를 위쪽으로 보고 DirectX 계열은 -Y를 사용하는 경우가 많지만 이미지 파일만 보고 정답을 확정할 수는 없습니다.

자동 검증에서는 프로젝트의 탄젠트 규약을 기준으로 원본 후보와 Y 반전 후보를 모두 디코드한 뒤 TBN으로 변환하고 기준 노멀 또는 기준 렌더와의 각도 오차를 비교할 수 있습니다. AI는 의심 영역 분류와 메타데이터 분석을 돕고 최종 판정은 결정론적인 수치 검증으로 처리하는 편이 좋습니다.

노멀 맵 Invert Y 오류는 왜 발생하는가?

노멀 맵은 RGB 색상이 아니라 표면의 방향을 저장하는 탄젠트 스페이스 벡터입니다. 일반적인 디코딩은 다음과 같습니다.

nt=normalize(2c−1)=(2R−1,2G−1,2B−1)n_t = normalize(2c - 1) = (2R-1, 2G-1, 2B-1)

여기서 R은 탄젠트 방향인 X축, G는 바이탄젠트 방향인 Y축, B는 표면 법선 방향인 Z축을 나타냅니다. Y축을 반전하면 G 채널의 의미가 바뀝니다.

nt′=(x,−y,z)n'_t = (x, -y, z)

텍스처 색상으로 표현하면 다음과 같습니다.

G′=1−GG' = 1 - G
규약탄젠트 Y 방향텍스처에서 주로 바뀌는 채널주의점
OpenGL 계열 관례+YG툴과 엔진의 실제 설정을 확인해야 함
DirectX 계열 관례-YGDirectX용으로 만든 맵을 다른 규약에서 사용하면 굴곡이 뒤집힐 수 있음
프로젝트 사용자 정의 규약별도 정의보통 G임포터와 셰이더가 같은 규약을 사용해야 함

문제는 동일한 노멀 맵이 어느 규약으로 제작되었는지 픽셀만으로는 알 수 없다는 점입니다. 오목한 홈이 실제로는 볼록한 돌출부일 수도 있고 그 반대일 수도 있습니다. 따라서 Invert Y는 이미지 분석만으로 확정하는 문제가 아니라 제작 규약과 렌더링 결과를 함께 검증하는 문제입니다.

탄젠트 스페이스와 TBN 행렬은 어떤 관계인가?

탄젠트 공간의 노멀을 월드 공간으로 변환할 때는 TBN 기저를 사용합니다. T는 tangent, B는 bitangent, N은 vertex normal입니다.

nw=normalize(TBN×nt)n_w = normalize(TBN \times n_t)

TBN 행렬은 다음과 같이 구성할 수 있습니다.

TBN=[T B N]TBN = [T\ B\ N]

바이탄젠트는 UV 미러링 여부를 반영하는 handedness 값에 따라 계산해야 합니다.

B=h×normalize(cross(N,T))B = h \times normalize(cross(N, T))

여기서 h는 보통 +1 또는 -1입니다. UV가 미러링된 메시에서 handedness를 무시하면 G 채널을 올바르게 처리해도 특정 섬이나 경계에서 노멀 방향이 틀어질 수 있습니다. 즉 Invert Y 오류처럼 보이는 현상이 실제로는 탄젠트 생성 방식이나 미러 UV 문제일 수도 있습니다.

탄젠트 스페이스 노멀 맵에서 원본과 Y 반전 결과의 라이팅 차이를 비교한 예시

자동 검증은 어떤 순서로 구현하는가?

1단계: 프로젝트 노멀 맵 프로파일을 고정한다

먼저 엔진이나 DCC 툴의 설정을 추측하지 말고 프로젝트 프로파일로 명시해야 합니다.

  • 노멀 맵 규약: OpenGL, DirectX 또는 사용자 정의 규약
  • 임포트 시 Green Channel 반전 여부
  • 텍스처 색공간: sRGB 해제 여부
  • 압축 방식: BC5, 일반 RGBA 또는 플랫폼별 압축
  • 탄젠트 생성 방식: MikkTSpace 등
  • 메시의 UV 미러링과 tangent handedness 처리 방식
  • 셰이더에서 추가로 Y 반전을 적용하는지 여부

발생할 수 있는 실수 중 하나는 임포터가 이미 G 채널을 반전했는데 셰이더에서 다시 n.y *= -1을 적용하는 것입니다. 이 경우 두 번 반전되어 결과적으로 원래 방향으로 돌아오지만 플랫폼이나 머티리얼 경로가 달라지면 일관성이 깨질 수 있습니다.

2단계: 원본 후보와 Invert Y 후보를 모두 만든다

검증기는 하나의 정답을 가정하지 않고 두 후보를 생성해야 합니다.

import numpy as np
from PIL import Image


def decode_normal(path, invert_y=False):
    # RGB에 XYZ 성분이 직접 저장된 선형 노멀 맵을 가정합니다.
    # BC5, DXT5nm 등 채널 재배치 형식은 먼저 형식에 맞게 디코드해야 합니다.
    rgb = np.asarray(Image.open(path).convert('RGB'), dtype=np.float32) / 255.0
    normal = rgb * 2.0 - 1.0

    if invert_y:
        normal[..., 1] *= -1.0

    length = np.linalg.norm(normal, axis=-1, keepdims=True)
    return normal / np.clip(length, 1e-6, None)


def angular_error(test_normal, reference_normal):
    dot = np.sum(test_normal * reference_normal, axis=-1)
    dot = np.clip(dot, -1.0, 1.0)
    return np.degrees(np.arccos(dot))


candidate_native = decode_normal('candidate.png', invert_y=False)
candidate_flipped = decode_normal('candidate.png', invert_y=True)
reference = decode_normal('reference.png', invert_y=False)

error_native = angular_error(candidate_native, reference)
error_flipped = angular_error(candidate_flipped, reference)

native_median = float(np.median(error_native))
flipped_median = float(np.median(error_flipped))
native_p95 = float(np.percentile(error_native, 95))
flipped_p95 = float(np.percentile(error_flipped, 95))

if abs(native_median - flipped_median) < 1.0:
    print('AMBIGUOUS: 수동 검토 필요')
elif native_median < flipped_median:
    print('PREFERRED: 원본 Y 후보가 기준과 더 가깝습니다')
else:
    print('PREFERRED: Invert Y 후보가 기준과 더 가깝습니다')

print(f'native median={native_median:.2f}, p95={native_p95:.2f}')
print(f'flipped median={flipped_median:.2f}, p95={flipped_p95:.2f}')

이 코드는 동일한 UV 정렬과 동일한 탄젠트 스페이스를 가진 기준 노멀 이미지가 있을 때 사용할 수 있습니다. 기준 이미지가 다른 규약으로 저장되어 있다면 기준 이미지도 해당 규약에 맞게 디코드해야 합니다.

이 예시 코드는 중앙값으로 두 후보의 상대 순위만 표시하며 통과 여부를 확정하지 않습니다. 실제 차단 규칙에는 유효한 UV 영역 마스크와 프로젝트별 평균·95 백분위 절대 오차 기준도 적용해야 합니다.

3단계: 픽셀 비교보다 기준 렌더를 우선한다

베이크된 노멀 맵은 동일한 RGB 값이라도 메시의 탄젠트와 UV 배치에 따라 최종 라이팅이 달라집니다. 따라서 가능하면 기준 노멀 이미지 대신 기준 렌더를 비교하는 편이 좋습니다.

기준이 되는 고해상도 메시의 월드 노멀을 알고 있다면 탄젠트 공간으로 변환할 수 있습니다.

nt=transpose(TBN)×nwn_t = transpose(TBN) \times n_w

이때 TBN이 직교 정규화되어 있다는 전제가 필요합니다. 그렇지 않다면 TBN을 먼저 직교화하거나 역행렬을 사용해야 합니다. 저해상도 메시와 하이 폴리 메시의 UV, 버텍스 위치, 탄젠트 생성 조건도 같아야 의미 있는 비교가 가능합니다.

렌더 검증에서는 다음 조건을 고정합니다.

  • 동일한 메시와 UV
  • 동일한 카메라와 해상도
  • 동일한 머티리얼의 노멀 강도
  • 정면, 사선, 후면에 가까운 여러 방향의 테스트 라이트
  • 동일한 톤 매핑과 노출

단일 조명에서는 오차가 잘 드러나지 않을 수 있습니다. 특히 평평한 표면은 Y를 반전해도 차이가 작게 나타나므로 사선 조명과 곡률이 있는 영역을 함께 사용해야 합니다.

flowchart TD
    A[프로젝트 규약과 임포트 설정 수집] --> B[원본 및 Invert Y 후보 생성]
    B --> C[TBN으로 월드 공간 변환]
    C --> D[기준 노멀 또는 기준 렌더와 비교]
    D --> E{각도 오차와 임계값 평가}
    E -->|원본 후보 우세| F[통과]
    E -->|반전 후보 우세| G[Invert Y 경고]
    E -->|차이가 작음| H[수동 검토]

AI는 노멀 맵 검증에서 무엇을 담당해야 하는가?

AI는 Invert Y의 진위를 이미지 하나만 보고 결정하는 도구로 사용하면 안 됩니다. 올바른 역할은 검증 우선순위를 정하고 사람이 확인해야 할 영역을 좁히는 것입니다.

AI 기능입력출력최종 판정 여부
노멀 맵 분류RGB 이미지와 파일 메타데이터노멀 맵 가능성, 채널 패킹 의심아니오
아티팩트 탐지원본·반전 후보의 렌더 차이뒤집힌 홈, 하이라이트 불일치 영역아니오
에셋 그래프 분석익스포터, 임포터, 셰이더 설정규약 충돌 가능성아니오
기준 렌더 비교 보조테스트 렌더와 레퍼런스오류 영역 마스크와 우선순위아니오
규칙 엔진각도 오차, p95, 후보 간 차이통과·경고·차단예

예를 들어 비전 모델이 특정 영역에서 하이라이트 방향이 반대로 보인다고 분류하면 해당 영역의 각도 오차와 메타데이터를 다시 검사하도록 만들 수 있습니다. 그러나 모델의 분류 결과만으로 텍스처를 자동 수정하면 정상적인 오목 표현이나 특수한 스타일라이즈드 노멀까지 반전할 위험이 있습니다.

노멀 맵 자동 검증 결과에서 오차 영역과 임포트 설정을 함께 표시한 예시

셰이더에서 Invert Y를 안전하게 적용하는 방법

런타임에서 프로젝트별 규약을 제어해야 한다면 반전 위치를 한 곳으로 제한합니다. 예를 들어 Unity 계열의 HLSL 스타일 코드에서는 다음처럼 처리할 수 있습니다.

float3 n = UnpackNormal(tex2D(_NormalMap, uv));
n.y *= _InvertY ? -1.0 : 1.0;
n = normalize(n);

float3 worldNormal = normalize(mul(TBN, n));

이 곱셈은 TBN의 열을 T, B, N 기저로 구성한 경우를 가정합니다. 엔진이 제공하는 행렬을 사용할 때는 해당 행렬의 저장·곱셈 규약을 따르세요.

실제 엔진의 UnpackNormal 구현이나 임포터가 이미 규약 변환을 수행하는지 확인해야 합니다. 셰이더 코드에 반전 옵션을 넣더라도 머티리얼 생성 단계에서 임포트 설정과 중복되지 않도록 다음 중 하나만 선택하는 것이 좋습니다.

  1. 임포터에서 프로젝트 규약으로 변환하고 셰이더에서는 추가 반전을 하지 않는다.
  2. 임포터는 원본을 유지하고 셰이더의 명시적인 _InvertY 옵션으로 변환한다.

두 경로를 에셋마다 섞으면 동일한 텍스처가 머티리얼 종류나 플랫폼에 따라 다르게 보일 수 있습니다.

실무 임계값과 리포트 설계

자동 검증의 임계값은 에셋 유형에 따라 달라져야 합니다. 아래 값은 시작점으로 사용할 수 있는 예시이며 프로젝트 테스트 데이터로 조정해야 합니다.

  • 평균 또는 중앙값 각도 오차: 2도 이하를 통과 후보로 검토
  • 95 백분위 각도 오차: 8도 이하를 통과 후보로 검토
  • 원본과 반전 후보의 중앙값 차이: 1도 미만이면 판정 보류
  • UV 미러 섬이나 메시 경계: 전체 평균과 별도로 지역 오차 검사

검증 리포트에는 최소한 다음 정보를 남깁니다.

  • 에셋 경로와 버전
  • 텍스처 색공간과 압축 설정
  • 탄젠트 생성 방식
  • Invert Y 적용 위치
  • 원본·반전 후보의 평균, 중앙값, 95 백분위 오차
  • 오류가 집중된 UV 섬과 머티리얼 슬롯
  • AI가 표시한 의심 영역과 결정론적 검증 결과

문제가 임포트 설정 불일치에서 비롯된 경우에는 이 기록을 바탕으로 텍스처를 다시 굽지 않고 임포트 설정만 고쳐 해결할 수도 있습니다.

자주 묻는 질문 FAQ

노멀 맵의 G 채널을 단순히 반전하면 항상 해결되는가?

아닙니다. G 채널 반전은 탄젠트 Y 규약이 다를 때만 해결책입니다. TBN handedness, UV 미러링, 잘못된 탄젠트 생성, sRGB 활성화가 원인이라면 G 채널만 수정해도 문제가 남습니다.

기준 노멀 맵이나 하이 폴리 메시가 없으면 자동 검증할 수 있는가?

부분적으로 가능합니다. 메타데이터와 프로젝트 프로파일의 충돌은 자동 검출할 수 있지만 독립된 노멀 맵 이미지의 Y 방향이 의도에 맞는지는 확정할 수 없습니다. 이 경우 테스트 패턴, 아티스트의 규약 라벨 또는 기준 렌더를 추가해야 합니다.

BC5 압축 노멀 맵에서도 Invert Y 검증이 필요한가?

필요합니다. BC5는 보통 X와 Y 성분을 저장하고 Z를 재구성하므로 G 채널의 규약이 여전히 중요합니다. 다만 압축 오차와 Z 재구성 오차를 Invert Y 오류와 분리해서 평가해야 합니다.

정리

Invert Y 문제의 핵심은 텍스처의 G 채널 자체가 아니라 텍스처 규약, TBN 생성, 임포트 설정, 셰이더 변환이 하나의 탄젠트 스페이스 정의를 공유하는지에 있습니다. 검증에 활용할 수 있는 한 가지 자동화 방법은 원본과 반전 후보를 모두 만들고 기준 노멀 또는 기준 렌더와의 각도 오차를 비교하는 것입니다.

AI는 의심스러운 렌더 영역과 설정 충돌을 찾는 보조 수단으로 사용하고 통과·경고·차단 결정은 명시적인 프로파일과 수치 임계값으로 관리하면 아트 에셋 파이프라인에서 반복적인 라이팅 오류의 원인을 추적하는 데 도움이 됩니다.