이 글은 Today I Learn 시리즈의 40번째 기록입니다. (총 125개)

소리꽃을 시작한 첫날. 화면을 영상으로 뽑고, 파티클을 많이 그리고, 여러 모션을 만드는 뼈대를 잡았다.

시작할 때 정하는 것

우회를 정하기 전에 표준으로 되는지 본다

화면에서 도는 파티클을 영상 파일로 뽑아야 하는데, 처음엔 OBS 같은 화면 녹화 프로그램을 따로 쓰게 할 생각이었다. 사용자에게 프로그램을 하나 더 깔라고 하는 것이라 마음에 안 들었지만 방법이 없다고 봤다.

찾아보니 브라우저에 이미 표준 기능이 있었다. 캔버스(그림을 그리는 화면 영역)를 영상 스트림으로 뽑는 captureStream과, 그 스트림을 파일로 녹화하는 MediaRecorder다. 서버도 외부 라이브러리도 없이 됐다.

시작 단계에서 “이건 안 되니까 우회하자”를 빨리 정하면 나중에 되는 방법을 알고도 못 바꾼다. 그 우회 위에 다른 것들이 쌓여 있기 때문이다. 제약을 전제로 설계를 굳히기 전에 한 번은 확인하고 넘어가는 게 싸다.

한계를 알고 미루는 것과 모르고 두는 것은 다르다

이 방식에는 한계가 있었다. 재생을 실시간으로 지켜보며 녹화하는 것이라, 도중에 컴퓨터가 버벅이면 그게 영상에 그대로 남는다.

초기 버전에는 충분하다고 보고 넘어갔다. 대신 “왜 이걸 골랐고 무엇이 아쉬운지”를 남겨 뒀다. 나중에 결과물 품질이 중요해졌을 때 다시 이 지점으로 돌아와 방식을 바꾸기로 했다.

빠르게 만들 때 아쉬운 선택을 하는 건 문제가 아니다. 문제는 그게 아쉬운 선택인 줄 모르고 넘어가는 것이다. 알고 미루면 나중에 되돌아올 지점이 되고, 모르고 두면 나중에 원인 불명의 증상이 된다.


많이 그릴 때

같은 그림을 매번 새로 만들지 않는다

파티클 개수를 올리니 화면이 느려졌다. 파티클을 ●■♥★ 같은 글자 기호로 그리고 있었는데, 글자는 하나 그릴 때마다 글꼴에서 외곽선을 꺼내 그 크기의 픽셀로 계산하는 일(래스터화)이 일어난다. 하나면 티가 안 나는데 수천 개를 매 프레임 반복하면 그게 쌓인다.

모양을 미리 한 번 그려 둔 그림 조각, 곧 스프라이트로 만들어 두고 그걸 복사해 붙이게 바꿨다. 만드는 일이 한 번으로 줄고 그다음은 옮겨 붙이기만 한다.

“매번 만들 것인가 한 번 만들고 재사용할 것인가”는 개수가 정한다. 몇 개면 매번 만드는 게 단순하고, 수천 개면 재사용이 압도적이다. 다만 재사용에는 “언제 다시 만들 것인가”라는 문제가 딸려 온다는 걸 같이 봐야 한다.

연속된 값은 단계로 나누면 미리 만들 수 있다

스프라이트를 미리 구우려면 종류가 정해져 있어야 하는데, 파티클 색은 그라데이션 위에서 제각각이라 종류가 무한했다.

색을 24단계로 끊었다. 연속된 값을 정해진 개수의 단계로 나누는 걸 양자화라고 한다. 24개를 미리 굽고 각 파티클은 자기 색에 제일 가까운 것을 고른다. 눈으로 보면 부드럽게 이어져 보인다.

무한한 것을 미리 만들 수 없을 때, 사람이 구분하지 못하는 선까지 단계를 나누면 유한해진다. 몇 단계가 충분한지는 계산이 아니라 눈으로 정한다.


갈래를 나누기 전에

공통으로 환원해 본다

파티클 모션이 여섯 가지였다. 피어오름, 방사형, 흐트러짐, 나선, 분수, 불꽃. 처음에는 종류마다 움직이는 코드를 따로 짤 뻔했다.

