本文 AI 產出,尚未審核
Three.js 與 React 整合(react‑three‑fiber)── 使用 drei 套件快速開發
簡介
在現代前端開發中,Three.js 已成為打造 3D 網頁體驗的事實標準,而 react‑three‑fiber(簡稱 r3f)則把 Three.js 的 API 包裝成 React 元件,使開發者能夠以宣告式的方式撰寫 3D 場景。雖然 r3f 已經大幅降低了學習門檻,但在實作常見功能(如相機控制、光源、模型載入、環境貼圖等)時,仍會需要撰寫大量樣板程式碼。
這時 drei(全名 @react-three/drei)就派上用場了。drei 提供一系列即插即用的 React 元件與 hooks,讓你 只需一行程式碼就能完成複雜的 3D 功能,大幅提升開發效率,也讓程式碼更具可讀性與可維護性。本文將從核心概念切入,示範幾個實用範例,並說明常見陷阱與最佳實踐,幫助你在 React 專案中快速上手 drei。
核心概念
1. 為什麼要使用 drei
| 項目 | 直接使用 Three.js / r3f | 使用 drei |
|---|---|---|
| 樣板程式碼 | 需要自行建立相機控制、光源、環境貼圖等 | 一行元件即可完成 |
| 可讀性 | 低,程式散落在多個 Hook 中 | 高,以語意化的元件呈現 |
| 維護成本 | 高,升級時需自行調整 | 低,drei 會隨版本更新自行修正相容性 |
簡單來說,drei 把常見需求抽象成「即用元件」,讓你把注意力集中在場景設計與互動邏輯上。
2. 安裝與基本設定
# 先安裝 react-three-fiber 與 three
npm i three @react-three/fiber
# 再安裝 drei
npm i @react-three/drei
在 React 專案的入口檔(例如 App.jsx)中,引入 Canvas(r3f 的渲染容器)以及 OrbitControls(drei 提供的相機控制):
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
function App() {
return (
<Canvas camera={{ position: [0, 2, 5], fov: 60 }}>
{/* 讓使用者可以拖曳、縮放、平移相機 */}
<OrbitControls enableZoom={true} />
{/* 其他場景內容放這裡 */}
</Canvas>
);
}
小技巧:
Canvas內部的所有子元件都會自動成為 Three.js 物件,無需手動呼叫new THREE.Mesh()。
3. 常用 drei 元件與 Hook
| 元件 / Hook | 功能說明 | 常見使用情境 |
|---|---|---|
OrbitControls |
滑鼠/觸控相機操作 | 基礎檢視、模型預覽 |
PerspectiveCamera |
宣告式相機 | 多相機切換、相機動畫 |
Environment |
HDR 環境貼圖 | 真實感光照、反射 |
Html |
在 3D 場景中嵌入 HTML | UI 標籤、說明文字 |
useGLTF / useFBX |
載入 3D 模型 | 靜態或動態模型 |
useTexture |
載入貼圖 | 材質、法線貼圖 |
ContactShadows |
簡易接觸陰影 | 物體放置感提升 |
Stars |
星空背景 | 科幻或宇宙場景 |
下面的範例將逐一示範這些元件的使用方式。
程式碼範例
範例 1️⃣ 基本場景:相機控制 + 環境光
import { Canvas } from '@react-three/fiber';
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei';
export default function BasicScene() {
return (
<Canvas shadows camera={{ position: [0, 1, 5] }}>
{/* 1️⃣ 讓使用者自由旋轉、縮放 */}
<OrbitControls enableDamping={true} />
{/* 2️⃣ 加入 HDR 環境貼圖,提升光照真實感 */}
<Environment preset="sunset" background />
{/* 3️⃣ 簡易平面與接觸陰影 */}
<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[10, 10]} />
<shadowMaterial opacity={0.4} />
</mesh>
<ContactShadows position={[0, 0.01, 0]} opacity={0.6} scale={5} blur={2} />
</Canvas>
);
}
說明:
Environment內建多種 HDR 預設(sunset、dawn、night…),只要指定preset即可自動載入。ContactShadows為一個即時渲染的軟陰影,不需要額外的光源或 ShadowMap 設定。
範例 2️⃣ 載入 GLTF 模型 + 動畫控制
import { Canvas } from '@react-three/fiber';
import { OrbitControls, useGLTF, Html } from '@react-three/drei';
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
function Model() {
// 取得模型與動畫資料
const { scene, animations } = useGLTF('/models/Duck.gltf');
const ref = useRef();
// 只播放第一段動畫
useFrame((state, delta) => {
if (ref.current) ref.current.rotation.y += delta * 0.5;
});
return (
<primitive
ref={ref}
object={scene}
// 啟用投射與接收陰影
castShadow
receiveShadow
/>
);
}
export default function ModelScene() {
return (
<Canvas shadows camera={{ position: [0, 2, 5] }}>
<OrbitControls />
{/* 載入環境光提升質感 */}
<ambientLight intensity={0.3} />
<directionalLight position={[5, 5, 5]} intensity={1} castShadow />
<Model />
{/* 載入中 UI,使用 Html 元件 */}
<Html center>
<div style={{ color: 'white' }}>Loading...</div>
</Html>
</Canvas>
);
}
重點:
useGLTF會自動快取模型,避免重複載入。Html元件能把 React JSX 直接渲染在 3D 空間中,常用於載入指示、說明文字或 UI。
範例 3️⃣ 星空背景 + 互動式文字標籤
import { Canvas } from '@react-three/fiber';
import { Stars, Html, OrbitControls } from '@react-three/drei';
import { useState } from 'react';
function Tag({ position, text }) {
const [hovered, setHover] = useState(false);
return (
<mesh position={position} onPointerOver={() => setHover(true)} onPointerOut={() => setHover(false)}>
{/* 隱形的球體,作為點擊區域 */}
<sphereGeometry args={[0.2, 16, 16]} />
<meshBasicMaterial transparent opacity={0} />
{/* Html 標籤會跟隨 3D 位置 */}
<Html distanceFactor={10} style={{ pointerEvents: 'none' }}>
<div style={{ color: hovered ? 'orange' : 'white', fontWeight: hovered ? 'bold' : 'normal' }}>
{text}
</div>
</Html>
</mesh>
);
}
export default function StarScene() {
return (
<Canvas camera={{ position: [0, 0, 15] }}>
<OrbitControls />
{/* 4️⃣ 星空背景,參數可自行調整 */}
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade />
{/* 5️⃣ 多個互動文字標籤 */}
<Tag position={[-2, 1, 0]} text="星系 A" />
<Tag position={[3, -0.5, -2]} text="星系 B" />
</Canvas>
);
}
技巧:
Stars是一個 GPU 加速的點雲,即使大量星星也不會拖慢效能。- 透過
Html搭配pointerEvents: 'none',可避免文字遮擋 3D 互動。
範例 4️⃣ 使用 useTexture 快速載入貼圖
import { Canvas } from '@react-three/fiber';
import { useTexture, OrbitControls } from '@react-three/drei';
function TexturedBox() {
// 同時載入顏色貼圖、法線貼圖與粗糙度貼圖
const [colorMap, normalMap, roughMap] = useTexture([
'/textures/wood/color.jpg',
'/textures/wood/normal.jpg',
'/textures/wood/roughness.jpg',
]);
return (
<mesh castShadow receiveShadow>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial
map={colorMap}
normalMap={normalMap}
roughnessMap={roughMap}
metalness={0}
/>
</mesh>
);
}
export default function TextureScene() {
return (
<Canvas shadows camera={{ position: [5, 5, 5] }}>
<OrbitControls />
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} castShadow />
<TexturedBox />
</Canvas>
);
}
說明:
useTexture會一次回傳一個或多個貼圖,自動完成快取與解碼,大幅簡化材質設定。
範例 5️⃣ PerspectiveCamera + 逐格動畫
import { Canvas } from '@react-three/fiber';
import { PerspectiveCamera, OrbitControls, Html } from '@react-three/drei';
import { useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
function RotatingCube() {
const ref = useRef();
const [angle, setAngle] = useState(0);
useFrame(() => {
angle += 0.01;
setAngle(angle);
if (ref.current) ref.current.rotation.y = angle;
});
return (
<mesh ref={ref} castShadow receiveShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="royalblue" />
</mesh>
);
}
export default function MultiCameraScene() {
return (
<Canvas shadows>
{/* 6️⃣ 宣告式相機,預設為主相機 */}
<PerspectiveCamera makeDefault position={[0, 2, 8]} fov={70} />
<OrbitControls />
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} intensity={1} castShadow />
<RotatingCube />
{/* 7️⃣ 在畫面右上角顯示相機資訊 */}
<Html position={[0.8, 0.9, 0]} style={{ color: '#fff' }}>
<div>Camera Pos: {`[${0}, ${2}, ${8}]`}</div>
</Html>
</Canvas>
);
}
要點:
PerspectiveCamera加上makeDefault會把此相機設定為 Canvas 的預設相機,取代Canvas內的自動相機。Html可用於即時顯示相機資訊或除錯。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方案 / Best Practice |
|---|---|---|
忘記在 Canvas 中啟用 shadows |
陰影不會呈現,模型看起來「扁平」 | 在 <Canvas shadows> 加上 shadows,同時確保光源 (directionalLight、spotLight) castShadow 為 true,物件 castShadow / receiveShadow 也要設定 |
使用 useGLTF 時路徑錯誤 |
模型載入失敗,畫面空白或報錯 | 檢查相對路徑,或改用 import model from './assets/model.glb' 再傳入 useGLTF(model) |
Html 標籤遮擋 3D 互動 |
滑鼠點擊不到底下的 mesh | 為 Html 加上 style={{ pointerEvents: 'none' }},或使用 transform 讓文字保持在 3D 空間外 |
過度使用 Environment 的 HDR 大檔案 |
首次載入時間過長,影響使用者體驗 | 使用 drei 內建的 preset(已經壓縮)或自行先行預先載入 (useLoader) 再傳入 Environment |
| 未釋放資源 (如貼圖、模型) | 記憶體泄漏,長時間使用後崩潰 | 在組件 unmount 時呼叫 dispose(),或使用 useGLTF.preload('/path') 讓 drei 自動管理快取 |
| 相機控制與自訂相機衝突 | OrbitControls 無法正常旋轉或卡住 |
確保 OrbitControls 的 camera 參考與 PerspectiveCamera 的 makeDefault 為同一個相機,或在 OrbitControls 加上 target 手動設定 |
最佳實踐
- 模組化:將常用的 drei 元件(如
Lighting,Ground,CameraControls)封裝成獨立的 React 組件,讓場景主文件保持乾淨。 - 懶載入 (Lazy Loading):使用
React.Suspense包裹useGLTF、useTexture,配合fallback顯示載入 UI,提升感知效能。 - SSR 注意:Three.js 只能在瀏覽器端執行,若使用 Next.js 等 SSR 框架,請在
dynamic(() => import('./Scene'), { ssr: false })中載入。 - 效能優化:
- 盡量使用 InstancedMesh 處理大量相同模型。
- 對於環境光,使用 低解析度的 HDR 或
blur參數降低計算量。 useFrame中避免大量的console.log或不必要的 state 更新。
實際應用場景
| 場景 | 使用的 drei 元件 | 為何適合 |
|---|---|---|
| 產品 3D 展示(如家具、鞋子) | OrbitControls, Environment, Html(價格標籤) |
使用者可以自由旋轉、放大,環境光讓材質更真實,HTML 標籤即時顯示資訊。 |
| 教育互動模擬(化學分子、天文模型) | PerspectiveCamera, Stars, ContactShadows, useGLTF |
多相機切換、星空背景、接觸陰影提升沉浸感,模型載入快速。 |
| 資料視覺化(3D 柱狀圖、熱力圖) | Html(座標軸標籤)、OrbitControls、useTexture(貼圖) |
HTML 標籤方便顯示數值,控制器讓使用者探索資料。 |
| 遊戲原型(平台跳躍、第一人稱視角) | FirstPersonControls(drei 內建)、Environment、useGLTF、Stars |
控制器提供即時操作,環境光與星空渲染氣氛,模型快速載入。 |
| AR/VR 前置(WebXR) | XR(drei 內建的 XR 支援)、OrbitControls(測試模式) |
直接切換至 XR 渲染,開發階段仍可使用 OrbitControls 測試。 |
總結
- drei 是 react‑three‑fiber 生態系中最實用的工具箱,它把繁雜的 Three.js 設定抽象成易於閱讀的 React 元件與 Hook。
- 只要掌握
OrbitControls、Environment、useGLTF、useTexture、Html等核心功能,就能在 數分鐘內完成一個完整且具備互動性的 3D 場景。 - 在開發過程中,避免陰影、模型快取、HTML 互動等常見陷阱,並遵循「模組化、懶載入、SSR 防護」的最佳實踐,能讓專案更具可維護性與效能。
- 無論是 電商商品展示、教育模擬、資料視覺化或遊戲原型,drei 都能提供即插即用的解決方案,讓前端開發者把焦點放在 體驗設計與業務需求 上,而不是底層渲染細節。
現在就把 @react-three/drei 加入你的 React 專案,用最少的程式碼,打造最炫的 3D 網頁吧!祝開發順利 🎉