사전 스프라이트 시트
개념

스프라이트 시트

gabury1고친 사람 github-actions[bot]

스프라이트 시트는 작은 이미지 여러 장을 큰 이미지 한 장으로 묶어 한 번에 불러오게 하는 방법입니다. 쓸 때는 그 큰 이미지에서 필요한 칸 하나만 잘라 화면에 보여 줍니다. 게임에서는 캐릭터가 걷는 장면들을 한 장에 늘어놓고 차례로 잘라 보여 줍니다. 웹에서는 아이콘 수십 개를 한 파일로 묶어 서버에 보내는 요청 수를 줄입니다.

쉽고 빠른 이해

스프라이트 시트는 작은 이미지들을 한 장에 모아 둔 묶음입니다. 캐릭터가 걷는 장면 넷과 달리는 장면 넷을 두 줄로 늘어놓은 이미지 한 장이 그 예입니다.

이게 없으면 작은 이미지마다 따로 불러와야 합니다. 웹에서는 아이콘 하나에 요청이 하나씩 붙습니다. 게임에서는 이미지를 바꿀 때마다 그래픽 카드에 그리라는 명령을 새로 내야 합니다. 이 명령은 낼 때마다 준비 비용이 붙어서, 명령이 많으면 화면이 느려집니다.

어떻게 도나:

  1. 작은 이미지들을 큰 이미지 한 장에 겹치지 않게 배치합니다
  2. 칸마다 위치를 알 수 있게 합니다 — 칸 크기가 같으면 셈으로, 크기가 다르면 목록에 적어서
  3. 화면에 그릴 때는 그 위치의 칸만 잘라 보여 줍니다

대가는 묶음이라는 데서 옵니다. 아이콘 하나만 바뀌어도 큰 이미지 전체를 다시 받아야 합니다. 그 화면에서 안 쓰는 이미지까지 같이 받아 메모리에 올립니다.

상세

만화책 한 페이지에는 칸이 여러 개 있습니다. 칸마다 장면이 하나씩 그려져 있습니다. 읽는 사람은 페이지를 한 번 펼쳐 놓고 칸을 순서대로 하나씩 봅니다.

스프라이트 시트는 이미지에서 그 페이지에 해당합니다. 영어로는 sprite sheet 라고 씁니다. 스프라이트는 화면에 한 덩어리로 그려지는 작은 평면 이미지입니다. 게임의 캐릭터나 총알, 웹 페이지의 아이콘이 스프라이트입니다.

시트 안에서 작은 이미지 하나가 들어가는 구역을 이 글에서는 칸이라고 부릅니다. 시트를 쓰는 쪽은 시트 전체를 한 번 불러온 뒤, 그릴 때마다 칸 하나의 위치를 골라 그 부분만 보여 줍니다.

이 절은 시트 한 장이 어떻게 배치되는지에서 출발합니다. 그다음 게임과 웹이 각각 무엇을 아끼려고 시트를 쓰는지 봅니다. 끝으로 시트가 치르는 대가와 비슷한 이름들을 가릅니다.

격자로 배치한 시트

가장 단순한 시트는 같은 크기의 칸을 바둑판처럼 늘어놓습니다. 한 줄에 한 동작을 놓는 일이 많습니다. 아래는 칸 크기가 같은 시트 한 장의 배치입니다.

block-beta
columns 4
  a["0 걷기 1"] b["1 걷기 2"] c["2 걷기 3"] d["3 걷기 4"]
  e["4 달리기 1"] f["5 달리기 2"] g["6 달리기 3"] h["7 달리기 4"]

윗줄은 걷는 동작의 장면 넷입니다. 아랫줄은 달리는 동작의 장면 넷입니다. 칸 앞의 숫자는 0부터 센 칸 번호입니다. 애니메이션의 장면 하나를 프레임이라고 부릅니다. 이 시트는 프레임 여덟 장을 이미지 한 장에 담은 셈입니다.

격자 시트는 위치 목록이 따로 필요 없습니다. 칸의 크기와 한 줄의 칸 수만 알면 몇 번째 칸이 어디 있는지 셈으로 나옵니다.

몇 번째 칸의 위치를 구하는 셈

칸 번호를 0부터 셉니다. 번호를 한 줄의 칸 수로 나눈 나머지가 몇째 열인지, 몫이 몇째 줄인지 알려 줍니다. 열과 줄에 칸 크기를 곱하면 그 칸의 왼쪽 위 모서리 좌표가 됩니다.

아래 코드는 위 시트에서 6번 칸, 곧 「달리기 3」의 위치를 구합니다. 칸 하나는 가로세로 64픽셀이라고 놓았습니다.

JavaScript
const W = 64, H = 64;   // 칸 하나의 크기
const COLS = 4;         // 한 줄에 칸 넷
const i = 6;            // 달리기 3

const col = i % COLS;   // 2
const row = Math.floor(i / COLS);  // 1
const x = col * W;      // 128
const y = row * H;      // 64

