Communitygithub.com

perspective-illusion

일본식 착시 원근법 스타일(예: @garments__)로 거리, 교차로, 역, 가게 통로, 방, 공원, 옥상 등 어떤 장소든 반복되는 3D 일러스트레이션 비디오를 제작합니다. 깨끗한 선화 장면이 망원 렌즈에 의해 평평하게 눌렸다가, 돌리 줌과 함께 깊은 원근감으로 펼쳐지며, 평면처럼 보였다가 3D로 전환되는 코너 뷰로 전환되고, 뷰가 평평한 동안 물체가 조용히 깊이를 교환합니다.

perspective-illusion란 무엇인가요?

이 스킬은 사용자가 제공한 장소(예: 거리, 교차로, 방)에 대해 일본식 착시 원근법 스타일의 3D 루프 비디오를 생성하는 시스템입니다. 주로 Claude Code와 같은 AI 에이전트에서 사용되며, Node.js 기반의 Three.js와 Playwright를 활용하여 브라우저에서 3D 장면을 렌더링하고, FFmpeg로 비디오를 인코딩합니다. 스킬의 핵심은 평평한 2D 선화처럼 보이는 장면이 망원 렌즈 효과(dolly zoom)와 카메라 움직임을 통해 깊이 있는 3D 공간으로 전환되는 착시 효과를 만드는 데 있습니다. 템플릿(예: street.html, scramble_night.html)을 제공하여 사용자가 원하는 장면을 쉽게 커스터마이즈할 수 있으며, 깨끗한 잉크 선화, 투톤 쉐이딩, 해칭 처리된 그림자, 페인팅된 하늘 등 독특한 시각적 스타일을 구현합니다. 10초 내외의 부드러운 루프를 목표로 하며, 미리보기 및 품질 검사(blink_map)를 통해 완성도를 높입니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/wsxiaolin/perspective-illusion/tree/main

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

원근법 착시 (Perspective Illusion)

스타일, 한 문단으로 설명

수평 유지된 카메라가 매우 긴 렌즈(약 400m 거리에서 2°)로 깊은 장면을 평평한 그림처럼 보이게 만듭니다. 그런 다음 카메라는 돌리 줌(dolly zoom)을 수행합니다. 렌즈가 넓어지면서 카메라는 앞으로 이동하여 피사체 크기는 동일하게 유지되지만 뒤쪽 공간은 펼쳐집니다. 카메라는 또한 코너 뷰(corner view)로 선회합니다. 각 코너 뷰는 평평하게 시작하여 3D로 열립니다. 뷰가 평평한 동안 물체는 아무도 볼 수 없는 깊이에서 미끄러져 이동하므로, 깊이가 돌아왔을 때 행이 대각선이 됩니다. 모든 것은 깨끗한 잉크 선화와 평평한 투톤 쉐이딩(two-tone shading), 해칭(hatching) 처리된 그림자, 페인팅된 하늘로 그려지며, 전체가 끊김 없이 반복됩니다.

빠른 시작

