img2threejs 實測:同一張鍵盤照片,ChatGPT 5.6 和 Opus 5 各跑完八個 Pass
看到 hoainho/img2threejs 時,我原本以為它是把圖片丟進模型,直接吐出一份 3D mesh。實際裝起來跑過才發現,它做的是另一件事:先讓 agent 看參考圖、拆出物件結構,再用 Three.js primitive、材質和程式化幾何重建物件。
最後拿到的不是 .glb 或 .obj,而是一份 JSON 規格與可進版控的 TypeScript。這個方向很適合網站上的互動物件、遊戲道具原型和需要繼續改程式的 WebGL 場景,但它不是一鍵式圖片轉 3D。
這次我拿日常使用的 FILCO NINJA 來測試。我想實際觀察 img2threejs 如何處理大量重複按鍵、不同寬度的鍵帽、功能鍵分區與線材,因此讓它一路跑完八個 pass——而且跑了兩輪:先用 ChatGPT 5.6,再用完全相同的條件換 Opus 5 跑一次。

兩輪的 spec 都通過一般驗證和 --strict-quality,八個 pass 也都留下 render、comparison 與 review 紀錄,流程狀態跑到 complete(八個階段全部走完、每階段都有審查紀錄)。本文的「成品」是這輪流程完成後可互動、可繼續修改的即時網頁模型,不是 photogrammetry(用大量實拍照片反推出真實立體形狀的技術),也不是從單張照片還原出的精準產品 mesh。
工具內建那道「照片 vs. render 像素級比對」兩輪都沒有通過。它叫 Tier 1,用程式硬比兩張圖的輪廓有多重疊;之所以是「第一關」,是因為它最便宜,過了才輪到後面更貴的視覺審查——這次兩輪都卡在第一關。
這項結果我會和 agent 的視覺 review 分開列出,因為兩者結論不同:agent 看畫面覺得可以,程式比對輪廓覺得不行。 我不用「八個 pass 跑完」掩蓋輪廓與取景仍有差距。
先給趕時間的人:三句話結論
- 它產出的是可讀、可改、可進版控的 Three.js 程式碼(TypeScript)+一份 JSON 規格,不是
.glb/.obj模型檔。要拿去 Blender 雕或 3D 列印的話,這支不適合。 - 成品是「一眼認得出是這個東西、可以旋轉、可以點按鍵」的網頁 3D 物件,不是照片級複製。 想像產品頁上那顆可以轉的小物件,不是電商實拍的替代品。兩輪都沒過工具自己的像素級比對。
- 你不需要懂 3D 就能開始跑。 上手成本是一句中文提示詞加一張圖;真正的成本在後面——工具的自動材質判斷會錯,需要人去覆寫。第二輪那次我把它判錯的四種材質整理成一張表,錯在哪、錯的方向是什麼都記在裡面。
img2threejs 實際產生什麼
它用 agent 做視覺判斷,再由 Python 腳本檢查規格與流程:
參考圖
→ 圖片適用性檢查
→ Pre-Spec Assessment
→ ObjectSculptSpec
→ 規格驗證
→ Three.js TypeScript factory
→ 瀏覽器 render
→ 參考圖與 render 對照
→ agent 決定繼續或修正
八個 pass 各自負責什麼
一般物件的完整 build 順序是(這張表後面兩輪都會用到):
| Pass | 這個階段在做什麼 |
|---|---|
blockout | 先用大形狀抓出物件輪廓、尺寸比例和主要量體,暫時不處理材質。 |
structural-pass | 拆出物件的零件階層、重複結構與接合關係,確認把手、支架等部件接在正確位置。 |
form-refinement | 修細外型,包括曲線、斜面、倒角(把尖銳的邊修成小斜面或圓弧)、厚度、錐度(一頭粗一頭細)、不對稱與局部幾何。 |
material-pass | 對齊各部位的顏色、粗糙度(霧面還是鏡面)、金屬度(是不是金屬)、凹凸感與材質變化。 |
surface-pass | 加入刮痕、污漬、磨損、顆粒、AO(縫隙與夾角自然變暗的效果)、normal/bump(用貼圖假造凹凸,不真的增加幾何)等表面細節。 |
lighting-pass | 設定主光、補光、輪廓光與環境光,讓形狀和材質在畫面上看得清楚。 |
interaction-pass | 補上 pivot、socket、collider 等節點,讓模型之後能旋轉、動畫、碰撞或拆解。 |
optimization-pass | 在外觀確認後控制三角形、draw call、instancing、LOD 與 FPS,降低執行成本。 |
每個階段只處理一類問題。輪廓不對就不該急著補材質,零件接合還沒處理好,也不該先花時間做刮痕。這種 gate 設計比「請 agent 一次把模型寫完」更容易找出是哪一步開始走偏。
產生的 factory 回傳 THREE.Group,並在 root.userData.sculptRuntime 放入節點、socket、collider 等 runtime 資訊。也就是說,輸出不只是畫面上的一坨幾何,後續仍能用程式控制零件。
安裝:Codex 與 Claude Code
流程和輸出大概是這樣。接下來是怎麼把它裝進來——這一步比我預期的短,兩個環境都只有一段指令。
Codex 可以用內建的 skill installer 直接從 GitHub 安裝:
python3 ~/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
--repo hoainho/img2threejs \
--path . \
--name img2threejs
安裝位置是 ~/.codex/skills/img2threejs。開新的 Codex 任務後,就能讓 agent 讀取這個 skill 的完整工作流程。
官方 README 也提供 Claude Code 的安裝方式,本文第二輪就跑在這裡:
git clone https://github.com/hoainho/img2threejs.git \
~/.claude/skills/img2threejs
兩輪的共同條件
兩輪是同一個實驗跑兩次,所以先把「固定了什麼、只變了什麼」講清楚,後面的差異才有意義。
鍵盤照片只有一張,是普通的網頁尺寸產品照。兩輪的基本圖片檢查與 reference admission 都通過。
第一次使用不需要先懂 assessment、spec 或 blockout。GitHub README 給 Claude Code 的基礎提示詞只有一句:
/img2threejs Rebuild this object as a Three.js model, keep the proportions, angles, and colours.
附上參考圖後,改成一般說法即可。兩輪用的都是這一句,逐字相同:
請使用 img2threejs skill,把這個物件重建成 Three.js 模型,
保留原圖的比例、角度和顏色。
後面的圖片檢查、spec、分階段生成與 render 對照,都交給 skill 處理。你只需要描述想重建哪個物件,不必先學會內部術語。
兩輪相同:同一張 keyboard-reference.png、上面那句中文提示詞,以及同一組 img2threejs、Python、Node.js、Three.js 與 Vite 版本(實測版本記在下方,不是專案宣告的最低需求)。
兩輪不同:執行的 agent。第一輪是 ChatGPT 5.6、推理強度 xhigh;第二輪是 Claude Code 的 Opus 5(1M context)session。
所以後面看到的差別可以歸到 agent 身上,不是環境。
實測版本:img2threejs 1.3.0、Python 3.14.3、Node.js 24.4.0、Three.js 0.178.0、Vite 7.3.6。兩輪的輸出都是 TypeScript,也都跑過 tsc 檢查;第二輪的 TypeScript 是 5.9.3,另外裝了 @types/three。
還有一件事值得先講清楚:這些檢查底下的 Python 腳本全部只做「量測、檢查、記錄」,沒有一支會判斷「圖裡是什麼」或「做得像不像」——那兩件事從頭到尾都是 agent 在做。腳本的作用是讓 agent 沒辦法自己說「我覺得可以了」就往下走。
第一輪:ChatGPT 5.6
起手式與 spec
starter spec 一開始確實過不了 strict-quality。這次我補完材質 local override、每個 component 的 colorMaterialRecipe、detail inventory、物件專屬 feature target、燈光描述與 reference PBR 證據後,鍵盤的 strict 驗證才變成通過、而且沒有留下 warning。
四種材質的 PBR 取樣證據都過了工具的信心門檻。這代表 crop 可供後續材質實作參考,不代表從單張照片反推出真實物理材質。
spec 定案之後,orchestrate_passes.py 才會解鎖第一個 pass。下面是這輪八個階段的實際 render。
八個 pass
1. blockout:只留下低矮長方形底座,先看整體比例。

