React Three Fiber 入门指南 – wiki基地


React Three Fiber 入门指南:使用声明式方式构建 Web 3D 应用

前言:为什么选择 React Three Fiber?

Web 3D 应用正变得越来越流行,从产品展示、数据可视化到沉浸式体验,3D 技术在浏览器中的应用场景日益广泛。而 Three.js 是目前 Web 上最流行的 3D 库之一,它极大地简化了 WebGL 的复杂性,让我们能够用 JavaScript 来创建和控制 3D 场景。

然而,传统的 Three.js 开发方式是指令式(Imperative)的。这意味着你需要手动创建场景、相机、渲染器、物体、光源等,然后不断地在渲染循环中更新它们的位置、旋转等属性,最后手动调用 renderer.render() 方法来渲染场景。随着项目规模的增大,代码会变得越来越复杂,状态管理和组件复用也 becomes a challenge。

对于熟悉 React 生态系统的开发者来说,这种指令式的方式与 React 的声明式(Declarative)开发模式形成了鲜明对比。React 推崇通过组件化的方式构建 UI,并通过 state 和 props 来管理数据流,框架会自动处理 DOM 的更新。

React Three Fiber (R3F) 应运而生,它是一个 Three.js 的 React 渲染器。R3F 的核心思想是让你能够像构建 React UI 一样,使用 JSX 语法声明式地构建 Three.js 场景。它将 Three.js 的对象(如 Scene, Camera, Mesh, Light)映射成 React 组件,你可以像操作 DOM 元素一样操作 3D 对象,享受 React 带来的组件化、状态管理、Hooks 等便利。

选择 R3F 的理由:

  1. 拥抱 React 生态: 无缝集成 React 的组件化、Hooks、Context、State Management 等特性。
  2. 声明式开发: 用 JSX 构建 3D 场景,代码更直观、易读、易维护。
  3. 自动化处理: R3F 负责 Three.js 对象的创建、更新和销毁,以及渲染循环的管理。
  4. 性能优化: R3F 内部通过实例化和更新 diffing 等技术进行性能优化。
  5. 庞大的生态系统: 围绕 R3F 发展出了丰富的第三方库和工具,特别是 @react-three/drei,提供了大量常用的 3D helper 组件和 Hooks。

如果你已经熟悉 React,那么学习 R3F 将是一个非常自然和高效的过程。

入门准备:环境搭建

