做了什麼 #
一個可以在瀏覽器裡即時操作的 3D 頭盔展示系統:換顏色、調材質、切換環境光。
GitHub: maymap/r3f-product-viewer
動機 #
看到一份 Three.js 工程師的職缺,想搞清楚這份工作實際在做什麼、作品集要做到什麼程度。從沒碰過 Three.js,但有 React 基礎,就從這個方向切入。
技術棧 #
- React Three Fiber(R3F):Three.js 的 React 封裝,讓你用 JSX 寫 3D 場景
- @react-three/drei:R3F 的工具庫,
useGLTF、OrbitControls、Environment、ContactShadows都來自這裡 - Three.js:底層渲染引擎
學到的幾件事 #
PBR 材質不是「好看的貼圖」 #
一個 GLB 模型裡面通常打包了好幾張圖,每張負責不同的事:
- Normal Map:用貼圖騙光線,讓平面的 mesh 看起來有凹凸細節
- Roughness Map:每個像素記錄那個點的粗糙程度,所以金屬件亮、橡膠件霧,不是畫上去的顏色
- AO Map:預先算好縫隙和角落的陰影,讓模型接縫看起來自然
三張圖加在一起,讓幾千個三角面的低模看起來像高模。
useFrame 是動畫的核心
#
useFrame((state) => {
groupRef.current.position.y = Math.sin(state.clock.elapsedTime * 0.8) * 0.06
})
每一幀執行一次,用時間軸驅動位移,做出浮動效果。Three.js 幾乎所有動畫都走這個模式。
直接改 material 要小心累積誤差 #
第一版的 slider 實作是直接乘以倍率修改 material.roughness,結果拉來拉去之後值就歪掉了——因為每次都在改上次改過的值。
解法是在模型載入時用 useRef 快取原始材質數值,之後每次都從原始值計算:
const originals = useRef(null)
if (!originals.current) {
const cache = new Map()
scene.traverse(node => {
if (node.isMesh) {
cache.set(node.uuid, {
roughness: node.material.roughness,
metalness: node.material.metalness,
})
}
})
originals.current = cache
}
這份工作實際在做什麼 #
做完之後對職缺的理解:
- 這個 demo 大概是職缺要求能力的 10%
- 真實專案要處理的是:多零件模型的獨立換色、draw call 優化、行動裝置 60fps、GLB asset pipeline(KTX2 貼圖壓縮)
- 「懂 PBR 材質」不是會調 slider,是知道哪張貼圖出問題要怎麼 debug
還差得遠,但至少現在知道差在哪裡。