사전 GPU 텍스처 포맷
포맷

GPU 텍스처 포맷

gabury1고친 사람 github-actions[bot]

GPU 텍스처 포맷은 그래픽 카드에게 그림의 한 칸을 몇 바이트로 적고 어떤 값으로 읽을지 알려 줍니다. 같은 바이트라도 포맷이 다르면 그래픽 카드가 꺼내 쓰는 색이 달라집니다. 그래픽 카드는 이 약속을 회로로 풀기 때문에 정해진 목록 안에서만 포맷을 고를 수 있습니다.

쉽고 빠른 이해

GPU 텍스처 포맷은 그래픽 카드 메모리에 올린 그림에 붙이는 이름표입니다. 「빨강·초록·파랑·불투명도를 한 바이트씩, 한 칸에 4바이트」가 흔한 이름표 하나입니다.

메모리에는 바이트만 줄지어 있습니다. 이름표가 없으면 그래픽 카드는 몇 바이트가 한 칸인지, 그 바이트가 색인지 거리인지 모릅니다.

어떻게 도나:

  1. 그림을 그래픽 카드 메모리에 올릴 때 포맷을 하나 고릅니다
  2. 그래픽 카드는 그 포맷대로 한 칸의 바이트를 끊어 읽습니다
  3. 읽은 바이트를 화면을 칠하는 프로그램이 바로 계산에 쓰는 숫자로 바꿔 넘깁니다

대가는 메모리와 정밀도의 맞바꿈입니다. 한 칸에 바이트를 많이 주면 색이 고와지는 대신 메모리를 많이 먹습니다. 그래픽 카드마다 받는 포맷이 달라 같은 그림을 여러 벌 준비하기도 합니다.

상세

이 절은 포맷 이름 하나가 무엇을 정하는지 다룹니다. 가로세로 1024칸짜리 그림 한 장을 그래픽 카드 메모리에 올리는 장면을 놓고 따라갑니다.

데이터베이스 테이블의 컬럼 타입을 떠올려 봅시다. 같은 네 바이트라도 정수 컬럼이면 숫자 하나로, 문자 컬럼이면 글자 넷으로 읽힙니다. 컬럼 타입이 정해져 있어야 한 행이 몇 바이트인지도 셀 수 있습니다.

텍스처는 물체 표면에 입힐 그림을 격자로 담은 데이터입니다. 앞에서 「그림」이라 부른 것이 텍스처입니다. 벽돌 무늬나 캐릭터 얼굴 그림이 텍스처로 들어갑니다.

텍스처 격자의 한 칸을 텍셀이라고 부릅니다. 앞에서 「한 칸」이라 부른 것입니다. 화면의 점인 픽셀과 헷갈리지 않으려고 따로 붙인 이름입니다.

이 그림을 누가 어디에 두고 읽는지 알아야 포맷이 왜 필요한지 보입니다. 텍스처를 읽어 화면을 칠하는 장치가 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 앞에서 그래픽 카드라 부른 것이 GPU 이고, 정확히는 그래픽 카드에 올라간 계산 칩입니다. 이 절부터는 GPU 라고 부릅니다.

GPU 는 텍스처를 VRAM(Video Random Access Memory, 그래픽 메모리)이라는 전용 메모리에 올려 둡니다. 앞에서 그래픽 카드 메모리라 부른 것입니다. 화면을 그리는 동안 텍스처를 빠르게 꺼내 쓰려고 GPU 곁에 따로 둔 메모리입니다.

텍스처에서 색을 꺼내 쓰는 쪽도 있어야 합니다. 화면의 점마다 무슨 색을 칠할지 계산하는 GPU 위의 작은 프로그램을 셰이더라고 합니다. 앞에서 화면을 칠하는 프로그램이라 부른 것입니다.

VRAM 에 올라간 텍스처는 바이트의 줄일 뿐입니다. 바이트만 봐서는 한 텍셀이 몇 바이트인지, 그 바이트가 색인지 거리인지 알 수 없습니다. GPU 텍스처 포맷이 이것을 정합니다. 컬럼 타입이 테이블에 하는 일을 포맷이 텍스처에 합니다.

