Three.js 3D 技術棧
檢查於 2026-10: three 0.186, @react-three/fiber 9.8, @react-three/drei 10.7, @react-three/rapier 2.2, @react-three/postprocessing 3.2 (+ postprocessing 6.39)。Fiber 9 / Drei 10 / Rapier 2 / postprocessing 3 需要 React 19 (fiber 9.8 的同儕依賴: react >=19 <19.4)。
npm i three @react-three/fiber @react-three/drei
npm i @react-three/rapier # 物理引擎
npm i @react-three/postprocessing postprocessing # 後處理效果
npm i -D @types/three
基礎場景 (網頁)
import { useRef, Suspense } from 'react'
import * as THREE from 'three'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei'
function Box() {
const ref = useRef<THREE.Mesh>(null!)
useFrame((_, delta) => { ref.current.rotation.y += delta }) // 直接改變 refs,切勿在每一影格使用 setState
return (
<mesh ref={ref} castShadow>
<boxGeometry />
<meshStandardMaterial color="#4a9eff" />
</mesh>
)
}
<Canvas camera={{ position: [3, 2, 5], fov: 45 }} dpr={[1, 2]} shadows>
<ambientLight intensity={0.3} />
<directionalLight position={[5, 5, 5]} castShadow />
<Suspense fallback={null}>
<Box />
<Environment preset="city" />
</Suspense>
<ContactShadows position={[0, -0.5, 0]} opacity={0.5} blur={2} />
<OrbitControls />
</Canvas>
規則:
- 任何非同步資源 (useGLTF, useTexture, Environment, Text 字型, Physics WASM) 都需要置於
<Suspense>之下。 - 對於首次繪製就會用到的模型,在模組層級使用
useGLTF.preload(url)進行預載。 - 場景尺寸保持在 1 到 10 個單位之間;過大或過小的比例會破壞陰影與控制項的運作。
MeshStandardMaterial需要光源或<Environment>;若兩者皆無,物體會渲染成黑色。
Drei (按需選用)
- 攝影機/控制:
OrbitControls,CameraControls,PerspectiveCamera makeDefault - 素材:
useGLTF,useTexture,Environment(預設場景: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse),Center,Bounds - 場景佈置:
ContactShadows,AccumulativeShadows,Stage,Float,Sparkles,Stars,Cloud - 材質:
MeshTransmissionMaterial(玻璃),MeshReflectorMaterial(反射地板) - 文字:
Text(SDF, 可使用任何字型網址),Text3D(需要 typeface JSON 格式),Html(DOM 覆蓋層)
Rapier 物理引擎
import { Physics, RigidBody, CuboidCollider, type RapierRigidBody } from '@react-three/rapier'
<Suspense>
<Physics gravity={[0, -9.81, 0]} /* debug */>
<RigidBody colliders="hull" restitution={0.6} ref={bodyRef}>
<mesh><torusGeometry /><meshStandardMaterial /></mesh>
</RigidBody>
<RigidBody type="fixed">
<CuboidCollider args={[10, 0.1, 10]} /> {/* 半邊長 */}
</RigidBody>
</Physics>
</Suspense>
// bodyRef.current?.applyImpulse({ x: 0, y: 5, z: 0 }, true)
colliders:"cuboid"(預設),"ball","hull","trimesh",false(自行添加碰撞體元件)。type:"dynamic"(預設),"fixed","kinematicPosition","kinematicVelocity"。timeStep預設為固定的 1/60。timeStep="vary"不具確定性。手動步進:useRapier().step(1 / 60)。- 在
<Physics>上啟用debug可繪製碰撞體。
後處理效果
import { EffectComposer, Bloom, Vignette, DepthOfField, Noise } from '@react-three/postprocessing'
<EffectComposer>{/* 作為 <Canvas> 的最後一個子元素 */}
<Bloom luminanceThreshold={0.9} intensity={1.2} mipmapBlur />
<Vignette offset={0.1} darkness={0.9} />
</EffectComposer>
- Bloom 只會捕捉亮度高於
luminanceThreshold的像素。若想讓單一物件發光: 在其材質上設定emissive顏色 +emissiveIntensity> 1 +toneMapped={false}。 - 每個效果都需要一個全螢幕通道;使用 2 到 4 個效果就已足夠。
用於影片
Remotion (請參閱 remotion-markup/3d.md): 使用 @remotion/three 中的 <ThreeCanvas width height>;不使用 useFrame;所有動畫皆來自 useCurrentFrame()。此外:
- 不要使用
OrbitControls— 透過frame來移動攝影機 (使用interpolate/spring)。 - 那些依賴自身內部時鐘來進行動畫的 Drei 輔助元件 (
Float,Sparkles,Stars,Cloud,MeshWobbleMaterial) 在渲染時會產生閃爍。請將它們移除,或改用frame來重現動畫效果。 - 物理: Remotion 會跨分頁以非順序方式渲染影格,因此即時模擬無法匹配。請將其烘焙: 初始化
@dimforge/rapier3d-compat一次 (即 @react-three/rapier 底層的引擎),對每一影格執行world.step()並設定timestep = 1 / fps,儲存每個物體在每一影格的位置與旋轉,最後在索引為frame時渲染這些變換數值。
在 Remotion 之外 (螢幕錄製、GSAP 驅動): 可以使用 useFrame;但當鏡頭必須可重複時,應透過時間軸 (tl.time()) 來驅動時間,而非使用 delta。