사전 텍스처
자료구조

텍스처

gabury1

텍스처는 표면에 입힐 그림을 담아 두는 자리입니다. 값이 격자로 늘어서 있고, 좌표를 주면 그 자리의 값이 나옵니다. 크기와 차원은 만들 때 정합니다.

쉽고 빠른 이해

텍스처는 표면에 입힐 그림 데이터를 그래픽 카드 메모리에 격자로 담아 두는 자원입니다. 큐브 표면에 입히는 이미지가 그 예입니다. 좌표를 주면 그 자리의 값을 곧바로 읽어 화면에 입히는 식입니다.

좌표를 그대로 주소 계산에 써서 값을 상수 시간에 찾으려고 이렇게 만듭니다. 해시테이블처럼 충돌한 자리를 훑거나 트리처럼 높이만큼 내려가는 과정이 없습니다.

도는 방식은 대략 이렇습니다.

  1. 만들 때 크기·차원·포맷을 정해 격자를 확보합니다
  2. 좌표를 주면 곧장 그 자리의 값을 계산해 돌려줍니다
  3. 원본보다 작게 줄인 사본도 여러 장 같이 두고, 그중 알맞은 것을 그때그때 골라 씁니다

대가는 공간입니다. 작게 줄인 사본까지 전부 담으면 원본 하나만 쓸 때보다 공간을 더 씁니다 — 2차원 텍스처라면 약 3분의 4배입니다. 필터를 정밀하게 쓸수록 한 번에 더 많은 값을 읽어야 합니다.

상세

텍스처는 GPU(Graphics Processing Unit, 그래픽 처리 장치) 메모리가 떠받치는 자원입니다. 담는 것은 1차원·2차원·3차원으로 늘어선 데이터 배열입니다. WebGPU 명세는 이 배열의 원소 하나가 여러 값을 담을 수 있다고 적습니다. 색처럼 성분이 여럿인 것을 원소 하나에 그대로 담기 위해서입니다. 이 원소 하나를 텍셀이라고 부릅니다.

읽고 쓰는 방식은 하나가 아닙니다. 같은 명세는 텍스처를 만들 때 준 용도에 따라 여러 방식으로 읽고 쓸 수 있다고 적습니다. 렌더 파이프라인과 컴퓨트 파이프라인의 셰이더(GPU 에서 도는 작은 프로그램)에서 샘플링(좌표 주변 값을 읽어 하나로 섞는 것)하고 읽고 쓸 수 있습니다. 렌더 패스의 출력으로 텍스처에 쓰이기도 합니다.

메모리에 놓이는 방식도 선형이 아닙니다. 명세는 텍스처가 GPU 메모리 안에서 선형 접근보다 다차원 접근에 맞춘 배치로 담기는 일이 잦다고 적습니다.

텍스처 하나는 통째로 한 장이 아닙니다. 하나 이상의 서브리소스, 곧 따로 가리킬 수 있는 조각으로 이루어집니다. 서브리소스를 유일하게 가리키는 것은 밉맵 레벨(이 문서에서는 밉 레벨이라고도 씁니다)이고, 2차원 텍스처에서만 배열 레이어가 여기에 더해집니다. 한 밉 레벨의 서브리소스는 번호가 하나 작은 레벨의 것보다 각 공간 차원에서 대략 절반 크기입니다. 어느 레벨을 고를지는 샘플러와 셰이더 언어가 맡습니다. 레벨 사이를 어떻게 섞을지도 이 둘이 정합니다. 명시적으로 고를 수도 있고 자동으로 맡길 수도 있습니다.

이름을 붙이는 방식은 명세마다 다릅니다. OpenGL 명세는 텍스처를 이름이 붙은 객체로 둡니다. 이름 공간은 부호 없는 정수이고, 0 은 기본 텍스처 객체를 가리키도록 예약되어 있습니다.

복잡도

읽기·쓰기·샘플링은 전부 상수 시간이고, 예외는 밉 사슬을 다 담을 때의 공간뿐입니다. 이유는 좌표가 곧 주소 계산의 입력이기 때문입니다.

flowchart TD
    좌표["좌표 (x, y)"] -. 주소 계산 .-> t22
    subgraph 격자["텍셀 격자"]
        direction TB
        subgraph 행0["행 0"]
            direction TD
            t00["·"] ~~~ t01["·"] ~~~ t02["·"] ~~~ t03["·"]
        end
        subgraph 행1["행 1"]
            direction TD
            t10["·"] ~~~ t11["·"] ~~~ t12["·"] ~~~ t13["·"]
        end
        subgraph 행2["행 2"]
            direction TD
            t20["·"] ~~~ t21["·"] ~~~ t22["■"] ~~~ t23["·"]
        end
    end

