Chapter 10

React Three Fiber

用 React 的声明式思维写 Three.js——组件化 3D 场景、Hook 驱动动画、Drei 组件库一键实现星空/文字/模型

1. R3F 简介:声明式 Three.js

React Three Fiber(R3F) 是 Three.js 的 React 渲染器。它把 Three.js 对象映射为 React 组件,让你用 JSX 声明式地描述 3D 场景,享受 React 的状态管理、组件复用和生态系统。

原生 Three.js

const geo = new THREE.BoxGeometry();
const mat = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(geo, mat);
mesh.position.x = 2;
scene.add(mesh);

React Three Fiber (JSX)

<mesh position-x={2}>
  <boxGeometry />
  <meshStandardMaterial />
</mesh>

2. 安装

# 创建 Vite + React 项目
npm create vite@latest my-r3f-app -- --template react
cd my-r3f-app

# 安装 R3F 核心库
npm install three @react-three/fiber

# 安装 Drei(R3F 的工具组件库)
npm install @react-three/drei

# TypeScript 类型(推荐)
npm install --save-dev @types/three

3. Canvas 与基础场景

Canvas 是 R3F 的根组件,它创建 Three.js 的 WebGLRenderer 和渲染循环,内部的所有组件都是 Three.js 对象的 JSX 表示:

import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';

// Three.js 对象名转为 camelCase 即为 JSX 组件名
// new THREE.BoxGeometry() → <boxGeometry />
// new THREE.MeshStandardMaterial() → <meshStandardMaterial />
// new THREE.DirectionalLight() → <directionalLight />

function Scene() {
  return (
    <>
      {/* 灯光 */}
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={2} />

      {/* 立方体 */}
      <mesh position={[0, 0, 0]}>
        <boxGeometry args={[1, 1, 1]} />        {/* args = 构造函数参数 */}
        <meshStandardMaterial color="#049EF4" metalness={0.8} roughness={0.2} />
      </mesh>

      {/* 控制器 */}
      <OrbitControls enableDamping />
    </>
  );
}

export default function App() {
  return (
    <Canvas
      camera={{ position: [0, 0, 5], fov: 75 }}
      shadows
      style={{ width: '100vw', height: '100vh' }}
    >
      <Scene />
    </Canvas>
  );
}

4. useFrame — 动画 Hook

useFrame 是 R3F 最核心的 Hook,在每一帧(requestAnimationFrame)调用,用于实现动画、物理更新等逻辑:

import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';

function RotatingBox() {
  const meshRef = useRef();

  // state: { clock, camera, scene, gl, ... }
  // delta: 上帧到这帧的时间差(秒)
  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta * 0.5;
    meshRef.current.rotation.y += delta * 0.8;

    // 用时间驱动动画(如悬浮)
    meshRef.current.position.y = Math.sin(state.clock.elapsedTime) * 0.3;
  });

  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshStandardMaterial color="#049EF4" />
    </mesh>
  );
}

// 交互式组件:点击变色
function InteractiveSphere() {
  const [hovered, setHovered] = useState(false);
  const [clicked, setClicked] = useState(false);

  return (
    <mesh
      onClick={() => setClicked(c => !c)}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
    >
      <sphereGeometry args={[1, 32, 16]} />
      <meshStandardMaterial
        color={clicked ? '#ff4444' : hovered ? '#88ccff' : '#049EF4'}
        roughness={0.2} metalness={0.8}
      />
    </mesh>
  );
}

5. useThree — 访问 Three.js 内部对象

import { useThree } from '@react-three/fiber';

function SceneSetup() {
  const { gl, scene, camera, size, viewport } = useThree();
  // gl      — WebGLRenderer
  // scene   — THREE.Scene
  // camera  — 当前相机
  // size    — canvas 的像素尺寸 { width, height }
  // viewport — 视口世界单位尺寸 { width, height }

  useEffect(() => {
    // 访问底层 renderer 设置
    gl.shadowMap.enabled = true;
    gl.toneMapping = THREE.ACESFilmicToneMapping;

    // 加载 HDR 环境贴图
    const rgbeLoader = new RGBELoader();
    rgbeLoader.load('/env.hdr', (tex) => {
      tex.mapping = THREE.EquirectangularReflectionMapping;
      scene.environment = tex;
      scene.background = tex;
    });
  }, [gl, scene]);

  return null;
}

