유니티 2D 노말맵 · URP 세컨더리 텍스처 자동 설정
유니티 2D 노말맵이 안 먹힐 때는 대개 네 가지 중 하나가 빠져 있습니다. URP와 2D 렌더러, 조명을 받는 스프라이트 머티리얼, _NormalMap이라는 이름의 세컨더리 텍스처, 노멀맵을 켠 Light2D입니다. 텍스처 작업 공간은 노말맵을 만들고, 텍스처 쪽 설정을 대신 해 주는 에디터 스크립트를 함께 내보냅니다. Unity 6에서 Gamma·Linear 프로젝트 모두 예측한 조명대로 그려졌습니다.
Unity 6에서 2D 노멀맵은 네 가지가 맞아야 읽힙니다. 프로젝트가 URP와 2D 렌더러로 그리고, 스프라이트가 Sprite-Lit-Default 머티리얼을 쓰며, 노멀맵이 스프라이트 텍스처에 이름이 _NormalMap인 보조 텍스처로 붙어 있고, Light 2D의 Normal Map Quality가 Fast나 Accurate여야 합니다(기본값은 Disabled). Nerulio는 PNG, JSON, 그리고 텍스처 쪽 설정을 대신하고 맞는 Light 2D까지 배치하는 에디터 스크립트를 내보내며, Unity 6000.5.3f1·URP 17.5에서 12회 중 12회 통과했습니다.