좌표 (x, y)가 격자 안의 정확히 그 자리를 곧장 가리킵니다. 사이에 있는 다른 텍셀은 거치지 않습니다.

세는 대상을 먼저 밝힙니다. n 은 텍스처에 든 텍셀의 개수입니다. m 은 밑면(밉 레벨 0)에서 가장 긴 변의 길이입니다.

필터는 세 가지입니다. 최근접은 좌표에 가장 가까운 값 하나만 쓰고, 이중선형은 좌표를 둘러싼 값 네 개를 섞고, 삼선형은 그 결과를 인접한 밉 레벨 두 장에서 한 번씩 구해 다시 섞습니다.

연산 평균 최악 왜 그 값인가
텍셀 하나 읽기 O(1) O(1) 좌표에서 주소를 계산해 곧장 찾아갑니다. 담긴 텍셀이 몇 개든 걸음 수가 안 늘어납니다
텍셀 하나 쓰기 O(1) O(1) 읽기와 같은 주소 계산입니다
한 번 샘플링 O(1) O(1) 읽는 텍셀 수를 필터가 상수로 못 박습니다. 최근접 1개, 이중선형 4개, 삼선형 8개입니다
공간 n × 텍셀당 바이트 같습니다 격자를 통째로 들고 있습니다. 텍셀당 바이트는 포맷이 정합니다
공간 · 밉 사슬 포함(2차원) 밑면의 약 3분의 4배 같습니다 2차원 텍스처는 레벨마다 각 축이 대략 절반이라 텍셀 수가 4분의 1로 줄고, 그 합이 등비급수입니다

조회에서 평균과 최악이 안 갈리는 까닭은 자리를 찾는 방법이 탐색이 아니기 때문입니다. 해시테이블처럼 충돌한 사슬을 훑는 일도 없고, 트리처럼 높이만큼 내려가는 일도 없습니다. 좌표가 곧 주소 계산의 입력입니다. 삼선형 필터가 8개를 읽는 것도 밉 레벨 두 장에서 네 개씩 읽기 때문이지 텍스처가 커져서가 아닙니다.

최악이 오는 자리

표기상 상수여도 실제 비용을 정하는 것은 메모리 접근입니다. WebGPU 명세는 텍스처가 GPU 메모리 안에서 선형 접근보다 다차원 접근에 맞춘 배치로 담기는 일이 잦다고 적습니다. 최악은 그 배치와 어긋나는 순서로 읽을 때 옵니다 — 가까운 좌표끼리 묶어 읽지 않고 폭 전체를 한 행씩 훑은 뒤 다음 행으로 넘어가는 식으로 읽으면, 다차원 배치를 살리지 못해 실제 비용이 더 커질 수 있습니다.

밉 레벨 장수의 상한

레벨 장수의 상한은 밑면 크기가 정합니다. WebGPU 명세는 최대 밉 레벨 수를 이렇게 정의합니다. 1차원이면 답이 1입니다. 2차원이면 폭과 높이 중 큰 값을 m 으로 둡니다. 3차원이면 폭·높이와 깊이 또는 레이어 수 중 큰 값을 m 으로 둡니다. 그리고 floor(log2(m)) + 1 을 돌려줍니다. 폭과 높이가 각각 8이면 m 이 8이고 레벨은 4장입니다.

텍셀당 바이트

압축하지 않은 포맷이면 텍셀마다 바이트가 붙습니다. 압축 포맷은 텍셀 하나가 아니라 블록 단위로 셉니다. Khronos 데이터 포맷 명세는 BC1 블록 하나가 4×4 텍셀을 8바이트에 담는다고 적습니다. 텍셀당 4비트입니다. BC7 은 같은 4×4 텍셀을 16바이트에 담습니다. 텍셀당 8비트입니다. 위 표의 텍셀당 바이트 자리에 들어가는 값이 이것입니다.

예시

WebGL 의 texImage2D

MDN(Mozilla Developer Network) 문서가 적는 WebGL 1 의 texImage2D 시그니처는 둘입니다.

JavaScript
texImage2D(target, level, internalformat, width, height, border, format, type, srcData)
texImage2D(target, level, internalformat, format, type, source)