그런데 뜯어 보니 전부 같은 값들로 표현됐다. 속도, 중력, 항력(속도에 비례해 감속시키는 저항), 좌우 흔들림, 수명. 종류가 다른 게 아니라 이 값들이 다른 것이었다. 그래서 움직임을 계산하는 부분은 하나만 두고 — 매 프레임 위치를 조금씩 갱신하는 이런 계산을 적분기라고 부른다 — 종류마다 넣을 값만 다르게 주게 했다.

새 모션을 추가하는 게 값 한 벌을 더하는 일이 됐다. 나선을 위해 흔들림이 시간에 따라 커지는 값을 하나 추가했는데, 다른 모션은 그 값이 0이라 아무 영향이 없었다.

갈래가 여러 개 보일 때 각각 만들기 전에 “이것들이 같은 것의 다른 값은 아닌가”를 한 번 물어야 한다. 맞으면 앞으로 늘어날 갈래가 전부 공짜에 가까워진다. 아니면 그때 갈라도 늦지 않다.


나중을 위한 단위

저장은 해상도와 무관한 단위로, 픽셀은 마지막에

지금은 화면 비율이 16:9 하나지만 나중에 세로 9:16이나 정사각형으로도 뽑고 싶었다. 위치를 화면 픽셀로 저장하면 비율이 바뀌는 순간 전부 어긋난다.

그래서 모든 위치와 크기를 0~1 사이 비율로 저장하고, 실제 픽셀은 그릴 때만 화면 크기를 곱해 낸다. 이렇게 해상도에 안 매인 좌표를 정규화 좌표라고 한다. 데이터는 그대로 두고 화면 크기만 바꿔 다시 곱하면 어떤 비율에서도 같은 그림이 나온다.

같은 판단을 전에 여둘까에서도 했다. 가구 배치를 밀리미터로 저장하고 픽셀은 그릴 때 환산한 것이다. 결국 규칙은 하나다 — 저장하는 단위는 화면에 안 매이게 두고, 화면 단위로 바꾸는 일은 마지막 한 곳에서만 한다.

이 판단은 처음에 하면 거의 공짜인데 나중에 하면 데이터를 전부 옮겨야 한다. 시작할 때 “이 값이 화면에 매여 있나”를 한 번 보는 게 좋다.

밖에 안 물어보고 만들 수 있으면 원칙이 지켜진다

반주 파일을 안 넣어도 타이밍 확인용 소리가 나면 좋겠는데, 진짜 악기 소리를 쓰려면 음원 파일을 어디선가 받아 와야 한다. 이 앱은 밖에 아무것도 안 물어보고 혼자 도는 걸 원칙으로 삼았다.

브라우저 표준인 Web Audio의 오실레이터(파형을 직접 만들어 소리를 내는 발진기)로 간단한 음을 만들어 썼다. 진짜 악기 소리는 아니지만 타이밍을 확인하는 데는 충분했다.

원칙을 세워 두면 이런 자리에서 판단이 빨라진다. “밖에 안 물어본다”를 정해 두니 음원을 받아 오는 선택지가 애초에 후보에서 빠지고, 그 안에서 되는 방법을 찾게 된다. 제약이 오히려 결정을 쉽게 만든다.


