3D

나전 장롱과 빛

옻칠한 장롱 문의 자개 무늬가 등불 각도에 따라 은은한 무지갯빛으로 바뀐다.

프롬프트를 복사해 claude.ai 에 붙여 넣으면 바로 실행됩니다 · Opus 5.5 · Effort High 기준

나전 장롱과 빛 실행 화면

조작 등불을 끌어 옮기기 · 폰을 기울이면 빛 각도가 바뀐다

전체 화면으로

무대를 누르면 키보드로도 조작할 수 있습니다

권장 모델
Claude Opus 5.5 · Effort High
화면비
3:4 (세로)
조작
등불을 끌어 옮기기 · 폰을 기울이면 빛 각도가 바뀐다
결과물
HTML 파일 하나 · three.js 를 인터넷에서 불러옵니다

프롬프트

Claude 가 읽는 제작 지시문입니다. 맨 위 [바꿀 곳]의 값을 바꿔 붙여 넣어도 됩니다.

옻칠한 검은 나전 장롱 앞면에 작은 등불이 떠 있고, 등불을 끌어 옮기거나 폰을 기울이면 자개 조각마다 은은한 무지갯빛이 흘러가는 three.js 장면을 HTML 파일 하나로 만들어 줘.

핵심 아이디어: 자개의 빛은 물감 색이 아니라 얇은 막(박막)에서 빛이 겹쳐 생기는 간섭색이다. 장롱 앞면 전체를 직접 짠 셰이더 하나로 그리고, 자개 조각마다 막 두께와 기울기를 조금씩 다르게 줘서 등불이 움직이면 색의 띠가 조각에서 조각으로 건너간다. 색은 늘 저채도 — 진주빛 바탕 위에 옅은 금빛·연두·청록·연분홍이 스치는 정도다. 검은 옻칠은 거울처럼 등불의 작은 반사점을 품는다.

[바꿀 곳]
- seed: 20261009 — 자개 무늬(조각 갈라짐·매화 위치)를 정하는 시드
- iriSat: 0.40 — 간섭색 채도(0 = 회백색 진주빛, 1 = 원색 무지개). 0.30~0.50 밖으로 두지 않는다
- filmMin, filmMax: 380, 560 — 자개 막 두께 범위(nm)
- lampColor: #ffcf94, lampIntensity: 2.2
- autoLampPeriod: 8 — 입력이 없을 때 등불이 좌우로 한 번 왕복하는 초
- gyroGain: 1.0 — 기울기에 대한 빛 이동 배율

[크기]
- 화면 짧은 변을 S 라 한다. 기준 비율은 세로 3:4(폭 = S).
- 장롱 앞면은 월드 단위 폭 3.0 × 높이 4.0(정확히 3:4). 카메라 거리를 뒤에 나오는 카메라 절의 공식으로 맞춰서 3:4 화면에서 장롱 앞면이 화면 폭과 높이의 각각 90% 를 채운다. 즉 세로 화면에서는 1 월드 단위 ≈ 0.3S.
- 가로로 긴 화면에서는 장롱 높이가 화면 높이의 90% 가 되고 양옆에 어두운 방이 보인다. 장롱이 화면 한가운데 작게 떠 있는 구도는 실패다.
- 문짝의 둥근 자개 원판 지름 1.0 월드 = 약 0.30S. 매화 한 송이 지름 0.10~0.18 월드 = 0.03S~0.054S. 등불 몸통 높이 0.24 월드 ≈ 0.07S.