开始使用 R3F 前,确保你已经安装了 Node.js 和 npm 或 yarn。我们将使用现代的前端构建工具(如 Vite)来快速搭建项目。

  1. 创建一个 React 项目:
    使用 Vite 创建一个基于 React 和 JavaScript 或 TypeScript 的新项目。

    “`bash
    npm create vite@latest my-r3f-app –template react

    或 yarn create vite my-r3f-app –template react

    或 pnpm create vite my-r3f-app –template react

    “`

    按照提示完成项目创建,然后进入项目目录:

    bash
    cd my-r3f-app

  2. 安装必要的依赖:
    我们需要 three@react-three/fiber。强烈建议同时安装 @react-three/drei,它提供了大量实用的组件和抽象,能极大地提高开发效率。

    “`bash
    npm install three @react-three/fiber @react-three/drei

    或 yarn add three @react-three/fiber @react-three/drei

    或 pnpm add three @react-three/fiber @react-three/drei

    “`

  3. 启动开发服务器:

    “`bash
    npm run dev

    或 yarn dev

    或 pnpm dev

    “`

    打开浏览器访问指定的地址(通常是 http://localhost:5173),你应该能看到默认的 Vite + React 欢迎页面。现在,我们可以开始编写 3D 代码了。

核心概念:R3F 的基石

理解以下几个核心概念是使用 R3F 的关键:

1. <Canvas> 组件

<Canvas> 是 R3F 应用的入口点。它替代了传统的 Three.js 设置代码(创建 WebGLRendererSceneCameraClock 等),并负责管理 WebGL 渲染上下文、渲染循环以及所有子组件的 Three.js 对象实例化和更新。

你的所有 3D 组件都需要放在 <Canvas> 内部。

“`jsx
import { Canvas } from ‘@react-three/fiber’

function App() {
return (


{/ 你的 3D 组件将在这里 /}
{/ 例如:, , etc. /}

)
}
“`

<Canvas> 组件有很多重要的 props,用于配置渲染器、相机等,例如:

  • camera: 配置相机的属性,如 position (位置), fov (视场角), near, far 等。
  • gl: 配置 WebGLRenderer 的属性,如 antialias, shadows, powerPreference 等。
  • shadows: 快速启用阴影渲染。
  • dpr: 设置设备像素比,影响渲染质量和性能。可以是数字或数组 [min, max]
  • linear: 禁用默认的 sRGB 颜色空间,使用线性颜色空间。
  • flat: 禁用 Tonemapping。
  • orthographic: 使用正交相机代替默认的透视相机。

2. JSX 与 Three.js 对象

在 R3F 中,Three.js 的大多数对象都被封装成了首字母大写的 JSX 组件。例如:

  • Three.js 的 THREE.Mesh 对应 R3F 的 <mesh>
  • THREE.BoxGeometry 对应 R3F 的 <boxGeometry>
  • THREE.MeshStandardMaterial 对应 R3F 的 <meshStandardMaterial>
  • THREE.AmbientLight 对应 R3F 的 <ambientLight>
  • THREE.PerspectiveCamera (默认) 或 THREE.OrthographicCamera 对应 <perspectiveCamera><orthographicCamera>(通常通过 <Canvas>camera prop 或 @react-three/dreiCameraControls 等来控制)。

这些组件的 props 通常直接映射到对应的 Three.js 对象的属性。例如,设置一个 Mesh 的位置:

jsx
<mesh position={[1, 2, 3]}>
{/* ... geometry and material ... */}
</mesh>

这里的 position={[1, 2, 3]} 会设置底层 Three.js Mesh 对象的 position 属性为 { x: 1, y: 2, z: 3 }。类似的,你可以设置 rotation (欧拉角或四元数), scale, color, intensity 等属性。

对于构造函数需要参数的 Three.js 对象(如 BoxGeometry(width, height, depth)),这些参数通过 args prop 传入一个数组:

jsx
<boxGeometry args={[2, 2, 2]} /> {/* 创建一个边长为 2 的立方体 */}

3. <mesh>:几何体与材质的组合

在 3D 世界中,一个可见的物体通常由两部分组成:

  • 几何体 (Geometry): 定义了物体的形状(顶点、边、面)。在 R3F 中,它们通常是 <mesh> 组件的子元素,如 <boxGeometry>, <sphereGeometry>, <planeGeometry> 等。
  • 材质 (Material): 定义了物体的外观(颜色、纹理、光滑度、透明度等)。它们也是 <mesh> 的子元素,如 <meshStandardMaterial>, <meshBasicMaterial>, <meshPhongMaterial> 等。

一个 <mesh> 组件通常包含一个几何体和一个材质作为其子元素:

jsx
<mesh>
<boxGeometry args={[1, 1, 1]} /> {/* 一个立方体形状 */}
<meshStandardMaterial color="hotpink" /> {/* 粉红色的标准材质 */}
</mesh>

你可以通过 props 来配置几何体和材质的属性。

4. 光源 (Lights)

3D 场景需要光源才能被看到(除非使用 MeshBasicMaterial 等不受光照影响的材质)。R3F 提供了多种光源组件,对应 Three.js 的光源类型:

  • <ambientLight />: 环境光,均匀地照亮场景中的所有物体,没有方向或阴影。常用于提升整体亮度。
  • <directionalLight />: 平行光,模拟远处的光源,如太阳。光线是平行的,可以投射阴影。需要设置 positionintensity
  • <pointLight />: 点光源,从一个点向所有方向发射光线,如灯泡。需要设置 position, intensity, distance (光照范围), decay (衰减)。
  • <spotLight />: 聚光灯,从一个点沿一个方向发射锥形光线,常用于模拟手电筒或舞台灯光。需要设置 position, target (指向的目标), angle (锥形角度), penumbra (半影), decay 等。

你可以通过 intensity prop 控制光的强度,color prop 控制光的颜色,position prop 控制光源位置(对于有位置的光源)。

jsx
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} intensity={1} />

5. 相机 (Camera)

相机决定了用户从哪个角度看到 3D 场景。<Canvas> 默认会创建一个透视相机 (PerspectiveCamera) 并将其添加到场景中。你可以通过 <Canvas>camera prop 配置默认相机,或在场景中明确放置一个相机组件,但通常情况下,默认相机加上 @react-three/drei 的控制组件就足够了。