포맷을 미리 알기 때문에 GPU 는 셰이더 코드 없이 장치 회로만으로 바이트를 끊어 읽습니다. 셰이더는 「이 자리의 색」을 달라고만 합니다. 바이트를 숫자로 바꾸는 일은 GPU 가 맡습니다. 포맷이 없으면 셰이더가 바이트를 하나하나 풀어야 합니다.

텍스처 필터링은 이웃 텍셀 몇 개를 섞어 부드러운 색을 만드는 일입니다. 그림을 크게 늘려 그릴 때 칸 경계가 계단처럼 보이지 않게 해 줍니다. GPU 는 포맷을 알기 때문에 이 섞기도 회로로 해 줍니다. 포맷이 없으면 이런 하드웨어 도움도 못 받습니다.

한 텍셀의 바이트

포맷이 먼저 정하는 것은 한 텍셀에 무엇을 몇 비트씩 담느냐입니다. 이 소절은 채널과 비트 수를 보고, 그 둘이 그림 한 장의 메모리를 어떻게 정하는지 셈해 봅니다.

채널은 한 텍셀 안에 담긴 값 하나입니다. 색이면 흔히 빨강·초록·파랑·불투명도 넷을 담습니다. 머리글자를 따서 RGBA(Red·Green·Blue·Alpha)라고 부릅니다. 흑백 마스크처럼 값 하나면 되는 그림은 R 채널 하나만 둡니다.

채널마다 비트를 몇 개 줄지도 포맷이 정합니다. 8비트면 값이 256단계로 나뉩니다. 단계가 모자라면 하늘처럼 색이 천천히 변하는 그림에 계단 같은 띠가 보입니다. 16비트를 주면 65,536단계라 띠가 거의 사라집니다.

가장 흔한 포맷은 네 채널에 8비트씩, 한 텍셀에 4바이트를 씁니다. 그 4바이트가 메모리에 놓이는 순서는 아래와 같습니다.

packet-beta
0-7: "R 빨강"
8-15: "G 초록"
16-23: "B 파랑"
24-31: "A 불투명도"

첫 바이트가 빨강이고 넷째 바이트가 불투명도입니다. 이 순서도 포맷이 정합니다.

포맷 이름은 이 결정을 그대로 적습니다. 글자는 채널과 그 순서이고, 글자 뒤 숫자는 그 채널의 비트 수입니다. 위 그림의 포맷은 R8G8B8A8 이고, 파랑을 먼저 두는 B8G8R8A8 도 자주 쓰입니다.

채널마다 비트가 같으면 RGBA8 처럼 숫자를 한 번만 적기도 합니다. 끝에 붙는 F 는 채널을 정수가 아니라 소수점이 있는 부동소수점 수로 담는다는 뜻입니다. 이름을 끝까지 읽는 법은 아래 「포맷 이름 읽는 법」에서 다시 봅니다.

텍셀당 바이트가 정해지면 그림 한 장의 크기는 곱셈 한 번으로 나옵니다. 1024×1024 텍셀은 1,048,576개입니다. 여기에 텍셀당 바이트를 곱하면 VRAM 에서 차지하는 크기가 됩니다. 크기는 MiB(메비바이트, 1,048,576바이트)로 적습니다.

포맷 채널 · 채널당 비트 텍셀당 바이트 1024×1024 한 장
R8 하나 · 8 1 1MiB
RGBA8 넷 · 8 4 4MiB
RGBA16F 넷 · 16 8 8MiB
RGBA32F 넷 · 32 16 16MiB

채널을 하나로 줄이면 크기가 4분의 1로 줄어듭니다. 채널당 비트를 네 배로 늘리면 크기도 네 배가 됩니다.

밉맵은 같은 그림을 가로세로 절반씩 줄여 여러 장 더 담아 둔 것입니다. 멀리 있는 물체를 그릴 때 작은 그림을 씁니다. 절반씩 줄인 그림을 다 더하면 원래 그림의 3분의 1쯤이라 4MiB 짜리 그림은 5.3MiB 남짓이 됩니다.

값을 읽는 방식

같은 비트라도 어떤 숫자로 읽을지는 따로 정해야 합니다. 이 소절은 바이트 하나 0xFF 가 포맷에 따라 셰이더에 어떤 값으로 넘어가는지 봅니다.

