텍스처 필터링
고친 사람 github-actions[bot]
텍스처 필터링은 물체에 입힌 그림에서 색을 읽을 때 돌려줄 색 하나를 만듭니다. 화면의 점과 그림의 칸은 크기도 위치도 잘 맞지 않습니다. 그래서 가까운 칸 하나를 집을지 이웃 칸 여럿을 섞을지를 정합니다. 이 고르기에 따라 같은 그림도 각져 보이거나 부드럽게 보입니다.
쉽고 빠른 이해
텍스처 필터링은 그림을 입힌 물체를 그릴 때 화면 점 하나에 들어갈 색을 정합니다. 벽돌 무늬 그림을 바닥에 깔면 바닥을 덮은 화면 점마다 그림 어디쯤의 색을 칠할지 물어야 합니다.
화면 점이 그림의 칸 한가운데에 딱 떨어지는 일은 드뭅니다. 바닥이 코앞이면 칸 하나가 화면 점 여럿으로 늘어납니다. 멀리 있으면 화면 점 하나가 칸 여럿을 덮습니다. 아무 칸이나 하나 집으면 가까이서는 네모가 각져 보입니다. 멀리서는 무늬가 지글거립니다.
어떻게 도나:
- 물은 지점이 그림의 칸 사이 어디쯤인지 잽니다
- 가까이 크게 보이면 둘레 네 칸을 거리에 따라 섞습니다
- 멀리 작게 보이면 미리 줄여 둔 그림에서 읽습니다. 크기가 애매하면 두 크기에서 읽어 다시 섞습니다
대가는 읽는 횟수입니다. 섞을수록 칸을 더 많이 읽습니다. 섞어 나온 색은 어느 칸에도 없던 색이라 색 대신 번호를 담은 그림에는 쓰지 않습니다.
상세
이 절은 텍스처 필터링이 무엇을 받아 무엇을 내주는지, 방식마다 칸을 몇 개 읽어 어떻게 섞는지를 다룹니다. 벽돌 무늬 그림 한 장을 긴 바닥에 깔아 두고 발밑부터 먼 끝까지 따라가며 봅니다.
물체 표면에 입힐 그림을 텍스처라고 합니다. 텍스처는 색을 담은 칸이 가로세로로 늘어선 격자입니다. 그 칸 하나를 텍셀이라고 부릅니다. 이 편에서는 그냥 칸이라고 쓰겠습니다.
화면도 격자입니다. 화면을 이루는 점 하나가 픽셀입니다. 바닥을 그리려면 바닥을 덮은 픽셀마다 텍스처의 어느 색을 칠할지 정해야 합니다.
텍스처 필터링은 이 색을 정하는 계산입니다. 넣는 것은 텍스처와 읽을 지점입니다. 나오는 것은 색 하나입니다. 텍스처에서 색을 한 번 읽을 때마다 이 계산이 한 번 돕니다.
칸 사이에 떨어지는 읽기 지점
읽을 지점은 텍스처 좌표로 줍니다. 텍스처의 왼쪽 끝을 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 · 텍스쳐 필터링 · 텍스처 필터