6. Hooks:useFrame, useThree, useRef

R3F 提供了一些重要的 Hooks,让你能够与 R3F/Three.js 的内部机制交互:

  • useFrame(callback): 这是 R3F 中执行动画或任何需要在每一帧渲染时更新的代码的核心 Hook。callback 函数会在渲染循环的每一帧被调用。它接收两个参数:

    • state: 包含了 R3F 的当前状态,如 state.gl (WebGLRenderer), state.scene (Scene), state.camera (Camera), state.clock (Clock) 等。
    • delta: 距离上一帧的时间间隔(以秒为单位)。这对于创建帧率无关的动画非常重要。

    “`jsx
    import { useFrame } from ‘@react-three/fiber’
    import { useRef } from ‘react’

    function AnimatedMesh() {
    const meshRef = useRef() // 获取 Mesh 对象的引用

    useFrame((state, delta) => {
    // 在每一帧更新 mesh 的旋转
    if (meshRef.current) {
    meshRef.current.rotation.x += delta // 基于时间差旋转
    meshRef.current.rotation.y += delta
    }
    })

    return (

    {/ … geometry and material … /}

    )
    }
    “`

  • useThree(): 这个 Hook 允许你在组件内部访问 R3F 的核心状态,包括 scene, camera, gl (renderer), size (canvas dimensions), viewport 等。这对于需要直接操作 Three.js 对象或获取当前渲染状态非常有用。

    “`jsx
    import { useThree } from ‘@react-three/fiber’

    function Info() {
    const { camera, gl, scene } = useThree()
    console.log(‘Camera:’, camera)
    console.log(‘Renderer:’, gl)
    console.log(‘Scene:’, scene)
    return null // 这个组件不渲染任何 3D 对象
    }
    “`

  • useRef(): 虽然这不是 R3F 特有的 Hook,但在 R3F 中非常常用。由于 R3F 组件是声明式的,你不能像在指令式代码中那样直接持有 Three.js 对象的引用。使用 useRef 并将 ref 赋值给 R3F 组件的 ref prop,可以在 useFrame 或事件处理器中访问到底层 Three.js 对象。

    jsx
    const meshRef = useRef()
    // ...
    <mesh ref={meshRef}> {/* now meshRef.current holds the THREE.Mesh instance */}
    {/* ... */}
    </mesh>

7. @react-three/drei

@react-three/drei(德语“三”的意思)是 R3F 生态中一个极其重要的辅助库。它提供了大量预制的、常用的组件和 Hooks,极大地简化了常见的 3D 开发任务,例如:

  • 相机控制器: OrbitControls, MapControls, FirstPersonControls 等,实现场景的旋转、缩放、平移等交互。
  • 模型加载: useGLTF, useFBX, useTexture 等 Hooks 用于加载各种格式的 3D 模型和纹理。
  • 几何体/材质Helpers: Box, Sphere, Plane 等组件,直接包含几何体和标准材质,写法更简洁;Trail, Line, Billboard 等特殊几何体。
  • 场景Helpers: Stage, Environment, Lightformer 用于快速搭建逼真的灯光环境。
  • UI Helpers: Html, Text 用于在 3D 场景中嵌入 HTML 或渲染文本。
  • 性能Helpers: PerformanceMonitor, useProgress 等。

强烈建议在你的 R3F 项目中安装并使用 @react-three/drei

实践:构建一个简单的可交互立方体

现在,让我们把上面学到的概念结合起来,创建一个包含一个旋转立方体的 3D 场景,并让它能够响应鼠标事件。

修改 src/App.jsx 文件:

“`jsx
import React, { useRef, useState } from ‘react’
import { Canvas, useFrame } from ‘@react-three/fiber’
import { OrbitControls } from ‘@react-three/drei’
import ‘./App.css’ // 确保你的CSS设置了 canvas 父容器的高度

// 这是一个简单的立方体组件
function Box(props) {
// This reference will give us direct access to the THREE.Mesh object
const meshRef = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)

// Subscribe this component to the render-loop, rotate the mesh every frame
// 使用 useFrame 在每一帧更新物体的旋转
useFrame((state, delta) => {
if (meshRef.current) {
// 基础旋转,让立方体自己转动
meshRef.current.rotation.x += delta * 0.5
meshRef.current.rotation.y += delta * 0.5

  // 如果被点击,放大/缩小
  // meshRef.current.scale.x = active ? 1.5 : 1
  // meshRef.current.scale.y = active ? 1.5 : 1
  // meshRef.current.scale.z = active ? 1.5 : 1
}

})

// Return view, these are regular three.js elements expressed in JSX
return (
// 组件用于表示一个 3D 物体,它包含几何体和材质
setActive(!active)} // 点击事件处理器
onPointerOver={(event) => setHover(true)} // 鼠标悬停开始事件
onPointerOut={(event) => setHover(false)} // 鼠标悬停结束事件
>
{/ boxGeometry 定义了立方体的形状。args 是构造函数参数 [width, height, depth] /}

{/ meshStandardMaterial 定义了物体的外观。color 可以是颜色字符串、十六进制数或 THREE.Color 对象 /}
{/ 根据鼠标悬停状态改变颜色 /}


)
}

// 主应用组件
function App() {
return (
// 是 R3F 场景的入口,负责设置 WebGL 渲染器、场景和相机
// dpr={window.devicePixelRatio} 设置设备像素比,提高渲染质量
// shadows 启用阴影渲染

{/ 在场景中添加环境光,均匀照亮所有物体 /}

{/ 添加定向光,模拟太阳光,可以投射阴影 /}
{/ position={[x, y, z]} 设置光源位置 /}
{/ intensity 设置光源强度 /}

{/ 光源也可以有子元素,例如用于投射阴影的相机设置 /}

  {/* 将自定义的 Box 组件添加到场景中 */}
  {/* position={[x, y, z]} 设置 Box 组件的位置 */}
  <Box position={[-1.2, 0, 0]} />
  <Box position={[1.2, 0, 0]} />

  {/* OrbitControls 组件允许用户通过鼠标拖动来旋转、缩放、平移场景 */}
  {/* 它来自 @react-three/drei 库 */}
  <OrbitControls enableZoom={true} enablePan={true} enableRotate={true} />
</Canvas>

)
}

export default App
“`

同时,确保你的 CSS 文件 (src/App.csssrc/index.css) 中为 <Canvas> 的父容器设置了明确的高度,否则 Canvas 可能无法显示。例如:

“`css

root {

width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
}

/ 如果你在 App 组件外部包裹了 div /

canvas-container {

width: 100%;
height: 100%;
}
“`

现在,重新运行 npm run dev (如果服务器已经停止),打开浏览器,你应该会看到两个橙色的旋转立方体。你可以使用鼠标拖动来环绕、缩放和移动它们。当你将鼠标悬停在立方体上时,它会变成粉红色;点击它时,它会放大或缩小。

让我们回顾一下代码:

  • 我们在 App 组件中导入了必要的模块:Canvas, useFrame (来自 @react-three/fiber), OrbitControls (来自 @react-three/drei), useRef, useState (来自 react)。
  • App 组件中,我们将所有的 3D 内容包裹在 <Canvas> 中。我们在 <Canvas> 中添加了环境光和定向光,这样立方体才能被照亮并显示阴影。
  • 我们创建了一个 Box 组件。这是一个标准的 React 函数组件。
  • Box 组件内部,我们使用 useRef 获取对 <mesh> 的引用,以便在 useFrame 和事件处理器中直接操作它。
  • 我们使用 useState 来管理立方体的悬停 (hovered) 和激活 (active) 状态,并根据这些状态改变材质颜色和缩放。
  • 我们在 <mesh> 上添加了 onClick, onPointerOver, onPointerOut 等事件处理器。R3F 会自动处理 Three.js 中的射线投射(raycasting),使得 3D 对象能够像 DOM 元素一样响应指针事件。
  • 我们使用 useFrame Hook 来实现每一帧的更新,这里用于让立方体自动旋转。delta 参数确保了无论帧率如何,旋转速度都是一致的。
  • <mesh> 的子元素 <boxGeometry> 定义了形状,<meshStandardMaterial> 定义了外观。我们通过 args prop 为几何体传递构造参数,通过 color prop 为材质设置颜色。
  • 我们在 <Canvas> 中添加了 <OrbitControls>,它会自动查找 <Canvas> 中的相机,并添加鼠标事件监听器来实现交互控制。