6. Drei 常用组件

@react-three/drei 是 R3F 的工具库,提供大量预制组件,大幅提升开发效率:

import {
  OrbitControls, Stars, Text, Environment,
  useGLTF, MeshReflectorMaterial, Float,
  Center, useTexture, Html
} from '@react-three/drei';

// Stars — 直接使用内置星空(无需手写粒子系统)
<Stars
  radius={100}
  depth={50}
  count={5000}
  factor={4}
  saturation={0}
  fade
/>

// Text — 3D 文字(内置 SDF 字体渲染)
<Text
  fontSize={0.5}
  color="#049EF4"
  anchorX="center"
  anchorY="middle"
>
  Hello 3D World!
</Text>

// Environment — 一行代码加载 HDR 环境贴图
<Environment preset="sunset" />
{/* 可选 preset: sunset/dawn/night/warehouse/forest/apartment/studio/city/park/lobby */}

// Float — 悬浮动画包装器
<Float speed={2} rotationIntensity={0.5} floatIntensity={1}>
  <mesh>...</mesh>
</Float>

// useGLTF — 加载 GLTF 模型的 Hook
function Model() {
  const { scene, animations } = useGLTF('/models/helmet.glb');
  return <primitive object={scene} />;
}
// 预加载(防止首次加载卡顿)
useGLTF.preload('/models/helmet.glb');

// Html — 在 3D 场景中放置 HTML(如标签、UI)
<Html position={[0, 1.5, 0]} center>
  <div className="label">Click me!</div>
</Html>

// MeshReflectorMaterial — 地面反射材质
<mesh rotation-x={-Math.PI / 2}>
  <planeGeometry args={[20, 20]} />
  <MeshReflectorMaterial
    blur={[300, 100]}
    resolution={1024}
    mixBlur={1}
    mixStrength={15}
    roughness={1}
    mirror={0}
  />
</mesh>

7. 性能优化:Suspense 懒加载

模型加载是异步操作,用 React Suspense 优雅处理加载状态:

import { Suspense } from 'react';
import { Html, useProgress } from '@react-three/drei';

// 加载进度组件
function Loader() {
  const { progress } = useProgress();
  return (
    <Html center>
      <div style={{ color: 'white' }}>
        加载中... {progress.toFixed(0)}%
      </div>
    </Html>
  );
}

export default function App() {
  return (
    <Canvas shadows camera={{ position: [0, 2, 8], fov: 50 }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} castShadow />
      <Environment preset="studio" />
      <Suspense fallback={<Loader />}>
        <Float speed={1.5}>
          <Model />
        </Float>
        <Stars count={5000} fade />
      </Suspense>
      <OrbitControls enableDamping />
    </Canvas>
  );
}

8. R3F 与原生 Three.js 的对比

场景推荐方案原因
React 应用中的 3D 组件React Three Fiber与 React 状态/路由完美集成,组件可复用
独立 3D 网站/游戏原生 Three.js无 React 开销,完全控制渲染管线
3D 产品展示页R3F + DreiDrei 的 Environment/Float 等极大简化代码
游戏引擎级别应用原生 Three.js精细控制每一帧,避免 React reconciliation 开销
数据可视化 3D 图表R3F与 React 数据流天然契合
ℹ️

R3F 与 Three.js 版本对应:R3F v8 对应 Three.js r140+,使用 `@react-three/fiber@8` 和 `three@latest` 即可。Drei 的版本需要与 R3F 主版本匹配,查看 Drei 的 README 确认兼容版本。

全教程总结:你已经完成了 Three.js 从基础到进阶的完整旅程!从 WebGL 原理到 Scene/Camera/Renderer 三要素,从 PBR 材质到 GLSL 着色器,从粒子系统到 React Three Fiber 声明式 3D——现在你具备了在浏览器中构建任何 3D 世界的能力。下一步:尝试结合本教程的知识,构建一个完整的 3D 作品集或产品展示页面。