사전 텍스처 필터링
알고리즘

텍스처 필터링

gabury1고친 사람 github-actions[bot]

텍스처 필터링은 물체에 입힌 그림에서 색을 읽을 때 돌려줄 색 하나를 만듭니다. 화면의 점과 그림의 칸은 크기도 위치도 잘 맞지 않습니다. 그래서 가까운 칸 하나를 집을지 이웃 칸 여럿을 섞을지를 정합니다. 이 고르기에 따라 같은 그림도 각져 보이거나 부드럽게 보입니다.

쉽고 빠른 이해

텍스처 필터링은 그림을 입힌 물체를 그릴 때 화면 점 하나에 들어갈 색을 정합니다. 벽돌 무늬 그림을 바닥에 깔면 바닥을 덮은 화면 점마다 그림 어디쯤의 색을 칠할지 물어야 합니다.

화면 점이 그림의 칸 한가운데에 딱 떨어지는 일은 드뭅니다. 바닥이 코앞이면 칸 하나가 화면 점 여럿으로 늘어납니다. 멀리 있으면 화면 점 하나가 칸 여럿을 덮습니다. 아무 칸이나 하나 집으면 가까이서는 네모가 각져 보입니다. 멀리서는 무늬가 지글거립니다.

어떻게 도나:

  1. 물은 지점이 그림의 칸 사이 어디쯤인지 잽니다
  2. 가까이 크게 보이면 둘레 네 칸을 거리에 따라 섞습니다
  3. 멀리 작게 보이면 미리 줄여 둔 그림에서 읽습니다. 크기가 애매하면 두 크기에서 읽어 다시 섞습니다

대가는 읽는 횟수입니다. 섞을수록 칸을 더 많이 읽습니다. 섞어 나온 색은 어느 칸에도 없던 색이라 색 대신 번호를 담은 그림에는 쓰지 않습니다.

상세

이 절은 텍스처 필터링이 무엇을 받아 무엇을 내주는지, 방식마다 칸을 몇 개 읽어 어떻게 섞는지를 다룹니다. 벽돌 무늬 그림 한 장을 긴 바닥에 깔아 두고 발밑부터 먼 끝까지 따라가며 봅니다.

물체 표면에 입힐 그림을 텍스처라고 합니다. 텍스처는 색을 담은 칸이 가로세로로 늘어선 격자입니다. 그 칸 하나를 텍셀이라고 부릅니다. 이 편에서는 그냥 칸이라고 쓰겠습니다.

화면도 격자입니다. 화면을 이루는 점 하나가 픽셀입니다. 바닥을 그리려면 바닥을 덮은 픽셀마다 텍스처의 어느 색을 칠할지 정해야 합니다.

텍스처 필터링은 이 색을 정하는 계산입니다. 넣는 것은 텍스처와 읽을 지점입니다. 나오는 것은 색 하나입니다. 텍스처에서 색을 한 번 읽을 때마다 이 계산이 한 번 돕니다.

칸 사이에 떨어지는 읽기 지점

읽을 지점은 텍스처 좌표로 줍니다. 텍스처의 왼쪽 끝을 0, 오른쪽 끝을 1 로 두고 그 사이를 소수로 적는 좌표입니다. 세로도 같습니다. 텍스처 크기가 달라도 같은 좌표가 같은 곳을 가리키게 하려는 약속입니다.

한 줄에 칸이 여덟 개인 텍스처에서 가로 좌표 0.3 을 물었다고 합시다. 0.3 에 8 을 곱하면 2.4 입니다. 왼쪽 끝에서 칸 2.4 개만큼 간 곳이라는 뜻입니다.

칸의 가운데는 0.5, 1.5, 2.5 처럼 반 칸씩 밀린 곳에 있습니다. 그래서 2.4 는 가운데가 1.5 인 칸과 2.5 인 칸 사이에 떨어집니다.

좌표는 대개 이렇게 칸 가운데를 비껴갑니다. 첫째 물음이 여기서 생깁니다. 칸과 칸 사이에 떨어진 지점에 무슨 색을 줄 것인가입니다.

가까이 크게 보일 때와 멀리 작게 보일 때

둘째 물음은 크기입니다. 발밑 바닥에서는 칸 하나가 화면에서 여러 픽셀로 늘어납니다. 텍스처를 확대해 보는 셈이라 이 경우를 확대라고 부릅니다.

먼 끝 바닥에서는 반대입니다. 픽셀 하나가 칸 여럿을 덮습니다. 이 경우를 축소라고 부릅니다.

두 경우는 곤란한 대목이 다릅니다. 확대에서는 칸 사이를 어떻게 메우느냐가 문제입니다. 축소에서는 픽셀 하나에 덮인 칸 여럿을 어떻게 한 색에 담느냐가 문제입니다.

