선형 색 공간
고친 사람 github-actions[bot]
선형 색 공간은 색을 섞고 더하는 계산이 빛이 실제로 섞이는 모습과 맞아떨어지게 해 줍니다. 저장한 숫자가 빛의 양에 비례하도록 색을 적습니다. 화면에 보내는 색은 대개 이렇게 적혀 있지 않습니다. 그래서 그래픽 프로그램은 계산하는 동안만 이 방식으로 옮겨 왔다가 내보낼 때 되돌립니다.
쉽고 빠른 이해
선형 색 공간은 색 계산이 빛이 섞이는 방식과 어긋나지 않게 해 줍니다. 검정과 흰색을 반씩 섞으면 흰색 빛의 절반을 내는 회색이 나와야 합니다. 빛의 양에 비례하는 숫자로 계산하면 그 회색이 나옵니다.
사진과 웹의 색은 보통 어두운 쪽에 눈금을 많이 준 화면용 숫자로 적혀 있습니다. 이 숫자는 빛의 양에 비례하지 않습니다. 이 숫자로 바로 평균을 내면 섞은 색이 어둡게 나옵니다. 경계도 탁해집니다.
어떻게 도나:
- 이미지를 읽을 때 화면용 숫자를 빛의 양에 비례하는 값으로 되돌립니다
- 섞기·조명·흐리기 같은 계산을 그 값으로 합니다
- 화면에 내보낼 때 다시 화면용 숫자로 바꿉니다
대가는 둘입니다. 먼저 읽을 때와 내보낼 때 변환을 한 번씩 거칩니다.
다음은 비트 수입니다. 선형 값을 같은 비트 수에 담으면 어두운 쪽 눈금이 성깁니다. 그러면 부드럽게 이어져야 할 어두운 밝기 변화가 층층이 끊겨 보입니다. 이를 막으려고 계산하는 동안에는 비트를 더 씁니다.
쓰지 않는 곳도 있습니다. 눈에 고르게 보이는 밝기 단계를 만들 때는 쓰지 않습니다. 색이 아닌 데이터를 담은 이미지에도 쓰지 않습니다.
상세
양동이에 물을 붓는 장면을 떠올려 봅니다. 한 사람이 세 컵, 다른 사람이 두 컵을 부었으면 양동이에는 다섯 컵이 들어 있습니다. 컵 수를 더하는 계산이 물의 양을 그대로 따라갑니다.
색을 숫자로 적으려면 규칙이 있어야 합니다. 어떤 숫자를 어떤 색으로 읽을지 정한 규칙이 색 공간입니다. 컴퓨터 화면의 색은 보통 빨강·초록·파랑 세 빛의 세기를 숫자 셋으로 적습니다. 규칙이 다르면 같은 숫자 셋이 다른 색이 됩니다.
선형 색 공간은 이 숫자가 빛의 양에 비례하도록 적는 색 공간입니다. 0.5 는 1.0 이 내는 빛의 절반입니다. 0.25 는 다시 그 절반입니다. 양동이의 컵 수를 더하듯 이 숫자를 더하거나 평균을 내면 빛을 더하거나 섞은 결과가 나옵니다.
이 성질이 필요한 까닭은 빛이 더해지는 방식에 있습니다. 전등 두 개가 벽 하나를 비추면 벽에 닿는 빛은 두 전등의 빛을 더한 양입니다. 그래픽스의 조명·반투명 겹치기·흐리기는 모두 이렇게 빛을 더하고 나누는 계산입니다. 숫자가 빛의 양에 비례해야 이 계산이 화면에서도 맞게 보입니다.
숫자와 빛의 양 사이 관계를 나타낸 곡선이 전달 함수입니다. 선형 색 공간은 이 곡선이 곧은 직선입니다. 「선형」이라는 이름이 여기서 왔습니다.
아래 소절은 화면에 보내는 숫자가 왜 선형이 아닌지에서 시작합니다. 이어 흰색과 검정 한 쌍을 섞어 두 방식의 계산이 어떻게 갈리는지 봅니다. 그다음 그래픽 프로그램이 두 방식을 오가는 순서와 선형 값을 담는 비트 수를 봅니다. 끝으로 선형 값이 맞지 않는 계산과, 빨강·초록·파랑을 어떤 빛으로 잡느냐는 문제를 짚습니다.
화면으로 보내는 숫자
화면에 보내는 색은 대개 sRGB(standard Red Green Blue, 표준 적녹청 색 공간)라는 색 공간으로 적혀 있습니다. 사진 파일, 웹 페이지의 색 코드, 모니터가 받는 신호가 대부분 이 규칙을 따릅니다. sRGB 의 숫자는 빛의 양에 비례하지 않습니다.
sRGB 는 숫자와 빛 사이에 휘어진 곡선을 둡니다. 0 에서 255 까지의 숫자를 255 로 나눠 0~1 로 만든 뒤 대략 2.2 제곱하면 빛의 양이 됩니다. 이 빛의 양은 흰색이 내는 빛을 1 로 친 비율입니다.
0 에서 255 까지의 눈금에서 128 은 가운데 숫자입니다. 128 을 255 로 나누면 약 0.5 입니다. 0.5 를 2.2 제곱하면 약 0.22 입니다. 128 이 내는 빛은 255 가 내는 빛의 5분의 1 남짓에 그칩니다.
이렇게 휘게 적는 까닭은 사람 눈에 있습니다. 눈은 어두운 쪽의 밝기 차이를 밝은 쪽보다 잘 알아챕니다.
8비트 숫자는 0 부터 255 까지 256 칸 눈금입니다. 칸이 성기면 부드럽게 이어져야 할 밝기 변화가 층층이 끊겨 보입니다. 이 계단 자국이 밴딩입니다.
곡선은 한정된 256 칸을 어두운 쪽에 더 많이 나눠 줍니다. 눈이 예민한 어두운 쪽의 칸이 촘촘해집니다. 같은 8비트로도 어두운 면에 밴딩이 덜 보입니다.
숫자를 이렇게 비틀어 적고 화면 쪽에서 되돌리는 일을 감마 보정이라고 부릅니다. 곡선의 지수를 그리스 문자 감마로 적어 온 데서 붙은 이름입니다.
흰색과 검정을 반씩 섞는 계산
이 소절은 가장 단순한 섞기 하나로 두 방식의 차이를 봅니다. 검정과 흰색을 반씩 섞어 회색 하나를 만드는 계산입니다.
화면용 숫자로 바로 평균을 내면 0 과 255 의 가운데인 128 이 나옵니다. 앞 소절에서 본 대로 128 은 흰색 빛의 5분의 1 남짓만 냅니다. 빛을 반씩 섞었다면 절반이 나와야 하므로 이 회색은 너무 어둡습니다.
선형 값으로 옮겨서 섞으면 결과가 달라집니다. 검정은 0.0, 흰색은 1.0 이고 둘의 평균은 0.5 입니다. 이 0.5 를 다시 화면용 숫자로 바꾸면 186 이 됩니다.
아래 코드는 두 계산을 나란히 둡니다. 곡선은 2.2 제곱으로 어림했습니다. 각 줄이 내는 값은 오른쪽 주석에 적었습니다.
def to_linear(v):
return (v / 255) ** 2.2
def to_screen(x):
return round(255 * x ** (1 / 2.2))
(0 + 255) / 2 # 127.5
round(to_linear(128), 2) # 0.22
to_screen((0.0 + 1.0) / 2) # 186
round(to_linear(186), 2) # 0.5
to_linear 는 화면용 숫자를 선형 값으로 되돌리는 함수입니다. to_screen 은 그 반대 방향입니다. 코드 끝의 네 줄 가운데 앞의 두 줄이 화면용 숫자로 섞은 결과입니다. 평균 127.5 를 반올림한 128 은 가운데 숫자이지만 빛은 0.22 에 그칩니다.
뒤의 두 줄은 선형 값으로 섞은 결과입니다. 화면용 숫자로는 186 입니다. 이 186 이 내는 빛이 흰색의 절반입니다.
이 어긋남은 색을 섞는 곳마다 되풀이됩니다. 화면은 픽셀이라는 작은 점들로 이루어져 있습니다. 사진을 줄이거나 흐리게 하는 계산은 이웃 픽셀을 평균 냅니다. 화면용 숫자로 하면 같은 이유로 결과가 어두워집니다.
두 색이 맞닿은 경계를 부드럽게 다듬는 안티에일리어싱도 경계 픽셀에 두 색을 섞어 칠합니다. 화면용 숫자로 섞으면 경계에 어두운 테두리가 끼어 탁해 보입니다. 빨강에서 초록으로 넘어가는 그러데이션도 마찬가지입니다. 화면용 숫자로 이으면 가운데가 칙칙하게 가라앉습니다.
읽기에서 내보내기까지의 변환 순서
그래픽 프로그램은 한 흐름 안에서 두 색 공간을 오갑니다. 이 소절은 이미지 한 장이 읽혀서 화면에 나가기까지 어느 단계에서 바뀌는지를 봅니다.
읽는 쪽에서 먼저 되돌립니다. 그래픽 프로그램이 그리는 물체의 겉면에 입히는 이미지를 텍스처라 합니다. 텍스처는 대개 sRGB 로 저장돼 있습니다. 그래서 읽는 즉시 선형 값으로 바꿉니다.
이 변환은 대개 하드웨어가 맡습니다. GPU(Graphics Processing Unit, 그래픽 처리 장치)는 화면을 그리는 계산을 맡는 칩입니다. 텍스처가 sRGB 로 적혀 있다고 표시해 두면 GPU 가 읽을 때 변환을 알아서 해 줍니다.
계산은 선형 값으로 합니다. 픽셀 색을 정하는 작은 프로그램이 셰이더입니다. 셰이더는 물체에 닿는 빛을 더해 픽셀 색을 냅니다.
블렌딩은 반투명한 색을 겹칠 때 쓰는 섞기입니다. 셰이더가 낸 색을 이미 그려진 색과 섞습니다. 이 섞기도 선형 값으로 합니다.
내보낼 때 다시 바꿉니다. 화면에 보낼 픽셀을 모아 두는 메모리가 프레임버퍼입니다. 계산이 끝난 선형 값은 이 메모리에 적히는 순간 sRGB 숫자로 바뀝니다.
이 변환을 쓰는 순간에 알아서 해 주는 프레임버퍼를 sRGB 프레임버퍼라고 합니다. 모니터는 여기 적힌 sRGB 숫자를 받아 자기 곡선대로 빛을 냅니다.
아래 그림은 이 순서를 한데 모은 것입니다. 맨 위와 맨 아래만 sRGB 숫자입니다. 가운데에서 계산하는 동안은 선형 값이 흐릅니다.
flowchart TD
A["sRGB 로 저장된 텍스처"] -->|읽을 때 되돌린다| B["선형 값"]
B --> C["셰이더 · 빛을 더한다"]
C --> D["블렌딩 · 이미 그려진 색과 섞는다"]
D -->|쓸 때 바꾼다| E["sRGB 프레임버퍼"]
E --> F["모니터"]
투명도는 되돌리지 않습니다. 이미지의 네 번째 숫자인 알파는 빛의 양이 아니라 얼마나 가리는지를 적은 비율입니다. 그래서 sRGB 로 저장된 텍스처에서도 빨강·초록·파랑만 변환하고 알파는 손대지 않습니다.
선형 값을 담는 비트 수
선형 값을 8비트에 담으면 어두운 쪽 눈금이 모자랍니다. sRGB 곡선은 256 칸을 어두운 쪽에 몰아 줍니다. 선형으로 적으면 256 칸이 빛의 양에 고르게 나뉩니다. 눈이 예민한 어두운 쪽에는 칸이 적게 돌아갑니다.
칸이 성기면 어두운 그러데이션에 앞에서 본 밴딩이 생깁니다. 이를 피하려고 계산하는 동안의 선형 값은 16비트 부동소수점처럼 더 넓은 형식에 담습니다. 파일로 저장하거나 화면에 내보낼 때만 8비트 sRGB 로 줄입니다.
선형 값이 맞지 않는 계산
선형 색 공간은 빛의 양을 기준으로 삼습니다. 눈에 보이는 밝기를 기준으로 삼지는 않습니다. 선형 값 0.5 는 빛으로는 흰색의 절반입니다. 눈에는 가운데보다 한참 밝은 회색으로 보입니다.
「빛을 반씩 섞은 색」과 「눈에 가운데로 보이는 색」은 서로 다른 질문입니다. 섞기의 답은 빛의 절반인 186 입니다. 눈에 가운데로 보이는 회색은 오히려 128 쪽에 가깝습니다. 앞 소절에서 128 이 너무 어둡다고 한 것은 섞기의 답으로 보았을 때의 말입니다.
검정에서 흰색까지 눈에 같은 간격으로 보이는 회색 열 칸처럼, 사람이 보기에 고르게 이어져야 하는 계산에는 선형 값이 맞지 않습니다. 선형 값을 같은 간격으로 나누면 밝은 칸이 대부분입니다. 어두운 칸은 몇 개 안 됩니다. 이런 계산에는 눈의 감각에 맞춰 눈금을 고르게 만든 지각 균일 색 공간을 씁니다.
색이 아닌 숫자에도 변환을 걸지 않습니다. 겉면의 기울기나 거칠기를 적은 이미지는 이미지 파일에 담겨 있어도 빛의 양이 아닙니다. 이런 데이터를 sRGB 로 여겨 되돌리면 값이 휘어 계산이 틀어집니다.
원색과 곡선
「선형」은 숫자와 빛의 관계만 말합니다. 빨강·초록·파랑이 어떤 빨강·초록·파랑인지는 따로 정해야 합니다. 세 빛의 색이 달라지면 같은 숫자 셋도 다른 색이 됩니다. 이 기준이 되는 세 빛을 원색이라고 부릅니다.
sRGB 와 원색이 같고 곡선만 직선으로 편 색 공간을 흔히 linear sRGB라고 부릅니다. 이 문서의 예도 모두 이 색 공간을 가정했습니다. 원색을 더 넓게 잡은 선형 색 공간도 여럿 있습니다. 그래서 선형 값을 주고받을 때는 어느 원색 위의 선형인지도 함께 맞춰야 합니다.
관련 항목
선형 색 공간이 속하는 상위 분류
선형 색 공간과 짝을 이루는 비선형 부호화
sRGB · 감마 보정 · 전달 함수 · 감마 · IEC 61966-2-1
선형으로 정의된 색 공간
srgb-linear · scRGB · CIE XYZ · ACEScg
선형 값으로 도는 그래픽스 처리 단계
셰이더 · 블렌딩 · 텍스처 필터링 · 밉맵 · 안티에일리어싱 · 조명 · 물리 기반 렌더링
선형 값을 담고 옮기는 메모리와 형식
텍스처 · sRGB 텍스처 · 프레임버퍼 · sRGB 프레임버퍼 · 부동소수점 · 반정밀도 부동소수점 · 알파 채널 · 픽셀
눈금이 성길 때의 계단 자국과 그 처방
밴딩 · 디더링 · 비트 깊이 · 양자화
선형 값을 화면에 맞춰 내보내는 처리
톤 매핑 · HDR · 노출 · 색 관리
선형 색 공간과 헷갈리는 이웃 개념
지각 균일 색 공간 · CIELAB · OKLab · 휘도 · 명도
다른 이름: linear color space · linear colour space · 선형 색공간 · 리니어 색 공간