셰이더는 색을 0.0 에서 1.0 사이 실수로 다룹니다. 그런데 실수 하나를 32비트로 담으면 채널마다 4바이트가 듭니다. 그래서 흔히 8비트 정수로 적어 두고, 읽을 때 GPU 가 0.0~1.0 실수로 바꿔 넘깁니다. 이렇게 정해진 범위로 늘이거나 줄여 읽는 것을 정규화라고 합니다.

UNORM(Unsigned Normalized, 부호 없는 정규화)은 바이트를 0~255 정수로 보고 0.0~1.0 으로 읽습니다. 0 은 0.0 이 되고 255 는 1.0 이 됩니다. 색은 음수가 없으니 UNORM 을 씁니다.

SNORM(Signed Normalized, 부호 있는 정규화)은 바이트를 음수도 되는 정수로 보고 -1.0~1.0 으로 읽습니다. 방향처럼 음수가 필요한 값은 SNORM 을 씁니다.

정규화하지 않고 정수 그대로 넘기는 포맷도 있습니다. UINT(Unsigned Integer, 부호 없는 정수)와 SINT(Signed Integer, 부호 있는 정수)입니다. 물체마다 붙인 번호처럼 섞이면 뜻이 사라지는 값을 담습니다.

부호 있는 8비트 정수는 맨 앞 비트가 켜지면 음수로 읽습니다. 이 방식에서 여덟 비트가 다 켜진 0xFF 는 -1 이고, 0x80 은 -128 입니다. SNORM 은 정수를 127 로 나눠 -1.0~1.0 을 만듭니다. 그래서 -128 은 -1.0 으로 깎아 읽고, 범위를 -127~127 로 적습니다.

이름 끝 읽는 법 0xFF 를 읽으면
UNORM 0~255 를 0.0~1.0 으로 1.0
SNORM -127~127 을 -1.0~1.0 으로 약 -0.008
UINT 부호 없는 정수 255
SINT 부호 있는 정수 -1

같은 바이트가 네 가지 값이 됩니다. SNORM 과 SINT 는 0xFF 를 -1 로 봅니다. SNORM 의 약 -0.008 은 -1 을 127 로 나눈 값입니다.

채널을 부동소수점 수로 담는 포맷도 있습니다. 이름 끝에 F 나 FLOAT 가 붙습니다. 1.0 을 넘는 밝기를 담을 수 있어 HDR(High Dynamic Range, 넓은 밝기 범위) 그림에 씁니다. 햇빛과 그늘의 밝기 차이처럼 8비트 0~255 로는 못 담는 폭을 다룹니다.

sRGB 포맷

포맷 이름 끝에 sRGB(standard Red Green Blue, 표준 빨강·초록·파랑)가 붙은 것이 있습니다. 이 소절은 이 꼬리표가 바이트를 바꾸는지, 읽는 방식을 바꾸는지 봅니다.

색 공간은 숫자를 어떤 색·밝기로 읽을지 정한 규칙입니다. 같은 숫자 128 이라도 색 공간이 다르면 화면에 나오는 밝기가 다릅니다.

사람 눈은 밝은 쪽보다 어두운 쪽의 밝기 차이를 더 잘 알아챕니다. sRGB는 8비트의 256단계를 어두운 쪽에 더 촘촘히 나눠 적는 색 공간입니다. 사진이나 그림 파일 대부분이 이 방식으로 저장됩니다.

빛을 계산할 때는 숫자가 밝기에 정비례해야 맞습니다. 두 빛을 더하면 밝기도 그만큼 더해져야 하기 때문입니다. 이렇게 숫자와 밝기가 정비례하는 색 공간을 선형 색 공간이라고 합니다.

sRGB 포맷을 고르면 GPU 가 텍스처를 읽을 때 sRGB 값을 선형 값으로 바꿔 셰이더에 넘깁니다. GPU 가 그린 결과를 이 포맷의 텍스처에 적을 때는 거꾸로 sRGB 값으로 바꿔 적습니다. 바이트는 같습니다. 달라지는 것은 읽고 쓸 때 끼는 변환입니다.

이웃 텍셀을 섞는 필터링은 평균을 내는 일입니다. 평균은 숫자가 밝기에 정비례하는 선형 값에서 내야 맞습니다. 그래서 GPU 는 sRGB 값을 선형 값으로 바꾼 뒤에 섞습니다.