결과는 왼쪽에서 128픽셀, 위에서 64픽셀 떨어진 곳입니다. 그리는 쪽은 이 점에서 시작하는 가로세로 64픽셀만 잘라 화면에 옮깁니다.

이 번호를 일정한 간격으로 하나씩 늘리면 애니메이션이 됩니다. 4번에서 7번까지 돌다가 7번 다음에 4번으로 돌아가면 캐릭터가 계속 달립니다. 1초에 몇 칸을 넘기느냐가 동작의 빠르기를 정합니다.

크기가 제각각인 이미지를 빽빽이 채운 시트

이미지 크기가 서로 다르면 격자에 넣을 때 빈 공간이 많이 생깁니다. 격자는 칸 크기를 가장 큰 이미지에 맞추므로, 작은 아이콘이 든 칸에는 빈 공간이 남습니다. 이런 이미지들은 퍼즐 맞추듯 빈틈없이 채워 넣기도 합니다.

이렇게 채우면 셈으로는 칸 위치를 알 수 없습니다. 그래서 이미지마다 이름과 위치, 크기를 적은 목록 파일을 시트 옆에 함께 둡니다. 목록은 대개 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법) 같은 텍스트 형식으로 적습니다.

여러 이미지를 받아 이런 배치와 목록을 만들어 주는 도구가 따로 있습니다. 사람이 칸을 하나씩 옮기는 일은 드뭅니다.

게임에서 아끼는 것

게임은 화면을 1초에 수십 번 새로 그립니다. 이 일은 대개 GPU(Graphics Processing Unit, 그래픽 처리 장치)가 맡습니다. GPU 는 그릴 이미지를 먼저 자기 메모리에 올려 둡니다. 이렇게 올려 두고 그리기에 쓰는 이미지를 텍스처라고 부릅니다.

프로그램은 GPU 에 「이 텍스처로 이것들을 그려라」라는 명령을 냅니다. 이 명령 한 번을 드로 콜이라고 부릅니다. 드로 콜마다 준비하는 비용이 붙어서, 화면 한 장에 드로 콜이 많으면 그리기가 느려집니다.

캐릭터마다 이미지가 따로 있으면 쓰는 텍스처가 그때마다 바뀝니다. 드로 콜은 내기 전에 정해 둔 텍스처로만 그리기 때문에, 텍스처가 바뀌면 드로 콜을 새로 내야 합니다. 화면에 스프라이트가 수백 개면 드로 콜도 수백 번이 됩니다.

여러 스프라이트가 시트 한 장을 같이 쓰면 텍스처를 바꿀 일이 없습니다. 그래서 같은 시트를 쓰는 스프라이트들을 드로 콜 한 번에 묶어 그릴 수 있습니다. 이렇게 묶어 그리는 일을 배칭이라고 합니다.

웹에서 아끼는 것

CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)는 웹 페이지의 모양을 정하는 언어입니다. 웹에서는 스프라이트 시트를 쓰는 방법을 CSS 스프라이트라고 부릅니다. 아이콘 수십 개를 이미지 파일 하나에 모아 둡니다. 아이콘마다 그 파일의 한 부분만 보여 줍니다.

브라우저는 페이지에 걸린 이미지마다 서버에 요청을 하나씩 보냅니다. 요청마다 헤더가 붙습니다. 응답을 기다리는 시간도 붙습니다. 아이콘이 서른 개면 이 비용도 서른 번 붙습니다.

HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 앞선 판인 HTTP/1.1에서는 이 비용이 더 컸습니다. 연결 하나가 응답 하나를 다 보내야 다음 요청을 받았습니다. 브라우저가 한 서버에 여는 연결 수에도 상한이 있었습니다. 그래서 요청이 많으면 나머지는 줄을 서서 기다렸습니다.

시트 한 장으로 묶으면 서른 번의 요청이 한 번이 됩니다. 아래 그림은 시트가 없을 때와 있을 때 아이콘을 받는 흐름을 위아래로 견줍니다.

sequenceDiagram
    participant 브라우저
    participant 서버
    Note over 브라우저,서버: 시트가 없을 때
    loop 아이콘마다 한 번씩, 서른 번
        브라우저->>서버: 아이콘 한 장을 달라
        서버-->>브라우저: 아이콘 한 장
    end
    Note over 브라우저,서버: 시트가 있을 때
    브라우저->>서버: icons.png 한 장을 달라
    서버-->>브라우저: 아이콘 서른 개가 든 이미지
    Note over 브라우저: 아이콘마다 칸 하나만 보여 준다

아이콘 수가 늘수록 위쪽의 왕복만 늘어납니다. 아래쪽은 아이콘이 몇 개든 icons.png 를 주고받는 왕복 한 번으로 끝납니다. 나머지는 브라우저 안에서 잘라 보여 주는 일입니다.

칸 하나만 보여 주는 CSS

CSS 스프라이트는 아이콘 하나를 시트 크기가 아니라 칸 크기의 상자로 만듭니다. 그 상자의 배경에 시트를 깝니다. 그런 다음 배경을 옮겨 원하는 칸이 상자 안에 오게 합니다.

