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 的理由:
- 拥抱 React 生态: 无缝集成 React 的组件化、Hooks、Context、State Management 等特性。
- 声明式开发: 用 JSX 构建 3D 场景,代码更直观、易读、易维护。
- 自动化处理: R3F 负责 Three.js 对象的创建、更新和销毁,以及渲染循环的管理。
- 性能优化: R3F 内部通过实例化和更新 diffing 等技术进行性能优化。
- 庞大的生态系统: 围绕 R3F 发展出了丰富的第三方库和工具,特别是
@react-three/drei
,提供了大量常用的 3D helper 组件和 Hooks。
如果你已经熟悉 React,那么学习 R3F 将是一个非常自然和高效的过程。
入门准备:环境搭建
开始使用 R3F 前,确保你已经安装了 Node.js 和 npm 或 yarn。我们将使用现代的前端构建工具(如 Vite)来快速搭建项目。
-
创建一个 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 -
安装必要的依赖:
我们需要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
“`
-
启动开发服务器:
“`bash
npm run dev或 yarn dev
或 pnpm dev
“`
打开浏览器访问指定的地址(通常是
http://localhost:5173
),你应该能看到默认的 Vite + React 欢迎页面。现在,我们可以开始编写 3D 代码了。
核心概念:R3F 的基石
理解以下几个核心概念是使用 R3F 的关键:
1. <Canvas>
组件
<Canvas>
是 R3F 应用的入口点。它替代了传统的 Three.js 设置代码(创建 WebGLRenderer
、Scene
、Camera
、Clock
等),并负责管理 WebGL 渲染上下文、渲染循环以及所有子组件的 Three.js 对象实例化和更新。
你的所有 3D 组件都需要放在 <Canvas>
内部。
“`jsx
import { Canvas } from ‘@react-three/fiber’
function App() {
return (
)
}
“`
<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/drei
的CameraControls
等来控制)。
这些组件的 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 />
: 平行光,模拟远处的光源,如太阳。光线是平行的,可以投射阴影。需要设置position
和intensity
。<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 (
//
onPointerOver={(event) => setHover(true)} // 鼠标悬停开始事件
onPointerOut={(event) => setHover(false)} // 鼠标悬停结束事件
>
{/ boxGeometry 定义了立方体的形状。args 是构造函数参数 [width, height, depth] /}
{/ meshStandardMaterial 定义了物体的外观。color 可以是颜色字符串、十六进制数或 THREE.Color 对象 /}
{/ 根据鼠标悬停状态改变颜色 /}
)
}
// 主应用组件
function App() {
return (
//
{/* 将自定义的 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.css
或 src/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/drei
的useGLTF
等 Hook 加载.gltf
,.glb
,.fbx
,.obj
等格式的 3D 模型。 - 纹理与材质: 深入了解 Three.js 的各种材质类型 (
MeshBasicMaterial
,MeshLambertMaterial
,MeshPhongMaterial
,MeshStandardMaterial
,MeshPhysicalMaterial
等) 以及如何加载和应用纹理 (useTexture
)、法线贴图、金属度贴图等。 - 阴影: 正确配置光源和材质的
castShadow
和receiveShadow
属性,以及<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
(可视化调试),zustand
或jotai
(与 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 组件的功能和配置。在 useFrame
或 useThree
中,你也经常需要直接操作底层的 Three.js 对象。
总结与下一步
React Three Fiber 通过将 Three.js 集成到 React 的声明式范式中,极大地降低了 Web 3D 开发的门槛,并提高了开发效率和可维护性。通过 <Canvas>
, JSX 组件映射,以及 useFrame
等 Hooks,R3F 提供了一种直观且强大的方式来构建复杂的 3D 场景。
这篇入门指南带你了解了 R3F 的核心概念,并通过一个简单的旋转立方体示例展示了如何开始使用它。但这仅仅是开始。要深入掌握 R3F,建议你:
- 阅读官方文档: React Three Fiber 的官方文档 (docs.pmnd.rs/) 是最权威的学习资源,其中包含了更详细的概念解释、API 参考和示例。
- 探索
@react-three/drei
: 熟悉并利用drei
提供的各种 Helper 组件,它们能帮助你快速实现常见功能。 - 查看示例代码: 查阅 R3F 社区提供的各种示例项目,学习如何实现更复杂的场景和效果。
- 学习 Three.js 基础: 即使使用 R3F,了解 Three.js 的基本原理(场景图、几何体、材质、光源、相机、渲染管线)仍然非常有益。Three.js 官方文档 (threejs.org/docs/) 是一个很好的参考。
- 动手实践: 尝试修改本文的示例代码,添加更多物体、光源、交互,或者加载一个外部模型。
Web 3D 的世界充满无限可能,React Three Fiber 为你打开了这扇大门,让你能够利用熟悉的 React 技术栈,构建令人惊叹的 3D 体验!祝你编码愉快!