그래서 확대와 축소에 쓸 방식을 따로 고릅니다. 텍스처를 읽는 규칙을 이렇게 한 벌로 묶어 둔 설정을 샘플러라고 합니다.

아래 그림은 이 편이 따라갈 갈림길입니다. 그림의 상자마다 뒤의 소절이 하나씩 풉니다.

flowchart TD
    A["픽셀 하나가 덮는 칸 수를 잰다"] --> B{"한 칸보다 적은가"}
    B -->|"적다 · 확대"| C["가장 가까운 칸 하나를 읽거나 이웃 네 칸을 섞는다"]
    B -->|"많다 · 축소"| D["미리 줄여 둔 장 가운데 알맞은 레벨을 고른다"]
    D --> E{"면이 비스듬히 누웠나"}
    E -->|아니다| F["레벨 하나나 이웃 두 레벨에서 읽어 섞는다"]
    E -->|누웠다| G["누운 방향을 따라 여러 번 읽어 섞는다"]

가장 가까운 칸 하나

가장 단순한 방식은 읽을 지점에서 가장 가까운 칸 하나의 색을 돌려줍니다. 이 방식을 최근접 이웃 샘플링이라고 부릅니다. 칸을 한 번만 읽으므로 가장 쌉니다.

확대에서 이 방식을 쓰면 칸 하나가 여러 픽셀에 같은 색으로 칠해집니다. 칸 경계에서 색이 뚝 바뀌므로 네모가 각지게 드러납니다.

이 각진 모양을 일부러 고를 때가 있습니다. 칸 하나하나를 손으로 찍어 그린 도트 그림은 경계가 번지면 그림이 뭉개집니다. 이런 그림은 이 방식으로 읽습니다.

이웃 네 칸을 섞는 읽기

다음 방식은 읽을 지점을 둘러싼 칸 넷을 읽습니다. 지점과 가까운 칸일수록 큰 몫을 주어 섞습니다. 이 방식을 쌍선형 필터링이라고 합니다. 가로로 한 번, 세로로 한 번 섞는다고 해서 붙은 이름입니다.

한 번의 섞기는 선형 보간입니다. 두 값 a 와 b, 그리고 0 과 1 사이의 비율 t 를 받습니다. 그리고 a 에서 b 쪽으로 t 만큼 간 값을 냅니다. 아래 코드는 이 계산을 lerp 라고 적습니다.

아래는 칸 넷의 밝기를 놓고 섞는 순서입니다. 읽을 지점은 왼쪽 칸에서 오른쪽 칸 쪽으로 0.25, 위 칸에서 아래 칸 쪽으로 0.5 만큼 가 있습니다.

// 위 두 칸: 0, 100
// 아래 두 칸: 40, 80
top = lerp(0, 100, 0.25)    // 25
bot = lerp(40, 80, 0.25)    // 50
c   = lerp(top, bot, 0.5)   // 37.5

위 두 칸을 가로로 섞어 25 를 얻었습니다. 아래 두 칸도 가로로 섞어 50 을 얻었습니다. 그 둘을 세로 비율로 한 번 더 섞은 값이 37.5 입니다. 넷 중 어느 칸에도 없던 값입니다.

확대에서 이 방식을 쓰면 칸 경계가 부드럽게 번집니다. 대신 아주 크게 확대하면 전체가 흐릿해 보입니다. 섞기는 칸 사이를 메울 뿐이라 원래 텍스처에 없던 세부를 만들어 내지 못합니다.

멀리 작게 보일 때의 지글거림

축소에서는 네 칸 섞기로 모자랍니다. 픽셀 하나가 칸 열여섯 개를 덮어도 네 칸만 읽으면 나머지 열두 칸의 색은 화면에 못 나옵니다. 바닥이 조금만 움직여도 읽히는 칸이 바뀝니다. 그래서 무늬가 반짝이고 지글거립니다.

이렇게 칸을 드문드문 읽어 무늬가 깨지는 현상을 에일리어싱이라고 합니다. 축소에서 텍스처 필터링이 막으려는 것이 이 깨짐입니다.

벽돌 줄눈처럼 규칙적인 무늬에서는 깨짐이 다른 꼴로 드러납니다. 원래 없던 물결 무늬가 생깁니다. 이 물결을 모아레라고 부릅니다.

덮인 칸을 전부 읽어 평균을 내면 이 깨짐이 사라집니다. 그러나 멀어질수록 덮는 칸이 수십, 수백 개로 늘어납니다. 픽셀마다 그만큼 읽으면 비용이 거리에 따라 함께 불어납니다.

미리 평균을 내 둔 텍스처

그래서 평균을 그리기 전에 미리 내 둡니다. 텍스처를 가로세로 절반으로 줄인 장을 만듭니다. 그것을 다시 절반으로 줄인 장도 만듭니다. 칸이 하나 남을 때까지 이렇게 줄인 장들을 함께 둔 한 벌을 밉맵이라고 합니다.

