本文 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 預設(sunsetdawnnight…),只要指定 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,同時確保光源 (directionalLightspotLight) castShadowtrue,物件 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 無法正常旋轉或卡住 確保 OrbitControlscamera 參考與 PerspectiveCameramakeDefault 為同一個相機,或在 OrbitControls 加上 target 手動設定

最佳實踐

  1. 模組化:將常用的 drei 元件(如 Lighting, Ground, CameraControls)封裝成獨立的 React 組件,讓場景主文件保持乾淨。
  2. 懶載入 (Lazy Loading):使用 React.Suspense 包裹 useGLTFuseTexture,配合 fallback 顯示載入 UI,提升感知效能。
  3. SSR 注意:Three.js 只能在瀏覽器端執行,若使用 Next.js 等 SSR 框架,請在 dynamic(() => import('./Scene'), { ssr: false }) 中載入。
  4. 效能優化
    • 盡量使用 InstancedMesh 處理大量相同模型。
    • 對於環境光,使用 低解析度的 HDRblur 參數降低計算量。
    • useFrame 中避免大量的 console.log 或不必要的 state 更新。

實際應用場景

場景 使用的 drei 元件 為何適合
產品 3D 展示(如家具、鞋子) OrbitControls, Environment, Html(價格標籤) 使用者可以自由旋轉、放大,環境光讓材質更真實,HTML 標籤即時顯示資訊。
教育互動模擬(化學分子、天文模型) PerspectiveCamera, Stars, ContactShadows, useGLTF 多相機切換、星空背景、接觸陰影提升沉浸感,模型載入快速。
資料視覺化(3D 柱狀圖、熱力圖) Html(座標軸標籤)、OrbitControlsuseTexture(貼圖) HTML 標籤方便顯示數值,控制器讓使用者探索資料。
遊戲原型(平台跳躍、第一人稱視角) FirstPersonControls(drei 內建)、EnvironmentuseGLTFStars 控制器提供即時操作,環境光與星空渲染氣氛,模型快速載入。
AR/VR 前置(WebXR) XR(drei 內建的 XR 支援)、OrbitControls(測試模式) 直接切換至 XR 渲染,開發階段仍可使用 OrbitControls 測試。

總結

  • drei 是 react‑three‑fiber 生態系中最實用的工具箱,它把繁雜的 Three.js 設定抽象成易於閱讀的 React 元件與 Hook。
  • 只要掌握 OrbitControlsEnvironmentuseGLTFuseTextureHtml 等核心功能,就能在 數分鐘內完成一個完整且具備互動性的 3D 場景
  • 在開發過程中,避免陰影、模型快取、HTML 互動等常見陷阱,並遵循「模組化、懶載入、SSR 防護」的最佳實踐,能讓專案更具可維護性與效能。
  • 無論是 電商商品展示、教育模擬、資料視覺化或遊戲原型,drei 都能提供即插即用的解決方案,讓前端開發者把焦點放在 體驗設計與業務需求 上,而不是底層渲染細節。

現在就把 @react-three/drei 加入你的 React 專案,用最少的程式碼,打造最炫的 3D 網頁吧!祝開發順利 🎉