model-by-coding 是做什麼的?
透過程式碼從照片建立實體物件的3D模型,再將其轉化為拆解展示影片和GIF。此流程對一件事有明確的堅持:模型在每一步都與照片進行驗證,因此影片呈現的是被拍攝的實際物體,而非僅是合理的推測。
何時使用
- 需要透過短影片解釋或展示一個硬表面產品的內部結構(如風扇、燈具、喇叭、相機、工具、廚房家電);
- 物件實際存在,且能從多個角度拍攝照片;
- 輸出需為可用於 README、貼文或展示影片的循環拆解片段;
- 現有照片集需轉變為可編輯、可重新渲染的模型——而非掃描網格。
何時不使用
- 需要照片級逼真的資產:請改用攝影測量或掃描器;
- 物件為有機、柔軟或會變形的材質(布料、植物、臉部);
- 沒有可用照片,或物件只能在影片中看到;
- 單張靜態圖片就已足夠——則不需要 HyperFrames 的步驟;
- 主題為文字/UI動畫:此流程專注於幾何形狀。
流程
| 步驟 | 產出 | 驗收標準 |
|---|---|---|
| 1. 測量 | 一個尺規尺寸、比例表、一個 DIM 物件 | 比例在 ≥2 張照片上交叉比對,每個假設標記為推測 |
| 2. 建模 | 程式碼建立的 Three.js 場景、一個 IIFE 套件、檢視器頁面 | 組合狀態無縫隙;p = 1 時每個零件分離 |
| 3. 驗證 | 六張相同姿勢的照片/渲染比對圖 | 所有六張圖的輪廓和特徵比例在容許範圍內 |
| 4. 影片製作 | HyperFrames 組合、check 報告、交付 MP4 | ok: true,無錯誤,時長等於 data-duration |
| 5. 剪輯 | 循環 GIF | 起始與結束於穩定姿勢,小於 ~1 MB |
單一物件通常需要花費數小時到兩天,大部分時間在步驟 2–3。跳過步驟 3 就是讓一週的建模工作變成按時交付但錯誤的模型。
步驟 1 — 從照片測量
收集六個姿勢(正面、正側面、背面、後3/4側面、頂面、底面),選擇一個大尺寸作為尺規,用徑向掃描測量它,並將所有其他尺寸表示為其比例。根據角度週期數算離散特徵(肋條、葉片、輻條),而非憑肉眼估算。將所有數字保存在一個 DIM 物件中,並用註解標註其來源照片名稱及是測量值還是推測值;絕對的真實世界尺寸是決策,不是測量值。
詳細說明:references/01-modeling.md §0–2。
步驟 2 — 程式碼建模
一個場景模組,具有純狀態 API:create、setView、setExplode、setExplodeMap、setSpin、setHeadTilt、setLift、render、getAnchors、resize、dispose。世界慣例:物件面向 +Z,底部在 y = 0,單位為公尺。將外殼建為 lathe 輪廓,格柵建為扁平帶狀(非圓管),葉片建為參數化曲面,並透過一個函數註冊每個零件,該函數儲存其組合位置、爆炸偏移量和錨點偏移量。零件名稱只定義一次;影片、標籤、錨點和聲音都以此名稱作為索引。
詳細說明:references/01-modeling.md §3–6。
步驟 3 — 與照片驗證
使用 esbuild 建立套件,在無頭 Chrome 中為每個姿勢渲染一幀,並將每個渲染結果與其照片以物件 bounding box 裁切後並排比對。以物件自身 bounding box 的比例來讀取比例和零件位置;不要在複雜背景上對邊緣做圓形擬合——擬合會抓住背景弧線。六個姿勢,每個姿勢迭代數次。
詳細說明:references/03-verify.md。
步驟 4 — 渲染影片
按照 HyperFrames 合約建立組合:一個帶有 data-duration 的根組合元素、一個在 window.__timelines["main"] 註冊的暫停 GSAP 時間軸(僅由 hf-seek 重新繪製 Three.js,不透過 rAF/定時器)、帶有 class="clip"、data-start/data-duration 屬性的媒體、一個音訊混合作為音訊片段、28 px 或更大的文字,以及將大型套件放在 assets/vendor/*.min.js。在製作動畫前先編寫鏡位表;從一個導演腳本驅動相機、爆炸和標籤,以便音訊提示可共用其常數。執行 check,閱讀報告,然後執行 render --quality delivery。
詳細說明:references/02-render.md。
步驟 5 — 剪輯 GIF
挑選一個 3–4 秒的穩定片段(通常是爆炸分解後的停頓),用 gif-master(寬度 480,12 fps,循環)或 ffmpeg 雙通道調色板指令剪輯,然後在發佈前檢查大小、幀數和循環。詳細說明:references/04-gif.md、scripts/make_gif.md。
硬性限制
以下是違反時會破壞流程的規則,非風格偏好。
- 合約優先。 物件面向 +Z,底部在 y = 0,單位為公尺,相機
target + dist * (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch)),yaw/pitch 以度為單位。每個腳本和影片都共用此慣例。 - 純狀態。 場景 API 必須將相同狀態渲染為相同像素。沒有動畫循環、沒有即時時鐘、不依賴
Math.random()的幀。 - 幀 = f(t)。 相機、爆炸、旋轉、標籤、卡片和聲音都是影片時間的函數;別無其他。
- 組合合約。 根元素上需有
data-composition-id、data-start、data-duration、data-width、data-height;暫停的時間軸註冊為window.__timelines["<id>"];在hf-seek內部使用e.detail.time重新繪製。 - 片段。 帶有自身開始和持續時間的媒體(音訊、影片、插入片段)需加上
class="clip"、data-start、data-duration;音訊額外加上data-track-index。由時間軸驅動的 DOM 動畫不需要片段屬性。 - 可讀性。 在 1080p 下,資訊性文字不得小於 28 px(檢查器的字型下限為 28 px,即短邊的 2.6%)。
- 外部依賴。 three.js 和 GSAP 作為
.min.js放置於assets/vendor/下;使用本地字型;渲染期間不得有網路連線。 - 腳本通用化。
scripts/中不得包含物件名稱、絕對路徑或個人目錄;每個腳本必須能從全新克隆中執行。 - 指令驗證。 寫入文件、腳本或影片中的每個指令和參數,必須在鎖定版本下至少執行過一次,才能信任。
參考路由
| 任務 | 閱讀 |
|---|---|
| 測量、比例、lathe 輪廓、格柵/葉片參數、零件、爆炸、錨點 | references/01-modeling.md |
| 組合合約、導演/分鏡、音訊同步、HyperFrames 指令與輸出 | references/02-render.md |
| 螢幕截圖循環、比對圖、讀取角度、圓形擬合失敗 | references/03-verify.md |
| gif-master 操作卡、致謝、ffmpeg GIF 配方 | references/04-gif.md |
| 儲存庫、GitHub Release、社群列表 PR、YouTube/X | references/05-publish.md |
| 症狀 → 原因 → 解決方案目錄(光照、陰影、肋條、字型、套件、Chrome) | references/06-gotchas.md |
本技能內建工具:
| 檔案 | 角色 |
|---|---|
scripts/view.html | 單幀檢視器;姿勢和套件來自查詢參數 |
scripts/verify_sheet.py | 照片與渲染並排比對圖(--photo --render --out --bbox) |
scripts/hf_read_check.py | 彙總 HyperFrames 檢查報告;--fail-on-error 用於 CI |
scripts/make_gif.md | 瀏覽器/ffmpeg GIF 製作操作卡 |
最小可執行範例
以下指令從儲存庫根目錄執行,針對 examples/fan/ 中的範例;對於新物件,請替換 src/scene.js、photos/ 和 video/。工作檔案會寫入 %TEMP%,以便保持工作目錄乾淨。保留這些參數——它們是已驗證的參數。在 PowerShell 中執行。
$work = Join-Path $env:TEMP "mbc-verify"; New-Item -ItemType Directory -Force $work | Out-Null
# 1. 打包場景(新物件的範本;需要安裝 `three`)。
# 範例已附上其套件於 examples/fan/video/assets/vendor/fan.bundle.min.js,
# 因此此步驟可跳過。
npx --yes [email protected] src/scene.js --bundle --format=iife --global-name=Scene `
--minify --target=chrome120 --outfile=dist/scene.bundle.min.js
# 2. 對範例截取一個驗證姿勢(1920x1080,每次執行使用全新設定檔)
$chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$png = Join-Path $work "front.png"
$udd = Join-Path $env:TEMP ("view-" + [guid]::NewGuid().ToString("N"))
& $chrome --headless=new --use-angle=d3d11 --window-size=1920,1080 `
--hide-scrollbars --virtual-time-budget=12000 `
--screenshot=$png --user-data-dir=$udd `
"file:///$(($PWD.Path).Replace('\','/'))/scripts/view.html?bundle=../examples/fan/video/assets/vendor/fan.bundle.min.js&yaw=-10&pitch=12&dist=0.85&ty=0.185&fov=30&noshadow=1"
# 3. 將該渲染結果與相同姿勢的正面照片進行比對
python scripts/verify_sheet.py --photo examples/fan/reference/fan.jpg --render $png `
--out (Join-Path $work "front-sheet.png") --bbox "130,245,1195,1455"
# 4. 檢查影片是否符合 HyperFrames 合約,然後閱讀報告
Set-Location examples/fan/video
# 需要 assets/audio/mix.wav(Release 資產;請見 examples/fan/README.md)
npx --yes [email protected] check --at 21.15,27.6,35.18
$report = Get-ChildItem .hyperframes/artifacts/checks/*.json |
Sort-Object LastWriteTime | Select-Object -Last 1
python ../../../scripts/hf_read_check.py $report.FullName --fail-on-error
npx --yes [email protected] snapshot --at 29.6
# 5. 渲染交付主檔
npx --yes [email protected] render --quality delivery
Set-Location ../../..
# 6. 使用 ffmpeg 剪輯 GIF(瀏覽器方法請見:scripts/make_gif.md)
$mp4 = (Get-ChildItem examples/fan/video/renders/*.mp4 | Sort-Object LastWriteTime | Select-Object -Last 1).FullName
ffmpeg -hide_banner -loglevel error -y `
-ss 29.6 -t 3.8 -i "$mp4" `
-vf "fps=12,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=sierra2_4a" `
-loop 0 (Join-Path $work "teardown.gif")
步驟 3 會印出兩個裁切框;驗證完成的執行會印出 saved ...\front-sheet.png 1112x702 | photo (130, 245, 1195, 1455) | render (652, 264, 1252, 1080)。
預期輸出:check 會寫入 video/.hyperframes/artifacts/checks/<uuid>.json;snapshot 會寫入 video/.hyperframes/artifacts/snapshots/<uuid>/frame-00-at-29.6s.png;render 會寫入 video/renders/video_<YYYY-MM-DD>_<HH-MM-SS>.mp4(對於 43.5 秒、1920×1080/30 fps 的影片約 36 MB,使用硬體 GPU 時約需 2.5 分鐘的捕捉時間)。
需求
- Node.js(已測試 22.19.0)搭配
npx;esbuild 0.24.2 透過 npx 安裝。 - Python 3.11(已測試 3.11.1)搭配 Pillow 處理比對圖;numpy 為選用。
- ffmpeg 需在 PATH 環境變數中(已測試 2025-12-28 git 版本)。
- 用於無頭螢幕截圖的 Chrome(已測試 157.0.8103.40);上述路徑為 Windows 預設。
- HyperFrames 鎖定為 0.9.36,透過
npx --yes [email protected]使用。 check和render會自動以--browser-gpu驅動無頭 Chrome:有硬體 GPU 時使用硬體,否則使用軟體(SwiftShader);preview在有 GPU 時更流暢。
完成標準
- 六張比對圖閱讀可接受,並列出與說明偏差;
hf_read_check.py --fail-on-error在最終檢查報告上退出碼為 0;- 交付 MP4 存在,且其時長等於組合的
data-duration; - GIF 循環乾淨且大小適合發佈;
- 本技能中的每個指令曾在全新克隆中執行過一次。