스프라이트 아틀라스
고친 사람 github-actions[bot]
스프라이트 아틀라스는 게임에 쓰이는 작은 그림 여러 장을 큰 이미지 한 장으로 합쳐, 그래픽 카드가 한꺼번에 그리게 해 줍니다. 크기가 제각각인 그림을 빈틈이 적게 채워 넣습니다. 그림마다 어디에 놓였는지는 함께 적어 둔 목록이 알려 줍니다. 게임 프로그램은 그 목록에서 필요한 그림의 위치를 찾아 그래픽 카드에 넘깁니다. 그래픽 카드는 넘겨받은 부분만 잘라 그립니다.
쉽고 빠른 이해
스프라이트 아틀라스는 작은 그림 여러 장을 큰 이미지 한 장으로 합쳐 그래픽 카드가 한 번에 그리게 해 줍니다. 캐릭터, 동전, 열쇠, 나무 같은 그림을 가로세로 1024픽셀 이미지 한 장에 빽빽이 채워 넣은 것이 그 예입니다.
그림마다 따로 두면 그래픽 카드는 그림이 바뀔 때마다 그리기 명령을 새로 받아야 합니다. 이 명령은 낼 때마다 준비 비용이 붙습니다. 화면에 그림이 수백 개면 그 비용이 쌓여 화면이 느려집니다. 한 장에 모아 두면 명령 한 번에 여러 그림을 같이 그릴 수 있습니다.
어떻게 도나:
- 게임을 만들 때 도구가 그림들을 큰 이미지 한 장에 겹치지 않게 채워 넣습니다
- 그림마다 이름과 위치, 크기를 목록에 적습니다
- 프로그램은 그릴 때 목록에서 위치를 찾아 그래픽 카드에 넘깁니다
- 그래픽 카드는 넘겨받은 부분만 잘라 그립니다
대가도 있습니다. 그 화면에서 안 쓰는 그림까지 같이 메모리에 올라갑니다. 그림끼리 바짝 붙어 있어서, 크기를 바꿔 그리면 옆 그림의 색이 테두리에 묻어날 수 있습니다. 반복해 까는 무늬나 화면을 가득 채우는 큰 배경, 게임이 도는 중에 받아 오는 이미지는 아틀라스에서 빼 둡니다.
상세
이삿짐을 쌀 때는 크기가 다른 물건을 상자 하나에 되도록 빈틈없이 넣습니다. 상자 겉에는 무엇이 어느 쪽에 들었는지 적어 둡니다. 나중에 상자를 열면 그 쪽지를 보고 필요한 물건만 꺼냅니다.
스프라이트 아틀라스는 이미지에서 그 상자와 쪽지에 해당합니다. 이 절은 상자와 쪽지가 각각 무엇인지에서 출발합니다. 그다음 게임이 왜 이미지를 한 장으로 합치는지, 쪽지를 어떻게 읽는지, 상자를 어떻게 채우는지 차례로 봅니다. 끝으로 대가와 비슷한 이름들을 가릅니다.
텍스처 한 장과 위치 목록
아틀라스는 두 부분으로 이루어집니다. 하나는 작은 그림들을 채워 넣은 큰 이미지 한 장입니다. 다른 하나는 그림마다 이름과 위치, 크기를 적은 목록입니다.
이 작은 그림 하나하나를 스프라이트라고 부릅니다. 화면에 한 덩어리로 그려지는 작은 평면 이미지입니다. 게임의 캐릭터, 총알, 아이템 아이콘이 스프라이트입니다.
아틀라스는 원래 지도책을 뜻하는 낱말입니다. 여러 지역의 지도를 한 권에 묶듯, 여러 스프라이트를 이미지 한 장에 묶는다는 뜻으로 이 이름을 씁니다. 영어로는 sprite atlas 라고 씁니다.
프로그램은 스프라이트를 채운 큰 이미지를 GPU(Graphics Processing Unit, 그래픽 처리 장치)에 올려 두고 씁니다. GPU 는 화면을 그리는 일을 맡은 처리 장치입니다. 흔히 그래픽 카드라고 부르는 부품에 들어 있습니다.
GPU 에 올려 그리기에 쓰는 이미지를 텍스처라고 부릅니다. 아틀라스의 큰 이미지도 텍스처 한 장이라서 아틀라스 텍스처라고 부릅니다.
아래는 크기가 다른 스프라이트 일곱 개를 아틀라스 한 장에 채운 모습을 단순하게 그린 것입니다. 넓은 스프라이트는 칸을 여럿 차지하게 그렸습니다.
block-beta columns 4 a["캐릭터"]:2 b["동전"]:1 c["열쇠"]:1 d["나무"]:3 e["하트"]:1 f["구름"]:2 g["상자"]:1 h["빈 공간"]:1
캐릭터나 나무처럼 넓은 것과 동전처럼 작은 것이 한 장 안에 섞여 있습니다. 오른쪽 아래처럼 아무것도 못 넣은 빈 공간도 조금 남습니다. 실제 배치는 픽셀 단위로 맞추므로 이 그림보다 훨씬 빽빽합니다.
위치 목록은 스프라이트 이름 하나에 사각형 하나를 짝지어 둡니다. 사각형은 왼쪽 위 모서리의 좌표와 가로세로 크기로 적습니다. 목록은 대개 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법) 같은 텍스트 형식의 파일로 텍스처 옆에 함께 둡니다.
이미지를 한 장으로 합치는 까닭
프로그램은 GPU 에 「이 텍스처로 이것들을 그려라」라는 명령을 냅니다. 이 명령 한 번을 드로 콜이라고 부릅니다. 드로 콜마다 GPU 가 그릴 준비를 하는 비용이 붙습니다. 화면 한 장에 드로 콜이 많으면 그만큼 그리기가 느려집니다.
드로 콜 한 번은 그 앞에 정해 둔 텍스처 한 장으로만 그립니다. 스프라이트마다 텍스처가 따로면 다른 스프라이트로 넘어갈 때마다 텍스처를 바꾸고 드로 콜을 새로 내야 합니다. 화면에 스프라이트가 수백 개면 드로 콜도 수백 번이 됩니다.
스프라이트들이 아틀라스 한 장에 들어 있으면 텍스처를 바꿀 일이 없습니다. 그래서 여러 스프라이트를 드로 콜 한 번에 묶어 그릴 수 있습니다. 이렇게 묶어 그리는 일을 배칭이라고 합니다.
아래 그림은 스프라이트 셋을 그릴 때 프로그램이 GPU 에 내는 명령을 견줍니다. 위는 아틀라스가 없을 때, 아래는 있을 때입니다.
sequenceDiagram
participant 프로그램
participant GPU
Note over 프로그램,GPU: 아틀라스가 없을 때
loop 스프라이트마다 한 번씩, 세 번
프로그램->>GPU: 그 스프라이트의 텍스처로 바꾼다
프로그램->>GPU: 드로 콜
end
Note over 프로그램,GPU: 아틀라스가 있을 때
프로그램->>GPU: 아틀라스 텍스처로 바꾼다
프로그램->>GPU: 드로 콜 한 번에 셋을 그린다
스프라이트가 늘수록 위쪽은 명령 쌍이 하나씩 늘어납니다. 아래쪽은 스프라이트가 몇 개든 명령 두 번이면 됩니다.
드로 콜마다 정하는 것이 텍스처만은 아닙니다. 반투명한 그림을 뒤에 있는 그림과 어떻게 섞을지도 드로 콜마다 정합니다. 이런 설정이 서로 다른 스프라이트는 아틀라스 한 장에 있어도 드로 콜을 나눠 그립니다.
목록으로 스프라이트를 찾는 셈
프로그램은 목록에서 스프라이트의 사각형을 꺼내 GPU 에 넘깁니다. 이때 GPU 는 위치를 픽셀 수로 받지 않습니다. 텍스처의 가로와 세로를 각각 1로 놓은 비율로 받습니다.
이 비율을 텍스처 좌표라고 부릅니다. 가로를 u, 세로를 v 로 적습니다. 두 글자를 붙여 uv 좌표라고도 합니다. 비율로 적어 두면 텍스처의 픽셀 수가 달라져도 좌표가 그대로입니다. 가로 0.5 는 텍스처가 1024픽셀이든 512픽셀이든 한가운데를 가리킵니다.
아래 코드는 칼 스프라이트의 텍스처 좌표를 구합니다. 아틀라스는 가로세로 1024픽셀입니다. 목록에서 꺼낸 칼의 사각형을 r 이라 놓았습니다. 칼은 왼쪽에서 256, 위에서 512 픽셀 떨어진 곳에 가로 128, 세로 64 픽셀로 놓여 있습니다.
const AW = 1024; // 아틀라스 가로
const AH = 1024; // 아틀라스 세로
const r = { x: 256, y: 512, w: 128, h: 64 };
const u0 = r.x / AW; // 0.25
const v0 = r.y / AH; // 0.5
const u1 = (r.x + r.w) / AW; // 0.375
const v1 = (r.y + r.h) / AH; // 0.5625
u0, v0 은 칼의 왼쪽 위 모서리입니다. u1, v1 은 오른쪽 아래 모서리입니다. GPU 는 이 네 값이 가리키는 사각형만 텍스처에서 읽어 칼을 그립니다.
v 를 위에서부터 세는지 아래에서부터 세는지는 그래픽 라이브러리마다 다릅니다. 위 코드는 위에서부터 셌습니다.
크기가 다른 스프라이트를 채워 넣는 방법
스프라이트들은 크기가 제각각입니다. 모두 같은 크기의 칸에 넣으면 작은 스프라이트 둘레에 빈 공간이 많이 남습니다. 빈 공간도 GPU 메모리를 차지하므로 되도록 빽빽하게 채웁니다.
크기가 다른 사각형을 정해진 넓이에 가장 빽빽하게 넣는 일은 빈 패킹 문제(bin packing problem)의 한 갈래입니다. 빈(bin)은 상자라는 뜻입니다. 가장 빽빽한 배치를 찾으려면 따져 볼 경우의 수가 너무 많아서, 스프라이트가 늘면 현실적인 시간 안에 풀 수 없습니다.
그래서 도구들은 가장 빽빽한 배치 대신 거기에 가까운 배치를 빨리 찾는 어림 방법을 씁니다. 흔한 방법은 큰 스프라이트부터 넣는 것입니다. 하나를 넣을 때마다 남은 빈 공간들을 기억해 둡니다. 다음 스프라이트는 그중 맞는 곳에 넣습니다.
도구에 따라서는 넣기 전에 스프라이트 둘레의 투명한 여백을 잘라 냅니다. 잘라 낸 만큼 아틀라스가 작아집니다. 대신 원래 크기와 잘라 낸 양을 목록에 같이 적어, 그릴 때 원래 위치에 맞춰 놓습니다.
이 채워 넣기는 대개 게임을 실행할 때가 아니라 만들 때 미리 합니다. 이렇게 미리 만들어 두는 일을 흔히 굽는다(bake)고 부릅니다. 스프라이트 하나가 바뀌면 아틀라스를 다시 굽습니다.
가장자리 번짐과 여백
스프라이트를 원래보다 크거나 작게 그리면 GPU 는 이웃한 픽셀들의 색을 섞어 매끄럽게 보이게 합니다. 이 처리를 텍스처 필터링이라고 합니다.
아틀라스에서는 스프라이트 바로 옆에 다른 스프라이트가 붙어 있습니다. 가장자리 픽셀의 색을 섞을 때는 그 이웃이 옆 스프라이트의 픽셀이 됩니다. 그러면 옆 스프라이트의 색이 테두리에 가는 선처럼 묻어납니다. 이 현상을 텍스처 블리딩(texture bleeding)이라고도 부릅니다.
이를 막으려고 스프라이트 사이에 몇 픽셀 여백을 둡니다. 여백을 비워 두는 대신 가장자리 픽셀의 색을 바깥으로 늘려 칠하기도 합니다. 그러면 테두리에서 섞이는 색이 자기 색이라 번져도 티가 안 납니다.
밉맵은 텍스처를 절반씩 줄여 미리 만들어 둔 사본들입니다. 멀리 있어 작게 보이는 것을 그릴 때 작은 사본을 씁니다. 미리 줄여 둔 사본을 쓰면 크게 줄여 그릴 때 거칠게 깨져 보이는 일이 줄어듭니다.
밉맵을 쓰면 여백도 사본마다 절반으로 줄어듭니다. 2픽셀 여백은 첫 사본에서 1픽셀이 됩니다. 그다음 사본에서는 1픽셀도 안 남아 이웃 스프라이트와 맞닿습니다. 그래서 밉맵을 쓸 때는 여백을 더 넓게 잡습니다.
텍스처 크기의 상한
GPU 는 텍스처 한 장의 가로세로 크기에 상한을 둡니다. 상한은 GPU 마다 다릅니다. 스프라이트가 한 장에 다 안 들어가면 아틀라스를 여러 장으로 나눕니다.
아틀라스의 가로세로는 512, 1024, 2048 처럼 2의 거듭제곱으로 맞추는 일이 많습니다. 예전에는 이런 크기만 받는 GPU 가 있었습니다. 밉맵을 절반씩 줄여 만들 때 나머지 없이 나누어떨어진다는 이점도 있습니다.
한 장에 묶는 기준
묶는 기준은 같이 그려지는지입니다. 한 화면에 함께 나오는 스프라이트를 한 장에 모으면 드로 콜이 줄어듭니다. 아틀라스를 여러 장으로 나눌 때도 같이 그려지는 것끼리 같은 장에 두어야 배칭이 안 깨집니다.
서로 다른 장면의 스프라이트를 한 장에 섞으면 반대 문제가 생깁니다. 텍스처는 한 장 단위로 메모리에 올라가므로, 그 장면에서 안 쓰는 스프라이트까지 같이 올라갑니다. 그래서 메뉴 화면의 버튼과 아이콘은 한 장에, 첫 스테이지의 캐릭터와 배경 조각은 다른 한 장에 두는 식으로 나눕니다.
아틀라스에서 빼는 이미지
모든 이미지가 아틀라스에 어울리지는 않습니다. 흔히 빼 두는 이미지를 까닭과 함께 모으면 이렇습니다.
| 이미지 | 빼는 까닭 |
|---|---|
| 바닥 타일처럼 반복해 까는 무늬 | 반복 설정이 텍스처 전체를 되풀이한다 |
| 화면을 가득 채우는 큰 배경 | 혼자 한 장을 거의 다 차지해 같이 묶을 스프라이트가 없다 |
| 게임이 도는 중에 받아 오는 이미지 | 미리 구운 아틀라스에는 넣을 수 없다 |
첫째 줄은 같은 무늬를 넓게 깔 때 걸립니다. 텍스처의 반복 설정은 텍스처 좌표가 1을 넘으면 0으로 돌아가 처음부터 다시 읽게 합니다. 기본 설정에는 아틀라스 안의 스프라이트 하나만 되풀이하는 방법이 없어서, 반복하면 아틀라스 전체가 깔립니다.
스프라이트 시트와의 차이
스프라이트 시트도 작은 이미지 여러 장을 한 장에 모은 것입니다. 흔히 한 캐릭터의 동작 장면들을 같은 크기의 칸에 격자로 늘어놓은 것을 시트라고 부릅니다. 격자라서 칸 번호만 알면 위치가 셈으로 나옵니다.
아틀라스는 크기가 제각각인 여러 스프라이트를 빽빽이 채운 것을 가리키는 일이 많습니다. 셈으로는 위치가 안 나오므로 위치 목록이 꼭 따라붙습니다. 두 이름을 가르는 기준은 도구와 엔진마다 달라서, 같은 물건을 두 이름으로 부르기도 합니다.
텍스처 아틀라스는 같은 생각을 스프라이트에 한정하지 않고 부르는 이름입니다. 3D 모델의 겉면에 입히는 이미지 여러 장을 한 장에 모을 때도 이 이름을 씁니다. 게임 엔진에서는 스프라이트를 모아 아틀라스로 굽는 기능을 「스프라이트 아틀라스」라는 이름으로 두기도 합니다.
관련 항목
스프라이트 아틀라스를 이루는 구성 요소
스프라이트 · 텍스처 · 텍스처 좌표 · 픽셀 · 텍셀 · JSON
스프라이트 아틀라스로 줄이는 그리기 비용
드로 콜 · 배칭 · GPU · 렌더링 파이프라인 · 머티리얼 · 상태 변경
스프라이트 아틀라스를 굽는 배치 알고리즘
빈 패킹 문제 · 사각형 패킹 · 휴리스틱 · MaxRects · 스카이라인 패킹 · 길로틴 패킹
스프라이트 아틀라스 가장자리에서 터지는 화질 문제
텍스처 블리딩 · 텍스처 필터링 · 밉맵 · 이중선형 필터링 · 앨리어싱
스프라이트 아틀라스 텍스처를 담는 포맷
텍스처 압축 · GPU 텍스처 포맷 · 2의 거듭제곱 텍스처 · PNG
스프라이트 아틀라스와 같은 생각을 다른 이름으로 부르는 기법
스프라이트 시트 · 텍스처 아틀라스 · CSS 스프라이트 · 텍스처 배열
스프라이트 아틀라스를 받아 쓰는 게임 기법
다른 이름: sprite atlas · 스프라이트아틀라스