Kimi K3가 디지털 만년필 스트로크를 만들어낸 방법
Lulucat Notes에 Freeform의 방향성 만년필이 필요했다. 스펙은 손글씨 스크린샷 하나였다. 모델 두 개, 판별 질문 하나, 타원 펜촉 하나를 거치자 수성펜이 제대로 쓰이기 시작했다. 연구, 코드, 검증은 Fireworks 위의 Kimi K3가 맡았다.

이 글이 다루는 Lulucat Notes Metal 파이프라인으로 렌더링했다.
Lulucat Notes에는 평범한 펜과 형광펜이 있다. 로드맵의 다음 도구는 수성펜이었다 — Apple Notes와 Freeform에서 익숙한 방향성 만년필로, 세로 스트로크는 굵게, 가로 스트로크는 얇게 나온다. 이 도구의 스펙은 문서가 아니었다. 스크린샷 하나였다. 손글씨 세 줄, “pencilkit”, “无边记”, 그리고 “这种有方向的水笔能力” — 이 방향성 수성펜 능력이라는 글자들이 담긴 스크린샷이었다.
이전의 Metal 파이프라인처럼, 이 작업은 Fireworks에서 실행되는 Moonshot의 오픈 모델 Kimi K3가 맡았다. 측정부터 모델링, 코드, 검증까지 모두 맡았다. 사람은 스크린샷을 건네고, 질문 하나에 답하고, 실제 iPad에서 느낌을 판정했다.
스펙은 스크린샷이다
정적 이미지는 스트로크가 왜 얇은지 알려주지 못한다. 얼마나 얇은지, 어디서 얇은지만 알 수 있다. 그래서 첫 단계는 측정이었다. 스크린샷을 행과 열로 훑으며 각 스트로크의 중심선 이동을 추적해 방향을 얻고, 그 각도의 사인으로 스캔 폭을 실제 폭으로 바꿨다:
| 스트로크 | 방향 | 실제 폭 |
|---|---|---|
| “l” 어센더, “pencilkit” | ≈ 78° | 27.3 px |
| “k” 기둥, “pencilkit” | ≈ 90° | 28 px |
| 力의 왼쪽 내리그음 | ≈ 66° | 25.7 px |
| 필기체 연결선 | ≈ 8° | 11 px |
| 한자 가로획 (横) | ≈ 0° | 6–11 px |
굵기 대 얇기 비율은 ≈ 2.5였다. 66° 점에
첫 번째 버전: 방향에서 폭으로
첫 모델은 당연한 방식이었다. 모든 입력 점의 방향을 계산하고, 맞춘 곡선으로 방향을 폭에 매핑한 뒤, 캡처 시점에 그 결과를 점의 반지름에 구워 넣었다. 방향은 인과적으로 추정했다. 호의 마지막 몇 점에 대한 지수 감쇠 가중 평균을 이동 방향이 반전되어도 추정값이 상쇄되지 않도록 겹각 공간에서 계산했다. 과거 점만 사용하므로 라이브 스트로크와 커밋된 스트로크가 바이트 단위로 일치한다.
빌더는 단위 검사를 통과했다. 합성 가로, 세로, 45°, 역전 경로 모두 이론적 폭 — 0.99, 2.52, 2.00, 0.99포인트 — 을 구워 넣었다. 렌더링에는 새 코드가 전혀 필요 없었다. 반지름이 구워진 스트로크는 둥근 스탬프의 사슬일 뿐이고, 우리의 point-sprite 파이프라인이 이미 그것을 그렸기 때문이다.
iPad에서 사람은 약 10초 만에 기각했다. “이건 수성펜이 아니라 아트 펜이야.”
한 장의 그림에 맞는 두 가지 설명
왜 어색했을까? 두 가지 후보 설명이 있었고, 스크린샷만으로는 둘을 가릴 수 없었다:
- 방향 고정. 펜촉 기하학이 손이 어떻게 움직이든 가로는 얇게, 세로는 굵게 만든다. 첫 번째 버전이 구현한 것이 이것이다.
- 필압과 속도. 펜이 필압으로 구동되고, 샘플의 패턴은 그저 손글씨의 역학이다. 내리그음은 자연스럽게 눌리고, 연결선은 자연스럽게 빠르고 가볍다.
둘 다 가로가 얇고 세로가 굵은 스크린샷을 만든다. 차이는 가로 스트로크를 세게 누를 때 어떻게 되느냐다. 첫 번째 버전은 얇게 유지한다. 필압 펜은 굵어진다. 그래서 사람에게 질문 하나를 던졌다. 가로를 세게 누르면 더 굵어져야 하는가?
“아니. 가로는 얇게 둔다.”
방향 고정이 확인됐다. 하지만 다른 무언가가 여전히 틀렸다. 첫 번째 버전 역시 방향 고정이었으니까.
답은 스트로크 끝에 있었다
다음 단서는 어센더 꼭대기에 있었다. 샘플의 “l”과 “k” 기둥을 확대하면 두 가지가 보였다. 곧은 스트로크는 처음부터 끝까지 하나의 폭을 유지하고, 스트로크 끝은 납작한 대각선 절단이다 — 들어 올려지는 끌(chisel) 펜촉의 모양이다. 둥근 점이 아니다. 필압 테이퍼도 아니다.