셰이더에서 직접 바꾸면 sRGB 값끼리 먼저 섞고 나서 바꾸게 됩니다. 그러면 경계의 색이 실제보다 어둡게 어긋납니다. sRGB 포맷이 따로 있는 까닭입니다.

색이 아닌 데이터에는 sRGB 포맷을 쓰지 않습니다. 노멀 맵은 표면이 향하는 방향을 세 채널에 숫자로 담은 텍스처입니다. 여기에 sRGB 변환이 끼면 방향 값이 휘어 조명이 틀어집니다.

포맷 이름 읽는 법

앞 세 소절에서 본 결정은 포맷 이름에 차례로 적힙니다. 이 소절은 이름 하나를 쪼개 읽고, 그래픽 API(Application Programming Interface, 응용 프로그램 인터페이스)마다 그 이름을 어떻게 적는지 봅니다.

flowchart TD
    N["R8G8B8A8_UNORM_SRGB"] --> A["R G B A · 채널 넷이 이 순서로 놓인다"]
    N --> B["8 · 채널마다 8비트"]
    N --> C["UNORM · 0~255 를 0.0~1.0 으로 읽는다"]
    N --> D["SRGB · 읽고 쓸 때 sRGB 변환을 낀다"]

이름을 왼쪽부터 읽으면 채널 순서, 채널당 비트, 읽는 방식, 색 공간이 나옵니다. R16G16_SFLOAT 라면 빨강·초록 두 채널에 16비트 부동소수점을 담은 포맷입니다.

프로그램이 GPU 에 일을 시키는 통로가 그래픽 API 입니다. 같은 포맷도 API 마다 철자가 다릅니다.

그래픽 API 8비트 RGBA · 선형 같은 포맷 · sRGB
Vulkan VK_FORMAT_R8G8B8A8_UNORM VK_FORMAT_R8G8B8A8_SRGB
Direct3D DXGI_FORMAT_R8G8B8A8_UNORM DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
OpenGL GL_RGBA8 GL_SRGB8_ALPHA8
Metal MTLPixelFormatRGBA8Unorm MTLPixelFormatRGBA8Unorm_sRGB
WebGPU rgba8unorm rgba8unorm-srgb

철자는 달라도 담긴 결정은 같습니다. 한 API 의 이름을 읽을 줄 알면 다른 API 의 이름도 거의 그대로 풀립니다.

묶음 포맷과 블록 압축 포맷

지금까지 본 포맷은 채널 하나가 바이트 하나나 둘에 딱 맞았습니다. 이 소절은 채널이 바이트 경계를 넘나드는 포맷과 여러 텍셀을 한 덩어리로 적는 포맷을 봅니다.

RGB565 는 16비트 한 덩어리에 빨강 5비트, 초록 6비트, 파랑 5비트를 넣습니다. 초록에 한 비트를 더 주는 것은 사람 눈이 초록의 차이를 가장 잘 알아채기 때문입니다. 채널이 바이트 경계에 맞지 않습니다. 한 덩어리에 묶여 있어서 묶음(packed) 포맷이라고 부릅니다.

R11G11B10F 도 묶음 포맷입니다. 32비트에 부호 없는 작은 부동소수점 셋을 넣습니다. 불투명도와 음수를 버리는 대신 HDR 색을 4바이트에 담습니다.

블록 압축 포맷은 텍셀 하나가 아니라 가로세로 4텍셀짜리 블록 하나를 단위로 적습니다. BC1(Block Compression 1)은 16텍셀을 8바이트에 담아 텍셀당 반 바이트를 씁니다. 1024×1024 한 장이 512KiB(키비바이트, 1KiB 는 1,024바이트)가 됩니다. 블록을 어떻게 적는지는 텍스처 압축이 다룹니다.

블록 압축 포맷은 GPU 가 그린 결과를 받아 적는 렌더 타깃으로 쓰지 못합니다. 텍셀 하나를 적을 때마다 블록 전체를 다시 압축할 수 없기 때문입니다.

데이터에 맞춰 고르는 포맷

포맷은 담을 데이터가 정합니다. 이 소절은 흔한 데이터 여섯 가지에 어떤 포맷을 고르는지 봅니다.

깊이 버퍼는 화면의 점마다 카메라에서 얼마나 먼지를 적는 텍스처입니다. 앞의 물체가 뒤의 물체를 가리게 하려면 점마다 거리를 견줘야 해서 필요합니다. 깊이 포맷은 색 채널 대신 깊이 값 하나를 둡니다.