2. structural-pass:加入六排按鍵、功能鍵、導覽區、方向鍵與後方線材。

3. form-refinement:底座和鍵帽改成圓角幾何,鍵列加入高度差。

4. material-pass:分開底座、deck、鍵帽與橡膠線材的 PBR 反應。

5. surface-pass:補上鍵帽文字、前緣接縫與 logo。

6. lighting-pass:以柔和主光、補光、輪廓光和接觸陰影拉開黑色層次。

7. interaction-pass:替按鍵加上可按壓 action、鍵盤 collider 與線材 socket。

8. optimization-pass:依鍵帽寬度分組 instancing,保留外觀並減少重複物件。

最終 reference/render 對照:

我也補拍參考視角、俯視與側視。multi-angle 檢查回傳 degenerate: false,證明它不是只在單一角度成立的平面假 3D:

直接轉轉看
這輪的結果與限制
這輪的 spec 把鍵盤拆成一棵相當細的元件樹,四種材質,八個 pass 每一階段都留了 review。agent 視覺 review 的最終分數在可接受範圍。成品已經有可辨識的 TKL 分區、不同寬度鍵帽、文字、前緣、線材與按鍵互動;它仍不是原廠 CAD,鍵帽側面 sculpt、字型和隱藏底部都是近似。
最終 generator factory 是把 spec 逐一展開成 component 的寫法,篇幅相當長,我另外調整了幾何、互動與 instancing。tsc --noEmit 和 Vite production build 通過,瀏覽器裡沒有 runtime error。
真正沒有跟著人工 review 一起「通過」的是 deterministic 的檢查。Tier 1 做的事是把 render 的剪影疊到照片的剪影上,看兩者重疊多少——這輪的重疊率離門檻還很遠。純數學檢查的 divine_eye.py 也直接退回(它綜合輪廓、比例、感知雜湊、明暗等訊號,不呼叫任何模型)。主要差異來自照片與 render 的取景、尺寸和實際輪廓,因此本文把它稱為「八個 pass 完成的即時網頁成品」,不稱為照片級還原。
第一輪就停在這裡:八個 pass 跑完了,deterministic 的門檻沒過。當下我沒辦法判斷這是 skill 的上限,還是這一個 agent 的上限——這兩件事的差別很大,值不值得繼續投時間也差很多。所以我把同樣的條件再跑一次,只換掉執行的 agent。
第二輪:Opus 5,同一張圖、同一句提示詞
這輪的照片、提示詞、img2threejs 版本和 Node/Three.js/Vite 都和第一輪一樣,執行的是 Claude Code 的 Opus 5(1M context)session。共同條件都寫在前面〈兩輪的共同條件〉,所以下面看到的差別可以歸到 agent 身上。
起手式與 spec
Stage 1/2 一樣通過,但腳本的材質分類全錯。
參考圖 check_reference_admission.py 回傳 ADMITTED。複雜度我評為 complex,detail inventory 把圖切成網格逐區掃過,找出的細節條目超過工具要求的下限。四種材質的 PBR 取樣證據也都過了信心門檻。
真正值得記下來的是這裡:analyze_texture.py 對四種材質的 finish 分類全部判錯,而且錯得很有方向性。
| 材質 | 腳本判定 | 照它的判定做會變成 | 我改成 |
|---|---|---|---|
外殼 case-satin-black | worn-composite(磨損複合材) | 完全不反光的粗糙塑膠——但前緣的小斜面上明明有一道明顯的高光帶 | 半亮面塑膠,加一層很薄的清漆 |
鍵帽 keycap-matte-black | painted-metal(烤漆金屬) | 霧面鍵帽變成一片油光——它把鍵帽上的白色字元誤認成反光亮點了 | 純霧面,清漆歸零 |
底板 deck-black | gem-metal(寶石/金屬) | 塑膠底板反射出周圍環境——它抓到的裁切圖是鍵縫的格狀圖案,讀錯了 | 非金屬 |
線材 cable-rubber-black | brushed-steel(拉絲不鏽鋼) | USB 線變成一條金屬管——橡膠是非導體,也沒有拉絲金屬那種方向性反光 | 非金屬,取消方向性反光 |
四種材質全錯,而且錯的方向一致:它傾向把黑色霧面的東西判成金屬或亮面。 如果你打算跑黑色、深色或霧面的物件,這一步預設要當成「需要人工覆寫」,不是「跑完就好」。
extract_part_color_recipe.py 也把鍵帽判成 stone、信心很低,即使我下了 --material-class-hint plastic 也沒被採納。最後我只留下它抽出來的顏色值,材質分類和粗糙度自己填,並把「腳本原本判成什麼、為什麼不採用」一起寫進那個零件的配方註解裡。
這正好對上 SKILL.md 那句 Never let a script *score* visuals — that is the agent's job。腳本抽出來的像素證據(albedo cluster、palette、粗糙度基準)是可用的;它們對「這是什麼材質」的判斷則不可信。
我把四次覆寫連同理由寫進 materials[].agentOverride,讓後面看 spec 的人知道哪些設定是機器給的、哪些是我改的。
每一種材質都要從照片上裁一小塊當證據,其中兩張沒有通過 admission 的檢查,理由都跟「這張圖能不能代表一個零件」有關:
- 底板那張裁到的是鍵與鍵之間的縫隙,畫面是一格一格的網狀而不是一整塊連續表面,工具判定它不像單一零件(mask coherence 不足)。
- 線材那張是因為線在原圖裡只有幾個像素寬,我把中心線重新取樣成一小張長條圖,結果整張都是線材、沒有背景可以對照(foreground coverage 滿載)。
這兩張我只拿來當顏色與粗糙度的參考,沒有拿去當輪廓來源。
spec 通過 strict-quality,但留一個 warning。
最後的 spec 拆成 macro/meso/micro 三層的元件樹、四種材質、三組重複結構系統,以及一份物件專屬的重點特徵清單。--strict-quality 通過,但留下一條 warning:suitability is pass but risks are present; confirm they are acceptable。
這條 warning 沒有辦法「回填確認」——validator 只要 suitability 是 pass 且 risks 非空就會發。要清掉它只能把 risks 刪掉,那等於把「單視角、四種黑會塌成一團、字型無法還原、取景會拖垮輪廓比對、分類器判錯」這五項風險藏起來。我選擇留著 warning。
過 strict-quality 的路上被擋了四次,每次都是真的缺東西:
primitive必須落在一份允許清單裡,我原本寫的rounded-box、dished-box、tube-along-curve都不合法。- 每個 component 都要
colorMaterialRecipe,顏色還得是rgba()字串。 referencePbr.maps要是物件,不能是字串陣列。unknownsToResolveBeforeImplementation不能留未解項目。
第四項我沒有走捷徑。 最偷懶的做法是把未解項目直接清空,validator 就閉嘴了;我改成新增 resolvedUnknowns,逐條寫下「接受為推測,信心壓低」,讓後面看 spec 的人知道哪些地方是猜的。
spec 過關之後才輪到 pass。下面同樣是八個階段的畫面,其中兩步我被擋回來重做,也一併寫在對應的 pass 下面。
八個 pass
1. blockout:底座量體、整體腳印比例與打字傾角。

