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>的cameraprop 或@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 组件的refprop,可以在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 元素一样响应指针事件。 - 我们使用
useFrameHook 来实现每一帧的更新,这里用于让立方体自动旋转。delta参数确保了无论帧率如何,旋转速度都是一致的。 <mesh>的子元素<boxGeometry>定义了形状,<meshStandardMaterial>定义了外观。我们通过argsprop 为几何体传递构造参数,通过colorprop 为材质设置颜色。- 我们在
<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>的shadowsprop 来实现真实的阴影效果。 - 后处理 (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 体验!祝你编码愉快!