스텐실 버퍼는 점마다 작은 정수 표시를 적어 두는 버퍼입니다. 그릴 영역을 가를 때 씁니다. 깊이와 스텐실은 한 포맷에 같이 담기도 합니다. D24S8 은 깊이에 24비트, 스텐실에 8비트를 줍니다.

담는 데이터 흔히 고르는 포맷 까닭
색 사진 · 그림 RGBA8 sRGB 사람 눈에는 8비트면 충분하다
노멀 맵 RGBA8 선형 · 블록 압축 방향 값이라 sRGB 변환을 빼야 한다
흑백 마스크 · 높이 R8 값이 하나면 채널도 하나
조명 계산 결과 RGBA16F · R11G11B10F 1.0 을 넘는 밝기를 담는다
물체 번호 R32UI 섞이면 안 되는 정수다
깊이 D32F · D24S8 깊이 버퍼 전용

R32UI 는 32비트 부호 없는 정수 채널 하나를 둔 포맷입니다. D32F 는 32비트 부동소수점 깊이 값 하나를 둔 포맷입니다.

피할 조합도 표에서 읽힙니다. 색이 아닌 값에 sRGB 를 고르지 않습니다. 8비트로 충분한 그림에 32비트 부동소수점을 쓰면 VRAM 만 네 배로 먹습니다.

GPU 마다 다른 지원 목록

포맷은 회로가 푸는 약속이라 GPU 가 아는 포맷만 쓸 수 있습니다. 이 소절은 지원이 어떻게 갈리고 프로그램이 그걸 어떻게 견디는지 봅니다.

지원은 「된다·안 된다」 하나가 아닙니다. 같은 포맷이라도 읽기만 되는 기기, 렌더 타깃으로도 쓰이는 기기, 필터링까지 되는 기기가 갈립니다. RGBA32F 는 읽기는 대개 되지만 필터링은 기기에 따라 안 됩니다. 정수 포맷은 섞으면 뜻이 사라지므로 텍스처 필터링을 아예 받지 않습니다.

블록 압축 포맷이 가장 크게 갈립니다. 데스크톱 GPU 는 BC 계열을 주로 받습니다. 모바일 GPU 는 ASTC(Adaptive Scalable Texture Compression)와 ETC2(Ericsson Texture Compression 2) 계열을 주로 받습니다.

그래서 프로그램은 시작할 때 GPU 에 지원 목록을 묻습니다. 원하는 포맷이 없으면 다른 포맷으로 대신합니다. 게임은 이 갈림을 피하려고 빌드 때 그림 파일을 변환하는 단계인 에셋 파이프라인에서 플랫폼마다 텍스처를 다른 포맷으로 미리 바꿔 둡니다.

관련 항목

GPU 텍스처 포맷이 값을 적는 그림 자원

텍스처 · 텍셀 · 밉맵 · 렌더 타깃 · 깊이 버퍼 · 스텐실 버퍼 · 노멀 맵 · 큐브 맵 · 프레임버퍼 · 스왑체인

포맷 이름을 이루는 구성 요소

채널 · 비트 · 바이트 · 정규화 · 부동소수점 · 반정밀도 부동소수점 · 엔디언 · 색 깊이

포맷이 다루는 색 공간

sRGB · 선형 색 공간 · 감마 보정 · 색 공간 · HDR · sRGB 프레임버퍼

GPU 텍스처 포맷의 하위 종류

텍스처 압축 · BC1 · BC5 · BC7 · ASTC · ETC2 · RGB565 · R11G11B10F · 깊이 스텐실 형식

포맷을 고르고 부르는 그래픽 API

Vulkan · Direct3D · OpenGL · Metal · WebGPU · DXGI · 이미지 뷰 · 픽셀 포맷

포맷에 따라 동작이 갈리는 GPU 기능

GPU · VRAM · 셰이더 · 샘플러 · 텍스처 샘플링 · 텍스처 필터링 · 텍스처 캐시 · 대역폭

포맷을 담아 나르는 파일과 파이프라인

KTX2 · DDS · Basis Universal · 에셋 파이프라인 · 게임 개발

다른 이름: texture format · GPU texture format · 텍스처 포맷