這個 pass 我被 gate 擋了一次。第一次把傾角放在 form-refinement,結果 blockout 的 critical feature case-silhouette 分數低於門檻,append_review.py 直接拒絕寫入 continue。傾角本來就屬於「主要量體」,我把它移到 blockout 才過關。這是 gate 在做該做的事。
2. structural-pass:全部鍵帽、六排、導覽鍵區、倒 T 方向鍵與線材。

鍵位由一張 layout table 驅動,每顆鍵的位置與寬度都由表格指定——從一般字母鍵到空白鍵,各種不同寬度都在表裡。這一步讓工具的比例檢查與尺寸檢查雙雙進到門檻內。
3. form-refinement:圓角外框、前緣 chamfer、鍵帽碟形凹面與 R1–R4 高度階差。

工具的輪廓比對幾乎沒有反應。這一步改的是輪廓裡面的東西,deterministic 的輪廓指標對碟形、倒角和高度階差基本上是盲的。
4. material-pass:四種材質各自獨立的 albedo/roughness/normal/AO。

四個通道都用各自的 seed 生成,沒有把 albedo 拿去當 roughness 或 normal 用。這個 pass 的畫面明顯比參考圖暗,因為參考打光還沒建。
5. surface-pass:鍵帽文字、FILCO logo 與鍵縫暗化。