这个例子展示了 R3F 如何将 Three.js 的指令式 API 转换成声明式的 JSX 组件,以及如何与 React 的状态管理和 Hooks 无缝结合。

更多 R3F 特性与生态

这个入门指南只是 R3F 的冰山一角。在你深入学习的过程中,你会接触到更多强大的功能和生态库:

  • 加载外部模型: 使用 @react-three/dreiuseGLTF 等 Hook 加载 .gltf, .glb, .fbx, .obj 等格式的 3D 模型。
  • 纹理与材质: 深入了解 Three.js 的各种材质类型 (MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, MeshStandardMaterial, MeshPhysicalMaterial 等) 以及如何加载和应用纹理 (useTexture)、法线贴图、金属度贴图等。
  • 阴影: 正确配置光源和材质的 castShadowreceiveShadow 属性,以及 <Canvas>shadows prop 来实现真实的阴影效果。
  • 后处理 (Post-processing): 使用 @react-three/postprocessing 库为场景添加各种后期效果,如模糊、景深、辉光、色彩校正等。
  • 物理引擎: 集成物理引擎库,如 @react-three/rapier (基于 Rapier) 或 @react-three/cannon (基于 Cannon.js),为 3D 对象添加物理特性(重力、碰撞、力)。
  • 性能优化: 利用 r3f-perf 进行性能监控,理解实例化 (instancedMesh), useMemo 缓存复杂对象,以及 Three.js 本身的性能优化技巧。
  • 生态库: 除了 @react-three/drei, 还有如 react-three-gui (可视化调试), zustandjotai (与 useThree 结合的状态管理) 等。
  • Shader: R3F 也支持编写和使用自定义的 Three.js ShaderMaterial,允许你创造更复杂的视觉效果。

R3F 与 Three.js 的关系再探讨

再次强调,R3F 不是 Three.js 的替代品,而是 Three.js 的一个 React 渲染层

  • R3F 负责: 场景图的构建(基于 JSX 结构)、Three.js 对象的实例化和属性更新、渲染循环的管理、与 React state/props 的同步、事件处理的抽象。
  • Three.js 负责: 实际的 3D 渲染核心、几何体的计算、材质的光照模型、相机投影、各种底层 3D 算法的实现。

这意味着,虽然 R3F 让你用声明式方式开发,但要构建复杂的 3D 应用,你仍然需要理解 Three.js 的核心概念和 API。R3F 的组件 props 大多直接对应 Three.js 对象的属性和方法,了解 Three.js 的文档将极大地帮助你理解 R3F 组件的功能和配置。在 useFrameuseThree 中,你也经常需要直接操作底层的 Three.js 对象。

总结与下一步

React Three Fiber 通过将 Three.js 集成到 React 的声明式范式中,极大地降低了 Web 3D 开发的门槛,并提高了开发效率和可维护性。通过 <Canvas>, JSX 组件映射,以及 useFrame 等 Hooks,R3F 提供了一种直观且强大的方式来构建复杂的 3D 场景。

这篇入门指南带你了解了 R3F 的核心概念,并通过一个简单的旋转立方体示例展示了如何开始使用它。但这仅仅是开始。要深入掌握 R3F,建议你:

  1. 阅读官方文档: React Three Fiber 的官方文档 (docs.pmnd.rs/) 是最权威的学习资源,其中包含了更详细的概念解释、API 参考和示例。
  2. 探索 @react-three/drei 熟悉并利用 drei 提供的各种 Helper 组件,它们能帮助你快速实现常见功能。
  3. 查看示例代码: 查阅 R3F 社区提供的各种示例项目,学习如何实现更复杂的场景和效果。
  4. 学习 Three.js 基础: 即使使用 R3F,了解 Three.js 的基本原理(场景图、几何体、材质、光源、相机、渲染管线)仍然非常有益。Three.js 官方文档 (threejs.org/docs/) 是一个很好的参考。
  5. 动手实践: 尝试修改本文的示例代码,添加更多物体、光源、交互,或者加载一个外部模型。

Web 3D 的世界充满无限可能,React Three Fiber 为你打开了这扇大门,让你能够利用熟悉的 React 技术栈,构建令人惊叹的 3D 体验!祝你编码愉快!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部