target 은 지금 활성인 텍스처의 바인딩 지점을 고르는 값입니다. gl.TEXTURE_2D 는 2차원 텍스처를, gl.TEXTURE_CUBE_MAP_POSITIVE_X 는 큐브맵 텍스처의 +X 면을 가리킵니다. level 은 상세 수준, 곧 몇 번째 밉 레벨에 올리는지를 정합니다.

같은 사이트의 WebGL 텍스처 튜토리얼이 텍스처를 실제로 만드는 자리는 이렇습니다.

JavaScript
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
const level = 0;
const internalFormat = gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
gl.texImage2D(
  gl.TEXTURE_2D,
  level,
  internalFormat,
  width,
  height,
  border,
  srcFormat,
  srcType,
  pixel,
);

크기가 1×1 이고 텍셀 하나에 들어가는 값이 [0, 0, 255, 255], 곧 불투명한 파랑입니다. 이미지가 도착하기 전에 쓸 내용입니다. 튜토리얼은 image.onload 안에서 같은 텍스처를 다시 바인딩하고, 이번에는 이미지를 넘겨 같은 자리를 덮습니다.

WebGPU 의 createTexture

같은 사이트의 createTexture 문서는 텍스처를 만들고 이미지로 채우는 호출을 이렇게 싣습니다.

JavaScript
cubeTexture = device.createTexture({
  size: [imageBitmap.width, imageBitmap.height, 1],
  format: "rgba8unorm",
  usage:
    GPUTextureUsage.TEXTURE_BINDING |
    GPUTextureUsage.COPY_DST |
    GPUTextureUsage.RENDER_ATTACHMENT,
});
device.queue.copyExternalImageToTexture(
  { source: imageBitmap },
  { texture: cubeTexture },
  [imageBitmap.width, imageBitmap.height],
);

size 의 세 값이 폭·높이·레이어 수입니다. format 의 rgba8unorm 이 텍셀 하나의 생김새를 정합니다. usage 에 선 세 플래그가 이 텍스처에 허락되는 쓰임입니다. 셰이더에 바인딩하는 것, 복사의 목적지가 되는 것, 렌더 어태치먼트가 되는 것입니다. 만드는 호출과 채우는 호출이 갈라져 있다는 점도 이 예시가 보이는 자리입니다. 내용은 device.queue.copyExternalImageToTexture 가 따로 넣습니다. 변수 이름 cubeTexture 와 이 호출이 실린 WebGPU 샘플의 이름 「Textured Cube」가 보이듯, 이 텍스처가 입히는 표면은 큐브입니다.

Unity 의 Texture2D

Unity 스크립팅 레퍼런스는 텍스처를 C# 코드에서 다루는 클래스로 Texture2D 를 둡니다. 문서는 이 클래스를 텍스처를 만들거나 이미 있는 텍스처 에셋을 고치는 데 쓴다고 적습니다.

속성 무엇이 들어가나
width 텍스처의 폭. 픽셀 단위입니다
mipmapCount 이 텍스처에 밉맵 레벨이 몇 장 있나
activeMipmapLimit 이 텍스처에서 Unity 가 GPU 에 올리지 않는 고해상도 밉맵 레벨의 개수. 읽기 전용입니다
wrapMode 텍스처 좌표가 0 에서 1 사이를 벗어났을 때 샘플링하는 방식

생성자 Texture2D 는 빈 텍스처를 새로 만듭니다.

형태

텍스처 하나의 모양을 정하는 것은 만들 때 넘기는 서술자입니다. WebGPU 명세의 GPUTextureDescriptor 가 그 목록입니다. 모양을 정하는 필드는 이렇습니다.

필드 무엇을 정하나 안 주면
size 텍스처의 폭·높이와 깊이 또는 레이어 수 필수입니다
format 텍스처의 포맷 필수입니다
usage 이 텍스처에 허락되는 쓰임 필수입니다
mipLevelCount 텍스처가 담을 밉 레벨의 장수 1
sampleCount 텍스처의 샘플 수. 1보다 크면 멀티샘플 텍스처입니다 1
dimension 1차원인지, 2차원 레이어의 배열인지, 3차원인지 "2d"

만들어진 뒤에는 이 값들을 읽기만 합니다. GPUTexture 인터페이스는 width·height· depthOrArrayLayers·mipLevelCount·sampleCount·dimension·format·usage 를 읽기 전용 속성으로 둡니다. 메서드는 뷰를 만드는 createView 와 자원을 놓는 destroy 둘입니다.

밉 사슬