- Unity 6 URP 2D검증됨· Unity 6000.5.3f1 (URP 17.5)
- Godot 4 2D lights검증됨· Godot 4.7.2
- PNG set + JSON일반 파일
URP 2D 조명이 스프라이트 노멀맵을 읽는 방식
URP의 조명용 스프라이트 셰이더는 스프라이트의 보조 텍스처(Secondary Textures)에서 노멀맵을 읽습니다. 스프라이트와 함께 저장되고 이름으로 찾는 추가 텍스처이며, 셰이더가 찾는 이름이 _NormalMap입니다. 스프라이트와 UV를 공유하므로 시트와 크기·배치가 정확히 같아야 합니다. Unity는 Y+ 노멀맵(OpenGL 방식)을 쓰고, RGB가 XYZ이며 평평한 노멀은 (0.5, 0.5, 1)입니다.
노멀맵은 색이 아니라 벡터를 담으므로 sRGB(Color Texture)를 꺼야 합니다. 켜 두면 값이 감마 변환되어 모든 경사가 틀어집니다. Unity 매뉴얼은 Texture Type을 Normal Map으로 가져오지만, Nerulio 임포터는 Default 텍스처로 두고 sRGB 끔, 무압축, 밉맵 없음, Non-Power of 2 None으로 설정하며, 이것이 Unity 6에서 통과한 구성입니다.
조명 쪽에서는 Normal Map Quality가 노멀 매핑을 켜고(Fast 또는 Accurate), Normal Map Distance가 조명과 스프라이트 사이 거리를 Unity 단위로 정합니다. 높이처럼 작동해서, 거리가 작으면 빛이 옆에서 가파르게 들어오고 크면 거의 바로 위에서 들어옵니다.
2D 렌더러는 조명을 화면 해상도의 일부 크기 텍스처에 계산하며 기본값은 0.5입니다(Light Render Texture Scale). 부드러운 그림에서는 티가 안 나지만 도트 그림에서는 1픽셀짜리 조명 띠가 뭉개지므로, Nerulio README는 1을 권합니다.
- 보조 텍스처
- 스프라이트에 붙어 이름으로 찾는 추가 텍스처. 노멀은
_NormalMap, 마스크는_MaskTex. - Normal Map Quality
- Light 2D마다 있는 설정. Disabled(기본)·Fast·Accurate.
- Normal Map Distance
- 조명과 스프라이트 평면 사이 거리(Unity 단위). Nerulio는 Studio 높이 ÷ Pixels Per Unit으로 넣습니다.
- Pixels Per Unit
- Unity 1단위에 해당하는 텍스처 픽셀 수. 내보내기 기본값은 100.
예시: Studio 조명 하나를 Unity 단위로 바꾸기
32 × 32 프레임, Pixels Per Unit 100, Studio 조명이 x 8, y 6(픽셀, 아래가 +), 높이 24px, 반경 96px일 때:
위치 x = (8 − 32/2) / 100 = −0.08
위치 y = (32/2 − 6) / 100 = +0.10 (Unity는 위가 +)
Normal Map Distance = 24 / 100 = 0.24 Outer Radius = 96 / 100 = 0.96
스프라이트 영역, 시트 192 × 32, 3번 프레임: x = 96, y = 32 − (0 + 32) = 0
Light Render Texture Scale 0.5, 1920 × 1080 화면 → 조명을 960 × 540으로 계산
1.0 → 1920 × 1080, 화면 픽셀마다 조명 텍셀 하나위치는 임포터가 모든 프레임에 설정하는 가운데 피벗을 기준으로 잽니다. Unity는 텍스처 행을 아래에서부터 세기 때문에 영역도 아래 기준으로 기록됩니다.
이렇게 작동합니다
- Tools › Nerulio › Apply Texture JSON이 스프라이트 영역을 나누고 노말맵을 세컨더리 텍스처 _NormalMap으로 붙입니다. sRGB 끔, 무압축, 밉맵 없음, 2의 거듭제곱 크기 변경 없음.
- Tools › Nerulio › Create Lit Preview가 스프라이트와 Studio 조명마다 점 Light2D를 하나씩 만들고, Normal Map 품질 Accurate, Normal Map Distance = z ÷ Pixels Per Unit으로 맞춥니다.
- Pixels Per Unit은 nerulio-texture.json 안의 숫자(기본 100)이며, 조명 위치·반경·거리가 이 값으로 나뉩니다.
- Unity README는 도트 게임에 Light Render Texture Scale을 안내합니다. 새 2D 렌더러에서 0.5라 조명 해상도가 절반이 됩니다.
- Unity 6000.5.3f1, URP 17.5, 2D 렌더러에서 12/12 통과(실제 에셋 6건 × Gamma·Linear), 평균 오차 0.0037~0.0085.
- 노말맵은 유니티가 읽는 OpenGL 방식(Y+)입니다. 다른 툴에서 받은 DirectX 노말맵은 쓰기 전에 점검 패널로 판별할 수 있습니다.
사용 방법
- 스프라이트나 시트를 넣습니다(시트는 스프라이트 작업 공간에서 먼저 자릅니다).
- 베벨을 맞추고 조명을 놓은 뒤, C로 평면·조명 화면을 나눠 비교합니다.
- 내보내기(Ctrl+E)에서 Unity 6 URP 2D를 체크합니다.
- unity 폴더를 Assets/에 복사하고 에디터 스크립트가 컴파일되기를 기다립니다.
- Tools › Nerulio › Apply Texture JSON을 실행한 다음 Create Lit Preview를 누릅니다.
받게 되는 파일
torch.png라는 스프라이트라면 unity/ 폴더에 다음이 들어 있습니다.
| 파일 | 용도 |
|---|---|
torch.png, torch_n.png | 원본과 바이트까지 같은 스프라이트·시트와 OpenGL(Y+) 노멀맵. |
nerulio-texture.json | 크기, Pixels Per Unit, Point 필터 여부, 아래 기준 프레임 영역, 조명의 위치·높이·세기·반경·색. |
Editor/NerulioNormalMapImporter.cs | Tools › Nerulio › Apply Texture JSON과 Create Lit Preview 메뉴를 추가합니다. |
README.md | 이 단계, Light Render Texture Scale 안내, 검증 상태. |
Unity 6(URP 2D)에서 설정하기
- URP와 2D 렌더러로 그리는 프로젝트를 쓰고, 스프라이트 머티리얼은 Sprite-Lit-Default로 둡니다.
unity폴더를Assets/에 복사하면 Unity가Editor/NerulioNormalMapImporter.cs를 컴파일합니다.- Tools › Nerulio › Apply Texture JSON을 실행해
nerulio-texture.json을 고릅니다. 스프라이트에 프레임 영역(시트는 Multiple, 도트는 Point 필터)과 보조 텍스처_NormalMap이 들어가고, 노멀맵은 sRGB 끔·무압축·밉맵 없음·2의 거듭제곱 크기 변경 없음으로 설정됩니다. - Tools › Nerulio › Create Lit Preview를 실행하면 첫 프레임과 Studio 조명마다 Light 2D가 든
NerulioLitPreview오브젝트가 생기며, Normal Map Quality는 Accurate, Normal Map Distance는 높이 ÷ Pixels Per Unit입니다. - 직접 만든 조명(GameObject › Light, 예: Spot Light 2D)은 Normal Map Quality를 Fast나 Accurate로 바꾸고 Normal Map Distance를 정한 뒤, Target Sorting Layers에 스프라이트의 레이어가 포함되는지 확인합니다.
- 도트 그림이면 Renderer 2D 에셋을 열어 Light Render Texture Scale을 1로 둡니다.
결과 확인
- 스프라이트 텍스처를 선택 › Open Sprite Editor › Secondary Textures에서
_NormalMap이torch_n.png를 가리키는지 봅니다. torch_n.png를 선택해 sRGB(Color Texture)가 꺼져 있고 인스펙터의 크기가 PNG 크기와 같은지 확인합니다.- Light 2D를 스프라이트 위로 옮기면 윗가장자리가 밝아집니다. Normal Map Quality를 Disabled로 바꾸면 입체가 사라지는데, 이것으로 맵이 읽히고 있음이 확인됩니다.
유니티 2D 노말맵 설정
| 설정 | 임포터가 넣는 값 | 안 맞으면 생기는 일 |
|---|---|---|
| 세컨더리 텍스처 이름 | 알베도 스프라이트에 _NormalMap | 다른 이름이면 라이트용 스프라이트 셰이더가 읽지 않음 |
| sRGB (Color Texture) | 끔 — 색이 아니라 벡터 | 감마 변환이 걸려 요철 방향이 틀어짐 |
| Non-Power of 2 | None | 96×64가 128×64로 늘어나 노멀이 흐려짐(평균 오차 0.005 대신 0.045) |
| 압축·밉맵 | 무압축, 밉맵 끔 | 압축되거나 평균된 텍셀은 내보낸 벡터와 달라짐 |
| Max Size | 시트에 맞게 올림(2048~16384) | 기본 2048이면 큰 시트가 줄어듦 |
| Light2D 노멀맵 | 품질 Accurate, Distance = z ÷ Pixels Per Unit | 노멀맵이 꺼지면 무시되고, 거리가 틀리면 요철이 납작하거나 과해짐 |
| Light Render Texture Scale | 그대로 둠(프로젝트 설정), README에서 1 권장 | 0.5면 작은 스프라이트의 조명이 계단처럼 보임 |
표는 Unity 6000.5.3f1과 URP 17.5에서 배포 임포터가 한 일입니다. 다른 Unity·URP 버전은 돌려 보지 않았습니다.
문제 해결
| 증상 | 유력한 원인 | 확인 방법 | 해결 |
|---|---|---|---|
| 평평하게 빛나기만 하고 입체가 없음 | Light 2D의 Normal Map Quality가 기본값 Disabled | Light 2D 인스펙터 | Fast나 Accurate로 바꾸기(Create Lit Preview는 Accurate로 설정) |
| 입체가 반대로 기울고 아래에서 비친 듯함 | DirectX 방식(Y−) 맵이거나 sRGB가 켜져 벡터가 감마 변환됨 | 노멀맵 가져오기 설정, Studio 점검 패널의 판정 | Nerulio의 torch_n.png(Y+)를 쓰고 sRGB 끄기. OpenGL·DirectX 판별 참고 |
| 노멀이 흐리거나 픽셀과 어긋남 | 맵이 2의 거듭제곱으로 늘어나거나 압축됨(첫 실행에서 96 × 64가 128 × 64로 바뀜) | 인스펙터 크기가 PNG와 다름 | Non-Power of 2 None, 무압축. 현재 임포터는 둘 다 설정 |
| 도트 그림의 조명 띠가 뭉개지고 계단처럼 보임 | Light Render Texture Scale이 0.5 | Renderer 2D 에셋 | 1로 바꾸기. Unity 도트 흐림 해결도 참고 |
| 스프라이트가 전혀 밝아지지 않음 | Sprite-Unlit-Default 머티리얼이거나, 조명의 Target Sorting Layers에 스프라이트 레이어가 빠짐 | Sprite Renderer 머티리얼과 조명의 Target Sorting Layers | Sprite-Lit-Default를 쓰고 레이어 포함하기 |
| Create Lit Preview가 조명을 만들지 않음 | URP가 설치되지 않았거나(콘솔에 표시), Unity 6 이전 어셈블리에서 Light2D를 찾던 옛 임포터 | 명령 뒤의 콘솔 메시지 | URP 설치, 다시 내보내 현재 임포터 쓰기 |
다른 방법
| 방법 | 이럴 때 더 낫습니다 |
|---|---|
| Unity Sprite Editor › Secondary Textures에서 직접 | 스프라이트가 한두 개라면 _NormalMap을 직접 추가하세요. Unity 매뉴얼은 맵의 Texture Type을 Normal Map으로 두지만, 임포터는 sRGB를 끈 Default로 두며 검증한 것은 이 구성입니다. |
| 직접 만든 조명 스프라이트 셰이더(Shader Graph) | URP 2D 조명으로는 안 나오는 표현이 필요할 때. 위의 텍스처 설정은 그대로 적용됩니다. |
| Unity 스프라이트 시트 내보내기 | 노멀맵 없이 Unity에서 프레임만 잘리면 될 때. |
엔진별 내보내기와 검증 방법
"검증됨"은 실제 CC0 에셋으로 내보낸 파일을 해당 엔진·도구가 직접 불러와 원본과 비교했다는 뜻입니다. 불러오지 않은 형식은 그렇게 표시합니다.
| 대상 | 파일 | 검증 |
|---|---|---|
| Godot 4 2D lights | _lit.tscn · CanvasTexture .tres · normal / specular PNG | 검증됨 Godot 4.7.2실제 CC0 사례 6개를 Godot(gl_compatibility)가 렌더링했고, 확인한 모든 프레임이 Studio 조명 미리보기와 1/255 이내로 같았습니다. 초록을 뒤집은 같은 렌더는 3.8~37단계 어긋나므로 잘못된 규약을 잡아낼 수 있습니다. |
| Unity 6 URP 2D | PNG · nerulio-texture.json · Editor/NerulioNormalMapImporter.cs | 검증됨 Unity 6000.5.3f1 (URP 17.5)12회 중 12회 통과(사례 6개 × 감마·리니어 색 공간): URP가 적용한 조명이 내보낸 노멀맵과 일치합니다(평균 오차 0.0037~0.0085). 빛의 감쇠는 URP 방식이라 밝기는 미리보기가 아니라 Unity 기준입니다. |
| PNG set + JSON | albedo · _n (OpenGL) · _n_dx (DirectX) · 16-bit height · AO · specular · manifest | 일반 파일어떤 엔진이나 도구에서도 쓰는 일반 파일이며, 엔진 실행 검증은 주장하지 않습니다. |
검증 근거
Godot 4.7.2가 실제 CC0 사례 6개(도트, HD 스프라이트, 반복 텍스처, 가져온 DirectX 맵)를 렌더링했고 확인한 모든 프레임이 Studio 미리보기와 1/255 이내였습니다. Unity 6000.5 URP 2D: 12회 중 12회 통과(사례 6개 × 감마·리니어). 규약을 아는 실제 맵으로 OpenGL/DirectX 판별: 1K 전체 맵 128개 100% 정답, 표본 1,562개 중 틀린 "높음" 판정 0건(초록이 밝으면 OpenGL이라는 단순 규칙은 46%).
한계
- 밝기는 Studio가 아니라 URP 자체의 감쇠를 따릅니다. 검증한 것은 위치, 높이, N·L 항입니다.
- Unity용 스페큘러 맵은 내보내지 않습니다(Godot 전용).
- Unity 6000.5.3f1과 URP 17.5에서만 돌려 봤으며, 다른 버전은 조명 필드 이름이 다를 수 있습니다.
- 투명한 가장자리가 없는 그림(반복 텍스처)에서는 빨강이 뒤집힌 것과 DirectX 초록을 구별할 수 없습니다. 점검 패널이 이를 알리고 "빨강 뒤집기"를 제공합니다.
- 시트는 스프라이트 작업 공간에서 프레임을 자르기 전까지 그림 하나로 조명됩니다. 텍스처 작업 공간은 프레임을 자르지 않습니다.
- 림 라이트는 미리보기 전용이며, Unity로는 스페큘러 맵을 내보내지 않습니다(Godot는 내보냄).
- Unity는 자체 빛 감쇠를 씁니다. 내보낸 노멀맵은 Unity에서 검증했지만 밝기 곡선은 Unity 방식입니다.
버전과 출처
Unity 6000.5.3f1, URP 17.5, 2D 렌더러, Direct3D 12: 12회 중 12회 통과(실제 사례 6건 × Gamma·Linear 색 공간). 내보낸 맵 대비 N·L 평균 오차 0.0037~0.0085, p95 0.013~0.022였고, Unity 안에서 맵의 초록을 뒤집으면 모든 실행이 실패했습니다(평균 오차 0.07~0.19). 검사는 배포하는 임포터를 그대로 실행했습니다. 메뉴 이름, 기본값, 보조 텍스처 이름은 Unity 매뉴얼을 따릅니다.
참고한 공식 문서
자주 묻는 질문
유니티 2D 노말맵이 왜 안 먹히나요?
순서대로 확인하세요. 프로젝트가 2D 렌더러를 쓰는 URP인지, 스프라이트 머티리얼이 Sprite-Lit-Default인지, 노말맵이 정확히 _NormalMap이라는 이름의 세컨더리 텍스처인지, Light2D의 노멀맵이 켜져 있는지. 텍스처 쪽은 임포터가 맡고, 파이프라인·머티리얼·직접 둔 조명은 직접 설정해야 합니다.
도트 그림에 조명이 뭉개지고 계단처럼 보여요.
2D 렌더러는 기본으로 조명을 절반 해상도로 계산합니다(Light Render Texture Scale 0.5). 2D 렌더러 에셋에서 1로 바꾸세요. 프로젝트 설정이라 내보내기는 건드리지 않으며, 검증 때는 1로 두었습니다.
Unity 6에서 고치기 전에는 뭐가 문제였나요?
검증 중에 찾은 우리 임포터의 버그 세 개입니다. Unity 6가 Light2D를 Unity.RenderPipelines.Universal.2D.Runtime 어셈블리로 옮겨서 미리보기에 조명이 안 생겼고, URP 17에서 Normal Map Distance·Quality가 읽기 전용이라 이제 SerializedObject로 설정하며, 96×64 노말맵이 128×64로 늘어나 흐려졌습니다(평균 오차 0.045 → 크기 변경 None으로 0.005).
밝기도 Studio와 똑같나요?
아니요. 위치·색·높이·세기는 Studio 값이지만 감쇠는 URP가 자체 곡선으로 그려서 밝기 곡선은 유니티 것입니다. 검증은 노말맵 항(N·L)을 비교했고, 전체 밝기는 비교하지 않았습니다. Unity에는 스페큘러 맵도 가지 않습니다.
무료인가요? 계정이 필요한가요?
이 페이지의 기능은 모두 무료이며 계정 없이 쓸 수 있습니다. 워터마크나 막혀 있는 내보내기가 없습니다.
스프라이트가 업로드되나요?
아니요. Studio는 이 브라우저 탭 안에서 디코딩·패킹·내보내기를 합니다. 프로젝트는 브라우저(IndexedDB)에 자동 저장되며 .nerulio 파일로 저장할 수 있습니다.
Aseprite나 엔진 플러그인을 설치해야 하나요?
아니요. .aseprite 파일은 브라우저에서 읽고 씁니다. 엔진별 번들에는 그 엔진에 필요한 것이 들어 있습니다(Godot는 가져오기 설정과 씬, Unity와 Godot 타일셋은 작은 편집기 스크립트).