수묵 산수 패럴랙스
젖은 종이에 번진 먹산 여섯 겹이 안개 속을 흐르다, 안개가 걷히며 화면 절반 높이의 주봉이 먹이 마르듯 또렷이 선다.
설정값 미리보기
바꾼 문구·색으로 다시 재생합니다
프롬프트 설정
Claude 가 읽는 제작 지시문입니다. 직접 읽지 않아도 됩니다.
비 갠 아침, 물기 머금은 닥 생지 위에 먹으로 그린 산수화가 끝없이 옆으로 흐르는 애니메이션을 HTML 파일 하나로 만들어 줘. 미리보기에서 바로 돌아가게. 길게 설명할 테니 숫자는 그대로 지켜 줘. 목표는 「매끈한 그라디언트 산에 고른 굵기의 윤곽선을 두른 벡터 산」도, 「능선마다 빗살이 달린 둔덕」도 아니다. 젖은 종이에 먹이 스며 가장자리가 잎사귀처럼 번지고, 먹이 고인 가장자리는 짙고, 몸통은 농담이 얼룩지며, 마른 붓 자국이 종이 결에 걸려 끊기는 — 진짜 수묵화가 카메라 앞에서 깊이를 갖고 흐르는 장면이다.
핵심 아이디어: 먹의 농담이 다른 산 여섯 겹. 가까운 산일수록 짙고 빠르게, 먼 산일수록 옅고 느리게 흐른다(패럴랙스). 겹 사이는 안개 — 먹이 닿지 않은 종이 그대로의 여백이다. 먹이 스민 질감은 붓질을 수천 번 겹쳐서가 아니라, 타일을 구울 때 **픽셀마다 한 번 계산하는 「젖은 먹 굽기」**가 만든다: 번진 가장자리 · 가장자리 먹 고임 · 넓은 농담 얼룩 · 안개로 풀리는 아래끝 · 종이 결에 걸린 마른 붓. 이 질감은 폰 화면(가로 390px, 원본의 1/5)에서도 보이는 크기여야 한다 — 번짐은 진폭 표준편차 3~4px·파장 20~50px 의 잎 모양이고, 비탈은 한 줄 직선이 아니라 둥근 바위 덩이가 층층이 꺾인다. 첫 프레임부터 가까운 산 두 겹이 서 있다. 5초에 안개가 위에서부터 걷히며 화면 높이의 절반(0.48H)을 차지하는 가장 먼 주봉이 드러난다. 처음엔 젖은 먹처럼 흐리게 번져 비치다가, 먹이 마르듯 가장자리가 서고 얼룩이 짙어지며 아침 빛을 받는 왼쪽 허리에 엷은 대자(붉은 흙빛) 담채가 제 자리를 갖고 스민다. 그때까지 주봉은 꼭대기 끝만 아주 엷게 비쳐, 열림이 「발견」이 된다. 그동안 카메라는 숨을 멈추듯 느려지면서 크레인처럼 떠올라, 가까운 산일수록 더 많이 아래로 가라앉아 주봉의 허리까지 열린다. 이 열림은 30초마다 돌아온다.
[바꿀 곳]
- 색: 종이 #D9DBCF(비 갠 아침 물기 머금은 닥 생지의 회녹 기운) · 먹 #161A20(푸른 기가 도는 송연묵) · 담채 대자 #A8693F
- 카메라 속도: 가장 가까운 산 기준 초당 60px(1920 폭 기준)
- 주봉이 열리는 주기: 30초 (첫 열림 5초)
- 시드: 20261004
- 화면비: 16:9 (캔버스 1920×1080)
[크기] — y 는 화면 높이 H 의 비율(위가 0), 폭·간격은 화면 폭 W 의 비율
- 겹 L0(가장 먼 산, 주봉): 움직이지 않는다(무한히 멀어서). 봉우리 일곱 개, [중심 x, 꼭대기 y, 왼쪽 반폭, 오른쪽 반폭]:
주봉 [0.60W, 0.07H, 0.25W, 0.20W] · 주봉 오른 어깨 [0.66W, 0.16H, 0.040W, 0.050W] · 주봉 왼 어깨 [0.53W, 0.19H, 0.040W, 0.035W] · [0.43W, 0.29H, 0.10W, 0.09W] · [0.78W, 0.25H, 0.08W, 0.11W] · [0.32W, 0.38H, 0.11W, 0.10W] · [0.90W, 0.35H, 0.09W, 0.11W].
밑선 0.55H(봉우리 높이 = 0.55H − 꼭대기 y), 모든 봉우리 α = 1.3(왼쪽·오른쪽 같음) · β = 1.5. 주봉의 높이는 0.48H, 산덩이 전체 폭은 0.21W~1.01W(오른쪽 끝은 화면 밖으로 살짝). 어깨 둘은 주봉 비탈 위로 0.01~0.09H 솟는다. 일곱 봉우리 모두 흐르는 겹과 같은 「바위 덩이」(아래 [산 모양])를 rngFor(0, 봉우리 순번) 줄기 맨 앞에서 뽑아 얹는다 — 주봉이 매끈한 원뿔이 아니게.
L0 의 굽기 값: 몸통 길이 F 0.34H · 최대 높이 hmax 0.48H · 상자 평균 간격 sp 0.20W · 먹 진하기 ink 1.3.
- 흐르는 겹 L1~L5 (밑선 base · 최대 봉우리 높이 hmax · 봉우리 간격 sp · 흐름 배율 f · 먹 진하기 ink · 몸통 길이 F · 붓 폭 bw · 붓털 수 · 타일 해상도 배율 res · 가림막 불투명도 veil):
L1: 0.465H · 0.12H · 0.10W · 0.10 · 0.20 · 0.10H · 0.006H · 2 · 0.5 · 0.72
L2: 0.57H · 0.15H · 0.10W · 0.20 · 0.36 · 0.12H · 0.008H · 3 · 0.5 · 0.78
L3: 0.68H · 0.22H · 0.12W · 0.36 · 0.62 · 0.15H · 0.010H · 4 · 0.75 · 0.84
L4: 0.82H · 0.30H · 0.17W · 0.60 · 1.00 · 0.19H · 0.010H · 4 · 1 · 0.88
L5: 1.02H · 0.45H · 0.21W · 1.00 · 1.70 · 0.24H · 0.012H · 5 · 1 · 0.92
L5 는 밑선이 화면 아래(1.02H)라 골짜기는 화면 밖으로 빠지고, 큰 봉우리는 0.57H 까지 솟는다 — 가까운 큰 산이 지나간 뒤 골짜기 사이로 먼 겹이 열린다.
붓 폭 bw 는 먼 겹일수록 좁다. 붓털 한 가닥의 화면 폭(bw ÷ 붓털 수)이 L1 3.2 · L2 2.9 · L3 2.7 · L4 2.7 · L5 2.6px 로 거의 같아야, 먼 산의 붓 자국이 회색 캡슐 덩이로 뭉치지 않는다.
- 빈 하늘(모든 능선 위쪽, 4px 마다 표본)은 주봉이 잠겨 있을 때 화면의 약 42.8%, 주봉이 열리고 카메라가 떠오른 동안 약 34.8%. 이 비율이 30~45% 를 벗어나게 숫자를 바꾸지 않는다.
- 큰 봉우리(env ≥ 0.8)의 높이 ÷ 평균 반폭은 L3·L4·L5 에서 중앙값 1.40 · 1.29 · 1.60 이다(1.2 이상이어야 「봉우리」, 1 아래면 둔덕). L1·L2 는 먼 산줄기라 0.88 · 1.09 로 낮게 눕는다.
[먹 — 흡수(곱셈)로만 칠한다]
- 흡수색 A = −ln(먹색/종이색) 을 채널별로 구해 가장 큰 채널이 1 이 되게 나눈다(이 색이면 A ≈ [1, 0.931, 0.816], 먹 원색의 흡수량 2.289).
- 흡수량 a 의 「먹 인자 색」 = rgb(255·e^(−a·A_r), 255·e^(−a·A_g), 255·e^(−a·A_b)). 흰색 = 먹 없음. 화면에는 multiply 로 얹는다.
- 담채 대자의 흡수 Aw = −ln(대자색/종이색) 을 채널별로(나누지 않는다, ≈ [0.256, 0.735, 1.190]). 대자 양 u 를 더하면 인자 색 = 255·e^(−(a·A + u·Aw)). u = 1 이면 종이 위에 대자색이 그대로 앉는다. 담채도 빛이 아니라 흡수다.
- 한 픽셀의 흡수량은 실수로 다 더한 뒤 마지막에 한 번만 8비트 색으로 바꾼다. a 는 2.289 에서 자른다. 흐르는 겹은 a → 색 표(0.002 간격, 1,146칸, 채널 셋)를 시작할 때 한 번 만들어 쓴다 — 픽셀마다 Math.exp 를 세 번 부르지 않는다.
[산 모양 — 봉우리 도형의 합집합]
- 봉우리 하나의 단면: u = (x − 중심)/반폭(왼쪽이면 왼쪽 반폭·왼쪽 α), |u| < 1 에서 s(u) = (1 − |u|^α)^β, 밖은 0. 높이 = h·s(u).
- 흐르는 겹의 봉우리 k(정수, 음수 포함)는 rngFor(겹 번호, k) 한 줄기에서 이 순서로 뽑는다:
중심 p = (k + 0.5 + (r − 0.5)·0.5)·sp · env = 0.22 + 0.78·(0.5 + 0.5·sin(2πk/7.3 + 1.7·겹 번호))^2.5 · h = hmax·env·(0.75 + 0.25r) ·
왼쪽 α = 0.7 + 0.9r · 오른쪽 α = 0.7 + 0.9r · β = 1.0 + 0.5r · 왼쪽 반폭 = sp·(0.40 + 0.45r) · 오른쪽 반폭 = sp·(0.40 + 0.45r) · 바위 덩이 수 n = 3 + floor(4r) ·
바위 덩이 n 개(하나마다 이 순서로): 쪽 = (r < 0.5 ? 왼쪽 : 오른쪽), uu = 0.15 + 0.7r, 솟음 sh = 0.4 + 0.8r, sw = 0.06 + 0.10r.
덩이의 중심 c = p ± uu·그쪽 반폭, 반폭 w = sw·그쪽 반폭, 꼭대기 높이 = h·s(uu)(그쪽 α) + sh·w — 비탈 위로 제 폭의 0.4~1.2배 솟는다. 덩이의 단면은 α = 2 · β = 1 (둥근 바위)로 고정.
봉우리 k 의 높이 P_k(x) = max(본봉, 덩이들). 모양에 쓴 난수 개수 = 8 + 4n — 붓질 줄기는 그다음부터.
- L0 의 일곱 봉우리는 rngFor(0, 봉우리 순번) 줄기 맨 앞에서 n = 3 + floor(4r) 과 덩이 n 개(같은 순서·같은 식, α = 1.3)를 뽑는다. 모양에 쓴 난수 = 1 + 4n.
- 능선 ridge(x) = base − max_k P_k(x) (k 는 floor(x/sp) ± 3 만 보면 된다). 그 x 에서 가장 높은 봉우리가 「주인」이다.
- env 의 바닥이 0.22 라서 7.3 봉우리 주기로 큰 산덩이 하나와 낮게 누운 산줄기가 뚜렷이 갈린다. α 가 좌우 따로라 한쪽 비탈은 오목하게 서고 다른 쪽은 볼록하게 눕는다. 바위 덩이가 비탈을 층층이 꺾는다 — 능선은 여전히 도형의 합집합이지 노이즈가 아니다.
- rngFor(겹, k) = mulberry32((시드 ^ Math.imul(겹 + 1, 0x9E3779B1) ^ Math.imul(k, 0x85EBCA77)) >>> 0).
[젖은 먹 굽기 — 몸통·번짐·고임·얼룩을 픽셀마다 한 번 계산한다]
산의 부피와 먹의 스밈은 이 계산 하나가 만든다. 모든 값은 **세계 좌표**의 함수라서 어느 타일에서 계산해도 같은 픽셀이 나온다.
- 지도 두 장(시작할 때 한 번, 256×256 Float32Array, 가로·세로가 이어지는 무늬):
얼룩 지도 BL: rngFor(97, 0) 줄기에서 8×8 격자값 64개, 이어서 16×16 격자값 256개를 뽑는다. 각 격자를 칸 사이 smoothstep 보간(f²(3 − 2f))으로 256×256 에 펼치되 격자 번호는 끝에서 처음으로 돌아간다. BL = 0.65·(8×8) + 0.35·(16×16) 을 최소·최대로 0~1 정규화.
섬유 지도 FB: 256×256 캔버스를 검게 채우고 rngFor(97, 1) 로 섬유 260가닥 — 시작 (256r, 256r), 길이 6 + 18r, 각도 π·r, 휨 (r − 0.5)·0.3·길이, 밝기 140 + 115r. 가닥마다 시작점 → 끝점 quadraticCurveTo, 조절점 = 가운데에서 수직으로 휨×2, lineWidth 1, rgb(밝기, 밝기, 밝기). 이음매가 없게 같은 가닥을 (−256, 0, 256)² 아홉 자리에 그린다. 빨강 채널 / 255 가 FB.
sample(지도, x, y, s) = 지도를 (x/s, y/s) 에서 쌍선형 보간, 정수 번호는 & 255 로 감는다(음수도).
돌린 표본 sample2(x, y, s1, s2) = 0.5·(sample(BL, R31(x, y), s1) + sample(BL, R−58(x + 517, y + 211), s2)), Rθ = 좌표를 θ° 돌리기((x·cosθ − y·sinθ, x·sinθ + y·cosθ)). 격자 값잡음은 결이 가로·세로로 서고(기울기 방향의 3분의 1이 축 정렬) 256칸마다 같은 무늬가 되풀이된다 — 각도와 크기가 다른 두 표본의 평균이면 둘 다 사라진다. 얼룩·번짐·고임은 모두 sample2 로 뜬다.
- 겹 번호 i 의 표본 좌표는 x' = x + 1000·i (겹마다 다른 얼룩).
- 열(세로줄)마다 한 번 구하는 값:
r = ridge(x), m = (ridge(x + 2) − ridge(x − 2))/4 (오른쪽으로 내려가면 +, 그늘 비탈).
부드러운 산덩이 높이 Hs(x): 세계 4px 격자 xg = 4n 에서 높이 base − ridge(xg) 를 표본하고, 반폭 M = round(sp/8) 칸(L1·L2 24 · L3 29 · L4 41 · L5 50 · L0 48)의 상자 평균을 **두 번** 낸 뒤 x 에서 선형 보간한다. 상자 평균은 누적 합이 아니라 칸마다 직접 더한다(어느 타일에서 시작해도 실수 반올림까지 같게). 표본은 타일 구간 ± 1.5·sp(L0 는 화면 ± 2·sp)까지.
hs = clamp(Hs/hmax, 0, 1), Rs = base − Hs, ms = (Rs(x + 4) − Rs(x − 4))/8, nx = sample(BL, x', 131, sp/40), gap = smoothstep(0.42, 0.58, sample2(x', 77, 0.6, 0.9)), 덩이 높이 hr = clamp((base − r)/hmax, 0, 1).
- 픽셀마다(마른 판 w = 0, 젖은 판 w = 1 — 젖은 판은 L0 에만):
d = y − r · Bf = sample2(x', y, 1.6, 2.3) · Fb = sample(FB, x', y, 1) · B1 = sample2(x', y, 3.1, 4.7) · B0 = sample(BL, x' + 400, y + 400, 1) · B2 = sample(BL, x' + 911, 0.6y + 377, 5)
번짐 가장자리: E = 0.028H·(1 + 1.5w), e = d + E·(0.8·Bf − 0.40) + 0.004H·Fb, 덮임 cov = clamp(e/(1.2 + 5w) + 0.5, 0, 1). 능선이 잎 하나 20~50px 의 물결로 안팎 ±8px 안에서 번지고(변위 표준편차 약 3.4px — 폰 크기에서 0.7px, 거기서 1px 아래인 질감은 없는 질감이다), 섬유는 젖어도 4px 안에서만 털처럼 나간다.
몸통: Lm = F·(0.55 + 0.95·hs)·(0.7 + 0.6·nx), t = max(0, (y − Rs)/Lm) + 0.30·(B2 − 0.5), P = clamp(1 − t, 0, 1)^1.4.
얼룩 mott = 0.75 + 0.5·smoothstep(0.25, 0.85, B1) + 0.15·(B0 − 0.5)(진함 비 1.8배 안 — 젖은 판은 1 + 0.15·(B0 − 0.5)). 그늘 shade = 1 + 0.25·tanh(1.5·ms). 골짜기 안개 valley = 0.6 + 0.4·hs^0.8.
body = 0.40·P·mott·shade·valley.
가장자리 먹 고임: wr = 0.0024H·(0.5 + 0.6·clamp(m, 0, 1.5))·(0.4 + 1.6·B2), rim = 0.42·e^(−max(d, 0)/wr)·gap (젖은 판 0). 가장자리의 40% 남짓에만 고이고, 굵기가 굵다 가늘다 하며, 그늘 비탈에서 두껍다.
a = ink·(body + rim)·cov. L0 는 여기에 smoothstep(0.02, 0.15, hr) 를 더 곱한다 — 봉우리가 없는 열(능선 = 밑선)에서는 몸통·고임이 옆으로 풀려 사라진다.
몸통 진하기는 능선(r)이 아니라 부드러운 산덩이 선(Rs)에서 잰다. 능선은 덮임과 고임에만 쓴다 — 그래서 골짜기 밑으로 먹 혀가 늘어지지 않고, 봉우리 밑에 밝은 등뼈가 서지 않는다. 몸통 아래끝은 Lm 과 B2 로 들쭉날쭉 안개에 풀린다.
- 붓 자국 합치기: 그 타일의 붓질(아래 [붓질])을 먼저 흰 바탕 캔버스에 그려 두고 읽는다. 초록 채널 g 에서 s = −ln(max(g, 1)/255)/0.931. 마름 q = 1 − min(1, s/(0.25·붓질 ink)), 결 G = 0.6·Fb + 0.4·sample(BL, x', y, 0.12), 남김 keep = smoothstep(0.75q − 0.22, 0.75q + 0.06, G). a += s·keep. 진한 심은 남고, 엷은 꼬리만 종이 결의 높은 곳에 걸려 끊긴다 — 이것이 마른 붓(비백)이다.
- 가림막 알파(흐르는 겹만): vα = veil·smoothstep(−0.002H, 0.008H, e). 가림막도 같은 번진 가장자리를 쓴다.
- 빠른 길: d < −0.002H − 0.4·E − 0.004H 이고 그 픽셀에 붓 자국이 없으면 흰색·가림 0 으로 바로 넘긴다(하늘 쪽 픽셀 대부분 — 번짐이 닿을 수 없는 거리).
- L0 (960×540 = 해상도 0.5배, 두 판 — 굽는 때는 [타일]의 시작 순서):
마른 판 = 위 식 그대로 + 대자. 대자는 먹과 따로 스민 엷은 물감 자리다: u = 0.22·cov·rx·ry·smoothstep(0.20, 0.65, sample(BL, R31(x' + 300, y), 2.2)), rx = smoothstep(0.40W, 0.48W, x)·(1 − smoothstep(0.56W, 0.66W, x)), ry = smoothstep(0.16H, 0.24H, y)·(1 − smoothstep(0.40H, 0.50H, y)) — 주봉 왼쪽 허리의 제 가장자리를 가진 얼룩 자리. P 를 곱하지 않는다(먹이 가장 짙은 꼭대기에 앉으면 갈색 그을림이 된다). 대자가 앉은 자리는 먹이 엷다: a ← a·max(0.15, 1 − 1.6u)(붓 자국을 더한 뒤).
젖은 판 = w = 1 로 계산하고 붓 자국 s 를 0.5배, 마지막 흡수 전체(a)를 0.70배, 대자 0. 흐리고 넓게 번진, 아직 마르지 않은 먹이다.
두 판 모두 인자 색 = 255·e^(−(a·A + u·Aw)) 를 채널마다 직접 계산한다(한 번뿐이라 표가 필요 없다).
[붓질 — 준법: 산마루에서 아래로 쓸어내린다]
봉우리 k 의 붓질은 전부 그 봉우리의 rngFor 줄기(모양을 뽑은 바로 다음부터)로만 정한다. 그래야 어느 타일에서 그려도 같은 붓질이 나온다. 타일 밖이라 그리지 않는 붓질, 주인이 아니라 버리는 출발점, 없는 B줄도 난수는 똑같이 뽑는다.
- 붓질 캔버스: 타일과 같은 크기의 흰 캔버스, getContext('2d', { willReadFrequently: true }) — 컴퓨터(CPU)로 그려야 타일마다 가장자리 안티에일리어싱이 같고 읽기도 빠르다. setTransform 으로 세계 좌표, 먹은 multiply.
- 출발점은 다발로 놓는다: x = p − 왼쪽 반폭에서 시작해, 다발마다 출발점 수 n = 3 + floor(4r)(r 하나), 다발 뒤 빈칸 (1.5 + 1.5r)·bw(r 하나). 다발 안 출발점 간격 0.55·bw, 출발점마다 흔들림 (r − 0.5)·0.5·0.55·bw. x 가 p + 오른쪽 반폭을 넘으면 끝. 주인이 k 인 출발점만 그린다. 그 출발점의 P_k(x) 가 0.25h 미만이면(골짜기 바닥) 그리지 않는다 — 양쪽 비탈 다발이 골짜기에 모여 X 로 엇갈린다(난수는 똑같이 뽑는다).
- A줄·B줄은 그 겹의 몸통 안에만 그린다: 봉우리 붓질 전에 「능선(4px 세계 격자 표본)보다 1px 아래 → 타일 아래끝」 다각형으로 clip 을 걸고, 다 그린 뒤 푼다(save/restore). L0 의 A줄도 L0 능선 아래에 가둔다.
- 기울기 m = (ridge(x+2) − ridge(x−2))/4. 붓은 비탈 아래쪽(가로 방향 부호 = m 의 부호)으로 내려간다.
수직에서 잰 각도 θ0 = min(0.7·(90° − atan|m|), 60°). 가파른 비탈(m = 2)에서는 19° 로 비탈(수직에서 27°)보다 서서 몸통 안으로 들어가고, 완만한 비탈(m = 0.2)에서는 55° 로 비스듬히 쓸린다. 내려가며 θ(s) = θ0·(1 − 0.45s) + 휨, 휨 = (r − 0.5)·0.15 라디안(붓마다 일정). s 는 0→1 진행률, 10걸음으로 중심선을 적분한다.
- 빛은 왼쪽 위에서 온다: 오른쪽으로 내려가는 비탈(m > 0, 그늘)의 붓질 흡수량 ×1.35, 왼쪽으로 내려가는 비탈(m < 0, 빛) ×0.55.
- A줄(능선에서 출발): 시작 = (x, ridge(x) + 0.002H), 길이 F·(0.25 + 0.5r), 흡수량 m0 = 0.24·ink·(0.7 + 0.6r)·빛.
- B줄(몸통 안에서 출발, 확률 0.5): 깊이 d = F·(0.10 + 0.40r²) 아래에서 시작, 길이 F·(0.20 + 0.30r), m0 = 0.24·ink·(1 − d/F)^1.3·(0.7 + 0.6r)·빛. 방향은 같은 x 의 능선 기울기를 따른다.
- 출발점 하나에서 뽑는 순서: 흔들림 → A줄[길이, 흡수, 휨, 가닥마다(옆 간격, 폭, 끝 λ, 흡수, 마디 10개)] → B줄[있음, 깊이, 길이, 흡수, 휨, 가닥마다(같은 순서)].
- 붓 하나 = 붓털 n 가닥(표의 붓털 수). 가닥 h 의 옆 간격 = ((h + 0.5)/n − 0.5)·bw·(0.85 + 0.3r)·(1 + 0.6s) — 내려가며 붓이 벌어진다.
가닥 폭 = bw/n·(0.7 + 0.6r)·min(1, 0.55 + 0.45·s/0.12)·(1 − 0.8s) — 눌러 들어가 가늘게 빠진다. 가닥 끝 λ = 0.45 + 0.55r. 가닥 흡수량 = m0·(0.75 + 0.5r).
- 마디 비우기: 가닥을 길이 λ 의 10마디로 보고, 마디 i 는 r < 0.05 + 0.40·(i + 0.5)/10 이면 비운다. 남은 마디끼리 이어진 구간마다 리본 다각형 하나로 채우고(구간끼리 겹치지 않는다), 구간 양 끝 반 마디에 걸쳐 폭을 0 까지 줄여 끝이 뾰족하게 한다. 채움은 그 가닥의 시작점→끝점 선형 그라디언트 하나를 모든 구간이 같이 쓴다: 0 = 먹 인자 색(가닥 흡수량), 0.6 = 먹 인자 색(0.55배), 1 = 흰색. 나머지 마름은 위 [젖은 먹 굽기]의 결 문턱이 낸다.
- 태점(L4·L5 만): 봉우리마다 무리 3 + floor(5r)개. 무리 중심 x = p + (r − 0.5)·(왼쪽 반폭 + 오른쪽 반폭)/2, 주인이 k 이고 P_k(x) ≥ 0.55h 일 때만. 무리마다 점 2 + floor(3r)개: x + (r − 0.5)·0.02H, y = ridge + (0.002 + 0.018r)H, 가로 반지름 (0.003 + 0.002r)H, 세로는 그 절반, 기울기 = atan(m)·0.5 + (r − 0.5)·0.3, 흡수량 0.6·ink. 태점은 clip 밖에서 그린다.
- 봉우리마다 그리는 순서: B줄 → A줄 → 태점(태점의 r 들은 붓질 다음에 뽑는다).
- L0: A줄만(ink 0.45, F 0.20H, bw 0.020H, 붓털 3). B줄·태점은 없다 — 먼 산은 결보다 덩어리다.
[타일 — 화면 밖에서 생기고 화면 밖에서 버린다]
- 흐르는 겹은 세계 x 를 폭 TW = 640px 의 타일 j 로 나눈다. 타일 = 세계 x 구간 [j·TW − 24, (j+1)·TW + 24], 세로 띠 [round(base − hmax − 0.03H), H], 크기 = 기준 px × res. 타일 픽셀 (c, l) 의 세계 좌표 = (j·TW − 24 + (c + 0.5)/res, 띠 위 + (l + 0.5)/res) — 640·res 가 정수라 이웃 타일의 겹치는 48px 는 세계 좌표가 같아 픽셀이 같다.
- 타일 하나 = 캔버스 둘: 먹 타일(불투명, 인자 색) · 가림 타일(종이색 rgb + 알파 vα·255). 둘 다 보통 캔버스이고 굽기가 끝날 때 putImageData 한 번으로 채운다.
- 굽는 단계: ① 붓질 — 겹치는 봉우리(타일 구간 ± 3봉우리)를 하나씩, 봉우리 하나가 한 단계 ② 읽기 — getImageData 한 번 + 열 값(r, m, Hs, ms, nx, gap) 계산이 한 단계 ③ 젖은 먹 굽기 — 타일 픽셀 24행이 한 단계 ④ 마무리 — putImageData 두 번, 붓질 캔버스 버림.
- 매 프레임 그 겹의 화면 왼쪽 세계 x = cam(t)·f. 겹마다 화면에 필요한 타일과 오른쪽으로 두 장 앞까지를 Map 에 두고, 왼쪽으로 한 장 넘게 지난 타일은 버린다.
- 굽기 예산: 미리 굽는 타일은 다섯 겹 전체에서 프레임당 한 단계만 굽는다(처음 4초는 아래 시작 순서). 다 구워진 타일만 화면에 쓴다. 화면에 필요한 타일이 덜 구워졌으면 그 프레임에 마저 굽고 console.warn 한다(최후의 수단).
- 시작 순서 — 첫 화면이 늦게 뜨지 않게, 그 순간 보이는 것만 먼저 굽는다:
① 첫 프레임 전에는 t = 0 에 보이는 L5·L4 만, 처음 화면에 걸치는 타일과 그 오른쪽 한 장까지(겹마다 넉 장 — 화면 폭이 타일 세 장과 딱 맞아 둘째 프레임에 오른쪽 타일이 걸린다) 다 굽는다.
② 나머지는 드러나는 차례대로 대기열에 넣는다: L3 넉 장 → L2 넉 장 → L0 두 판(붓질 시작 · 봉우리 하나씩 · 읽기 · 마른 판 24행씩 · 젖은 판 24행씩 · 마무리) → L1 넉 장.
③ t < 4초 동안은 프레임마다 대기열에서 최대 6단계를, 굽기 시간이 12ms 를 넘으면 그 자리에서 멈춘다(최소 한 단계). 대기열이 비면 평소 예산으로.
④ 아직 안개 앞선이 띠 위에 있는 겹(드러나기 전)은 가림·먹·타일 확인까지 통째로 건너뛴다 — 그 뒤 겹들도 아직 안 보이므로 가릴 것이 없다.
이렇게 해서 첫 화면까지 데스크톱 약 0.3초, CPU 4배 느린 기기에서 약 1.3초(넉 장씩 다 굽던 때 0.7초 · 3.5초)다.
- 시계 T0 는 두 번째 requestAnimationFrame 의 타임스탬프로 잡는다(첫 콜백은 render(0) 만 그린다) — 첫 프레임을 띄우는 데 걸린 시간만큼 드러남이 건너뛰지 않게.
- 화면 x = 세계 x − cam(t)·f, 화면 y = 세계 y + 0.07H·f·lift(t). 타일·가림·안개 띠·안개 앞선·능선 표본이 이 두 식 하나로 맞는다. L0 와 구름 바다는 움직이지 않는다.
[안개]
- 겹 Li 를 얹는 순서: 가림 타일들을 화면에 source-over 로 제자리(소수점 위치 그대로)에 → 화면 크기 작업 캔버스의 그 겹 띠를 흰색으로 지우고 먹 타일들을 source-over 로 → 안개 앞선이 있으면 덮고 → 화면에 multiply. 가림이 먼 산을 가까운 산 몸통 뒤로 숨기고, 번진 가장자리를 따라 부드럽게 시작한다.
- 안개 앞선: 진행 r(0→1), 앞선 y_f, 부드러움 sf. 앞선 위쪽(y < y_f − sf)은 보임 hi, 아래(y > y_f + sf)는 보임 lo, 사이는 직선 보간. 작업 캔버스에 흰색 세로 그라디언트(y_f − sf 에서 불투명도 1 − hi, y_f + sf 에서 1 − lo)를 덮어 구현한다.
L1~L5: y_f = (띠 위 − 0.06H) + r·(base + F − 띠 위 + 0.12H) + 그 겹의 떠오름, sf = 0.06H, lo = 0, hi = 1.
L0: 보임 v(y) = max(열림, 유령). 열림 = 앞선 y_f = −0.08H + r0·0.90H, sf = 0.08H 위는 1, 아래는 0. 유령 = 앞선을 r = 0.12(y_g = 0.028H)에 고정하고 그 위만 유령 보임 g(0.05, 처음에만 0 에서 올라온다), 아래는 0 — 잠겨 있는 동안 주봉 꼭대기 끝만 아주 엷게 비친다(실루엣 전체가 미리 보이면 열림이 「발견」이 아니라 「아는 모양이 진해짐」이 된다).
흰 그라디언트 하나(y = −H → 2H)에 네 점 y_f ± sf, y_g ± sf 의 1 − v 를 색 정지점으로 넣어 덮는다.
- L0 작업 캔버스: 흰색 → 젖은 판(늘려 그림) → 마른 판을 globalAlpha = kd 로 → 보임 그라디언트 → 화면에 multiply. 젖은 판에서 마른 판으로는 이 섞기 하나로만 바뀐다. L0 두 판이 아직 안 구워졌는데 보여야 하면 마저 굽고 console.warn.
- 안개 덩어리 그림: 시작할 때 256×256 캔버스에 종이색 방사형 그라디언트(중심 불투명도 1 → 반지름 0.5 에서 0.55 → 가장자리 0)를 한 번 그려 둔다. 매 프레임 이 그림을 타원 크기로 drawImage 해 늘려 그리고 불투명도는 globalAlpha 로 준다.
- 흐르는 안개 띠: 겹 Li 의 먹 바로 뒤(다음 겹 가림 전)에 그린다. 안개 덩어리 j 는 rngFor(10 + i, j) 로: 세계 간격 0.42W, 중심 x = (j + 0.5 + (r − 0.5)·0.6)·0.42W, y = base − 0.15·hmax + (r − 0.5)·0.04H, 가로 반지름 (0.18 + 0.16r)W, 세로 반지름 (0.020 + 0.014r)H, 불투명도 (0.28 + 0.22r)·(0.8 + 0.2·sin(2πt/(14 + 8r) + 2πr)).
화면 x = 중심 − cam(t)·f·1.2 − 6·t (바람이 초당 6px 더 왼쪽으로 민다), 화면 y 는 그 겹의 떠오름을 더한다. 덩어리 하나 = 타원 셋: 중심에서 (−0.45rx, +0.25ry) 크기 0.6배 · (0, 0) 1배 · (+0.4rx, −0.2ry) 0.7배.
- 구름 바다(L0 바로 뒤): 같은 방식, rngFor(10, j), 간격 0.55W, y = 0.49H + 0.03H·r0, 가로 반지름 (0.30 + 0.15r)W, 세로 (0.040 + 0.015r)H, 불투명도 (0.55 + 0.15r)·(1 − 0.45·r0), 화면 x = 중심 − cam(t)·0.05 − 6·t. 하늘 위에서는 종이색이라 보이지 않고, 주봉의 허리만 끊으며 지나간다.
[장면] (ease = easeInOutSine. 시계는 멈추지 않는다. 주기 안 시각 τ = t − (5 + 30k), k = 그 t 에서 지난 열림 번호, t < 5 이면 열림 전)
1. t = 0 에 L5 는 다 드러나 있고 L4 는 거의(0.93) — 첫 프레임부터 종이 위에 젖은 먹 산 두 겹이 화면 아래 절반을 채운다(종이보다 12단계 넘게 어두운 픽셀 약 34%, 빈 종이로 시작하지 않는다). 카메라는 0초부터 흐른다.
2. 1.0–5.4 뒤 겹들이 안개 속에서 차례로 드러난다: 겹마다 앞선 r = ease((t − 시작)/2.4), 시작 L5 −3.0 · L4 −2.0 · L3 1.0 · L2 2.0 · L1 3.0(음수면 t = 0 에 이미 그만큼 진행돼 있다).
t = 0 에 r = L5 1 · L4 0.93 · 나머지 0, t = 2.0 에 L3 0.37, t = 3.0 에 L3 0.93 · L2 0.37, 5.4초에 모두 1. 굽기 대기열(L3 → L2 → L0 → L1)이 이 차례를 앞선다.
3. 3.0–4.5 L0 의 유령 보임 g 가 0 → 0.05(ease). 주봉 꼭대기 끝에 먼 곳의 기척만(젖은 판).
4. 카메라 cam(t) = V·(t − 0.8·Σ_k B(t; a_k)), V = 60, a_k = 4.5 + 30k, B(t; a) = 0(t ≤ a) · 3(t ≥ a + 6) · 0.5·(u − (6/2π)·sin(2πu/6)), u = t − a.
속도 = V·(1 − 0.8·(1 − cos(2πu/6))/2) — 4.5초부터 느려져 7.5초에 초당 12px(20%)로 가장 느리고 10.5초에 원래대로. 가장 느린 순간이 주봉 허리가 드러나는 7.8초와 겹친다. 멈추지는 않는다.
5. ★ 5.0–9.0 안개가 걷힌다(시그니처): r0 = ease(τ/4). 5.7초에 주봉 꼭대기(0.60W, 0.07H)가 비치기 시작해 6.3초에 또렷하고, 7.8초에 산허리(0.55H)까지, 9.0초에 끝. 구름 바다가 0.03H 가라앉으며 엷어진다.
같은 때 떠오름 lift = ease(τ/4.5)(5.0–9.5): 겹 Li 가 0.07H·f 만큼 내려간다(L5 76px · L4 45px · L3 27px · L2 15px · L1 8px).
6.4–9.0 먹이 마른다: kd = ease((τ − 1.4)/2.6). 넓게 번진 젖은 주봉의 가장자리가 잎 모양으로 서고 얼룩이 짙어지며 왼쪽 허리에 엷은 대자 자리가 스민다. 주봉 상자의 어둡기가 열리기 전 약 0 에서 약 47 로 오른다(화면 절반 크기가 4초 안에 바뀐다).
6. 9.0–17.0 머묾. 주봉이 열린 채 겹들이 천천히 흐르고 안개 띠가 몸통을 감았다 푼다.
7. 17.0–23.0 안개가 다시 차오른다: r0 = 1 − ease((τ − 12)/6), lift = 1 − ease((τ − 12)/6). 18.0–24.0 다시 젖는다: kd = 1 − ease((τ − 13)/6). 아래부터 잠기고 꼭대기가 마지막(21.9초)에 사라진다.
8. 열림은 5 + 30k 초마다 5~7을 되풀이한다(35초, 65초 …). 1~3 은 처음 한 번뿐이다.
- 이 작품은 반복 길이가 없다. 끝없이 흐르고, 같은 t 면 언제나 같은 화면이다.
[구현]
- HTML 파일 하나로 만든다. 외부 파일·글꼴·라이브러리·네트워크 요청을 쓰지 않는다(미리보기에서 막히거나 느려진다).
- 첫 <script> 맨 앞에 CONFIG 객체를 두고 [바꿀 곳]의 값과 이 글에 나온 숫자(시간표·크기·색·세기)를 이름 붙여 모은다. 나중에 숫자만 바꿔 다듬을 수 있게.
- 그림은 시간 t(초)만으로 계산하는 함수 render(t) 로 그린다. requestAnimationFrame 의 타임스탬프나 performance.now() 로 t 를 구한다. setTimeout·setInterval·CSS 애니메이션·transition 으로 움직이지 않는다(프레임마다 속도가 달라지고, 다시 재생할 때 어긋난다). 시뮬레이션처럼 상태가 쌓이는 작품은 고정 시간 간격으로 상태를 진행하되 그 진행도 타임스탬프로만 정한다.
- 난수는 시드 고정 난수(mulberry32 등)만 쓴다. Math.random 은 쓰지 않는다(매번 다른 화면이 나온다).
- 캔버스 내부 해상도는 화면비에 맞춘 기준 크기로 고정하고, 창 크기에 맞춰 화면비를 지키며 가운데에 꽉 차게 보이게 한다(남는 곳은 배경색). 스크롤이 생기지 않게 한다. devicePixelRatio 는 그리는 선명도에만 반영한다.
- 글자는 시스템 글꼴만 쓴다: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif. 붓글씨·손글씨 느낌은 글꼴이 아니라 획을 그려서 낸다.
- 길이가 정해진 작품은 끝나면 처음과 같은 화면으로 돌아가 이음매 없이 반복한다.
- 모든 함수·상수·셰이더 문자열을 먼저 정의하고, 시작(초기화와 첫 requestAnimationFrame)은 스크립트 맨 끝에서 한 번만 호출한다. 정의되기 전에 쓰면 첫 화면에서 멈춘다.
이 작품에만 해당하는 구현:
- 내부 해상도는 1920×1080 고정이다(타일 캐시가 기준 px 에 묶인다). 남는 곳은 종이색.
- 한 프레임의 순서: 종이색 채움 → L0(작업 캔버스, 젖은 판·마른 판 섞기, 안개 앞선, multiply) → 구름 바다 → L1~L5 각각 [가림 타일 → 먹 타일(작업 캔버스, 안개 앞선, multiply) → 안개 띠] → 종이 결 텍스처(multiply) → 굽기 예산 한 단계.
- 시작 순서: 지도 두 장 → 색 표 → L5·L4 첫 화면 타일 → 대기열(L3 → L2 → L0 두 판 → L1) 만들기 → 종이 결 → 첫 requestAnimationFrame. 콘솔 대조표는 대기열이 다 구워진 뒤 한 번 낸다(③ 은 첫 render(0) 화면에서 재 둔 값 — 대조표가 화면에 render(0) 을 다시 그리면 한 프레임 깜빡인다).
- 픽셀 굽기는 Float32Array 의 열 값과 Uint8ClampedArray 를 직접 다루는 반복문으로 쓴다. 픽셀마다 객체·배열·클로저를 만들지 않는다(가까운 겹 타일 한 장이 30만 픽셀이다).
- 종이 결 텍스처는 시작할 때 1920×1080 캔버스에 한 번 그린다(rngFor(99, 0)): 흰 바탕 · 얼룩 50개(반지름 0.10~0.30H 방사형, rgba(120,125,105,0.035) → 0) · 닥 섬유 2,200가닥(quadraticCurveTo, 길이 0.03~0.12H, 방향 완전 무작위, 조절점은 길이의 ±0.15 옆, 굵기 0.5~0.9px, rgba(105,112,96, 0.025~0.05)) · 껍질 티 260개(반지름 0.6~2.2px 타원, rgba(85,75,60, 0.25~0.5)) · 가장자리 어둡게(가운데 투명 → 모서리 rgba(95,100,85,0.08)). 화면에 고정된다 — 종이는 화면 면이라 카메라와 함께 움직이지 않는다.
- 녹화 버튼 문단을 함께 붙였다면 녹화 길이는 10초가 아니라 30초로 한다(산이 드러나고, 주봉이 열려 마르고, 다시 잠기기까지).
- CONFIG.timing 에 모은다: intro{starts L5..L1 [−3.0, −2.0, 1.0, 2.0, 3.0], dur 2.4} · ghost[3.0, 4.5, 0.05, r 0.12] · revealFirst 5.0 · period 30 · reveal 4.0 · hold 8 · close 6 · slow{offset −0.5, len 6, depth 0.8} · lift{amount 0.07, in 4.5} · dry{start 1.4, dur 2.6, wetStart 13, wetDur 6}.
- 시작할 때 아래 표를 console.table 로 내고, 기준을 벗어난 줄은 console.warn 한다(숫자를 바꾼 뒤에도 스스로 대조되게):
① 빈 하늘 비율 — t = 0(L0 제외)과 t = 12(L0 포함, 떠오름 1)의 카메라에서 능선 최솟값을 4px 마다 표본: 42.8% · 34.8% (±2pp)
② 이웃 타일이 겹치는 48px(× res)의 먹 타일 비교, 겹마다 둘로 나눈다: 붓 자국이 없는 픽셀(두 타일 붓질 캔버스 모두 흰색)의 최대 차이 2 이하(정상 0 — 세계 좌표 계산) · 붓 자국 픽셀 중 차이 8 넘는 픽셀 수 쌍마다 5 이하(정상 0~2 — 붓 가장자리 한두 픽셀은 CPU 캔버스여도 타일마다 다르게 래스터되고, 화면에서는 안 보인다). 붓질 캔버스의 겹침 열은 비교용으로 남겨 둔다.
③ 첫 render(0) 화면에서 종이보다 12단계 넘게 어두운 픽셀의 비율 — 12% 이상(정상 약 34%)
④ 주봉 꼭대기가 비치기 시작·또렷·산허리·사라지는 시각(앞선 식으로 계산) — 5.7 · 6.3 · 7.8 · 21.9초(±0.1)
⑤ 겹마다 타일 한 장 굽는 데 든 시간(ms) · L0 두 판 · 첫 프레임 전 굽기 · 첫 프레임까지(ms) — 표시만
⑥ L3·L4·L5 큰 봉우리(env ≥ 0.8, k = −200…199)의 높이 ÷ 평균 반폭 중앙값 — 1.2 이상(정상 1.40 · 1.29 · 1.60)
⑦ 몸통 매끈함 — L0 마른 판의 픽셀 식을 붓질 없이 x = 0.60W 세로 단면(주봉 꼭대기 + 0.03H ~ 0.50H, 0.5배 해상도)에만 계산해, 이웃 픽셀 밝기 차이 최대 — 6 이하(정상 약 1.1)
⑧ 처음부터 대조표를 낼 때까지 「덜 구워져 마저 굽는다」 경고 수 — 0
⑨ 주봉 상자(0.50W~0.72W × 0.12H~0.40H) 평균 어둡기(종이 밝기 − 픽셀 밝기, 각 판을 종이 위에 다 보이게 얹었을 때) — 마른 판 35 이상(정상 약 47), 젖은 판 ÷ 마른 판 0.80 이하(정상 약 0.68)
⑩ 대자 — 0.05 넘게 묻은 화면 비율 2~8%(정상 약 3%) · 그 픽셀들의 OKLab 밝기 L 평균 0.72 이상(정상 약 0.73 — 짙은 먹 위의 대자는 갈색 그을림이다)
⑪ 화면 크기 질감 — L5 번짐 변위(e − d, 2만 점 표본)의 표준편차 3~4px(정상 약 3.4) · 먹 고임이 있는(gap > 0.5) 가장자리 비율 35~60%(정상 약 41%) · 붓털 한 가닥 화면 폭(bw ÷ 붓털 수) L1..L5 의 최대 ÷ 최소 1.3 이하
⑫ L0 마른 판에서 x < 0.15W(봉우리가 없는 곳)의 종이보다 6단계 넘게 어두운 픽셀 — 0
[하지 말 것]
- 여러 옥타브 노이즈(fBm·펄린)로 능선을 만들지 않는다 — 어느 풍경 생성기에서나 보는 지글거리는 산맥이 되고, 한 봉우리 한 봉우리를 붓으로 세운 산수화의 구조가 사라진다. 능선은 봉우리 도형의 합집합이다. 얼룩·섬유 지도는 먹의 진하기와 6px 안쪽의 번짐에만 쓴다.
- 몸통 진하기를 능선에서 잰 깊이로 정하지 않는다 — 능선 모양이 아래로 복사돼 골짜기마다 짙은 먹 혀가 늘어지고 봉우리 밑에 밝은 등뼈가 서며, 안개 띠와 만나 「+」 십자 빛이 된다. 부드러운 산덩이 선(Rs)에서 잰다.
- 몸통을 다각형 띠 여러 장을 겹쳐 칠하지 않는다 — 몇 장이면 단이 진 등고선 판, 수십 장이면 에어브러시 원뿔이 된다. 몸통은 픽셀마다 한 번 계산한다.
- 흡수량을 8비트 색으로 여러 번 반올림해 쌓지 않는다 — 반올림이 채널마다 달리 쌓여 초록·보라 기가 돈다. 실수로 다 더하고 한 번만 색으로 바꾼다.
- 몸통·얼룩·번짐을 타일 픽셀 좌표나 타일 번호로 계산하지 않는다 — 겹치는 48px 가 타일마다 달라 세로 이음매가 줄지어 선다. 세계 좌표의 함수로만.
- 그늘·담채를 능선 기울기(m)로 열마다 정하지 않는다 — 봉우리 꼭대기에서 기울기 부호가 뒤집혀 산 한가운데 세로 경계선이 선다. 몸통 그늘은 부드러운 산덩이 선의 기울기(ms), 대자는 넓은 가로·세로 경사(rx·ry)로만.
- 능선을 고른 굵기의 선으로 두르지 않는다 — 벡터 윤곽선이 되어 「먹이 스민」이 아니라 「오려 낸 산」이 된다. 가장자리 진하기는 먹 고임(rim)으로, 그늘 비탈에서 두껍고 군데군데 끊긴다.
- 번짐을 화면에서 안 보이는 크기로 줄이지 않는다 — 진폭 1~2px 이면 폰(1/5 크기)에서 0 이 되어 산이 다시 자로 그은 벡터 비탈이 된다. 번짐은 진폭 ±8px 안·파장 20~50px 의 잎 모양(변위 표준편차 3~4px)이다.
- 번짐을 섬유 털처럼 길게 뻗치거나 블러로 내지 않는다 — 털 난 산(산 위 허공에 먹 티가 뜬다)이거나 초점이 나간 사진이 된다. 섬유 몫은 젖은 판에서도 0.004H 로 묶고, 젖은 판의 부드러움은 같은 식(E·부드러움 키움)으로 낸다.
- 얼룩·번짐·고임을 격자 값잡음 한 번 표본으로 뜨지 않는다 — 결이 가로·세로로 서서 몸통에 둥근 네모 반점이 생기고, 256칸마다 같은 무늬가 돌아온다. 각도·크기가 다른 두 표본(sample2)의 평균으로.
- 먹 고임을 가장자리 대부분에 잇지 않는다 — 고른 굵기의 윤곽선으로 읽힌다. 고이는 곳은 40% 남짓, 굵기는 B2 로 굵다 가늘다.
- 바위 덩이를 본봉과 같은 뾰족한 α·β 로 그리지 않는다 — 비탈에 바늘·안테나가 꽂힌다. 덩이는 α = 2 · β = 1 의 둥근 바위, 솟음은 제 폭에 비례.
- 먼 겹에 굵은 붓을 쓰지 않는다 — 붓털 한 가닥이 10px 이면 먼 산의 준이 회색 캡슐 덩이로 뭉친다. 가닥 화면 폭은 겹마다 거의 같게(2.6~3.2px).
- 얼룩 지도의 등치선을 가는 고리(물자국 선)로 긋지 않는다 — 대리석·지형도 무늬로 읽힌다. 얼룩은 넓고 부드러운 반점이다.
- 몸통을 비워 두고 능선 근처에만 붓질을 달지 않는다 — 능선마다 빗살 술이 달린 물결 선화가 되고, 폰 크기에서는 회색 털로 뭉개진다.
- L0 몸통을 밑선에서 수평으로 잘라 내지 않는다 — 잘린 단면이 안개 너머로 자 대고 그은 직선이 된다. 몸통은 P 로 0 까지 풀린다.
- 붓질이 능선 위 허공으로 삐져나가게 두지 않는다 — 산에 털이 난 것처럼 뒤 겹 하늘에 뜬다. 능선 아래 clip 으로 가둔다.
- 붓을 완만한 비탈에서 수직으로 내리지 않는다 — 세로로 늘어진 빗물 자국이 된다. θ0 = min(0.7·(90° − atan|m|), 60°).
- 비백 구간 끝을 가닥 폭 그대로 직각으로 자르지 않는다 — 회색 직사각형 조각이 줄지어 서서 점선·바코드로 읽힌다.
- 넓은 바탕 붓을 붓털로 쪼개 그리지 않는다 — 가장자리가 칼같은 연회색 막대가 흩어져 오린 종이띠처럼 보인다.
- 주봉(L0)을 준 붓질 빗살로만 그리지 않는다 — 속이 빈 흐린 「^」 철사가 되어 안개가 걷혀도 화면이 거의 바뀌지 않는다. 원산은 젖은 먹 덩어리다.
- 출발점을 고른 간격으로 능선 전체에 깔지 않는다 — 같은 밀도의 술이 달린 털 난 돔이 된다. 다발로 놓고 다발 사이를 비운다.
- 양쪽 비탈을 같은 진하기로 칠하지 않는다 — 빛 방향이 없어 산이 납작한 무늬로 읽힌다.
- 산을 단색 실루엣으로 채우거나, 세로로 늘인 노이즈로 붓 결을 흉내 내지 않는다 — 종이를 오려 붙인 판지 무대나 긁힌 해칭처럼 보인다. 결은 비탈을 따라 내려가는 붓털 가닥이 만든다.
- 덧셈·screen·lighter·overlay 블렌딩, 발광, 노을·하늘 그라디언트를 쓰지 않는다 — 종이에 스민 먹이 아니라 화면에서 나오는 빛이 된다. 먹과 대자는 흡수로만 어두워진다.
- 대자를 넓게 칠하거나 짙게 하지 않는다 — 녹슨 산이나 노을이 된다. 주봉 왼쪽 허리에만, 마른 판에만, 화면의 8% 안.
- 대자를 먹 진하기(P)를 따라 칠하지 않는다 — 먹이 가장 짙은 꼭대기에 가장 많이 앉아 녹슨 갈색 모자·청동 원뿔이 된다. 대자는 먹과 따로 스민 얼룩 자리이고, 앉은 곳의 먹을 엷게 한다.
- 유령 보임으로 주봉 실루엣 전체를 미리 보여 주지 않는다 — 열림이 「발견」이 아니라 「아는 모양이 진해짐」이 된다. 꼭대기 끝만, 0.05 로.
- 첫 프레임 전에 모든 겹과 L0 를 다 굽지 않는다 — 느린 기기에서 3초 넘게 빈 종이만 보인다. t = 0 에 보이는 L5·L4 만 먼저, 나머지는 드러나는 차례대로.
- L0 몸통을 봉우리가 없는 열에도 칠하지 않는다 — 산덩이 왼쪽 밖에 밑선(0.55H)을 따라 자 대고 그은 수평 띠가 남는다.
- 종이 바탕을 남색·짙은 색이나 채도 높은 색으로 바꾸지 않는다 — 안개(종이색)와 먹의 농담이 읽히지 않는다. 바탕은 비 갠 아침의 엷은 회녹 종이다.
- 안개를 흰색(#FFFFFF)이나 종이보다 밝은 색으로 칠하지 않는다 — 빛나는 연기처럼 떠 보인다. 수묵의 안개는 먹이 닿지 않은 종이, 곧 종이색 그대로다.
- 해·달·새·배·정자·소나무·사람·폭포를 넣지 않는다 — 산수화 상투이고 시선이 주봉의 열림에서 흩어진다. 낙관·글자·서명도 넣지 않는다 — 작품이 아니라 간판이 된다.
- 비·눈·반짝이·떠다니는 입자를 넣지 않는다 — 움직임은 카메라 흐름과 안개, 마르는 먹뿐이어야 깊이가 읽힌다.
- 붓질 난수를 타일 번호로 시드하거나, 타일 밖 붓질의 난수 뽑기를 건너뛰지 않는다 — 타일마다 붓질이 달라져 경계에서 끊긴다.
- 타일을 투명 바탕으로 만들거나 화면에 직접 multiply 로 겹쳐 그리지 않는다 — 겹치는 48px 에서 먹이 두 번 곱해져 진한 세로 줄이 생긴다. 불투명 먹 타일을 작업 캔버스에 source-over 로 깐 뒤 한 번만 곱한다.
- 붓질 캔버스를 GPU 캔버스로 두지 않는다 — 타일마다 가장자리 안티에일리어싱이 달라 겹침 비교가 10 넘게 어긋나고, getImageData 가 느리다. willReadFrequently: true.
- 매 프레임 다시 굽거나, 타일 한 장을 한 프레임에 통째로 굽지 않는다 — 가까운 겹 타일 한 장이 30만 픽셀이라 느린 화면에서 몇 프레임이 툭 끊긴다. 24행씩 나눠 미리 굽는다.
- 안개 덩어리마다 매 프레임 createRadialGradient 를 만들지 않는다 — 한 프레임에 백 개 넘게 생긴다. 미리 그린 덩어리 그림을 늘려 쓴다.
- ctx.filter 블러로 먼 산이나 젖은 판을 흐리게 하지 않는다 — 매 프레임 비싸고 브라우저마다 다르다. 먼 산의 부드러움은 낮은 타일 해상도, 젖은 먹은 미리 구운 젖은 판으로 낸다.
- 젖은 판에서 마른 판으로 바뀌는 것을 크기 변화나 깜빡임으로 내지 않는다 — 먹이 마르는 게 아니라 화면이 바뀌는 것으로 읽힌다. 미리 구운 두 판을 kd 로 섞는다.
- 붓 가닥을 짧은 선분 여러 개(lineCap round)로 이어 그리지 않는다 — multiply 에서 이음매마다 둥근 끝이 겹쳐 염주 같은 진한 점이 줄지어 생긴다. 가닥(비백 구간)은 다각형 하나씩으로 채운다.
- 한지 섬유를 짙고 굵게 그리지 않는다 — 종이 결이 아니라 스캐너 유리 위에 떨어진 머리카락처럼 보인다.
- 주봉이 열릴 때 카메라를 완전히 멈추거나 확대하지 않는다 — 흐름이 끊기면 패럴랙스가 사라져 깊이가 납작해진다. 숨을 멈추듯 느려지고, 겹마다 다른 양으로 가라앉는 떠오름으로 깊이를 키운다.
- 모든 봉우리를 좌우 대칭·같은 높이로 만들지 않는다 — 톱니 무늬가 되어 기계가 찍은 티가 난다.
- 겹을 투명도로 깜빡이며 나타나게 하지 않는다 — 드러남은 위에서 내려오는 안개 앞선으로만 한다. 산은 능선부터 보여야 안개가 걷히는 것으로 읽힌다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 재생됩니다.
- 권장 모델
- Claude Opus 5.5 · Effort High
- 화면비
- 16:9 (가로)
- 재생 길이
- 30초
- 결과물
- HTML 파일 하나 · 따로 설치할 것 없음
- 영상 파일
- 위 「MP4 저장 기능 포함」을 켜면 결과 화면에 녹화 단추가 생깁니다 1080 기준 · 12 Mbps · 브라우저에 따라 MP4 또는 WebM
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 종이를 오래 묵어 누렇게 바랜 한지 빛으로, 먹은 갈색 기가 도는 먹으로 바꾸고, 가장 큰 뒷산 허리의 엷은 갈색 물감(대자)은 빼 줘.
- 산이 흐르는 속도를 지금의 절반으로 늦추고, 가장 큰 뒷산이 안개 밖으로 드러나는 주기를 60초로 늘려 줘.
- 안개를 더 짙게 해서 가까운 두 겹만 또렷하게 보이고, 가장 큰 뒷산은 드러나도 허리 아래가 구름에 잠긴 채 꼭대기만 또렷해지게 해 줘.
한국 미학