이것이 “아트 펜 느낌”이 실제로 뜻한 것이었다. 첫 번째 버전은 샘플의 겉모습 — 추정 방향의 함수로서의 폭 — 을 모델링했지, 펜을 모델링하지 않았다. 방향 추정기는 센서다. 잡음이 있는 입력에서 떨리고, 모서리에서 뒤처지고, 모든 스트로크 끝을 원으로 만든다. 진짜 펜촉은 아무것도 계산하지 않기에 이런 문제가 없다. 폭은 기하학이다.
두 번째 버전: 타원 펜촉
최종 모델에는 방향 추정기가 전혀 없다. 펜촉은 방향이 정해진 타원이다. 장축은 가로이고 단축은 고정되어 있다. 이 타원의 스탬프를 스트로크 경로를 따라 빽빽이 놓으면, 나머지는 모두 기하학에서 나온다:
-
가로 스트로크는 열린 가장자리를 따라가므로 항상
의 폭으로 나온다 — 어떤 필압에서도 일정한 얇은 선이다. 확인된 스펙 그대로다. -
세로 스트로크는 장축 전체를 가로지른다.
, 굵은 끝이다. -
대각선 스트로크는 이동 방향에 수직인 타원의 현 폭을 가진다.
-
스트로크 끝은 타원 절단이다. 샘플의 납작한 펜촉 모양 끝이 별도 작업 없이 생긴다.
-
필압은 장축만 조정한다.
이므로 필압은 내리그음에서 잉크량을 늘릴 뿐, 가로를 결코 굵게 만들 수 없다.
샘플 측정값에서
렌더링은 새 프래그먼트 셰이더 하나만 필요로 했다. 정점 형식 — 위치, 지름, 색 — 은 이미 모든 것을 담고 있었다. 지름이 장축이고 단축은 패스별 유니폼이다. 셰이더는 둥근 스탬프와 같은 반 픽셀 커버리지 램프로 타원 SDF를 평가한다. 이것이 Core Graphics 참조 구현(스탬프당 fillEllipse)과 픽셀 단위로 비교할 수 있게 한다. 검증은 평소의 관문을 통과했다. 만년필 스트로크 두 개가 든 합성 코퍼스를 양쪽 방식으로 렌더링해 픽셀 단위로 비교했다 — 구조적 차이 0건, 그리고 표본 점검한 가로 스트로크는 두 렌더러 모두에서 12 px로 측정됐다.

왼쪽이 첫 번째 버전, 오른쪽이 두 번째 버전이다. 같은 손글씨 입력, 같은 파이프라인. 끝이 말해준다.
iPad에서 새 펜은 즉시 통과했다. “好,很好” — 좋아. 아주 좋아.
빗나간 모델을 남겨두기
첫 번째 버전은 쓰레기통에 들어가지 않았다. 흥미로운 브러시이기는 하다. 그저 수성펜이 아닐 뿐이다. 그래서 사람이 붙인 이름으로 새 실험용 브러시 메뉴의 첫 항목으로 출시됐다. 이름은 漏水的圆珠笔, 새는 볼펜이다. 도구 모음에는 플라스크 버튼이 생겨 실험 목록을 텍스트로 띄운다. 다음 실험을 추가하는 데는 레지스트리 한 줄이면 된다. 거부된 모델도 명확하게 이름 붙인 실험으로 출시할 수 있다면 헛된 일이 아니다.
한 저녁에 돌린 루프
전체 과정 — 측정, 모델링, 빌드, 기기에서 확인, 질문, 재모델링, 재빌드, 검증 — 은 하루 저녁에 끝났다. Fireworks 위의 Kimi K3는 루프의 기술적 측면 전체를 실행했다. 측정 스캔을 고안하고, 두 번째로 추측하는 대신 판별 질문을 제안하고, 증거가 바뀌자 자신의 방향 추정기를 삭제하고, 앱을 손대기 전에 픽셀 차이 하니스를 확장했다. Fireworks의 추론 속도는 루프를 인터랙티브하게 유지했다. 긴 Metal 및 Swift diff, 픽셀 분석 스크립트, 코퍼스 도구가 모두 충분히 빨리 도착해 남은 병목은 사람의 판단이었다.
협업 패턴은 Metal 파이프라인 글에서 보였던 것과 같았고, 이번에도 통했다. 모델은 빠르고 정확하다. 사람은 취향과 엔드투엔드 검증을 맡는다. 느낌에 기반한 피드백 한 문장 — “수성펜이 아니라 아트 펜” — 만으로도 모델은 정확한 모델링 오류를 찾아 더 단순한 설계로 바꿀 수 있었다.
올바른 모델이 틀린 모델보다 더 작았다. 두 번째 버전은 첫 번째보다 더 적은 움직이는 부품으로 출시됐다 — 추정기도, 평활화 창도, 맞춘 지수도 없다. 펜촉은 폭을 계산하지 않는다. 펜촉이 곧 폭이다.