[장면]
장롱(앞면 기준 좌표: x ∈ [-1.5, 1.5], y ∈ [0, 4.0], 앞면 z = 0.5, 깊이 1.0):
- 몸체: BoxGeometry 3.0×4.0×1.0, 가운데 (0, 2.0, 0). 옆·위·뒤는 MeshStandardMaterial 색 #0d0b0a, roughness 0.25, metalness 0.
- 앞면: z = 0.502 에 PlaneGeometry(3.0, 4.0) 한 장, 이것만 직접 짠 ShaderMaterial(셰이더는 구현 절에서). uv (0,0) = 왼쪽 아래.
- 앞면 나눔(앞면 좌표, 모두 월드 단위): 아래 받침 y 0~0.25, 서랍 두 개 y 0.30~1.10(각 폭 1.42, 가운데 틈), 문짝 두 개 y 1.20~3.82(각 폭 1.42 × 높이 2.62), 위 처마 3.85~4.0. 나눔 선은 폭 0.012, 깊이 0.004 의 아주 어두운(#030202) 얇은 상자로 앞면 위에 얹는다.
- 장석(놋쇠 장식): 문짝마다 경첩 2개(0.14×0.22×0.02 상자), 두 문이 만나는 가운데에 둥근 앞바탕(반지름 0.16, 두께 0.02 원기둥) 1개, 서랍마다 고리 손잡이(TorusGeometry 반지름 0.06, 관 0.008) 1개. 재질 색 #a8844f, metalness 0.85, roughness 0.35.

자개 무늬(코드로 그림 — 구현 절의 데이터 캔버스):
- 끊음질 테두리: 문짝·서랍 안쪽 0.07 들어간 자리에 폭 0.05 띠. 띠를 길이 0.03~0.09 의 짧은 자개 조각으로 끊어 늘어놓고 조각 사이 틈 0.004.
- 주름질 원판: 문짝마다 가운데 위쪽(문짝 중심에서 위로 0.35)에 지름 1.0 원. 안을 보로노이(점 70개, 시드 난수)로 갈라 조각 하나하나를 자개로 채우고, 조각 사이 틈 0.006 은 옻칠이 드러난다(깨진 조개껍데기를 붙인 주름질 기법). 원 둘레에 폭 0.02 고리.
- 매화 가지: 문짝마다 아래 바깥 모서리에서 원판 쪽으로 오르는 가지 1개. 시드 난수로 굽는 곡선(마디 6~9개, 곁가지 2~3개), 가지를 길이 0.04 짧은 띠 조각으로 끊어 놓는다. 가지 위에 꽃 10~14송이, 꽃잎 5장이 각각 다른 자개 조각.
- 서랍: 가운데에 귀갑문(육각 거북등) 줄 한 줄, 육각 테두리선 폭 0.012.
- 왼쪽 문과 오른쪽 문은 거울 대칭이되 시드를 달리해 조각 갈라짐은 다르다(대칭 속의 비대칭).
- 앞면 넓이 중 자개가 차지하는 비율: 18~32%.

방:
- 바닥 PlaneGeometry 20×20, 색 #241c16, roughness 0.85. 뒤 벽(z = -1.2) 색 #3a332c, roughness 0.95 — 어두운 한지 벽.
- 장롱 밑 접지 그림자: 그림자 맵 대신 바닥 위 0.003 에 놓은 3.6×1.6 평면, CanvasTexture 의 방사 그라디언트(가운데 불투명도 0.6 → 가장자리 0), transparent, depthWrite false.
- 배경색·안개 #14110f, 안개 near 10, far 22.

등불:
- 작은 한지 초롱: 몸통 CylinderGeometry(반지름 0.08, 높이 0.24, 16면), MeshBasicMaterial 색 #ffd9a8. 위아래 놋쇠 테 두 개(반지름 0.085, 높이 0.015, 색 #a8844f). 그 안에 PointLight.
- 등불은 앞면에서 1.3 떨어진 평면 z = 1.8 위에서만 움직인다. 범위 x ∈ [-1.6, 1.6], y ∈ [0.6, 3.9].
- 입력이 없을 때 위치: x = -1.1·cos(2π·t / autoLampPeriod), y = 2.6 + 0.5·sin(2π·t / 11). t = 0 에 왼쪽 문 위(x = -1.1, y = 2.6), t = 4 초에 오른쪽 문 위(x = +1.1).
- 등불 둘레에 은은한 빛무리 하나: 등불을 따라다니는 스프라이트(CanvasTexture 방사 그라디언트, 지름 0.9 월드, AdditiveBlending, 불투명도 0.35). 이것 말고 발광 효과는 없다.

시그니처 순간: 시작 후 1.0~4.0 초, 등불이 왼쪽 문 원판 앞(x ≈ -0.7)에서 오른쪽 문 원판 앞(x ≈ +0.7)으로 지나가는 동안, 두 원판(각 지름 약 0.30S, 화면 위쪽 절반)의 조각들 위로 옅은 금빛 → 연두·청록 → 연분홍의 색 띠가 등불 움직임을 따라 조각에서 조각으로 건너간다. 같은 순간 검은 옻칠 위에는 등불의 작은 반사점(지름 약 0.02S)이 함께 미끄러진다. 첫 프레임부터 장롱 전체와 등불이 다 보인다 — 서서히 밝아지기나 빈 화면으로 시작하지 않는다.

안내 글: 화면 아래 가운데에 한 줄 「끌어서 등불 옮기기 · 폰을 기울여 빛 바꾸기」, 13px, 색 #cbbfa8, 불투명도 0.7. 3초부터 5초 사이에 0으로 사라지고 다시 나오지 않는다 — 불투명도는 매 프레임 t 로 계산해 넣는다(CSS transition 아님). pointer-events: none.

[카메라]
- PerspectiveCamera 세로 시야각 35°, near 0.1, far 40. 바라보는 점 (0, 2.0, 0.5).
- 거리 d = max(dH, dW). dH = (4.0 / 0.9) / (2·tan(17.5°)), dW = (3.0 / 0.9) / (2·tan(17.5°)·aspect). 검산: tan(17.5°) = 0.3153, 3:4(aspect 0.75)에서 dH = 4.444 / 0.6306 = 7.05, dW = 3.333 / 0.4730 = 7.05 — 둘이 같아 폭과 높이 모두 90% 를 채운다. 창 크기가 바뀔 때마다 다시 구한다.
- 카메라 위치 = 바라보는 점 + d·(sin(yaw)·cos(pitch), sin(pitch), cos(yaw)·cos(pitch)).
- 입력이 없을 때: yaw = 0.22·sin(2π·t / 14), pitch = 0.06 + 0.04·sin(2π·t / 9).
- 끌기: 화면 폭만큼 가로로 끌면 yaw 1.2 라디안, 화면 높이만큼 세로로 끌면 pitch 0.8 라디안. 범위 yaw ∈ [-0.45, 0.45], pitch ∈ [-0.05, 0.30].
- 손을 뗀 뒤 3초가 지나면 2초에 걸쳐(easeInOutCubic) 자동 움직임 값으로 섞여 돌아간다. 자동 움직임의 시간 t 는 그동안에도 흐른다.
- 자이로가 있으면 yaw 에 기울기 오프셋 최대 ±0.12 를 더한다(자세한 것은 구현 절).

[빛]
- renderer.toneMapping = THREE.ACESFilmicToneMapping, toneMappingExposure 1.0, renderer.outputEncoding = THREE.sRGBEncoding.
- 등불 PointLight: 색 lampColor(#ffcf94), 세기 lampIntensity(2.2), distance 9, decay 2. 그림자 끔.
- HemisphereLight: 위 #3a3026, 아래 #0e0b09, 세기 0.25.
- 실시간 그림자는 쓰지 않는다(renderer.shadowMap.enabled = false). 접지는 [장면]의 그라디언트 평면으로.
- 앞면 셰이더는 같은 등불 위치·색·세기를 유니폼으로 받아 직접 빛을 계산한다. 감쇠 atten = lampIntensity / (1 + (거리 / 1.6)²). 등불이 앞면에서 1.3 떨어져 있으므로 바로 앞 지점에서 atten ≈ 2.2 / 1.66 = 1.33.
- 앞면 환경광: 자개 부분은 shellBase × 0.06, 옻칠 부분은 0.01 을 더한다(셰이더 상수, CONFIG 에 이름 붙여 둔다) — 등불에서 먼 모서리 자개도 어렴풋이 보인다.

[구현]
- HTML 파일 하나로 만든다. 라이브러리는 three.js 한 줄만 쓴다: <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> — 이 r128 판에 없는 것(모듈 import, OrbitControls 같은 확장 파일, CapsuleGeometry, outputColorSpace)은 쓰지 않는다. 그 밖의 바깥 파일·글꼴·네트워크 요청은 쓰지 않는다.
- three.js 다음 <script> 맨 앞에 CONFIG 객체를 두고 [바꿀 곳]의 값과 이 글에 나온 숫자(크기·색·빛·속도)를 이름 붙여 모은다.
- 장면은 requestAnimationFrame 의 타임스탬프로 구한 시간 t(초)로 진행한다. 한 프레임에 밀린 시간은 0.1초까지만 반영한다. setTimeout·setInterval·CSS 애니메이션으로 움직이지 않는다.
- 난수는 시드 고정 난수(mulberry32 등)만 쓴다. Math.random 은 쓰지 않는다.
- 텍스처는 코드로 만든다(CanvasTexture·DataTexture). 바깥 그림 파일을 쓰지 않는다.
- 캔버스는 늘 창 전체 크기다. 창 크기가 바뀌면 카메라 비율과 렌더러 크기를 다시 맞춘다. 스크롤이 생기지 않게 한다. renderer.setPixelRatio 는 2 까지만.
- 입력이 없으면 카메라가 스스로 천천히 움직인다. 마우스로 끌거나 손가락으로 끌면 시점이 돈다(가로 = 좌우 회전, 세로 = 위아래 기울기, 범위 제한). 손을 떼고 3초 뒤 자동 움직임으로 부드럽게 돌아간다. 터치는 페이지 스크롤·확대가 일어나지 않게 막는다(touch-action: none).
- 시점 각도를 window.__view = { yaw, pitch } (라디안)에 두고 매 프레임 갱신한다. 카메라는 이 두 각도로 놓는다 — 자동 움직임도 끌기도 이 각도를 바꾼다.
- 브라우저 저장소(localStorage·sessionStorage·IndexedDB·쿠키)를 쓰지 않는다 — 미리보기 칸에서 막혀 오류가 난다.
- 탭이 숨으면 멈추고 돌아오면 이어서 한다.
- WebGL 을 못 쓰는 기기에서는 오류 대신 화면에 짧은 안내 문구를 띄운다.
- 글자를 쓰면 시스템 글꼴만: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif.
- 모든 함수·상수를 먼저 정의하고, 시작은 스크립트 맨 끝에서 한 번만 부른다.
- 콘솔에 오류가 나지 않게 한다. 스스로 대조하는 표는 console.table, 어긋남은 console.warn 으로만 낸다.

끌기 한 번이 두 가지를 함께 한다:
- 위 규칙대로 시점(yaw, pitch)을 돌리고, 동시에 등불을 손가락(마우스) 아래로 옮긴다 — 매 이동마다 포인터 위치에서 카메라 광선을 쏘아 평면 z = 1.8 과 만나는 점을 구하고 [장면]의 범위로 자른다. 등불은 그 점을 1 − exp(−dt·12) 비율로 따라간다.
- pointerdown·pointermove·pointerup·pointercancel 로 마우스와 터치를 함께 처리하고 setPointerCapture 를 쓴다. 손을 뗀 뒤 3초 동안 등불은 그 자리에 머물고, 이어 2초에 걸쳐 자동 경로로 섞여 돌아간다(카메라와 같은 타이밍).

자이로:
- deviceorientation 의 beta·gamma 를 쓴다. 첫 값을 기준(beta0, gamma0)으로 잡고 차이를 ±25° 로 자른다. 등불 위치 오프셋 x += gyroGain·0.9·(Δgamma / 25), y −= gyroGain·0.7·(Δbeta / 25), yaw 오프셋 += 0.12·(Δgamma / 25). 오프셋은 1 − exp(−dt·6) 로 부드럽게 따라간다. 등불은 오프셋을 더한 뒤에도 범위 안으로 자른다.
- iOS: DeviceOrientationEvent.requestPermission 이 함수로 있으면, 첫 pointerup 또는 touchend 처리기 안에서 한 번만 부른다(사용자 동작 안이어야 허락 창이 뜬다). 'granted' 면 리스너를 붙인다. 거절·실패·함수 없음은 조용히 넘어가고, 실패만 console.warn 한 줄. 함수가 없는 기기(안드로이드)는 시작할 때 바로 리스너를 붙인다. 리스너 등록은 try 로 감싼다.

데이터 캔버스(자개 무늬):
- 1536×2048 캔버스 하나에 앞면 전체를 그린다(1 월드 = 512px). 바탕은 검정 rgb(0,0,0).
- 자개 조각마다 fillStyle = rgb(255, g, b): R = 자개 여부, G = 조각의 막 두께 값(0~255, 시드 난수), B = 조각의 기울기 방향(0~255 → 0~2π, 시드 난수). 조각은 실제 모양보다 1.5px 안쪽으로 줄여 그려 조각 사이에 검은 틈이 남게 한다.
- 캔버스 픽셀 (px, py) = ((x + 1.5)·512, (4.0 − y)·512) — 캔버스 위쪽이 앞면 위쪽. CanvasTexture 의 flipY 는 기본값(true) 그대로 두면 uv (0,0) = 왼쪽 아래와 맞는다. LinearFilter, 밉맵 없음(generateMipmaps = false), 색 공간 변환 없음(데이터로 읽는다).
- 자개 넓이 비율은 캔버스 R 채널 픽셀을 세어 구한다(4px 간격 표본이면 충분).

앞면 ShaderMaterial(이 작품의 심장):
- 유니폼: uData, uLampPos(월드), uLampColor, uLampIntensity, uSat(= iriSat), uFilmMin, uFilmMax. 카메라 위치는 three.js 가 주는 cameraPosition.
- 꼭짓점 셰이더에서 월드 위치와 uv 를 넘긴다.
- 조각 셰이더 순서:
  1. tex = texture2D(uData, vUv). m = tex.r, g = tex.g, ang = tex.b·2π.
  2. 법선: 평면 법선 (0,0,1)에 조각 기울기 — N = normalize(vec3(cos(ang)·0.12·m, sin(ang)·0.12·m, 1)). 조각마다 빛을 받는 각도가 달라야 무늬가 살아난다.
  3. 막 두께 d = mix(uFilmMin, uFilmMax, g) + 18·sin(120·dot(p, dir) + 0.6·sin(37·dot(p, perp))) — p = 앞면 위 월드 좌표(xy), dir = (cos ang, sin ang), perp 는 그 수직. 조개 성장선 같은 잔결이다(줄 간격 약 0.05 월드).
  4. 박막 간섭 함수 film(d, cosI): n = 1.56, cosT = sqrt(1 − (1 − cosI²) / n²), 파장 λ = (650, 540, 460) nm 마다 δ = 4π·n·d·cosT / λ, 값 = 0.5 − 0.5·cos(δ). 이를 묶은 iri(d, cosI): 두 겹 f = 0.6·film(d, cosI) + 0.4·film(1.37·d, cosI), 그다음 채도 낮추기 f = mix(vec3(dot(f, (0.299, 0.587, 0.114))), f, uSat).
  5. L = 등불 방향, V = 카메라 방향, H = normalize(L+V). atten = [빛]의 감쇠식. lamp = uLampColor·atten.
  6. 자개: shellBase = #d8d2c4(선형 값으로). fV = iri(d, max(dot(N,V),0)), fS = iri(d, max(dot(L,H),0)). shell = shellBase·(0.55 + 0.45·fV)·max(dot(N,L),0)·lamp·0.9 + fS·pow(max(dot(N,H),0), 40)·1.2·lamp + shellBase·0.06.
  7. 옻칠: N0 = (0,0,1). 프레넬 F = 0.04 + 0.96·(1 − dot(N0,V))⁵. lacquer = #0b0908·max(dot(N0,L),0)·lamp·0.3 + lamp·(pow(max(dot(N0,H),0), 600)·3.0 + pow(max(dot(N0,H),0), 18)·0.04)·(0.5 + F) + 0.01. 600 제곱 항이 거울 같은 작은 반사점이다.
  8. gl_FragColor = vec4(mix(lacquer, shell, m), 1.0). 그 뒤에 #include <tonemapping_fragment> 와 #include <encodings_fragment> 를 넣어 다른 재질과 같은 톤 매핑·sRGB 출력을 받는다.
- 같은 film·iri 함수를 JS 로도 만들어 아래 대조에 쓴다(uSat 은 CONFIG.iriSat).

시작할 때 console.table 로 대조하고 범위를 벗어나면 console.warn:
- 삼각형 수(renderer.info.render.triangles, 첫 프레임 뒤): 30만 이하. 기대 2만 이하.
- 자개 넓이 비율: 0.18~0.32.
- 자개 조각 수: 400 이상(원판 보로노이 140 + 꽃잎 + 끊음질 조각).
- 3:4 기준 장롱 높이 / 화면 높이 = 4.0 / (2·d·tan 17.5°): 0.88~0.92.
- JS iri(두 겹 + 채도 낮추기까지)로 d = 470, cosI = 1.0 과 0.8 의 색: 두 색의 RGB 차이 절댓값 합 ≥ 0.15(각도에 따라 색이 실제로 바뀐다 — 기대 약 0.39), 각 색의 최대 채널 − 최소 채널 ≤ 0.40(저채도 — 기대 약 0.10 과 0.38). 검산값: cosI 1.0 → 약 (0.45, 0.55, 0.52) 옅은 청록 회백, cosI 0.8 → 약 (0.42, 0.76, 0.38) 옅은 연두.
- 등불 자동 경로의 x 범위: −1.1~+1.1, t = 4 초에 x ≈ +1.1.
- pixelRatio ≤ 2.

[하지 말 것]
- 반짝이 입자·먼지·별 효과를 더하지 않는다 — 자개 자체의 빛 흐름이 주인공이고 입자는 그것을 덮는다.
- 렌즈 플레어·블룸·후처리(EffectComposer)를 쓰지 않는다 — r128 확장 파일이 필요하고 폰에서 무겁다.
- 간섭색을 원색 무지개(색상환 그라디언트, HSL 돌리기)로 대신하지 않는다 — 자개는 박막 간섭의 저채도 색이어야 하고, 원색이면 장난감처럼 보인다.
- 자개 무늬를 그림 파일이나 글자·이모지로 대신하지 않는다 — 조각은 캔버스 도형으로 직접 그린다.
- 화면 모서리에 제목·라벨·버튼·설정 패널을 두지 않는다 — 장롱이 화면을 채워야 한다. 글자는 사라지는 안내 한 줄뿐.
- 등불 말고 다른 발광체·불꽃 입자·깜빡이는 촛불 흔들림 과장을 넣지 않는다 — 빛의 움직임은 사용자의 손과 자동 경로만이 만든다.
- 태극·한복·단청 같은 상징 그림을 붙이지 않는다 — 한국다움은 옻칠·자개·놋쇠 장석이라는 재료로 충분하다.
- 바탕(벽·바닥·배경)에 남청색이나 채도 높은 색을 쓰지 않는다 — 어두운 따뜻한 방이어야 자개 색이 산다.
- 자이로 허락을 받으려고 시작하자마자 전체 화면 버튼이나 팝업을 띄우지 않는다 — 첫 화면은 장롱이어야 한다.
- 바깥 글꼴·그림·소리 파일을 불러오지 않는다.

사용 방법

필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다

  1. claude.ai 에서 새 대화를 엽니다.
  2. 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
  3. 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 바로 해 볼 수 있습니다.

추가 요청 예시

결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.

  • 자개 무늬를 학과 구름 무늬로 바꿔 줘
  • 등불이 천천히 저절로 움직이는 구경 모드를 넣어 줘
  • 장롱 문이 열리며 안이 보이게 해 줘

3D

같은 분류의 다른 작품

3D 전체 보기

모든 게임·3D 보기 AI 레시피 전체 RSS 로 새 글 받기