레벨을 여러 장 담으면 같은 그림이 크기를 줄여 가며 쌓입니다. 밉 레벨을 원본부터 가장 작은 것까지 순서대로 이어 놓은 것을 밉 사슬이라고 부릅니다.

block-beta
columns 1
  block:A["밉 레벨 0 · 8×8"]
    columns 8
    a1[" "] a2[" "] a3[" "] a4[" "] a5[" "] a6[" "] a7[" "] a8[" "]
    a9[" "] a10[" "] a11[" "] a12[" "] a13[" "] a14[" "] a15[" "] a16[" "]
    a17[" "] a18[" "] a19[" "] a20[" "] a21[" "] a22[" "] a23[" "] a24[" "]
    a25[" "] a26[" "] a27[" "] a28[" "] a29[" "] a30[" "] a31[" "] a32[" "]
    a33[" "] a34[" "] a35[" "] a36[" "] a37[" "] a38[" "] a39[" "] a40[" "]
    a41[" "] a42[" "] a43[" "] a44[" "] a45[" "] a46[" "] a47[" "] a48[" "]
    a49[" "] a50[" "] a51[" "] a52[" "] a53[" "] a54[" "] a55[" "] a56[" "]
    a57[" "] a58[" "] a59[" "] a60[" "] a61[" "] a62[" "] a63[" "] a64[" "]
  end
  block:B["밉 레벨 1 · 4×4"]
    columns 4
    b1[" "] b2[" "] b3[" "] b4[" "]
    b5[" "] b6[" "] b7[" "] b8[" "]
    b9[" "] b10[" "] b11[" "] b12[" "]
    b13[" "] b14[" "] b15[" "] b16[" "]
  end
  block:C["밉 레벨 2 · 2×2"]
    columns 2
    c1[" "] c2[" "]
    c3[" "] c4[" "]
  end
  block:D["밉 레벨 3 · 1×1"]
    columns 1
    d1[" "]
  end

2차원 텍스처는 레벨마다 각 축이 대략 절반으로 줄어 텍셀 수가 4분의 1이 됩니다. 그래서 사슬을 전부 담아도 공간이 밑면의 약 3분의 4배에서 멈춥니다. 레벨 장수 4는 밑면의 가장 긴 변 8에 앞의 공식을 적용한 값입니다.

갈래

텍스처는 dimension 값으로 갈립니다. WebGPU 명세는 이 값을 "1d"·"2d"·"3d" 셋 중 하나로 두고, 값마다 할 수 있는 것이 다릅니다.

"1d"

폭 하나만 가집니다. 밉맵을 가질 수 없고, 멀티샘플이 될 수 없고, 압축 포맷이나 깊이·스텐실 포맷을 쓸 수 없고, 렌더 타깃으로 쓰일 수 없습니다.

"2d"

폭과 높이를 가집니다. 레이어를 가질 수도 있습니다.

"3d"

폭·높이·깊이, 세 축을 가집니다. depthOrArrayLayers 값이 2d 텍스처와 달리 레이어 수가 아니라 실제 세 번째 공간 축, 곧 깊이를 가리킵니다.

OpenGL 명세는 같은 갈림을 타깃 이름으로 둡니다. 컨텍스트 초기화 때 기본 텍스처 객체가 TEXTURE_1D·TEXTURE_2D·TEXTURE_3D 타깃에 자리를 잡습니다. 그 밖에 배열·사각형·버퍼· 큐브맵·멀티샘플용 타깃도 따로 있습니다(이름은 아래 관련 항목 절에 있습니다). 새 텍스처 객체는 아직 안 쓰인 이름을 이 타깃 중 하나에 바인딩하면 만들어집니다.

관련 항목

텍스처를 읽을 때 관여하는 설정과 장치

샘플러 · 텍스처 좌표 · 텍스처 필터링 · 이방성 필터링 · 래핑 모드

OpenGL 이 갈래마다 따로 두는 타깃 이름

TEXTURE_1D_ARRAY · TEXTURE_2D_ARRAY · TEXTURE_RECTANGLE · TEXTURE_BUFFER · TEXTURE_CUBE_MAP · TEXTURE_CUBE_MAP_ARRAY · TEXTURE_2D_MULTISAMPLE · TEXTURE_2D_MULTISAMPLE_ARRAY

텍셀을 담는 방식

텍셀 · 밉맵 · 밉 레벨 · 텍스처 압축

텍스처와 함께 파이프라인에 서는 자원

셰이더 · 프레임버퍼 · 렌더 타깃 · 노멀 맵

다른 이름: texture · Texture · 텍스쳐