模組四|從 2D 到 3D(Day 20–25)
《矽墟》是我正在寫的一部科幻小說,拆成 200 回短篇連載,整部作品用管軟體專案的方式在管:敘事結構寫成規格檔,成品用腳本驗收。這個模組在做的,是其中一個角色的獨立角色誌網頁——使用者往下捲,鏡頭跟著推近拉遠、角色跟著轉;主題色則是點色票按鈕切換,角色身上的顏色跟著一起換。整頁必須是不經過任何建置流程、打開瀏覽器就能跑的單檔 HTML(chenger.html,最後真正上線的那一頁)。
角色的立體模型要從哪裡來,這個模組試了兩條路:一條是 image-to-3D(丟角色的圖片進去,生成流程直接吐出一個 3D 模型),另一條是手工路線——不生成模型,改用程式碼把角色用基本幾何體一塊一塊堆出來。Day 22 講的是 image-to-3D 的產物為什麼沒被採用。今天回到時間軸上更早的地方:手工路線動工之前的那 43 分鐘。
(時序提醒:手工路線是先做的,image-to-3D 是四天後才試的,Day 20 講過這個順序。規格檔的最後修改時間是 07-27 14:08,實作是 14:51,中間差 43 分鐘。這篇講的就是那 43 分鐘做了什麼。)
我要用基本幾何體把一個角色堆出來。這件事有一個很明顯的問題:
它一定不會完全像。
球體堆出來的頭髮,跟畫出來的頭髮不可能一樣。這不是技術問題,是這條路線的本質。
於是問題變成:要像到什麼程度?
而這個問題如果不先回答,會發生兩件事的其中一件:
我兩種都經歷過。所以我停下來,先寫了一份契約。
契約我沒有寫在腦子裡,寫成了一份檔案:docs/chenger-sculpt-spec.json——這條手工路線的角色設定檔,一份純 JSON,記的是「這個角色該長成什麼樣、由哪些零件和材質組成」。契約放在整份檔案的最前面:
{
"name": "Chenger",
"source": "assets/turnarounds/char-chenger-turnaround.png",
"subjectClass": "hybrid (chibi character + anchor prop)",
"complexity": "complex",
"viewLimits": "front/side/back all provided; underside of skirt and soles inferred",
"qualityContract": {
"targetFidelity": 0.72,
"mustMatch": [ ... ],
"allowedApproximation": [ ... ]
}
}
這段是整份檔案的表頭。上面幾欄交代的是背景:叫什麼名字、照哪張圖做(source 指的是這個角色的三視圖檔——正面、側面、背面畫在同一張圖上的設定圖)、屬於哪一類、有多難、來源缺了什麼。
qualityContract 才是這篇的主角,三個部分:一定要對的、可以簡化的、目標分數。
先寫死不准動的部分。這一欄是給驗收用的,不是拿來當靈感清單——做完之後我要拿它逐條核對:
"mustMatch": [
"chibi silhouette (~4.5 heads) with oversized twin messy buns + ahoge",
"orange hair / white wide-sleeve kimono top / black pleated skirt color zones",
"black-gold layered sleeve cuffs",
"rope belt with knot + hanging charms and tassels",
"white platform boots with black soles and orange laces",
"black iron anchor with chain, tethered by rope"
]
六條,全部是具體的視覺特徵,不是形容詞。
注意第一條:~4.5 heads。這是頭身比,一個可以量的數字。不是「Q 版的比例」,那個沒辦法驗收。
這六條就是驗收條件。做完之後我拿著這張清單一條一條看:頭身比對嗎?丸子頭有嗎?呆毛有嗎?色塊分區對嗎?
六個 yes,就合格。

然後是反過來的那一欄:明文寫出哪些地方,我准自己做得粗。
"allowedApproximation": [
"face drawn as stylized mesh parts (no texture painting)",
"skirt charm icons simplified to discs/tassels",
"hair strands merged into clustered volumes"
]
這一欄比上一欄更重要,而且是我覺得最少人做的一件事。
它說的是:臉不用畫貼圖(texture,貼在模型表面、決定那塊表面看起來是什麼顏色什麼花紋的圖片),用幾何拼;裙子掛飾的圖案不用畫,簡化成圓盤和流蘇;頭髮不用一根根做,併成團塊。
如果沒有這一欄,會發生什麼?
我做到臉的時候會卡住。因為原圖的臉有細節,而我用幾何體做不出來。然後我會覺得自己做得不夠好,開始想辦法。 可能去研究貼圖、可能加更多幾何體、可能開始懷疑整條路線。
**有了這一欄,臉做成幾何拼貼不是妥協,是規格。**它是一開始就寫進契約的、被允許的做法。
這就是 Day 3 講的那件事:禁止要有去處。 這裡是反過來的版本:限制要有明確的豁免。
一份只有 mustMatch 沒有 allowedApproximation 的規格,實務上等於要求「全部都要對」,因為沒有寫下來的簡化,做的人都會覺得是自己的失職。