밉맵 안의 장 하나하나를 밉 레벨이라고 부릅니다. 원본이 레벨 0, 한 번 줄인 장이 레벨 1, 두 번 줄인 장이 레벨 2 입니다. 번호를 붙여 두면 몇 번 줄인 장을 읽을지 수 하나로 말할 수 있습니다.

줄인 장의 칸 하나에는 원본 칸 여럿의 평균이 이미 담겨 있습니다. 레벨 1 의 칸 하나는 원본 칸 넷을, 레벨 2 의 칸 하나는 원본 칸 열여섯을 섞은 값입니다.

원본이 8×8 칸이면 층은 아래처럼 쌓입니다. 한 층 내려갈 때마다 가로세로 칸 수는 절반이 됩니다. 칸 하나가 품는 원본 칸은 네 배가 됩니다.

flowchart TD
    subgraph L0["레벨 0 · 원본"]
        A["8×8 칸 · 칸 하나 = 원본 칸 1"]
    end
    subgraph L1["레벨 1 · 한 번 줄인 장"]
        B["4×4 칸 · 칸 하나 = 원본 칸 4"]
    end
    subgraph L2["레벨 2 · 두 번 줄인 장"]
        C["2×2 칸 · 칸 하나 = 원본 칸 16"]
    end
    L0 -->|"가로세로 절반"| L1
    L1 -->|"가로세로 절반"| L2
    L2 -->|"가로세로 절반"| D["레벨 3 · 1×1 칸"]

축소에서는 픽셀이 덮는 칸 수에 맞는 레벨을 고릅니다. 한 변으로 원본 칸 둘을 덮으면 레벨 1, 넷을 덮으면 레벨 2 입니다. 고른 장에서는 다시 한 칸이나 네 칸만 읽으면 됩니다. 멀어져도 읽는 횟수가 늘지 않습니다.

두 레벨 사이를 잇는 섞기

덮는 칸 수가 두 배가 될 때마다 레벨이 하나 오릅니다. 그래서 덮는 칸이 2 와 4 사이면 레벨도 1 과 2 사이의 소수가 됩니다. 한 변으로 칸 2.6 개쯤을 덮으면 레벨은 1.4 쯤입니다.

가까운 레벨 하나만 고르면 1.4 는 레벨 1 이 됩니다. 1.6 은 레벨 2 가 됩니다. 바닥 위에서 레벨이 바뀌는 선을 따라 흐린 정도가 뚝 달라집니다. 그 선이 띠처럼 보입니다.

이 띠를 없애려면 위아래 두 레벨에서 각각 네 칸 섞기로 읽습니다. 그리고 소수 부분을 비율로 삼아 두 값을 한 번 더 섞습니다. 1.4 면 레벨 1 에서 읽은 값이 더 큰 몫을 갖습니다.

아래 그림은 레벨 1.4 에서 색 하나를 얻는 순서입니다. 두 갈래로 읽은 값이 마지막에 한 번 더 섞입니다.

flowchart TD
    A["레벨 1.4"] --> B["레벨 1 에서 네 칸 섞기"]
    A --> C["레벨 2 에서 네 칸 섞기"]
    B --> D["소수 0.4 를 비율로 한 번 더 섞기"]
    C --> D
    D --> E["색 하나"]

가로, 세로, 레벨 사이로 섞기가 세 번이라 이 방식을 삼선형 필터링이라고 부릅니다. 두 장에서 네 칸씩 읽으므로 칸을 여덟 번 읽습니다.

비스듬히 누운 면

바닥처럼 시선과 거의 나란히 누운 면에서는 픽셀 하나가 덮는 칸이 한쪽으로 길쭉합니다. 가로로는 칸 하나를 덮습니다. 깊이 방향으로는 칸 여덟을 덮습니다.

밉맵의 레벨은 가로와 세로를 같이 줄인 장입니다. 길쭉한 쪽에 맞춰 레벨을 고르면 짧은 쪽까지 여덟 배로 뭉개집니다. 먼 바닥이 필요 이상으로 흐릿해지는 까닭이 이것입니다.

이방성 필터링은 이 뭉개짐을 줄입니다. 방향마다 덮는 폭이 다르다는 뜻에서 이방성이라는 이름이 붙었습니다. 레벨은 짧은 쪽에 맞춰 덜 줄인 장을 고릅니다. 그리고 길쭉한 방향을 따라 여러 지점을 읽어 섞습니다.

그러면 먼 바닥의 줄눈이 흐려지지 않고 남습니다. 대가는 읽는 횟수입니다. 면이 많이 누울수록 여러 번 읽습니다. 몇 번까지 읽을지 상한은 설정으로 정합니다.