所有鍵帽的字其實是印在同一張大圖上(legend atlas),一顆鍵取其中一格。字印在鍵帽頂面左上角、數字排的 shift 符號在數字上方,和參考圖一致。字型是 canvas 畫的 condensed sans,不是原廠字面。
6. lighting-pass:主光、補光、輪廓光、ACES tone mapping 與接觸陰影。

這是第一個「四種黑分得開」的 pass。線材的投影我特意關掉——參考圖的背景被打到純白,照片裡看不到線材陰影,加上去會是照片不支持的假細節。
7. interaction-pass:每顆鍵一個轉軸與碰撞體,外殼與線材也各有自己的節點。

這張是壓著 G 拍的(?press=g),下壓行程有實際作用。
8. optimization-pass:所有鍵帽合併成少數幾個 geometry,外觀零變化。

這個 pass 我第一版寫錯,而且錯得很典型:鍵帽形狀幾乎一樣,直覺上該用 InstancedMesh(用同一份幾何複製很多份,只換位置,省效能),按鍵寬分組。但這招的前提是「這些複製品完全一樣」——而每顆鍵帽印的字都不一樣。 共用幾何就等於共用取圖座標,結果所有鍵帽全部去取整張 atlas,畫面變成一片亂碼。
改成「每個 row/cluster 合併成一個 geometry」之後,每顆鍵的取圖座標和每排的傾角都保住了,按鍵動作改用記錄下來的頂點範圍位移實作。key field 的 draw call(GPU 每一次「畫一批東西」的呼叫,數量直接影響效能)從「每顆鍵一次」降到「每排一次」。
最終 reference/render 對照:

參考視角、俯視與側視。diagnose_render_multi_angle.py 回傳 degenerate: false,轉到側面也沒有塌成薄片:

俯視那張是我最滿意的一張——鍵盤的分區、鍵寬和文字逐鍵對得上參考圖。
直接轉轉看
上面都是截圖,但這個 skill 的重點就是「輸出是活的程式」,所以我把這輪的成品直接跑在這頁上:
值得注意的是按鍵下壓不是我另外做的動畫。interaction-pass 產出的模型本來就在 root.userData.sculptRuntime 裡放好了每顆鍵的轉軸、碰撞體與可觸發的動作,這個頁面只是把滑鼠事件接上去而已。這也是為什麼前面說「輸出不只是畫面上的一坨幾何」。
切到「1 量體」再一路點到「8 優化」,會比看八張靜態圖更清楚每一個 pass 到底加了什麼。
這輪的結果與限制
這輪的元件樹比第一輪精簡,但同樣是四種材質、八個 pass 都留了 review,agent 視覺 review 的最終分數略高於第一輪。runtime 端每顆鍵都有自己的轉軸、碰撞體與可觸發的動作。
程式分成四個檔(layout table、材質貼圖產生器、模型 factory、review harness),起點是 generate_threejs_factory.py 產出的那份 factory,我在上面繼續改。tsc --noEmit 與 Vite production build 都通過,瀏覽器裡沒有 runtime error。
輪廓比對的重疊率明顯比第一輪高,divine_eye.py 也從「直接退回」變成「有機會但要再看」。仍然沒有到門檻——原因和下一節一起講。
兩輪對照與我的判斷
兩輪到這裡都跑完了。同一張照片、同一句提示詞、同一版 skill,換一個 agent,差別放在一起比較清楚。
| 項目 | ChatGPT 5.6 | Opus 5 |
|---|---|---|
| 元件拆解 | 拆得較細 | 較精簡 |
| 材質處理 | 四種材質分開 | 四種材質分開 |
| strict-quality | 通過,沒有 warning | 通過,留一條 warning(我刻意不清掉風險) |
| 逐階段 review | 八個 pass 都有紀錄 | 八個 pass 都有紀錄 |
| agent 視覺評分 | 兩輪接近 | 略高 |
| 像素級輪廓比對 | 未通過 | 未通過,但輪廓明顯更接近 |
| 純數學綜合判決 | 直接退回 | 有機會但要再看 |
| 多視角檢查 | 不是假 3D | 不是假 3D |
| 程式規模 | 逐一展開,較長 | 抽成表格驅動,短很多 |
| 可維護性 | 改鍵位要動很多處 | 改一行表格 |
一句話結論:兩輪都跑得完,Opus 5 這輪的輪廓明顯更接近照片。
程式長短不代表誰寫得比較好。第一輪是 spec 直接展開成逐一 component 的寫法;第二輪短很多,是因為我把所有鍵帽抽成一張 layout table 加一個 geometry 產生器。兩種都能跑,但前者改鍵位要動很多地方,後者改一行表格就好。
Opus 5 這輪的輪廓重疊率比第一輪好很多,純數學判決也從「直接退回」變成「有機會但要再看」,但都還沒摸到 pipeline 的硬門檻,兩輪都沒過。
卡住的地方很明確:我把 render 的剪影疊到照片上看,render 的輪廓一開始整體比照片小一圈,修掉之後剩下的差異是「照片的前後景深比 render 大、render 的左右兩端比照片寬」。那是真實相機的鏡頭特性,用虛擬相機的透視逼不出來。我試過換更廣的視角加近距離,重疊率反而掉下去。
一個附帶觀察:顏色和整體明暗其實對得很好,差的是邊緣。也就是說這次沒過關不是材質沒做對,是形狀與取景沒對上。
所以兩輪同樣都不叫照片級還原,是「八個 pass 完成、可互動、可繼續改程式的即時網頁模型」。
我會把它用在哪裡
產品頁上的旋轉小物件、遊戲道具 blockout、互動式教學圖,會是我願意繼續使用 img2threejs 的情境。輸出可以看 diff、改尺寸、換材質,也能把零件接到互動事件;若目標是可雕刻、可列印或高擬真的 mesh,我會改用真正的 image-to-3D 模型。
兩輪都不只停在 blockout。鍵盤各自完成八個 pass、strict-quality、逐階段 review、多視角檢查與最終 browser render。它的重複按鍵與清楚分區很適合程序化零件組裝;如果要再往產品級逼近,我會補拍側視與底部照片,繼續修正鍵帽輪廓、字型與底座細節。
留言