CSS
.icon {
  width: 32px;
  height: 32px;
  background-image: url(icons.png);
}
.home { background-position: 0 0; }
.mail { background-position: -32px 0; }

.icon 은 아이콘 상자를 가로세로 32픽셀로 정합니다. 그 배경에 시트를 깝니다. 상자보다 큰 배경은 상자 밖으로 넘친 부분이 안 보입니다.

.home 은 배경을 옮기지 않아서 시트의 첫 칸이 보입니다. .mail 은 배경을 왼쪽으로 32픽셀 옮깁니다. 그러면 첫 칸이 상자 밖으로 밀려나고 둘째 칸이 상자 안에 들어옵니다.

HTTP/2 이후의 웹 스프라이트

HTTP/2는 연결 하나 위에서 여러 요청을 한꺼번에 주고받게 합니다. 이 방식을 멀티플렉싱이라고 합니다. 요청이 서로를 기다리지 않으니 작은 파일 여럿을 따로 받는 비용이 크게 줄었습니다.

그러자 웹에서 시트로 얻는 몫도 줄었습니다. 아이콘 하나만 바뀌어도 시트 전체를 다시 받아야 하는 대가는 그대로 남았습니다. 요즘 웹에서는 아이콘을 따로 두거나 SVG(Scalable Vector Graphics, 확장 가능한 벡터 그래픽) 아이콘으로 바꾸는 쪽이 많습니다.

게임 쪽 사정은 다릅니다. 게임이 시트로 아끼는 것은 네트워크 요청이 아니라 GPU 의 드로 콜입니다. 전송 규칙이 바뀌어도 이 비용은 그대로라 게임은 지금도 시트를 씁니다.

시트가 치르는 대가

묶었기 때문에 생기는 대가가 있습니다. 대가와 흔히 쓰는 대응을 표로 모으면 이렇습니다.

대가 생기는 까닭 흔한 대응
하나만 바뀌어도 전부 다시 받는다 브라우저 캐시는 파일 단위로 맞는지 판단한다 자주 바뀌는 이미지는 시트에서 뺀다
안 쓰는 이미지도 받는다 시트는 한 장이라 일부만 받을 수 없다 화면이나 장면별로 시트를 나눈다
옆 칸 색이 번진다 GPU 가 확대·축소하며 색을 섞을 때 칸 경계 너머까지 읽는다 칸 사이에 여백을 둔다
시트 크기에 상한이 있다 GPU 가 받는 텍스처 크기에 한도가 있다 시트를 여러 장으로 나눈다

셋째 줄은 게임에서 자주 만납니다. 스프라이트를 크기를 바꿔 그리면 GPU 가 이웃한 픽셀들의 색을 섞어 매끄럽게 만듭니다. 이 처리를 텍스처 필터링이라고 합니다. 칸 가장자리에서는 그 이웃이 옆 칸의 픽셀이라 옆 이미지 색이 테두리에 묻어 나옵니다.

비슷한 이름들

같은 생각을 부르는 이름이 여럿입니다. 스프라이트 아틀라스와 텍스처 아틀라스도 작은 이미지 여러 장을 한 장에 모은 것을 가리킵니다.

흔히 한 캐릭터의 동작 프레임을 격자로 늘어놓은 것을 시트라 부릅니다. 크기가 제각각인 여러 이미지를 빽빽이 채운 것은 아틀라스라 부르는 일이 많습니다. 다만 이 구분은 도구와 엔진마다 다르게 쓰여서, 같은 물건을 두 이름으로 부르는 일도 흔합니다.

웹의 CSS 스프라이트도 같은 생각입니다. 웹에서는 시트라는 말보다 「스프라이트 이미지」라는 말을 더 자주 씁니다.

관련 항목

스프라이트 시트에 담기는 그림 단위

스프라이트 · 프레임 · 텍스처 · 텍셀 · 픽셀 · 이미지

스프라이트 시트와 같은 생각을 다른 이름으로 부르는 묶음

스프라이트 아틀라스 · 텍스처 아틀라스 · CSS 스프라이트 · 텍스처 배열 · SVG 스프라이트

게임에서 스프라이트 시트로 줄이는 그리기 비용

드로 콜 · 배칭 · GPU · 렌더링 파이프라인 · 텍스처 필터링 · 밉맵

스프라이트 시트를 받아 쓰는 게임 기법

게임 개발 · 스프라이트 애니메이션 · 프레임 애니메이션 · 스켈레탈 애니메이션 · 게임 루프 · 게임 엔진

웹에서 스프라이트 시트가 줄이려 한 요청 비용

HTTP/1.1 · HTTP/2 · 멀티플렉싱 · 헤드 오브 라인 블로킹 · 도메인 샤딩 · 인라이닝 · 브라우저 캐시

웹에서 스프라이트 시트를 대신하는 아이콘 수단

SVG · 아이콘 폰트 · 데이터 URI · CSS · background-position

다른 이름: sprite sheet · spritesheet · 스프라이트시트