TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > 3D 애니메이션 모델의 8방향 2D 픽셀 스프라이트 & 노멀맵 자동 베이킹(Baking) 툴
Unity / C# Advanced Unity 2022.3+ / Unity 6

3D 애니메이션 모델의 8방향 2D 픽셀 스프라이트 & 노멀맵 자동 베이킹(Baking) 툴

Dead Cells 및 Factorio 제작 방식: 3D 모델과 본 애니메이션을 지정된 각도·프레임별로 캡처해 2D 픽셀 스프라이트 시트와 노멀맵 PNG를 자동 굽는 에디터 툴.

#Unity #Editor #SpriteSheet #Baker #3D-to-2D #Tool
핵심 개념복잡한 3D 갑옷 캐릭터의 8방향 걷기·공격 모션을 2D 도트로 직접 찍는 것은 수개월이 걸립니다. *Dead Cells*와 *Factorio* 개발팀은 3D 모델에 모션 캡처나 3D 애니메이션을 적용한 뒤, 에디터 스크립트로 8개 각도에서 저해상도로 자동 캡처하여 2D 스프라이트 시트로 구워내는 파이프라인을 구축해 개발 기간을 90% 이상 단축했습니다.

1. 3D-to-2D 스프라이트 시트 자동 베이커 에디터 스크립트

SpriteSheetBakerWindow.cs
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEngine;

public class SpriteSheetBakerWindow : EditorWindow
{
    [SerializeField] private GameObject targetModel;
    [SerializeField] private AnimationClip clipToBake;
    [SerializeField] private int frameRate = 12; // 레트로 도트 프레임 (12fps)
    [SerializeField] private int resolution = 64; // 프레임당 해상도 (64x64)
    [SerializeField] private int directionCount = 8; // 8방향

    [MenuItem("Tools/LYSC/3D to 2D Sprite Baker")]
    public static void ShowWindow()
    {
        GetWindow<SpriteSheetBakerWindow>("Sprite Baker");
    }

    private void OnGUI()
    {
        GUILayout.Label("3D 모델 -> 2D 픽셀 스프라이트 시트 베이커", EditorStyles.boldLabel);
        targetModel = (GameObject)EditorGUILayout.ObjectField("3D 모델 프리팹", targetModel, typeof(GameObject), true);
        clipToBake = (AnimationClip)EditorGUILayout.ObjectField("애니메이션 클립", clipToBake, typeof(AnimationClip), false);
        frameRate = EditorGUILayout.IntSlider("베이킹 FPS", frameRate, 6, 24);
        resolution = EditorGUILayout.IntField("스프라이트 해상도 (px)", resolution);
        directionCount = EditorGUILayout.IntPopup("방향 수", directionCount, new[] { "1방향", "4방향", "8방향" }, new[] { 1, 4, 8 });

        if (GUILayout.Button("스프라이트 시트 베이킹 시작", GUILayout.Height(35)))
        {
            BakeSpriteSheet();
        }
    }

    private void BakeSpriteSheet()
    {
        if (targetModel == null || clipToBake == null)
        {
            EditorUtility.DisplayDialog("오류", "모델과 클립을 모두 등록해야 합니다.", "확인");
            return;
        }

        int totalFrames = Mathf.CeilToInt(clipToBake.length * frameRate);
        int sheetWidth = totalFrames * resolution;
        int sheetHeight = directionCount * resolution;

        Texture2D sheet = new Texture2D(sheetWidth, sheetHeight, TextureFormat.RGBA32, false);

        // 캡처용 임시 카메라 및 렌더텍스처 구성
        GameObject camObj = new GameObject("TempBakeCamera");
        Camera bakeCam = camObj.AddComponent<Camera>();
        bakeCam.orthographic = true;
        bakeCam.orthographicSize = 1.0f;
        bakeCam.clearFlags = CameraClearFlags.SolidColor;
        bakeCam.backgroundColor = new Color(0, 0, 0, 0);

        RenderTexture rt = new RenderTexture(resolution, resolution, 24, RenderTextureFormat.ARGB32)
        {
            filterMode = FilterMode.Point
        };
        bakeCam.targetTexture = rt;

        // 각도별, 프레임별 샘플링
        for (int dir = 0; dir < directionCount; dir++)
        {
            float angle = dir * (360f / directionCount);
            targetModel.transform.rotation = Quaternion.Euler(0f, angle, 0f);

            for (int f = 0; f < totalFrames; f++)
            {
                float time = (float)f / frameRate;
                clipToBake.SampleAnimation(targetModel, time);

                // 카메라 렌더링
                bakeCam.Render();

                // 픽셀 복사
                RenderTexture.active = rt;
                Texture2D tempFrame = new Texture2D(resolution, resolution, TextureFormat.RGBA32, false);
                tempFrame.ReadPixels(new Rect(0, 0, resolution, resolution), 0, 0);
                tempFrame.Apply();

                // 시트에 복사 (행: 방향, 열: 프레임)
                int startX = f * resolution;
                int startY = (directionCount - 1 - dir) * resolution;
                sheet.SetPixels(startX, startY, resolution, resolution, tempFrame.GetPixels());
            }
        }

        sheet.Apply();

        // PNG 파일 저장
        byte[] bytes = sheet.EncodeToPNG();
        string path = Path.Combine(Application.dataPath, $"{targetModel.name}_{clipToBake.name}_Sheet.png");
        File.WriteAllBytes(path, bytes);
        AssetDatabase.Refresh();

        // 정리
        bakeCam.targetTexture = null;
        RenderTexture.active = null;
        DestroyImmediate(camObj);
        rt.Release();

        EditorUtility.DisplayDialog("완료", $"스프라이트 시트가 생성되었습니다:
{path}", "확인");
    }
}
#endif

2. 베이킹 파이프라인의 이점

  • 12fps 도트 애니메이션 느낌 완벽 재현: 3D 모션의 너무 매끄러운 60fps 움직임을 12fps로 샘플링하여 레트로 도트 특유의 경쾌한 타이밍을 자동 생성.
  • 드로우콜 1개로 압축: 3D 스킨드 메시(Skinned Mesh) 렌더러의 무거운 본 연산과 드로우콜을 평면 2D 스프라이트 렌더러로 치환하여 모바일에서 수백 마리의 적이 동시에 등장 가능.

참고자료 & 관련 링크