一個小於 1 的數字。
這個數字沒有精確的量測方法,我沒有一個演算法可以算出「這個模型跟原圖的相似度是 0.71 還是 0.73」。
它的作用不是量測,是宣告。
它宣告的是:這件事的目標本來就不是 1。
寫下 0.72,等於預先回答了那個做到一半一定會冒出來的問題(「這樣夠像嗎?」)的答案是:它不需要很像,它需要達到約定的程度。
如果我寫 1.0,或者根本不寫,那麼每一個不完美的地方都是一個未完成的任務。清單永遠清不完,因為完美不是一個狀態,是一個方向。
這件事跟 SLO(Service Level Objective,服務水準目標——一套系統事先寫明「我要做到多好」的量化門檻,例如「一年之中有 99.9% 的時間是可用的」)是同一個道理。你不會把可用性目標訂在 100%,因為那會讓每一次抖動都變成事故,而且會讓你為了最後那 0.1% 付出不合比例的代價。訂 99.9% 不是放棄品質,是讓「達標」有定義。
契約之外還有一欄。它不規定品質,規定的是「我手上的資料到哪裡為止」:
"viewLimits": "front/side/back all provided; underside of skirt and soles inferred"
這一欄講的是來源資訊的缺口:三視圖有正面、側面、背面,但裙子底下和鞋底是沒有的。
那兩個部位只能推測。
寫下來的價值在於:之後如果有人(包括我自己)覺得裙底怪怪的,這裡已經說明它是推測的,不是做錯的。
已知的資訊缺口要明文寫出來,否則它會被誤認為是缺陷。
把上面幾欄收在一起,這份檔案的前半段長這樣(右邊是每一欄在做什麼):
qualityContract
├─ targetFidelity 0.72 ← 目標不是 1
├─ mustMatch 6 條 ← 驗收條件
└─ allowedApproximation 3 條 ← 明確授權的簡化
viewLimits ← 來源資訊的缺口
subjectClass / complexity ← 這個任務的類型與難度
然後才是 components(9 個)和 materials(8 組),那些是實作,明天講。
契約在實作前面。
代價一:0.72 這個數字是我掰的。
我沒有辦法解釋為什麼是 0.72 而不是 0.7 或 0.75。它不對應任何量測。
它的功能是心理上的:一個小於 1 的具體數字,比「差不多就好」有力得多,因為它看起來像個規格。
我認為這是合理的用法,但我不想假裝它有嚴謹的基礎。它是一個有效的自我約束工具,不是一個度量。
代價二:mustMatch 的六條也是我選的。
為什麼是這六個特徵?因為我看著三視圖,覺得這六個是「拿掉就認不出是這個角色」的東西。
這個判斷本身是主觀的。契約把主觀從「驗收時」移到了「訂契約時」,沒有消滅主觀,只是把它前置到一個比較冷靜的時刻。
不過這個前置很有價值:訂契約的時候我沒有沉沒成本,驗收的時候我有。
代價三:契約沒有涵蓋動態。
這份契約全部在講靜態外觀。它沒有說「轉起來要順」、沒有說「換色之後要好看」。
而那兩件事後來都需要調整,契約幫不上忙。契約只能保護你想到要保護的東西。
一、產出前先寫兩份清單:不准變的、准簡化的。
大部分人只會寫第一份。第二份才是讓事情能收工的那一份。
沒有第二份,所有沒被明文允許的簡化都會變成心裡的虧欠,而虧欠會導致無止盡的打磨或中途放棄。
這件事可以直接套用在:外包給別人做、交給 AI 生成、做原型、做 MVP。「哪些可以粗」跟「哪些要精」一樣需要白紙黑字。
二、目標值寫成小於 1 的數字。
不管是相似度、覆蓋率、完成度,寫 100% 等於沒寫,因為它不提供停止條件。
寫一個具體的、小於滿分的數字,你才有辦法回答「現在夠了嗎」。
那個數字精不精確不重要,重要的是它存在。
三、把來源的資訊缺口明文寫出來。
任何從既有素材衍生的工作,都會有「素材沒說的部分」。
寫下來的推測是設計決策,沒寫下來的推測是 bug。 差別只在有沒有那一行字。
四、主觀判斷要前置到沒有沉沒成本的時刻。
驗收時判斷「這樣夠好嗎」,你已經投入了大量時間,判斷一定會被沉沒成本污染,不是過度寬鬆(不想重做)就是過度嚴苛(不甘心)。
訂契約的時候你什麼都還沒做,那是最冷靜的時刻。 把判斷放在那裡。
明天 Day 24,講契約底下的實作:怎麼把一張三視圖拆成 9 個元件。每個元件標上拓撲類型(topology,這塊東西的表面本質上屬於哪一種形狀家族)和建構策略:球體、旋轉體、褶皺錐、管狀曲線,以及一個讓 617 行程式碼能被讀懂的欄位設計。