플립북 텍스처: Unity, Godot, VRChat에서 스프라이트를 애니메이션하는 방법
나만의 플립북 텍스처 만들기
플립북 텍스처는 하나의 표면에서 스프라이트 시트를 프레임 단위로 재생합니다. Unity, Godot, VRChat에서 플립북 텍스처를 만드는 방법과 실제 스프라이트 시트를 3D에서 실시간으로 재생하는 방법을 알아보세요.
플립북 텍스처는 셰이더가 하나의 표면에서 프레임 단위로 재생하는 스프라이트 시트입니다. 리깅도 타임라인도 없습니다. 시트가 고정된 그리드 안에 모든 프레임을 담고 있고, 머티리얼이 일정한 속도로 그 그리드 위에서 작은 창을 이동시킵니다. 8 x 8 시트는 64프레임을 담으며, 초당 12프레임이면 약 5.3초에 한 번 반복됩니다. 메시는 전혀 바뀌지 않고, 텍스처를 다시 업로드하지도 않으며, 전체 애니메이션이 텍스처 조회 한 번의 비용으로 처리됩니다.
이 페이지에서는 플립북 셰이더에 필요한 네 가지 수치와 프레임 인덱스가 UV 좌표에 매핑되는 방식, Unity에서 플립북 텍스처를 구동하는 두 가지 방법, VRChat 아바타에서 Poiyomi 플립북을 설정하는 방법, Godot과 Phaser의 동일 기능, 그리고 GIF나 글로 쓴 설명에서 직접 시트를 만드는 방법을 다룹니다.
빠른 답변
플립북 텍스처는 셰이더 애니메이션으로 사용되는 스프라이트 시트로, 시간에 따라 행과 열로 인덱싱됩니다. 시트는 하나의 텍스처 슬롯에 들어가고, 셰이더 평가 시마다 경과 시간과 프레임 레이트로 프레임 인덱스를 계산하고, 그 인덱스를 열과 행으로 축소한 뒤 표면 UV를 해당 셀로 다시 매핑합니다. Frame = floor(time * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). 6 x 6 시트를 10 FPS로 재생하면 36프레임이 3.6초에 순환하며, 전부 GPU에서 처리되고 프레임별 업로드도 드로우 콜 변화도 없습니다.
플립북 텍스처의 작동 방식
애니메이션은 네 가지 수치로 정의됩니다. Columns와 Rows는 시트 레이아웃을 나타냅니다. Frame count는 보통 columns 곱하기 rows이며, 인덱스가 순환하는 기준값입니다. Frame rate는 인덱스가 얼마나 빠르게 진행되는지를 나타냅니다. 나머지 — 필터링, wrap mode, UV 인셋 — 은 샘플러가 현재 셀 밖을 읽지 않도록 하기 위해 존재합니다.
인덱스 계산식은 짧으니 적어둘 만합니다. 경과 시간 t가 주어졌을 때: frame = floor(t * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). 그러면 UV 창은 tiling = (1 / columns, 1 / rows), offset = (column / columns, row / rows)가 됩니다. 세로 방향이 사람들이 자주 헷갈리는 부분입니다. 대부분의 시트 제작 도구는 이미지 맨 위를 행 0으로 쓰지만, Unity와 Godot의 UV 원점은 왼쪽 아래입니다. Unity에서 위쪽 행부터 시작하는 시트를 쓸 때 offset.y는 1 - (row + 1) / rows가 되며, 그렇지 않으면 행이 반전되어 재생되고 애니메이션의 첫 프레임이 시트의 마지막 행이 됩니다.
필터링이 결과의 깔끔함을 결정합니다. 시트의 wrap mode를 Clamp로 설정하세요. Repeat로 두면 텍스처 오른쪽 가장자리에서 샘플링할 때 왼쪽 가장자리를 읽어서, 프레임 0의 픽셀 열이 모든 행의 마지막 프레임 안에 나타납니다. 날카로운 픽셀 아트에는 밉맵을 끄세요. 밉 체인이 인접 셀을 평균화하기 때문에 표면이 몇 미터만 멀어져도 애니메이션이 뭉개집니다. 대상 엔진이나 셰이더가 밉맵을 강제로 켠다면 모든 셀 주위에 1~2픽셀의 투명 여백을 두거나, UV 창을 네 변 모두 반 텍셀씩 인셋하세요.
| 값 | 제어 대상 |
|---|---|
| Columns | 나란히 놓인 프레임 수. 한 셀의 가로 타일링을 1 / columns로 설정합니다. |
| Rows | 시트의 행 수. 세로 타일링을 1 / rows로 설정합니다. 스트립이면 1을 사용하세요. |
| Frame count | 인덱싱되는 총 프레임 수로, 보통 columns x rows입니다. 인덱스가 여기서 순환합니다. |
| FPS | 인덱스가 얼마나 빠르게 진행되는지. floor(time * fps)가 현재 프레임을 반환합니다. |
| Wrap mode | Clamp. Repeat로 두면 샘플러가 시트 가장자리를 넘어 읽습니다. |
| Filter mode | Point, 밉맵 끔. 또는 셀당 반 텍셀씩 UV를 인셋합니다. |
Unity의 플립북 텍스처
두 가지 방법이 있으며 실패하는 방식도 서로 다릅니다. 첫 번째는 행과 열 인덱스를 셰이더가 직접 소유하고 스스로 애니메이션하는 방식입니다. 두 번째는 일반 Lit 또는 Unlit 셰이더가 샘플링을 담당하는 상태에서 Animation 창으로 머티리얼의 tiling과 offset에 키프레임을 넣는 방식입니다. 첫 번째는 게임 시간과 동기화되어야 하는 이펙트에 더 적합하고, 두 번째는 Animator 상태로 애니메이션을 구동하고 씬의 다른 요소와 함께 타임라인에서 보고 싶을 때 더 적합합니다.
어느 쪽이든 스프라이트 시트 임포트 설정을 먼저 맞추세요. Wrap Mode Clamp, Mip Maps off, Filter Mode Point, 그리고 픽셀 아트에는 압축을 적용하지 않습니다. 이 세 가지 설정이 셰이더 연산보다 플립북 버그를 더 많이 일으킵니다.
방법 1: 행과 열 인덱스를 쓰는 셰이더
Shader Graph에서는 Flipbook 노드에 Columns, Rows, 시간 기반 프레임 인덱스를 넣으면 재매핑된 UV가 출력됩니다. 직접 작성한 셰이더에서는 _Columns, _Rows, _Fps를 프로퍼티로 노출하고 버텍스 또는 프래그먼트 단계에서 셀을 계산합니다. 오브젝트 간에 애니메이션이 동기화되어야 한다면, 각 머티리얼에서 _Time.y를 따로 샘플링하지 말고 스크립트 float으로 프레임 인덱스를 구동해서 같은 오브젝트의 두 플립북이 어긋나지 않게 하세요.
방법 2: Animation 창으로 tiling과 offset 구동
이 방법은 텍스처의 _ST 벡터를 애니메이션합니다. 빌트인 Standard와 Unlit은 _MainTex_ST를 사용하고, URP Lit과 Unlit은 _BaseMap_ST를 사용합니다. Tiling은 (1, 1 / rows)로 고정되고 offset.y만 바뀝니다. 아래 단계는 이 방법에 해당합니다.
01
머티리얼을 선택하고 Animation 창을 엽니다. 클립을 만든 다음 텍스처의 _ST 벡터 프로퍼티를 트랙으로 추가합니다. 머티리얼에서 Vector4로 표시됩니다.
02
클립의 sample rate를 목표 FPS와 같은 값으로 설정합니다. sample rate가 원하는 FPS보다 낮으면 키프레임이 같은 샘플에 겹쳐지고 애니메이션이 의도한 것보다 느리게 재생됩니다.
03
프레임마다 offset.y에 키를 넣습니다. 위쪽 행부터 시작하는 시트라면 값 = 1 - (row + 1) / rows, tiling = (1, 1 / rows)입니다.
04
곡선 편집기에서 모든 키프레임의 보간을 Constant로 설정합니다. Linear는 두 셀을 섞어서 두 프레임이 동시에 겹쳐 보이는 고스팅이 생깁니다.
05
마지막 키가 첫 프레임의 값을 반복하지 않게 하세요. 그렇지 않으면 루프 이음새에서 한 프레임 동안 애니메이션이 멈춥니다.
06
여러 렌더러가 머티리얼을 공유한다면 renderer.material을 읽지 말고 렌더러마다 MaterialPropertyBlock을 애니메이션하세요. renderer.material은 처음 접근할 때 머티리얼을 복제하고, 그 복제본이 오브젝트 수명 동안 남습니다.
VRChat용 Poiyomi 플립북
Poiyomi 셰이더의 Flipbook 섹션은 머티리얼 자체에서 UV 창을 시트 위로 스크롤하므로, 하나의 머티리얼 슬롯으로 아바타 메시에 애니메이션 스프라이트를 표시할 수 있습니다. Rows, Columns, 프레임 레이트를 노출하며, 셰이더 시간에 맡기지 않고 프레임 인덱스를 직접 구동하는 모드도 있습니다.
그리드 대신 단일 행 스트립을 사용하세요. 스트립은 셰이더가 따라 이동할 축이 하나뿐이라 세로 이음새가 완전히 사라집니다. 그리드에서는 각 행이 끝날 때마다 UV 창이 아래 행으로 내려가야 하고, 그 경계에서 필터링, 밉 레벨, 반 텍셀 반올림 오차 중 하나라도 발생하면 인접 행을 샘플링해서 재생 중에 다른 프레임의 얇은 띠로 보입니다. 스트립은 프레임 인덱스도 단순한 곱셈이 됩니다. UV에서 프레임 너비는 1 / frameCount이고 offset은 그냥 frame / frameCount입니다. 256 x 256 셀 8프레임 애니메이션이면 스트립은 2048 x 256이 되어 2의 거듭제곱을 유지합니다.
알파는 평면 씬보다 아바타에서 더 중요합니다. 스프라이트 가장자리가 단단하면 Cutout 또는 Opaque를 사용하세요. Transparent 큐의 플립북은 깊이를 쓰지 않기 때문에, 아바타에서는 카메라 각도에 따라 몸체 및 서로 간에 정렬 순서가 달라집니다. 가능하면 애니메이션 표면을 이미 아바타에 있는 머티리얼에 올리세요. 머티리얼 슬롯이 하나 늘어날 때마다 아바타 성능 등급에 불리하게 작용합니다. 동기화된 파라미터가 있는 Animator 상태에서 프레임 인덱스를 구동하면 모든 클라이언트가 같은 프레임을 봅니다. 셰이더 시간으로 계산한 프레임 인덱스는 로컬에서 평가되므로 뷰어마다 루프의 다른 지점에 있을 수 있습니다.
01
프레임을 하나의 가로 스트립으로 내보냅니다. 스트립 높이는 셀 높이, 스트립 너비는 프레임 수 곱하기 셀 너비입니다. 끝에 빈 셀을 남기지 마세요.
02
텍스처를 Wrap Mode Clamp, Mip Maps off, sRGB on으로 임포트합니다. Alpha Is Transparency는 스트립에 실제로 알파가 있을 때만 켜세요.
03
Poiyomi 머티리얼을 만들어 애니메이션 메시에 할당하고 Flipbook 섹션을 엽니다.
04
Rows를 1로, Columns를 프레임 수로 설정합니다. 프레임 레이트는 원하는 길이에 맞춥니다. 8프레임을 8 FPS로 재생하면 1초 루프입니다.
05
프레임 소스를 선택합니다. Manual 모드는 Animator 상태에서 구동하는 프레임 인덱스를 노출하고, 시간 기반 모드는 스스로 반복됩니다.
06
Rendering에서 가장자리가 단단한 스프라이트에는 Cutout 또는 Opaque를 설정합니다. Transparent는 가장자리가 부드러운 이펙트에만 남겨두고 깊이 정렬 문제가 생길 수 있음을 예상하세요.
07
루프를 확인합니다. 마지막 프레임이 첫 프레임과 중복되면 매 사이클마다 한 프레임 동안 스프라이트가 멈춥니다.
08
업로드한 아바타에서 거울로 가까이서, 그리고 인스턴스 반대편에서 테스트합니다. 거리가 밉맵과 필터링 문제가 드러나는 지점입니다.
Godot과 Phaser의 플립북
Godot 4에서 2D 경로는 SpriteFrames 리소스를 사용하는 AnimatedSprite2D입니다. 시트를 임포트하고 SpriteFrames를 열어 Hframes와 Vframes를 열 및 행 수로 설정한 다음, 애니메이션의 프레임을 추가하고 애니메이션 속도를 초당 프레임으로 설정한 뒤 Loop를 켜두면 됩니다. SpriteFrames는 애니메이션을 프레임 인덱스 목록으로 저장하므로, 그리드 시트든 단일 스트립이든 UV 연산 없이 그대로 작동합니다.
애니메이션이 하나만 필요하다면 일반 Sprite2D가 더 가볍습니다. Hframes와 Vframes를 한 번 설정한 다음, AnimationPlayer에서 정수 frame 프로퍼티를 step 보간으로 진행시키세요. 기본 이징 곡선으로 frame을 트윈하지 마세요. 이 값은 정수 인덱스라서 보간하면 한 값으로 내림되거나 두 셀 사이에서 깜빡입니다.
Godot에서 3D 표면에 플립북을 올리려면 Unity 버전과 같은 행/열 연산을 쓰는 ShaderMaterial이 가장 짧은 경로입니다. uv = UV * vec2(1.0 / cols, 1.0 / rows) + vec2(col / cols, row / rows), 행은 프레임 인덱스에서 계산합니다. Godot의 UV 원점도 Unity와 같이 왼쪽 아래이므로, 위쪽 행부터 시작하는 시트에는 같은 세로 뒤집기가 필요합니다.
Phaser 3에서는 시트를 한 번 로드할 때 잘라둡니다. frameWidth와 frameHeight가 셀을 정의하고, frames는 왼쪽에서 오른쪽, 위에서 아래로 번호가 매겨지며, 애니메이션은 좌표가 아니라 프레임 번호로 지정됩니다. 셰이더를 작성할 필요도, 행과 열 연산을 틀릴 일도 없습니다.
| 엔진 | 오브젝트 | 설정 항목 |
|---|---|---|
| Godot 4 | AnimatedSprite2D + SpriteFrames | Hframes와 Vframes를 시트 레이아웃에 맞추고, 애니메이션 속도를 FPS로 설정한 뒤 Loop를 켭니다. |
| Godot 4 | Sprite2D | Hframes와 Vframes를 한 번 설정한 뒤, 정수 frame 프로퍼티를 AnimationPlayer에서 step 보간으로 진행시킵니다. |
| Godot 4 | 3D 메시의 ShaderMaterial | 셰이더에 columns, rows, 프레임 인덱스를 두고 Unity와 같은 UV 재매핑과 세로 뒤집기를 적용합니다. |
| Phaser 3 | this.load.spritesheet | frameWidth와 frameHeight. Phaser가 시트를 왼쪽에서 오른쪽, 위에서 아래로 자릅니다. |
| Phaser 3 | this.anims.create | anims.generateFrameNumbers로 프레임을 가져오고 frameRate와 repeat: -1로 반복합니다. |
3D에서 스프라이트 시트 실시간 재생
이 섹션의 플레이 가능한 씬은 Neta Studio 3D 게임 빌더에서 실행되는 The Cyclops' Island입니다. 화면의 플립북은 위에서 설명한 것과 같은 인덱스-UV 연산을 Three.js 셰이더에서 GPU로 평가한 것으로, 차이라면 실제 씬의 실제 메시 위에서 직접 조작하는 카메라와 움직이는 조명과 함께 실행된다는 점입니다.
이 점은 들리는 것보다 중요합니다. 평면적인 Unity 씬의 플립북은 보통 정면에서 보기 때문에 이음새 번짐과 밉 블렌딩을 알아채기 어렵습니다. 애니메이션 표면을 비스듬한 각도로 카메라를 돌리며 걸어보면 문제가 즉시 드러납니다. 프레임 가장자리에 인접 셀의 띠가 보이거나, 마지막 밉 레벨이 두 프레임을 평균해 흐릿하게 만들거나, 앞에서는 제대로 보이던 알파 가장자리가 옆에서는 사라지는 식입니다. 브라우저 빌드는 시트를 제작하고 표면에서 확인하기까지의 반복 주기를 빌드 단계 없이 돌릴 수 있을 만큼 짧게 만듭니다.
Mini World는 시트 하나를 테스트하기에 더 작은 씬입니다. 메시 수가 적고 로드도 짧습니다. 아직 시트가 없다면 Agent Sprite Forge가 프레임을 생성해 주며, 두 씬 모두 같은 플립북 경로를 실행하므로 Mini World에서 제대로 보이는 시트는 The Cyclops' Island에서도 동일하게 동작합니다.
나만의 시트 만들기
사용 가능한 시트로 가는 경로는 세 가지입니다. 기존 GIF를 변환하거나, 그리드를 단일 스트립으로 자르거나, 글로 쓴 설명에서 프레임을 생성하는 것입니다. 세 가지 모두 같은 파일로 귀결됩니다. 모든 셀 크기가 같고 왼쪽에서 오른쪽, 위에서 아래로 배치되며 시트 너비가 셀 너비의 정확한 배수인 PNG 하나입니다.
GIF 변환은 숨은 작업이 가장 많은 경로입니다. GIF는 전체 프레임의 연속이 아니기 때문입니다. 프레임은 이전 프레임에 대한 부분 차이인 경우가 많고, 지연 시간은 프레임마다 100분의 1초 단위로 저장됩니다. 플립북 셰이더는 프레임 레이트를 하나만 받으므로, 가변 타이밍은 내보내기 전에 프레임을 복제해 고정해야 합니다.
그리드를 스트립으로 자르는 것은 기계적인 작업입니다. 필요한 행만 남기고 높이를 한 셀로 자르고, 너비는 프레임 수 곱하기 셀 너비로 두면 됩니다. 그리드 시트를 애니메이션 하나로만 쓸 때 적합한 방법인데, 세로 축과 그에 따른 이음새가 사라지기 때문입니다. 설명에서 생성하는 것이 세 번째 경로입니다. Agent Sprite Forge는 동작과 프레임 수를 받아 고정 셀 크기의 시트를 반환하므로, 프레임을 하나씩 내보내 손으로 정렬하던 작업이 사라집니다.
01
내보내기 전에 모든 GIF 프레임을 평탄화하세요. 각 프레임을 이전 프레임을 이어받은 캔버스에 디코딩하지 않으면, 그림 대신 차이 사각형을 내보내게 됩니다.
02
타이밍을 정규화하세요. GIF 지연 중 가장 짧은 값이나 중앙값을 고르고, 원본이 더 오래 유지한 프레임은 복제해서 시트가 하나의 일정한 FPS로 재생되게 합니다.
03
프레임을 왼쪽에서 오른쪽, 위에서 아래로 배치하고 모든 셀 크기를 동일하게 맞추세요. 시트 크기를 셀 크기의 정확한 배수로 만드세요. 너비가 9.5셀인 시트는 영원히 잘못된 텍셀을 샘플링합니다.
04
스트립으로 자를 때는 높이를 한 셀로 자르고 너비를 프레임 수 곱하기 셀 너비로 유지하세요. 원본 그리드에 빈 끝 셀이 있었다면 삭제합니다.
05
대상 엔진에서 밉맵을 끌 수 없다면 모든 셀 주위에 1~2픽셀의 투명 여백을 추가하세요. 그렇지 않으면 UV 창을 네 변 모두 반 텍셀씩 인셋합니다.
06
변환이 아니라 생성하려면 동작, 프레임 수, 방향을 적고 Agent Sprite Forge가 고정 셀 시트를 반환하게 하세요. 프레임을 하나씩 내보내 손으로 정렬할 필요가 없습니다.
07
엔진에서 프레임 인덱스를 한 단계씩 넘기며 확인하세요. 어떤 셀에서든 가장자리에 인접 셀의 일부가 보인다면 셰이더가 아니라 시트 레이아웃이나 UV 인셋이 잘못된 것입니다.
그것이 속한 세계로 걸어 들어가기
스프라이트는 서 있을 곳이 없는 캐릭터입니다. 마지막 단계는 다른 곳에서는 제공하지 않는 것입니다. 방금 만든 시트를 누군가 걸어 다닐 수 있는 세계 — 작은 섬, 행성, 비 내리는 길모퉁이 — 에 넣는 것입니다. 이 사이트의 모든 세계는 하나의 글로 쓴 설명에서 만들어졌고 플레이할 수 있는 페이지로 공개되었습니다.
FAQ
Poiyomi 플립북이란 무엇인가요? +
Poiyomi 셰이더의 Flipbook 섹션으로, 머티리얼에서 UV 창을 스프라이트 시트 위로 스크롤하여 하나의 머티리얼 슬롯으로 아바타 메시에 애니메이션 스프라이트를 표시할 수 있게 합니다. Rows, Columns, 프레임 레이트를 설정하고, 프레임 인덱스를 셰이더 시간에 맡기거나 Animator 상태에서 구동합니다. 단일 행 스트립이 선호되는데, 행 사이의 세로 이음새가 사라지기 때문입니다.
GIF를 바로 플립북으로 쓸 수 있나요? +
아니요. GIF는 프레임별 지연 시간을 저장하고, 대개 변경된 픽셀만 저장합니다. 반면 플립북 셰이더는 텍스처 하나와 일정한 프레임 레이트 하나를 전제로 합니다. 먼저 변환하세요. 모든 프레임을 이전 프레임 위에 평탄화하고, 타이밍을 정규화한 뒤, 결과를 고정 셀 그리드에 기록합니다. 셰이더는 GIF를 보지 않습니다.
플립북 텍스처에서 프레임 간 번짐이 생기는 이유는 무엇인가요? +
샘플러가 현재 셀 창 밖을 읽고 있기 때문입니다. 텍스처 wrap mode를 Clamp로 설정하고, 가장자리가 단단한 아트에는 밉맵을 끄고, UV 창을 각 변마다 반 텍셀씩 인셋하세요. 번짐은 거리가 멀어질수록 심해지는데, 그 지점에서 밉 체인이 인접 셀을 평균내기 시작하기 때문입니다.
플립북에는 어떤 프레임 레이트를 써야 하나요? +
8~12 FPS는 수작업 애니메이션처럼 보여 캐릭터 스프라이트에 어울리고, 24~30은 불, 물 같은 연속 이펙트에 어울립니다. 셰이더는 레이트를 하나만 가지므로, 원본에 가변 타이밍이 있었다면 루프 중간에 속도를 바꾸려 하지 말고 프레임을 복제해 맞추세요.
그리드가 필요한가요, 한 행이면 충분한가요? +
시퀀스로 재생되는 것이라면 한 행이면 충분하고, 이음새 오차의 축이 하나 줄어듭니다. 그리드는 여러 애니메이션을 하나의 텍스처에 담아두고 셰이더나 엔진이 idle, run, attack에 맞는 행을 선택하는 시트를 위한 것입니다. 그리드 시트에서 애니메이션이 하나만 나온다면 스트립으로 잘라내세요.
플립북이 Unity URP의 Lit 셰이더와 함께 작동하나요? +
기본 상태로는 안 됩니다. URP Lit에는 프레임 인덱스가 없습니다. Columns, Rows, 시간 기반 인덱스를 받는 Flipbook 노드와 함께 Shader Graph를 사용하거나, 머티리얼의 _BaseMap_ST tiling과 offset을 상수 키프레임 보간으로 애니메이션하세요. 빌트인 Standard와 Unlit은 같은 규칙으로 _MainTex_ST를 사용합니다.
3D 게임 빌더로 돌아가기
완성된 31개의 3D 세계, 게임, 씬이 모두 한 페이지에 있습니다. 전부 플레이할 수 있습니다.
스프라이트 에셋 라이브러리 열기
이 라이브러리의 모든 에셋은 프롬프트가 기록된 투명한 게임용 PNG입니다 — 스프라이트 시트 리소스 및 무료 스프라이트 시트, 동작, 스타일, 주제별로 태그가 지정되어 있습니다. 무료로 둘러보고 다운로드할 수 있습니다.