Day 10 對照過兩種檢視器,今天開始拆解自己寫的那個:viewer-core。這一篇處理最基本的三件事:載入 GLB、轉動它、轉亂了能復位。
viewer-core 用 three.js(最普及的網頁 3D 函式庫)實作。它的入口是一個函式 createViewer:你給它一個網頁上的容器,加上一份「場景說明書」(scene.json),它就在容器裡蓋好整個展示櫃。
| 功能 | 怎麼做到的 |
|---|---|
| 載入 GLB | 用 three.js 的 GLTFLoader 讀取。Day 09 壓縮過的檔案,由 DRACOLoader 負責解壓縮(解壓縮器預設走網路,離線播放包要指向本機) |
| 載入進度條 | GLTFLoader 回報進度;如果不知道總大小,就顯示一個不定長度的動畫;成功或失敗都會把進度條收起來 |
| 拖曳旋轉、縮放 | OrbitControls(軌道控制器):鏡頭永遠繞著一個「目標點」轉,可拉近拉遠。開了「阻尼」,放手後帶點慣性,慢慢停下來 |
| 自動轉台 | 沒人碰時,鏡頭自己繞著展品轉;有人操作就停,閒置一段時間後又轉起來 |
| 復位 | 右下角按鈕,一按鏡頭回到說明書寫的初始位置;也可以設定「閒置太久自動復位」 |
| 打光 | 一盞半球環境光、一盞主燈、一盞補光,加上地面陰影 |
viewer-core 不寫死任何展品。以下全部來自說明書 scene.json:
camera.initial):看向哪裡、離多遠、水平與垂直角度、視野camera.limits):最近/最遠能拉到哪、最高/最低能看幾度camera.auto_rotate):開不開、每秒轉幾度、閒置多久(例如 20 秒)才重新開始轉interaction):要不要復位按鈕、閒置多久(例如 60 秒)自動復位environment):背景色、有沒有地面陰影這就是 Day 03 講的「模型」(asset)與「怎麼展示」(scene)分開存:同一個櫃子換一份說明書,就是不同的展示。
說明書的座標仍是 Day 03 約定的 Z 軸朝上、公釐;而 Day 09 的成品 GLB 已經是 Y 軸朝上、公尺。所以 viewer 拿說明書的數字去定位鏡頭之前,必須換算。這個換算集中在一個函式 convertPoint:先把公釐換成公尺(×0.001),再把軸向轉過去。整個套件只有這一個地方知道怎麼換算。
這個設計是被兩次意外教出來的:
raw.glb(Z-up)。viewer 照契約假設拿到的已經是 Y-up,所以整台機器躺著。這是預期中的,不該為了遷就半成品去猜它有沒有轉過。convertPoint 只轉了軸向、沒換算單位:鏡頭以為展品在幾百公尺外,實際展品只有零點幾公尺大,小到不到一個像素;連鏡頭的「近裁切面」都是以公尺思考,展品直接被裁掉。修法:convertPoint 同時管軸向與單位,並且把鏡頭距離限制、近遠裁切面一併換算成公尺。第一個問題在捷徑階段就看到,第二個問題只有換上真成品才炸出來,這正是「先用捷徑看到東西、再拿真正的輸出驗證」的價值。
Raycast 點選零件(Day 12)、導覽標記點(Day 13)。