- LUT(룩업 테이블) 는 미리 계산한 값을 표처럼 참조하는 방식으로, 흑백 열화상 영상의 컬러화부터 게임 오브젝트 변형과 영상 색보정까지 폭넓게 쓰임
- WebGL 예제는 비디오 프레임을 GPU에 2D 텍스처로 올린 뒤, 픽셀별 색을 결정하는 Fragment Shader에서 tinting, 1D LUT, 3D LUT를 적용하는 흐름을 보여줌
- 1D LUT 는 밝기값
[0.0 - 1.0]을 색상 배열 좌표로 사용해 흑백 입력을 RGB로 바꾸며, GPU의 쌍선형 필터링으로 작은 LUT도 중간값을 자연스럽게 보간함 - Valve의 Left 4 Dead 사례처럼 tinting과 LUT는 제한된 텍스처 메모리 안에서 자동차, 좀비 피부, 의상 색상 변형을 만드는 데 활용됨
- LUT는 추가 텍스처 읽기 비용을 만들지만, 감마 보정 같은 비싼 계산을 미리 저장하거나 외부 색보정 워크플로를 실시간 그래픽에 가져오는 실용적인 도구임
LUT가 그래픽 프로그래밍에서 하는 일
- 룩업 테이블(LUT)은 값을 미리 계산해 행이나 표에 저장한 뒤 필요할 때 참조하는 방식임
- 그래픽 프로그래밍에서는 이 단순한 구조가 여러 시각 효과로 이어짐
- 흑백 영상을 컬러로 바꿈
- 색상 보정과 스타일링을 적용함
- 비디오 게임에서 오브젝트 변형을 효율적으로 만듦
- 예제는 WebGL을 사용하지만, 같은 기법은 DirectX, OpenGL, Vulkan, Unity, 과학 데이터 시각화에도 적용 가능함
- 데모 영상은 Panasonic GH6와 TESTO 890 열화상 카메라 캡처이며, 처리 과정은 기기 밖으로 나가지 않고 GPU에서 로컬 처리됨
WebGL 파이프라인과 tinting
- 열화상 카메라 출력은 흑백 비디오이며, 각 프레임은 WebGL을 통해 GPU에 2D 텍스처로 전달됨
- Fragment Shader는 출력의 각 fragment에 대해 최종 색상을 결정하는 코드임
- 예제에서는
texture2D(video, tex).rgb로 현재 위치의 비디오 색을 읽음 - WebGL 1.0의 최종 출력은
gl_FragColor에 RGBA로 기록됨
- 예제에서는
- tinting은 텍스처 색상에 특정 색을 곱해 색조를 입히는 방식임
- 예제에서는
videoColor * vec3(1.0, 0.5, 0.0)으로 오렌지색을 곱함 - 흰색은 오렌지색이 되고, 검은색은 0을 곱하므로 검은색으로 남음
- 예제에서는
- 이 문맥에서 tinting의 곱셈 비용은 측정하기 어려울 수 있음
- 텍스처를 메모리에서 가져오는 비용이 곱셈보다 훨씬 큼
- 여러 스레드가 병렬 실행되면서 한 픽셀의 텍스처 읽기를 기다리는 동안 다른 픽셀의 곱셈이 수행될 수 있음
- 이는 tinting이 만드는 차이에 대한 설명이며, 전체 성능 최적화가 끝났다는 뜻은 아님
Valve의 tinting 활용
- Valve Software의 Left 4 Dead는 자동차 변형을 만들기 위해 tinting을 활용함
- 자동차 종류와 색상마다 별도 텍스처를 두는 방식은 텍스처 메모리 예산을 초과할 수 있음
- Valve는 추가 mask 텍스처를 사용해 자동차의 도장 영역을 인스턴스별로 다른 색으로 tinting함
- 한 색상 텍스처가 3개 자동차 모델에서 공유됨
- 두 개의 텍스처 비용으로 4개의 자동차 모델과 사실상 무제한 색상 변형을 만들 수 있음
- Source Engine에서는 자동차뿐 아니라 본질적으로 모든 것을 tinting할 수 있음
1D LUT로 흑백 값을 색상으로 바꾸기
- 1D LUT 는 숫자 배열이며, RGB 이미지라면 색상들의 1차원 배열임
- 흑백 영상의 밝기값을 LUT의 X축 좌표로 사용해 색을 가져옴
- 비디오 밝기값은
[0.0 - 1.0]범위를 가짐 0.0검은색은 LUT 왼쪽 색으로,1.0흰색은 LUT 오른쪽 색으로 매핑됨- 중간값은 대응하는 위치의 색으로 매핑됨
- 비디오 밝기값은
- WebGL 1.0에는 1D 텍스처가 없으므로 높이 1px인 2D 텍스처를 사용함
- 예제 코드는
texture2D(lut, vec2(videoColor, 0.5))형태임 - 입력은 1D 값이고 출력은 RGB이므로 “1D vector in, 3D vector out” 구조임
- 예제 코드는
- GPU의 쌍선형 필터링은 텍스처 읽기 때 중간값을 자동 보간함
- 8비트 흑백 영상은 256단계 밝기를 가짐
- 1D LUT가 32픽셀 폭이어도 픽셀 사이 접근은 선형 보간됨
- 8픽셀 정보만으로도 256픽셀 폭의 다채로운 그라디언트에 가까운 결과를 만들 수 있음
컬러맵 선택과 지각적 균일성
- 예제에는 matplotlib이 지원하는 컬러맵을 1D LUT로 내보낸 목록이 포함됨
- 데이터에 특정 구조가 없다면 지각적으로 균일한 컬러맵을 사용하거나 그에 기반해 색을 설정하는 것이 적합함
- viridis 계열이 대표적인 예임
- 지각적으로 균일한 컬러맵은 흑백으로 출력해도 차가운 부분은 어둡고 뜨거운 부분은 밝게 유지됨
- jet처럼 주로 색상(hue)만 바꾸고 지각 밝기를 무시하는 컬러맵은 이 속성을 보장하지 않음
- 색각 이상이 있는 사람도 데이터를 올바르게 해석할 수 있다는 점이 컬러맵 선택의 중요한 기준임
1D LUT의 성능과 다항식 근사
- 작은 1D LUT의 비용은 대체로 낮을 수 있지만, tinting보다는 고려할 점이 많음
- 주요 성능 우려는 의존적 텍스처 읽기임
- 첫 번째 텍스처 읽기의 결과로 두 번째 텍스처 읽기 좌표를 결정함
- 그래픽 프로그래밍에서는 최적화 가능한 경로 일부를 없앨 수 있는 패턴임
- GPU에는 텍스처 캐시가 있고, 작은 LUT는 캐시에 들어갈 수 있어 LUT 읽기 비용이 매우 낮을 수 있음
- 세밀한 성능 판단에는 플랫폼별 분석 도구가 필요함
- Nvidia NSight는 셰이더 단계별 성능 분석을 제공하지만 OpenGL은 지원하지 않음
- 컬러맵을 다항식 근사로 구현하면 LUT 텍스처를 다루지 않아도 됨
- Matt Zucker의 ShaderToy viridis 근사는 Horner’s method 형태로 작성됨
c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))))형태의 multiply-add 연산으로 색을 계산함- 실제로 LUT보다 빠른지는 플랫폼별 분석 없이는 판단하기 어려움
Left 4 Dead 2의 좀비 변형
- Left 4 Dead 2는 단순 tinting만으로 충분하지 않은 피부와 의상 색상 변형에 LUT를 활용함
- 단순 tinting은 색상의 휘도 다양성을 충분히 제공하지 못할 수 있음
- 피부와 의상에는 몇 개의 LUT를 무작위로 선택해 다양한 색상 변형을 만듦
- 아티스트가 적절한 color ramp를 선택해 최종 게임에 포함함
- 하나의 텍스처에서 밝은 정장과 어두운 정장 변형을 모두 만들 수 있음
- 관련 전체 발표는 GDC의 Shading a Bigger Better Sequel에서 확인할 수 있음
- “Exclusive Masking”은 한 채널 안에 두 텍스처 범위를 넣는 방식임
- Texture 1은 0-128 범위
- Texture 2는 128-256 범위
- 비용은 색 정밀도 감소임
계산을 미리 저장하는 1D LUT
- 1D LUT는 그래픽 프로그래밍에서 비싼 계산 캐시로도 쓰일 수 있음
- Gamma correction은 대표적인 사례임
- 근사가 아니라면
pow()함수가 필요함 - 특히 오래된 GPU에서는
pow()가 비싼 명령일 수 있음 - 표준 sRGB 조각별 곡선을 쓰면 분기 경로도 추가됨
- 근사가 아니라면
- 감마 변환을 1D LUT로 미리 계산하면 픽셀별 계산을 건너뛸 수 있음
- 이 경우 “1D vector in, 1D vector out” 구조임
- 1D LUT는 색상 채널 4개까지 출력할 수 있음
- Redshift는 야간에 모니터를 오렌지색으로 tinting해 눈 피로를 줄이는 소프트웨어 사례임
- 모니터의 Red, Green, Blue 채널별 Gamma Ramp를 바꿈
- Kelvin Warmth → RGB와 추가 Gamma 계산을 3개의 1D LUT로 미리 계산함
- 관련 구현은 Redshift 소스 코드에 있음
- 이 방식은 재매핑이 그래픽 카드가 아니라 모니터에서 이뤄져 성능 영향이 없지만, 하드웨어 인터페이스 지원은 현재 흔들리거나 깨진 경우가 많음
- Raspberry Pi 그래픽 스택은 새 업데이트에서 관련 지원을 잃은 사례가 있음
- Microsoft는 SetDeviceGammaRamp 사용을 경고함
3D LUT: RGB 공간 전체를 다시 매핑하기
- 3D LUT 는 전체 RGB 공간을 하나의 큐브로 표현하고, 모든 가능한 색을 다시 매핑함
- 입력 RGB 3차원 벡터를 출력 RGB 3차원 벡터로 바꾸므로 “3D vector in, 3D vector out” 구조임
- Red, Green, Blue 각각에 1D LUT를 적용하면 색상 균형은 바꿀 수 있음
- 3D LUT는 입력 RGB 조합이 필요한 변환에 필요함
- 채도 변경
- 색상(hue) 변경
- 특정 색상 조정
- 색상 분리
- 3D LUT는 보통 큐브 형태이며, 게임에서는 strip이나 square 형태로 저장되고 영상 편집 도구에서는 “Iridas/Adobe”
.cube파일로 사용됨
WebGL 1.0에서 3D LUT 구현하기
- 예제의 영상은 Panasonic GH6로 촬영한 Panasonic V-Log 색상 프로파일 영상임
- V-Log는 더 많은 다이내믹 레인지를 보존하고 Gamut과 Gamma 정의가 명확한 로그 프로파일임
- WebGL 1.0은 3D 텍스처를 지원하지 않으므로, 3D 텍스처 읽기를 2D 텍스처 읽기 두 번과 보간으로 구현함
- 이 접근은 Gregg Tavares의 Google I/O 2011 발표와 webglfundamentals.org 설명에 기반함
- 기존 예제 코드에는 큐브의 Z축 계산 문제로 색이 파랗게 이동하는 실수가 있었고, 2019년에 수정됨
- WebGL 1.0, OpenGLES 2, OpenGL 2.1에서
OES_texture_3D없이 호환성을 유지하려면 최신 버전의 3D 텍스처 샘플링 코드를 써야 함
3D LUT로 색보정 워크플로 가져오기
- 3D LUT에 적용한 보정은 해당 LUT를 통과하는 영상이나 그래픽 장면에 그대로 적용됨
- 예제에서는 V-Log 영상과 LUT를 DaVinci Resolve에 가져와 처리함
- Panasonic의 V-Log to V-709 3D-LUT를 적용함
- 대비와 white point를 보정해 흰색이 full-bright가 되도록 함
- 보정된 LUT를 다시 내보냄
- 실제 비디오는 원본 그대로이며, 색보정은 WebGL에서 LUT를 통해 실시간으로 적용됨
- 업로드하는 LUT는 같은 크기를 유지해야 함
- 예제 제약은 1024px × 32px
- 32³px 큐브 형태를 유지해야 함
게임 업계의 3D LUT 사용법
- 3D LUT로 게임 색을 외부 도구에서 스타일링하는 방식은 비디오 게임 업계에서 잘 알려진 표준 워크플로임
- 일반적인 절차는 다음과 같음
- 색보정할 장면의 스크린샷을 찍음
- 스크린샷과 초기화된 3D LUT를 Photoshop 같은 도구에서 함께 엶
- 스크린샷과 LUT에 같은 색보정을 적용함
- 3D LUT 부분을 잘라내 내보냄
- Left 4 Dead도 같은 방식을 사용함
- Photoshop의 색보정 도구는 자유롭게 사용할 수 있지만, 여러 픽셀 간 관계를 바꾸는 필터는 사용할 수 없음
- blur, sharpen, emboss 같은 convolution 계열은 remapped color를 흐리게 만들어 예기치 않은 결과를 낼 수 있음
복잡한 색보정을 하나의 LUT로 굽기
- DaVinci Resolve 같은 색보정 도구는 복잡한 색 변환과 color grade를 만들고 3D LUT로 내보낼 수 있음
- 예제는 필름 프린트 룩을 목표로 하는 Kodak 2383 LUT를 적용함
- V-Log gamma 영상을 Cineon Film Log로 변환함
- 색 공간을 Rec.709로 변환함
- 필름 에뮬레이션을 적용하고 gamma를 다시 Rec.709로 변환함
- 마지막으로 white point를 조정함
- 복잡한 세부 변환을 직접 구현하지 않고 하나의 LUT로 구워 그래픽 애플리케이션에 가져올 수 있음
- 예제 영상은 글 호환성을 위해 8비트 고압축 형식이지만, 일반적으로는 10비트 footage에서 수행되는 작업임
- LUT 크기는 생각보다 작게 쓰임
- Panasonic 공식 V-Log to V-709 3D-LUT는 33³px 크기임
- Panasonic의 카메라 내 모니터링 LUT는 17³px를 사용함
- Panasonic의 5자리 달러 가격대 시네마 카메라에서도 17³px가 쓰임
그래픽 밖의 LUT 사례
- LUT는 그래픽 프로그래밍 외에도 쓰임
- Tomb Raider를 Game Boy Advance에 포팅한 사례에서도 LUT가 활용됨
- 오픈소스 OpenLara 엔진 포트는 Game Boy Advance에 3D 기능이 없기 때문에 많은 부분을 소프트웨어로 처리함
- 그중 하나가 정수 나눗셈을 구현하는 LUT임
- 이 LUT는 load instruction을 건너뛰기 위해 ROM 주소 공간의 시작 부분에 배치됨