방식마다 읽는 칸 수

지금까지 본 방식을 한 번 읽을 때 칸을 몇 번 읽는지로 늘어놓으면 아래와 같습니다. 마지막 열은 그 방식을 써도 남는 흠입니다.

방식 읽는 칸 수 남는 흠
가장 가까운 칸 1 확대하면 각지고 축소하면 지글거린다
쌍선형 필터링 4 크게 확대하면 흐릿하다. 밉맵 없이 축소하면 지글거린다
밉맵에서 레벨 하나만 쌍선형으로 4 레벨이 바뀌는 선에 띠가 보인다
삼선형 필터링 8 비스듬히 누운 면이 흐릿하다
이방성 필터링 누운 정도만큼 여러 배 읽는 횟수가 가장 많다

아래 줄로 갈수록 화면의 흠은 줄고 읽는 칸은 늘어납니다. 칸을 읽는 일은 메모리를 오가는 일입니다. 화면의 픽셀 수가 많을수록 이 차이가 그리는 시간에 크게 드러납니다.

섞기를 맡는 하드웨어

이 섞기는 픽셀마다 몇 번씩 돌아갑니다. 그래서 GPU(Graphics Processing Unit, 그래픽 처리 장치)에는 칸을 읽어 섞는 일을 전담하는 회로가 따로 있습니다.

픽셀의 색을 계산하는 작은 프로그램을 프래그먼트 셰이더라고 합니다. 이 프로그램은 텍스처와 좌표와 샘플러를 넘기고 섞인 색만 돌려받습니다. 방식을 바꾸려면 프로그램이 아니라 샘플러 설정을 바꿉니다.

섞지 않고 읽는 경우

칸에 색 대신 번호를 담은 텍스처가 있습니다. 칸마다 땅의 종류를 적어 둔 지도가 그런 예입니다. 3 은 풀이고 5 는 물이라고 합시다.

이 지도를 섞어 읽으면 풀과 물 사이에서 4 가 나옵니다. 4 는 풀도 물도 아닌 엉뚱한 번호입니다. 그래서 이런 텍스처는 가장 가까운 칸 하나만 읽습니다.

섞을 필요가 아예 없는 읽기도 있습니다. 다 그린 화면을 텍스처 한 장에 담아 두고 그것을 다시 읽어 손보는 일을 포스트 프로세싱이라고 합니다. 화면 전체에 효과를 한 번에 입히려고 씁니다.

이때는 흔히 화면과 칸 수가 같은 텍스처를 픽셀마다 한 칸씩 읽습니다. 그러면 읽는 지점이 늘 칸 가운데에 떨어집니다. 칸 사이에 떨어지지 않으니 섞을 것이 없습니다.

가장자리에서 섞여 드는 이웃

네 칸 섞기는 텍스처 가장자리에서 텍스처 밖의 칸을 필요로 합니다. 밖을 무엇으로 볼지는 래핑 모드가 정합니다. 반대쪽 끝을 이어 붙여 되풀이할 수도 있습니다. 끝 칸을 바깥으로 늘려 쓸 수도 있습니다.

여러 그림을 한 장에 모아 붙인 텍스처 아틀라스에서는 가장자리 너머의 칸이 옆 그림의 칸입니다. 섞으면 옆 그림의 색이 경계에 번져 듭니다. 그래서 그림과 그림 사이에 여백을 둡니다.

필터라는 이름

소리나 사진을 다루는 신호 처리에서는 값 여럿을 섞어 거친 변화를 누그러뜨리는 계산을 필터라고 부릅니다. 텍스처 필터링도 칸 값 여럿을 섞어 화면에 고르게 담는 계산입니다. 그래서 같은 이름을 씁니다.

관련 항목

텍스처 필터링이 고르는 읽기 방식

최근접 이웃 샘플링 · 쌍선형 필터링 · 삼선형 필터링 · 이방성 필터링 · 바이큐빅 보간

텍스처 필터링이 읽어 섞는 그림 자원

텍스처 · 텍셀 · 텍스처 좌표 · 밉맵 · 밉 레벨 · 텍스처 아틀라스

필터링 방식을 담아 두는 읽기 설정

샘플러 · 래핑 모드 · 밉 레벨 바이어스 · 테두리 색

텍스처 필터링이 줄이려는 화면의 깨짐

에일리어싱 · 모아레 · 안티에일리어싱 · 계단 현상

텍스처 필터링이 도는 그리기 단계

렌더링 · 그래픽스 파이프라인 · 래스터화 · 프래그먼트 셰이더 · GPU · 텍스처 샘플링

텍스처 필터링을 떠받치는 계산

보간 · 선형 보간 · 샘플링 · 신호 처리 · 감마 보정

다른 이름: texture filtering · Texture Filtering · 텍스쳐 필터링 · 텍스처 필터