요약

  • 제약을 전제로 설계를 굳히기 전에 표준으로 되는지 한 번 확인한다
  • 아쉬운 선택을 하는 건 괜찮다. 아쉬운 줄 모르고 넘어가는 게 문제다
  • 몇 개냐가 “매번 만들기”와 “한 번 만들고 재사용”을 가른다
  • 무한한 값은 사람이 구분 못 하는 선까지 나누면(양자화) 유한해진다
  • 갈래를 각각 만들기 전에 “같은 것의 다른 값은 아닌가”를 묻는다
  • 저장 단위는 화면에 안 매이게(정규화 좌표), 화면 단위 변환은 마지막 한 곳에서
  • 원칙을 세워 두면 후보가 줄어 판단이 빨라진다

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 insertOnConflictUpdate vs DoUpdate(target) 27 세션 필터 28 아코디언(Accordiaon) UI를 펼친상태로 만들기 29 input의 step 30 Word Cloud 31 Google Sheets를 데이터 버스로(with AppSheet) 32 Django 모델 텍스트 필드 자동 수집 패턴 33 localStorage로 섹션 토글 상태 유지 34 순차 ID 생성(`select_for_update()` + `max()` 조합) 35 역참조 검색과 distinct() 36 xlsx 다운로드와 로딩 오버레이 충돌 37 Android 파일 공유 MIME 타입 38 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 39 UTF-8 BOM과 PowerShell 파일 쓰기 40 소리꽃 KeyBloom TIL 1 읽는 중 41 소리꽃 KeyBloom TIL 2 42 소리꽃 KeyBloom TIL 3 43 메트로놈 Simple Metronome TIL 1 44 소리꽃 KeyBloom TIL 4 45 메트로놈 Simple Metronome TIL 2 46 소리꽃 KeyBloom TIL 5 47 메트로놈 Simple Metronome TIL 3 48 정적 블로그 SEO 정비와 Pagefind 검색 도입 49 메트로놈 Simple Metronome TIL 4 50 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 51 메트로놈 Simple Metronome TIL 5 52 모래게임 Sandrop TIL 1 53 메트로놈 Simple Metronome TIL 6 54 모래게임 Sandrop TIL 2 55 모래게임 Sandrop TIL 3 56 메트로놈 Simple Metronome TIL 7 57 모래게임 Sandrop TIL 4 58 모래게임 Sandrop TIL 5 59 모래게임 Sandrop TIL 6 60 소리꽃 KeyBloom TIL 6 61 모래게임 Sandrop TIL 7 62 모래게임 Sandrop TIL 8 63 소리꽃 KeyBloom TIL 7 64 모래게임 Sandrop TIL 9 65 소리꽃 KeyBloom TIL 8 66 모래게임 Sandrop TIL 10 67 소리꽃 KeyBloom TIL 9 68 소리꽃 KeyBloom TIL 10 69 소리꽃 KeyBloom TIL 11 70 소리꽃 KeyBloom TIL 12 71 소리꽃 KeyBloom TIL 13 72 온실 GreenHouse TIL 1 73 소리꽃 KeyBloom TIL 14 74 온실 GreenHouse TIL 2 75 소리꽃 KeyBloom TIL 15 76 소리꽃 KeyBloom TIL 16 77 소리꽃 KeyBloom TIL 17 78 소리꽃 KeyBloom TIL 18 79 소리꽃 KeyBloom TIL 19 80 소리꽃 KeyBloom TIL 20 81 로그스톤 상표 셀프 출원 TIL 1 82 소리꽃 KeyBloom TIL 21 83 소리꽃 KeyBloom TIL 22 84 소리꽃 KeyBloom TIL 23 85 소리꽃 KeyBloom TIL 24 86 MiniMacro TIL 1 87 Astro가 무엇인지, 왜 옮기는지 88 MiniMacro TIL 2 89 메트로놈 Simple Metronome TIL 8 90 메트로놈 Simple Metronome TIL 9 91 블로그 Astro 이관 TIL 92 오픈데이 Openday TIL 1 93 오픈데이 Openday TIL 2 94 Unreal Engine MCP TIL 1 95 콘티온 Conti On TIL 11 96 오픈데이 Openday TIL 3 97 오픈데이 Openday TIL 4 98 오픈데이 Openday TIL 5 99 ScorePlayer TIL 1 100 BuildingHub TIL 1 101 ScorePlayer TIL 2 102 BuildingHub TIL 2 103 ScorePlayer TIL 3 104 Unreal Engine MCP TIL 2 105 궁 미로 PalaceMaze TIL 1 106 메트로놈 Simple Metronome TIL 10 107 메트로놈 Simple Metronome TIL 11 108 오픈데이 Openday TIL 6 109 BuildingHub TIL 3 110 Obsidian vault에서 코드만 빼기 — directory junction 111 ScorePlayer TIL 4 112 오픈데이 Openday TIL 7 113 URL은 바뀔 수 있는 것에 묶지 않는다 114 도구는 기능이 아니라 내 일에 맞는지로 고른다 115 돌이키기 힘든 변경은 작게 먼저 확인한다 116 궁 미로 PalaceMaze TIL 3 117 궁 미로 PalaceMaze TIL 2 118 소리꽃 KeyBloom TIL 29 119 오픈데이 Openday TIL 8 120 BuildingHub TIL 4 121 ScorePlayer TIL 5 122 BuildingHub TIL 5 123 MiniMacro TIL 3 124 소리꽃 KeyBloom TIL 30 125 궁 미로 PalaceMaze TIL 4