- 기존 Multi-Channel SDF 방식은 얇은 획, 큰 아틀라스, 축소·확대, 부드러운 이동에서 한계가 남아 GPU 런타임 래스터화로 다시 구현함
- 새 파이프라인은 현재 보이는 글리프의 Bézier 곡선 데이터만 GPU로 보내고, 런타임에 아틀라스에 그린 뒤 화면에서 샘플링함
- 글리프가 화면에 남아 있으면 아틀라스 캐시를 유지하면서 샘플을 누적해 품질을 높이고, 첫 프레임 8 samples-per-pixel에서 시작해 최대 512 samples까지 정제함
- 캐시 키에는 font, glyph_index, 픽셀 크기와 서브픽셀 오프셋이 포함되며, 8비트 고정소수점으로 가까운 위치와 크기를 같은 값으로 접어 재사용성을 높임
- 모니터별 RGB 서브픽셀 구조를 샘플 영역으로 모델링하면 OLED G9 같은 비표준 배열에서도 색 번짐을 줄일 수 있고, 4K 전체 화면 텍스트 데모는 Radeon 9070 기준 약 0.1ms 피크 비용을 보임
SDF 대신 런타임 벡터 래스터화를 택한 이유
- 기존 구현은 Multi-Channel Signed Distance Fields를 사용했고 대체로 잘 동작했지만, 실제 사용에서는 몇 가지 제약이 남아 있었음
- 품질: 얇은 획이나 세부가 많은 폰트에서 특징이 사라지거나 아티팩트가 생겼고, 더 높은 해상도의 SDF가 필요해지는 경우가 있었음
- 아틀라스 크기: SDF는 오프라인 생성 후 아틀라스에 저장되므로, 글리프가 많은 일본어·중국어 폰트는 단일 아틀라스로 굽기 어려움
- 유연성: 축소·확대 문제나 서브픽셀 안티앨리어싱 같은 새 아이디어를 적용하기 까다롭고, 런타임 생성·편집 가능한 벡터 이미지에도 맞지 않았음
- 단순성: 원본 글리프 곡선을 중간 텍스처로 바꾸는 단계가 시스템 복잡도를 늘림
- Miama 같은 라틴 폰트도 글리프마다
64×64영역을 쓰는4096×1152아틀라스가 필요했고, 여러 폰트를 런타임에 쓰면 메모리와 스트리밍 대역폭 비용이 커짐 - 새 접근의 목표는 글리프 제작자가 만든 Bézier 곡선을 더 직접적으로 사용해 원본 데이터에서 최종 픽셀까지의 변환 단계를 줄이는 것임
전체 파이프라인
- 핵심은 오프라인에 모든 글리프를 미리 굽는 대신, 현재 보이는 글리프의 곡선만 GPU로 보내 필요한 순간에 래스터화하는 방식임
- 처리 흐름은 단순함
- 폰트에서 글리프 곡선 데이터를 로드함
- GPU가 런타임에 글리프를 아틀라스에 래스터화함
- 화면 출력 시 해당 아틀라스를 샘플링함
- 같은 글리프가 이후 프레임에서도 계속 쓰이면 아틀라스에 남겨두고 샘플을 누적해 고품질 서브픽셀 안티앨리어싱까지 정제함
- 벡터 표현을 직접 렌더링하므로 해상도 변화에 대응하기 쉽고, 픽셀 전체 커버리지뿐 아니라 각 서브픽셀 요소의 커버리지까지 계산할 수 있음
글리프 곡선 처리
- 폰트 로딩에는 FreeType을 오프라인 도구의 중간 계층으로 사용해 지원 포맷을 읽고, 각 글리프의 곡선을 순회해 자체 애셋 포맷으로 저장함
- 글리프 곡선은 선, 2차 Bézier, 3차 Bézier를 포함할 수 있으며, 셰이더를 단순하게 만들기 위해 모두 2차 Bézier로 변환함
- 선은 두 점의 중간에 제어점을 하나 추가해 2차 Bézier로 만듦
- 3차 Bézier는 두 개의 2차 Bézier로 나누며, 이는 차수를 낮추는 손실 변환임
- 3차 Bézier를 두 개의 2차 Bézier로 나누는 방식은 시도한 폰트 대부분에서 잘 동작했지만, 오차를 더 줄이는 더 정교한 방법도 가능함
- 더 높은 품질의 변환이 필요하면 오프라인 도구로 TrueType
.ttf처럼 2차 Bézier만 가진 형식으로 바꿔 이 변환 자체를 피할 수 있음 - Desmos 그래프에서 입력 3차 Bézier와 결과 2차 Bézier 두 개의 형태를 비교할 수 있음
커버리지 계산과 곡선 접근 최적화
- 커버리지는 픽셀 단위로 수평 광선을 왼쪽에서 오른쪽으로 쏘고, 곡선과의 교차를 검사해 winding number를 누적하는 방식으로 계산함
- 수학적 배경과 구현은 GreenLightning의 GPU Font Rendering과 Sebastian Lague의 Rendering Text 영상을 참고할 수 있음
- 교차 계산의 부정확성은 특정 높이의 샘플에서 발생할 수 있지만, 수백 개 샘플을 누적하면 한두 개 오차는 평균 후 거의 보이지 않음
- 최대 512개 샘플을 누적할 때 한 샘플이 틀리면
0대신1/512=0.00195, 또는1대신511/512=0.99804가 됨 - 커버리지가 극단값에 가까울 때 클램프하는 임계값을 둘 수도 있음
- 최대 512개 샘플을 누적할 때 한 샘플이 틀리면
- 샘플 위치 분포에는 Martin Roberts의 $R_2$ sequence를 사용하고, Shadertoy 예제에서 시간에 따른 분포를 확인할 수 있음
- 곡선 접근 비용을 줄이기 위해 글리프를 여러 수평 밴드로 나누고, 각 밴드에 닿는 곡선만 비트셋으로 저장함
- 수평 광선만 추적하므로 각 텍셀이 검사할 곡선 집합을 크게 줄일 수 있음
- wave 단위로 같은 밴드 범위를 접근하게 하면 곡선 반복과 읽기를 스칼라화할 수 있음
- compute 셰이더에서 아틀라스에 래스터화할 때 스레드를 row-major로 수평 패킹해 wave가 닿는 밴드 범위를 최소화함
아틀라스 패킹과 캐시 키
- 처음에는 화면에 직접 래스터화했지만, 매 프레임 고품질 안티앨리어싱을 계산하는 비용이 컸음
- 대부분의 텍스트는 여러 프레임 동안 같은 크기와 위치로 남아 있고, 같은 글리프가 같은 크기로 반복되므로 아틀라스와 시간 누적이 적합함
- 아틀라스는 필요한 글리프가 없으면 공간을 할당해 래스터화를 시작하고, 이미 있으면 기존 결과를 그대로 사용함
- 프레임 중 아틀라스의 글리프를 검사해 계속 유지할지, 더 샘플링할지, 사용하지 않아 공간을 해제할지 결정함
- 아틀라스 키는 다음 요소를 포함함
fontglyph_indexquantized_size_in_pixels_x,quantized_size_in_pixels_yquantized_subpixel_offset_x,quantized_subpixel_offset_y
- 서브픽셀 오프셋은
frac(pixel_position)에 해당하며, 글리프가 픽셀 그리드에 정확히 맞지 않거나 부드럽게 스크롤될 때 위치에 맞는 안티앨리어싱 결과를 만드는 데 필요함 - 부동소수점 값을 그대로 키로 쓰면 수학적으로 같아야 할 값도 비트 단위로 달라질 수 있어, 8비트 소수부 고정소수점으로 가까운 위치와 크기를 같은 값으로 접음
- 정적 텍스트가 많은 텍스트 에디터에서 고정폭 폰트를 쓴다면 문자 간격과 줄 위치를 픽셀 경계에 맞춰 같은 글리프의 아틀라스 캐시 적중률을 높일 수 있음
Z-Order 기반 아틀라스 배치
- 런타임 글리프 배치는 Z-Order Packing과 자유 셀 비트셋을 사용함
- Morton code 기반 Z-Order는 2D 셀을 긴 1D 배열처럼 다루게 해주며, 2의 거듭제곱 개수만큼 연속 셀을 할당하면 2D 아틀라스에서 정사각 영역이 됨
- 기본 셀은
16×16texel이고, 글리프 크기는 다음 2의 거듭제곱으로 올림됨- 예를 들어
25×29글리프는32×32청크를 할당함 - 이 경우
16×16셀 4개가 필요하므로 정렬된 연속 4비트를 찾아 사용함
- 예를 들어
- 라틴 알파벳의 길고 얇은 글리프는 세로형이 많아, 전치된 Z-Order를 쓰면
l,j,i,1같은 글리프가 절반 공간만 사용할 수 있음 - 반대로 아랍어처럼 길고 얇은 글리프가 가로형인 경우에는 표준 Z-Order가 더 적합함
시간 누적으로 품질을 높이는 방식
- 글리프가 아틀라스에 남아 있으면 매 프레임 샘플을 조금씩 더해 결과를 정제할 수 있음
- 기본 스케줄은 글리프가 처음 나타난 프레임에 8 samples-per-pixel, 다음 프레임에 4, 그 다음 2, 이후 매 프레임 1개씩 추가해 총 512개까지 누적함
- 첫 프레임 품질을 높게 잡는 이유는 부드럽게 이동하거나 크기가 바뀌는 글리프가 매 프레임 새로 초기화되는 것과 같기 때문임
- 품질과 성능은 여러 방식으로 조절 가능함
- 매 프레임 추가할 샘플·광선 수
- 글리프 초기 몇 프레임의 샘플 증가 여부
- 프레임당 총 샘플 수 상한
- 기존 글리프를 매 프레임이 아니라 몇 프레임마다 갱신하는 time-slicing
- 글리프 곡선 수에 따라 교차 검사 비용을 제한하는 방식
- 이 구현에서 성능은 큰 문제가 되지 않았고, 도입부 전체 화면 텍스트는 Radeon 9070에서 4K 기준 약 0.1ms 피크를 보였으며, 글리프가 최대 샘플 수에 도달하면 비용은 빠르게 0에 가까워짐
서브픽셀 안티앨리어싱과 색 번짐
- 서브픽셀 안티앨리어싱은 모니터 픽셀 안의 빨강·초록·파랑 요소를 각각 샘플 영역으로 보고 렌더링함
- 전통적인 RGB LCD의 세로 줄무늬 구조에서는 수평 해상도가 사실상 3배가 되어, 4K 기준
3840×2160을3840×6480처럼 다룰 수 있음 - OLED G9 같은 모니터는 표준 RGB 세로 줄무늬와 다른 비표준 서브픽셀 구조를 가진다는 점이 문제임
- 기본 RGB 세로 직사각형 구조로 가정하면 위쪽에 초록, 아래쪽에 마젠타 색 번짐이 보임
- 모니터의 실제 서브픽셀 구조에 맞춰 샘플 위치를 설정하면 색 번짐이 거의 없고 부드러운 결과가 나옴
- Subpixel Zoo는 다양한 서브픽셀 구조 사례를 보여주며, LG WOLED의 red-white-blue-green 구조는 표준 순서와 다른 예임
- 서브픽셀 요소를 실제 물리 크기보다 크게 보고 서로 겹치게 설정하면 더 정확한 결과가 나왔음
- 서브픽셀의 빛은 자연스럽게 섞이고 약간 확산되므로, 샘플 영역이 물리적 서브픽셀보다 큰 것처럼 동작함
- 샘플 영역은 픽셀 밖으로도 번져야 하며, 주변 픽셀의 서브픽셀 빛과도 섞임
- Evan Wallace의 Easy Scalable Text Rendering은 서브픽셀 안티앨리어싱 후 수평 블러가 필요하다고 보며, 이는 서브픽셀 요소를 더 크고 겹치게 보는 방식과 사실상 같은 효과임
디스플레이 서브픽셀 정보를 소프트웨어가 알 수 있어야 함
- 모니터의 임의 서브픽셀 구조에 접근할 수 있다면, 일반적인 서브픽셀 안티앨리어싱과 텍스트 렌더링 품질을 개선할 수 있음
- 공통 디스플레이 프로토콜을 통해 이런 정보가 제공되면 표준 배열 모니터에서도 하드웨어별로 더 세밀한 렌더링이 가능함
- 디스플레이 제조사는 텍스트 렌더링 문제 때문에 더 나은 서브픽셀 구조 실험을 피하지 않아도 됨
- Samsung은 QD-OLED에서 G8에서 G9로 가며 이런 문제를 줄이기 위해 서브픽셀 구조를 바꿨고, LG WOLED와 Samsung QD-OLED에서는 색 번짐이 흔히 지적됨
- 이 문제는 하드웨어 교체보다 소프트웨어 보정으로 다룰 여지가 있음
실시간 글리프 렌더링의 실용적 가치
- 좋은 UI와 텍스트 품질은 제품의 체감 품질을 끌어올릴 수 있음
- 게임에서는 텍스트 박스, 메뉴, 제목, 알림처럼 사용자의 시선이 모이는 요소가 자주 등장하며, 텍스트 품질 저하는 렌더링이 나쁜 3D 장면만큼 경험에 영향을 줄 수 있음
- Persona 시리즈, Metaphor: ReFantazio, Nier: Automata는 UI와 텍스트 표현의 좋은 사례임
- 실시간 글리프 렌더링 품질을 개선하려는 시도는 UI와 게임 렌더링 모두에서 실질적인 가치가 있음