mkdir -p ~/Movies/edits/<date>-<name>/stills && cd $_
printf '{"type":"module"}' > package.json && npm i [email protected] [email protected]
cp -r ~/simcraft/skills/video/perspective-illusion/templates/* .
cp scramble_night.html scene.html                                # 또는 가장 가까운 템플릿(아래 참조)을 복사한 후 편집
node render.mjs scene.html x --stills=0.3,1.5,3,6,8.5           # 각 샷의 스틸 이미지, stills/ 폴더에 저장
node render_fast.mjs scene.html preview.mp4 --preview            # 전체 루프, 절반 크기, 약 1.5분 소요
node render_fast.mjs scene.html final.mp4                        # 전체 품질, 10초 기준 약 30분 소요
python3 ~/simcraft/skills/video/vox-explainer/scripts/motion_check.py final.mp4
python3 ~/simcraft/skills/video/perspective-illusion/scripts/blink_map.py ffmpeg final.mp4 blink.png

스틸 이미지와 미리보기에서 문제를 수정하세요. 미리보기가 승인된 후에만 전체 품질로 렌더링하세요. ffmpeg가 PATH에 없는 경우 $FFMPEG를 설정하세요.

템플릿

새 장면과 가장 가까운 템플릿에서 시작하세요. 카메라 리그, 타임라인, 포스트 패스 및 렌더 후크는 유지하고, 장면 섹션(// ---------- kit와 // ---------- cameras 사이의 모든 내용)만 교체하세요.

템플릿장면렌더러
street.html, street_plus.html기둥, 원뿔, 횡단보도, 빨간 하늘이 있는 낮 거리. street_plus는 세부 사항과 코너 뷰를 추가합니다.render_smooth.mjs
market.html, market_real.html슈퍼마켓 통로, 그림과 사실적 스타일.render_smooth.mjs (market.html: render.mjs)
scramble_night.html비오는 밤의 큰 횡단보도, 걷는 군중, 젖은 반사, 광고판. 모든 속도 향상 기능이 포함되어 있습니다.render_fast.mjs

새 장면의 경우 scramble_night.html이 엔진의 최상의 베이스입니다. 유일하게 빠른 렌더러 후크를 가지고 있기 때문입니다. 낮 모양을 위해 밤 관련 기능을 제거하세요(reference.md 참조).

1단계: 눈을 속일 수 있는 장면 만들기

다음 조건을 갖춘 모든 장소가 가능합니다.

  • 하나의 긴 축. 장면을 -z 축(거리, 통로, 플랫폼, 복도)을 따라 배치하고 카메라는 그 축을 바라봅니다. 이 축을 따른 깊이는 망원 렌즈가 숨깁니다.
  • 서로 다른 깊이에 반복되는 사물. 기둥, 램프, 선반, 기둥, 표지판, 나무, 사람들. 망원 렌즈에서는 하나의 평면으로 쌓입니다. 일렬로 늘어선 기둥은 하나로 합쳐지고, 먼 파사드의 표지판 더미는 하나의 벽으로 쌓입니다.
  • 교환 물체. 3~6개의 유사한 물체(원뿔, 사람, 쓰레기통, 의자, 상자)로, 배열이 행(row)과 대각선(diagonal) 사이에서 변경됩니다. 평평한 뷰(cs)에서 더 크게 그릴 수도 있어, 평평해졌을 때 크기가 맞게 보입니다.
  • 미터 단위의 실제 세계 스케일. 렌즈와 초점면은 실제 스케일에서만 의미가 있습니다: 0.75m 원뿔, 1.7m 사람, 3.2m 층.
  • 코너 뷰가 보이는 곳의 세부 사항. 모든 코너 뷰는 건물 측면, 물체 뒷면, 바닥을 드러냅니다. 이를 채우세요: 측면 창문, 배수구, 케이블, 연석, 자동차 아래의 접촉 그림자. 평평한 뷰는 대충 만든 것을 숨기고 코너 뷰는 드러냅니다.
  • 모든 세부 사항을 하나의 표면의 일부로 만들거나(텍스처, 적층 슬리브), 별도의 표면을 최소 0.4m 이상 떨어뜨려 놓으세요. 400m 거리에서 몇 cm 떨어진 표면은 깜빡입니다.

2단계: 카메라 안무 짜기

카메라는 절대 기울이지 않습니다. 수평을 유지하여 수직선이 건축 도면처럼 수직으로 유지됩니다. 각 샷은 일반 객체입니다:

{ x, y, zf, W, fov, H, el, yaw, lay, cs }

키의미
x, y카메라 측면 위치 및 높이 (m)
zf, W초점면 및 거기서 구성되는 너비 (m). fov를 변경하면서 W를 유지하는 것이 돌리 줌입니다.
fov수평 렌즈 각도. 약 2°는 평평함, 25~90°는 깊이감이 있습니다.
H렌즈 시프트: 수평선이 위치하는 곳 (-1 아래, 0 중간, 1 위).
el카메라를 올리고 렌즈를 시프트하여 아래를 내려다봄. 절대 기울이지 않음.
yaw초점을 중심으로 회전하여 코너 뷰 생성.
lay, cs교환 물체 레이아웃 (0 행, 1 대각선) 및 평평한 뷰에서의 스케일.

작동하는 샷 어휘:

샷설정
평평함fov 약 2, H 0. 전체 장면이 하나의 입면도로 읽힙니다.
벌레 눈무릎 높이, fov 90, 수평선 낮게 (H 약 -0.7).
높은 곳중간 렌즈로 카메라 높이, 수평선이 프레임 위에 있음.
코너 뷰yaw ±1835°, el 950°. 한 쌍으로 제공됨: 먼저 망원 버전(fov 4), 그런 다음 같은 뷰의 광각 버전(fov 25~45).
대각선 및 낮음교환 물체를 깊이감 있게 보여주는 중간 렌즈.

루프 구조. 평평함, 그 다음 깊은 뷰, 그 다음 코너 뷰(평평함에서 3D로), 다시 평평함(여기서 물체 교환), 높은 대각선, 다른 코너 뷰, 낮은 샷, 다시 평평함. 루프는 시작한 샷으로 끝납니다.

타이밍. 모든 매개변수는 키 타임(key time)을 통해 하나의 모노톤 3차 곡선(Fritsch-Carlson)을 따르므로 속도가 절대 튀지 않습니다. W와 fov는 로그 공간에서 보간합니다. 작은 "드리프트" 샷(W × 0.96)을 추가하여 홀드가 멈추지 않고 숨 쉬게 합니다. 10초가 부드럽게 느껴지고, 7초는 성급하게 느껴집니다. 길이를 변경하려면 키 타임을 다시 쓰지 말고 스케일링하세요.

평평할 때 속임수 쓰기. 평평한 홀드 동안에만 lay를 변경하세요. 카메라가 깊이에서 움직이는 동안 물체가 움직이면 속임수가 드러납니다.

첫 번째 코너 뷰는 주인공 물체(자동차, 표지판)가 가리지 않는 쪽으로 가야 합니다. 그래야 드러남(reveal)이 새로운 것을 보여줍니다.

3단계: 비주얼 스타일

페이지는 4개의 패스를 렌더링합니다: 컬러, 깊이를 가진 노멀, 플랫 컬러(조명 없음), 태양 전용 그림자 마스크. 최종 셰이더가 이를 결합합니다.

  • 잉크 라인은 깊이(2차 미분), 노멀 또는 플랫 컬러가 변하는 곳에 그려집니다. 가까운 곳은 라인을 더 굵게 만듭니다. 바닥 표시는 얇은 회색 라인을 얻습니다. 한 픽셀이 수 미터를 덮거나 먼 텍스처가 노이즈가 될 때 컬러 라인을 페이드아웃합니다.
  • 투톤 쉐이딩(two-tone shading). 그림자와 빛, 그라데이션 없음. 마스크 라이트를 2*PI로 설정합니다. three.js가 Lambert를 pi로 나누기 때문이며, 그렇지 않으면 모든 것이 그림자로 변합니다.
  • 해칭(hatching)은 캐스트 그림자에만 사용하며, 해칭은 월드 공간(절대 스크린 공간 아님)에 고정되고 fwidth로 페이드아웃됩니다.
  • 하늘은 시야 방향으로 페인팅하고, 화면 위치로 하지 않아 카메라가 움직여도 구름이 제자리에 있습니다. 구름 가장자리는 부드럽게 유지합니다.
  • 팔레트. 6~10개의 플랫 컬러와 하나의 액센트 컬러(빨간 하늘, 노란 표지판). 참조 이미지를 일치시키려면 k-means로 프레임에서 컬러를 추출하세요.
  • 조명. 하나의 낮고 단단한 태양을 주요 피사체가 계속 조명을 받고 바닥에 긴 그림자를 드리우도록 배치합니다. 키가 큰 쪽에서 들어오는 햇빛은 장면을 하나의 큰 그림자에 묻어버립니다.

야간 변형(달빛, 조명 켜진 창문, 젖은 반사, 글로우, 빗방울, 그레이딩), 걷는 군중, 광고판 및 표지판에 대해서는 reference.md를 참조하세요.

scramble_night.html에서 낮 모양을 얻으려면:

  • 달을 따뜻한 태양으로 교체하고, 주변광을 높입니다.
  • 점광(point-light) 루프를 제거합니다.
  • 포스트 셰이더에서 젖은 바닥 블록, 헤이즈 라인, 야간 그레이딩을 제거합니다.
  • 빗방울을 제거하고, 창문의 emissiveIntensity를 0으로 설정합니다.
  • skyAt을 낮 하늘로 페인팅합니다.

4단계: 디테일 패스 (작품을 훌륭하게 만드는 요소)

첫 번째 버전은 항상 텅 빈 느낌입니다. 훌륭한 버전은 의도적인 패스를 통해 나오며, 각 패스는 다음 패스를 시작하기 전에 스틸 이미지에서 확인됩니다.

  1. 형태. 실제 스케일의 주요 덩어리, 긴 축, 교환 물체, 카메라 타임라인. 평평한 샷이 정말 평평하게 읽히는지, 모든 움직임이 무언가를 드러내는지 확인하세요.
  2. 구조. 실제 장소가 가진 모든 것, 실제 세계의 논리로 구축. 거리의 경우 연석, 배수구, 맨홀, 팔과 상자가 있는 기둥, 양면 표지판, 울타리, 쓰레기통, 자판기, 자전거 거치대, 가공 전선, 창틀과 에어컨 유닛이 있는 창문 프레임을 의미합니다. 실제 장소의 사진을 보고 구축하기 전에 무엇이 있는지 나열하세요.
  3. 마모와 스토리. 생활감을 주는 작은 것들: 스티커, 테이프, 코드로 그려진 텍스트가 있는 포스터, 웅덩이 자국, 기대어 놓인 자전거, 빨래, 고양이, 커피 컵. 카메라가 머무는 표면당 3개 이상을 목표로 하세요.
  4. 코너 뷰 감사. 각 코너 뷰에 대해 광각 스틸을 렌더링하고 거기서 텅 빈 부분을 채우세요. 일반적으로 건물 측면, 표지판 뒷면, 옥상(탱크, 난간, 안테나), 물체 사이의 바닥입니다.
  5. 빛과 그림자. 모든 물체가 읽을 수 있는 그림자를 드리우는지 확인하고, 주차된 물체 아래에 접촉 그림자를 추가하고, 모든 주요 형태의 그림자 면이 투톤 단계에 포함되도록 하세요. 한 뷰에만 나타나는 그림자는 카메라 움직임에 대한 보상입니다.
  6. 생동감 (선택 사항). 일정한 속도로 움직이는 보행자, 기차 또는 자동차, 스크린, 새, 깃발. 모든 것이 정수 배로 반복됩니다.
  7. 마무리. 팔레트 조화, 선 굵기, 하늘, 비네트 및 그레이딩. 그런 다음 최종 깜빡임 검사.

밀도 규칙: 평평한 망원 샷에서 프레임은 가장자리부터 가장자리까지 가득 차 있어야 하며, 밀도 높은 일러스트레이션처럼 느껴져야 하고, 의도적인 시각적 휴식 공간이 아니라면 큰 빈 벽이나 바닥 면적이 없어야 합니다.

품질 기준

전체 렌더링 전에 모든 키 타임(및 이동 중간 지점)의 스틸 이미지를 다음 목록에 대해 확인하세요. 실패하는 항목을 수정한 후 다시 확인하세요.

  • 평평한 샷이 평평한 그림처럼 읽힙니다. 깊이를 암시하는 것이 없습니다.
  • 모든 움직임은 드러남으로 끝납니다: 숨겨진 대각선, 옆길, 멀리 떨어져 있던 물체들.
  • 코너 뷰는 평평하게 시작하여 펼쳐지고, 첫 번째 코너 뷰는 주인공 물체에서 멀어집니다.
  • 선은 선명하고 고르며, 이중선, 빛나는 면의 잉크, 먼 텍스처의 노이즈가 없습니다.
  • 투톤 쉐이딩은 모든 주요 형태에서 명확하고, 해칭은 캐스트 그림자에만 나타납니다.
  • 어떤 것도 기본 프리미티브처럼 읽히지 않습니다. 모든 상자에는 트림, 이음새, 베이스 또는 라벨이 있습니다.
  • 텍스트는 읽을 수 있고 맞춤법이 올바르며, 실제 폰트를 사용하고, 가짜 브랜드를 사용합니다.
  • 하늘은 장면의 빛 방향 및 색상과 일치합니다.
  • 팔레트는 제한적이고 조화로우며, 하나의 명확한 액센트가 있습니다.
  • 루프 이음새가 보이지 않으며(프레임 0이 마지막 프레임과 동일) 아무것도 튀지 않습니다.
  • 미리보기에 끊김이 없습니다. 최종본에서 blink_map 카운트가 낮고, 깜빡이는 것은 의도된 움직임(비, 스크린)입니다.

스틸을 검토할 때 일러스트레이터처럼 보세요: 각 프레임의 가장 약한 세 영역을 지목하고, 그것들을 수정하고, 반복하세요. "작동한다"에서 멈추지 마세요.

5단계: 렌더링 및 확인

  • **render_fast.mjs**는 각 프레임을 지터링된 서브 렌더의 평균으로 GPU에서 렌더링합니다: 안티앨리어싱을 위해 4개, 빠른 움직임에서 끊김 없는 모션 블러를 위해 최대 32개, 크기는 window.probe 포인트가 화면에서 얼마나 이동하는지에 따라 결정됩니다. --preview는 30fps의 절반 크기입니다. --from, --to 및 --fps도 사용 가능합니다.
  • **render_smooth.mjs**는 renderMany가 없는 페이지에 대해 동일한 품질을 제공하지만 ffmpeg에서 평균을 내므로 몇 배 더 느립니다.
  • 로드 시 재질별로 정적 메시를 병합합니다(BufferGeometryUtils.mergeGeometries). 한 장면은 7,654개의 드로우 콜에서 530개로 약 7배 빨라졌습니다. 움직이는 것은 병합에서 제외하세요.
  • motion_check.py(멈춤 없음) 및 **blink_map.py**로 최종본을 확인합니다. blink_map.py는 거의 정지된 프레임에서 깜빡이는 픽셀을 계산합니다. 변경 전후의 카운트를 비교하세요. 미리보기는 설계상 반짝이므로 최종본에서만 깜빡임을 판단하세요.

페이지 규약

모든 장면 페이지는 다음을 제공하는 경우 렌더러와 작동합니다:

후크역할
#frame스크린샷이 찍히는 캔버스.
window.READY에셋과 폰트가 로드되면 설정.
window.render(ms)시간 ms에서 프레임을 그립니다.
window.probe(ms)몇몇 월드 포인트의 화면 위치를 반환하며, 모션 블러 크기 조정에 사용됩니다.
window.setJitter(x, y)서브픽셀 렌즈 오프셋(픽셀 단위), projectionMatrix.elements[8] 및 [9]를 통해 설정됩니다.
window.renderMany([[ms, jx, jy], ...])한 번의 호출로 여러 렌더를 평균화합니다(render_fast에 필요).
?scale=.5절반 크기로 렌더링합니다.

애니메이션되는 모든 것은 ms의 순수 함수여야 하며, 루프 길이에서 주기적이어야 합니다. 단계, 기어감, 비는 루프당 정수 사이클을 사용하세요.

깜빡임 및 결함

원인수정
화면 공간에 있는 모든 것(흔들림, 해칭, 노이즈, 카메라 거리에 따른 헤이즈)월드 또는 시야 방향에 고정하세요.
몇 cm 떨어진 표면하나의 표면으로 병합하거나, 0.4m 이상 떨어뜨리세요.
겹치는 동일 평면(도로 표시, 바닥 조각)표시를 하나의 평면에 페인팅하고, 조각이 가장자리에서 가장자리로 맞닿게 하세요.
서브픽셀 디테일(난간, 그리드, 케이블, 비)모든 프레임에서 지터링 안티앨리어싱을 사용하고, 밀도 높은 선은 픽셀당 미터로 페이드아웃하세요.
프레임당 하나의 렌더최종본에서 이렇게 하지 마세요.
구름이나 반사의 단단한 가장자리부드럽게 만들고, 화면 경계에서 효과를 페이드아웃하세요.
감싸거나 튀는 움직이는 물체window.probe로 찾은 루프의 가장 빠른 카메라 이동 중에 감싸세요.

함정

  • 한 줄 코드에 추가된 // 주석은 줄의 나머지 부분(scene.add, 조명 등)을 삼킵니다. /* */를 사용하세요.
  • 장면 루트에 추가하는 도우미(예: plane())는 현재 그룹을 무시하고 물체 위에 놓일 수 있습니다. 그룹 내부에서 구축하세요.
  • 방사 표면(스크린, 표지판 패널)을 플랫 컬러 및 그림자 패스에서 제외하세요. 그렇지 않으면 모든 글리프에 잉크가 칠해지고 어두워집니다.
  • ffmpeg -stream_loop ... -c copy는 일부 플레이어가 일찍 중지하는 루프를 만듭니다. 대신 다시 인코딩하세요.
  • X의 경우, 32:9 사이드 바이 사이드는 업로드하기에 너무 넓습니다. 수직으로 쌓으세요.
  • 원래 캐릭터와 가짜 브랜드만 사용하세요. 참조의 기술을 복사하고, 아트워크는 절대 복사하지 마세요.

관련 스킬