r3f-fundamentals

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Three Fiber Fundamentals

React Three Fiber 基础

Declarative Three.js via React components. R3F maps Three.js objects to JSX elements with automatic disposal, reactive updates, and React lifecycle integration.
通过React组件实现声明式Three.js。R3F将Three.js对象映射为JSX元素,具备自动资源释放、响应式更新及React生命周期集成特性。

Quick Start

快速开始

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

function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="hotpink" />
      </mesh>
    </Canvas>
  );
}
tsx
import { Canvas } from '@react-three/fiber';

function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="hotpink" />
      </mesh>
    </Canvas>
  );
}

Core Principle: Declarative Scene Graph

核心原则:声明式场景图

R3F converts Three.js imperative API to React's declarative model:
Three.js (Imperative)R3F (Declarative)
new THREE.Mesh()
<mesh>
mesh.position.set(1, 2, 3)
<mesh position={[1, 2, 3]}>
scene.add(mesh)
JSX nesting handles hierarchy
mesh.geometry.dispose()
Automatic on unmount
R3F将Three.js命令式API转换为React的声明式模型:
Three.js(命令式)R3F(声明式)
new THREE.Mesh()
<mesh>
mesh.position.set(1, 2, 3)
<mesh position={[1, 2, 3]}>
scene.add(mesh)
JSX嵌套处理层级关系
mesh.geometry.dispose()
卸载时自动执行

Canvas Configuration

Canvas配置

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

<Canvas
  // Renderer settings
  gl={{ antialias: true, alpha: false, powerPreference: 'high-performance' }}
  dpr={[1, 2]}                    // Device pixel ratio range
  shadows                          // Enable shadow maps
  
  // Camera (default: PerspectiveCamera)
  camera={{ 
    fov: 75, 
    near: 0.1, 
    far: 1000, 
    position: [0, 0, 5] 
  }}
  
  // Or use orthographic
  orthographic
  camera={{ zoom: 50, position: [0, 0, 100] }}
  
  // Performance
  frameloop="demand"              // 'always' | 'demand' | 'never'
  performance={{ min: 0.5 }}      // Adaptive performance
  
  // Events
  onCreated={({ gl, scene, camera }) => {
    // Access Three.js objects after mount
  }}
  
  // Sizing
  style={{ width: '100vw', height: '100vh' }}
/>
tsx
import { Canvas } from '@react-three/fiber';

<Canvas
  // 渲染器设置
  gl={{ antialias: true, alpha: false, powerPreference: 'high-performance' }}
  dpr={[1, 2]}                    // 设备像素比范围
  shadows                          // 启用阴影贴图
  
  // 相机(默认:透视相机)
  camera={{ 
    fov: 75, 
    near: 0.1, 
    far: 1000, 
    position: [0, 0, 5] 
  }}
  
  // 或使用正交相机
  orthographic
  camera={{ zoom: 50, position: [0, 0, 100] }}
  
  // 性能设置
  frameloop="demand"              // 'always' | 'demand' | 'never'
  performance={{ min: 0.5 }}      // 自适应性能
  
  // 事件
  onCreated={({ gl, scene, camera }) => {
    // 挂载后访问Three.js对象
  }}
  
  // 尺寸设置
  style={{ width: '100vw', height: '100vh' }}
/>

Frameloop Modes

渲染循环模式

ModeWhen to Use
always
Continuous animation (games, simulations)
demand
Static scenes, only re-render on state change
never
Manual control via
invalidate()
tsx
// Demand mode with manual invalidation
import { useThree } from '@react-three/fiber';

function Controls() {
  const invalidate = useThree(state => state.invalidate);
  
  const handleDrag = () => {
    // Update state...
    invalidate(); // Request re-render
  };
}
模式使用场景
always
持续动画(游戏、模拟场景)
demand
静态场景,仅在状态变化时重新渲染
never
通过
invalidate()
手动控制
tsx
// 手动触发重渲染的demand模式
import { useThree } from '@react-three/fiber';

function Controls() {
  const invalidate = useThree(state => state.invalidate);
  
  const handleDrag = () => {
    // 更新状态...
    invalidate(); // 请求重渲染
  };
}

Scene Hierarchy

场景层级

JSX nesting = Three.js parent-child relationships:
tsx
<group position={[0, 2, 0]} rotation={[0, Math.PI / 4, 0]}>
  {/* Children inherit parent transforms */}
  <mesh position={[1, 0, 0]}>
    <sphereGeometry args={[0.5, 32, 32]} />
    <meshStandardMaterial color="blue" />
  </mesh>
  
  <mesh position={[-1, 0, 0]}>
    <boxGeometry args={[0.8, 0.8, 0.8]} />
    <meshStandardMaterial color="red" />
  </mesh>
</group>
JSX嵌套对应Three.js父子关系:
tsx
<group position={[0, 2, 0]} rotation={[0, Math.PI / 4, 0]}>
  {/* 子元素继承父元素变换属性 */}
  <mesh position={[1, 0, 0]}>
    <sphereGeometry args={[0.5, 32, 32]} />
    <meshStandardMaterial color="blue" />
  </mesh>
  
  <mesh position={[-1, 0, 0]}>
    <boxGeometry args={[0.8, 0.8, 0.8]} />
    <meshStandardMaterial color="red" />
  </mesh>
</group>

Common Container Components

常用容器组件

tsx
// Group: Transform container (no rendering)
<group position={[0, 0, 0]} />

// Object3D: Base class, rarely used directly
<object3D />

// Scene: Usually implicit (Canvas creates one)
<scene />
tsx
// Group:变换容器(无渲染内容)
<group position={[0, 0, 0]} />

// Object3D:基类,很少直接使用
<object3D />

// Scene:通常隐式创建(Canvas会自动生成)
<scene />

Camera Systems

相机系统

Default Perspective Camera

默认透视相机

tsx
<Canvas camera={{ 
  fov: 75,              // Field of view (degrees)
  aspect: width/height, // Auto-calculated
  near: 0.1,            // Near clipping plane
  far: 1000,            // Far clipping plane
  position: [0, 5, 10]
}} />
tsx
<Canvas camera={{ 
  fov: 75,              // 视野角度(度)
  aspect: width/height, // 自动计算
  near: 0.1,            // 近裁剪面
  far: 1000,            // 远裁剪面
  position: [0, 5, 10]
}} />

Custom Camera Component

自定义相机组件

tsx
import { PerspectiveCamera } from '@react-three/drei';

function Scene() {
  return (
    <>
      <PerspectiveCamera 
        makeDefault           // Set as active camera
        fov={60} 
        position={[0, 2, 8]} 
      />
      {/* Scene contents */}
    </>
  );
}
tsx
import { PerspectiveCamera } from '@react-three/drei';

function Scene() {
  return (
    <>
      <PerspectiveCamera 
        makeDefault           // 设置为活跃相机
        fov={60} 
        position={[0, 2, 8]} 
      />
      {/* 场景内容 */}
    </>
  );
}

Camera Access

相机访问

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

function CameraController() {
  const { camera } = useThree();
  
  useEffect(() => {
    camera.lookAt(0, 0, 0);
  }, [camera]);
  
  return null;
}
tsx
import { useThree } from '@react-three/fiber';

function CameraController() {
  const { camera } = useThree();
  
  useEffect(() => {
    camera.lookAt(0, 0, 0);
  }, [camera]);
  
  return null;
}

Lighting

灯光

Light Types

灯光类型

tsx
// Ambient: Uniform, directionless
<ambientLight intensity={0.4} color="#ffffff" />

// Directional: Sun-like, parallel rays
<directionalLight 
  position={[5, 10, 5]} 
  intensity={1} 
  castShadow 
/>

// Point: Radiates from position
<pointLight 
  position={[0, 5, 0]} 
  intensity={1} 
  distance={20}        // Range (0 = infinite)
  decay={2}            // Physical falloff
/>

// Spot: Cone-shaped
<spotLight 
  position={[0, 10, 0]} 
  angle={Math.PI / 6}  // Cone angle
  penumbra={0.5}       // Edge softness
  castShadow 
/>

// Hemisphere: Sky/ground gradient
<hemisphereLight 
  skyColor="#87ceeb" 
  groundColor="#362907" 
  intensity={0.6} 
/>
tsx
// 环境光:均匀无方向
<ambientLight intensity={0.4} color="#ffffff" />

// 平行光:类似太阳光,光线平行
<directionalLight 
  position={[5, 10, 5]} 
  intensity={1} 
  castShadow 
/>

// 点光源:从指定位置向外辐射
<pointLight 
  position={[0, 5, 0]} 
  intensity={1} 
  distance={20}        // 照射范围(0表示无限)
  decay={2}            // 物理衰减
/>

// 聚光灯:锥形照射范围
<spotLight 
  position={[0, 10, 0]} 
  angle={Math.PI / 6}  // 锥角
  penumbra={0.5}       // 边缘柔化度
  castShadow 
/>

// 半球光:天空/地面渐变光
<hemisphereLight 
  skyColor="#87ceeb" 
  groundColor="#362907" 
  intensity={0.6} 
/>

Shadow Setup

阴影设置

tsx
<Canvas shadows>
  <directionalLight
    castShadow
    position={[10, 10, 10]}
    shadow-mapSize={[2048, 2048]}
    shadow-camera-far={50}
    shadow-camera-left={-10}
    shadow-camera-right={10}
    shadow-camera-top={10}
    shadow-camera-bottom={-10}
  />
  
  <mesh castShadow>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>
  
  <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
    <planeGeometry args={[20, 20]} />
    <meshStandardMaterial />
  </mesh>
</Canvas>
tsx
<Canvas shadows>
  <directionalLight
    castShadow
    position={[10, 10, 10]}
    shadow-mapSize={[2048, 2048]}
    shadow-camera-far={50}
    shadow-camera-left={-10}
    shadow-camera-right={10}
    shadow-camera-top={10}
    shadow-camera-bottom={-10}
  />
  
  <mesh castShadow>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>
  
  <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
    <planeGeometry args={[20, 20]} />
    <meshStandardMaterial />
  </mesh>
</Canvas>

Render Loop (useFrame)

渲染循环(useFrame)

useFrame
runs every frame (60fps target). This is where animation happens.
tsx
import { useFrame, useThree } from '@react-three/fiber';
import { useRef } from 'react';

function RotatingBox() {
  const meshRef = useRef<THREE.Mesh>(null!);
  
  useFrame((state, delta) => {
    // state: R3F state (camera, scene, clock, etc.)
    // delta: Time since last frame (seconds)
    
    meshRef.current.rotation.x += delta;
    meshRef.current.rotation.y += delta * 0.5;
  });
  
  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshNormalMaterial />
    </mesh>
  );
}
useFrame
在每帧执行(目标60fps),是实现动画的核心。
tsx
import { useFrame, useThree } from '@react-three/fiber';
import { useRef } from 'react';

function RotatingBox() {
  const meshRef = useRef<THREE.Mesh>(null!);
  
  useFrame((state, delta) => {
    // state:R3F状态对象(相机、场景、时钟等)
    // delta:距上一帧的时间(秒)
    
    meshRef.current.rotation.x += delta;
    meshRef.current.rotation.y += delta * 0.5;
  });
  
  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshNormalMaterial />
    </mesh>
  );
}

useFrame State Object

useFrame状态对象

tsx
useFrame((state) => {
  state.clock        // THREE.Clock
  state.clock.elapsedTime  // Total time (seconds)
  state.camera       // Active camera
  state.scene        // Scene object
  state.gl           // WebGLRenderer
  state.size         // { width, height }
  state.viewport     // { width, height, factor, distance }
  state.mouse        // Normalized mouse position [-1, 1]
  state.raycaster    // THREE.Raycaster
});
tsx
useFrame((state) => {
  state.clock        // THREE.Clock
  state.clock.elapsedTime  // 总运行时间(秒)
  state.camera       // 活跃相机
  state.scene        // Scene对象
  state.gl           // WebGLRenderer
  state.size         // { width, height }
  state.viewport     // { width, height, factor, distance }
  state.mouse        // 归一化鼠标位置 [-1, 1]
  state.raycaster    // THREE.Raycaster
});

Render Priority

渲染优先级

tsx
// Lower priority runs first, higher runs later
// Default is 0

useFrame(() => {
  // Update physics
}, -1);  // Runs before default

useFrame(() => {
  // Update visuals
}, 0);   // Default

useFrame(() => {
  // Post-processing / camera
}, 1);   // Runs after default
tsx
// 优先级越低越先执行,越高越晚执行
// 默认优先级为0

useFrame(() => {
  // 更新物理效果
}, -1);  // 早于默认执行

useFrame(() => {
  // 更新视觉效果
}, 0);   // 默认优先级

useFrame(() => {
  // 后处理 / 相机控制
}, 1);   // 晚于默认执行

Accessing Three.js Objects

访问Three.js对象

useThree Hook

useThree钩子

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

function SceneInfo() {
  const { 
    gl,           // WebGLRenderer
    scene,        // THREE.Scene
    camera,       // Active camera
    size,         // Canvas dimensions
    viewport,     // Viewport in Three.js units
    clock,        // THREE.Clock
    set,          // Update state
    get,          // Get current state
    invalidate,   // Request re-render (demand mode)
    advance,      // Advance one frame (never mode)
  } = useThree();
  
  return null;
}
tsx
import { useThree } from '@react-three/fiber';

function SceneInfo() {
  const { 
    gl,           // WebGLRenderer
    scene,        // THREE.Scene
    camera,       // 活跃相机
    size,         // Canvas尺寸
    viewport,     // Three.js单位下的视口
    clock,        // THREE.Clock
    set,          // 更新状态
    get,          // 获取当前状态
    invalidate,   // 请求重渲染(demand模式)
    advance,      // 推进一帧(never模式)
  } = useThree();
  
  return null;
}

Refs for Direct Access

通过Ref直接访问

tsx
import { useRef } from 'react';
import * as THREE from 'three';

function DirectAccess() {
  const meshRef = useRef<THREE.Mesh>(null!);
  const materialRef = useRef<THREE.MeshStandardMaterial>(null!);
  
  useEffect(() => {
    // Direct Three.js API access
    meshRef.current.geometry.computeBoundingBox();
    materialRef.current.needsUpdate = true;
  }, []);
  
  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshStandardMaterial ref={materialRef} />
    </mesh>
  );
}
tsx
import { useRef } from 'react';
import * as THREE from 'three';

function DirectAccess() {
  const meshRef = useRef<THREE.Mesh>(null!);
  const materialRef = useRef<THREE.MeshStandardMaterial>(null!);
  
  useEffect(() => {
    // 直接调用Three.js API
    meshRef.current.geometry.computeBoundingBox();
    materialRef.current.needsUpdate = true;
  }, []);
  
  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshStandardMaterial ref={materialRef} />
    </mesh>
  );
}

Events

事件

R3F provides pointer events on meshes:
tsx
<mesh
  onClick={(e) => console.log('click', e.point)}
  onContextMenu={(e) => console.log('right click')}
  onDoubleClick={(e) => console.log('double click')}
  onPointerOver={(e) => console.log('hover')}
  onPointerOut={(e) => console.log('unhover')}
  onPointerDown={(e) => console.log('down')}
  onPointerUp={(e) => console.log('up')}
  onPointerMove={(e) => console.log('move')}
>
  <boxGeometry />
  <meshStandardMaterial />
</mesh>
R3F为网格提供指针事件支持:
tsx
<mesh
  onClick={(e) => console.log('点击', e.point)}
  onContextMenu={(e) => console.log('右键点击')}
  onDoubleClick={(e) => console.log('双击')}
  onPointerOver={(e) => console.log('悬停')}
  onPointerOut={(e) => console.log('离开悬停')}
  onPointerDown={(e) => console.log('按下')}
  onPointerUp={(e) => console.log('抬起')}
  onPointerMove={(e) => console.log('移动')}
>
  <boxGeometry />
  <meshStandardMaterial />
</mesh>

Event Object

事件对象

tsx
onClick={(e) => {
  e.stopPropagation();    // Stop event bubbling
  e.point                 // THREE.Vector3 intersection point
  e.distance              // Distance from camera
  e.object                // Intersected object
  e.face                  // Intersected face
  e.faceIndex             // Face index
  e.uv                    // UV coordinates
  e.camera                // Camera used for raycasting
  e.delta                 // Distance from last event
}}
tsx
onClick={(e) => {
  e.stopPropagation();    // 阻止事件冒泡
  e.point                 // THREE.Vector3类型的交点
  e.distance              // 距相机的距离
  e.object                // 被交互的对象
  e.face                  // 被交互的面
  e.faceIndex             // 面索引
  e.uv                    // UV坐标
  e.camera                // 用于射线检测的相机
  e.delta                 // 距上一次事件的距离
}}

Suspense & Loading

Suspense与加载

R3F integrates with React Suspense for async loading:
tsx
import { Suspense } from 'react';
import { useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

function Model() {
  const gltf = useLoader(GLTFLoader, '/model.glb');
  return <primitive object={gltf.scene} />;
}

function App() {
  return (
    <Canvas>
      <Suspense fallback={<LoadingSpinner />}>
        <Model />
      </Suspense>
    </Canvas>
  );
}

function LoadingSpinner() {
  const meshRef = useRef<THREE.Mesh>(null!);
  useFrame((_, delta) => {
    meshRef.current.rotation.z += delta * 2;
  });
  
  return (
    <mesh ref={meshRef}>
      <torusGeometry args={[1, 0.2, 16, 32]} />
      <meshBasicMaterial color="white" wireframe />
    </mesh>
  );
}
R3F集成React Suspense实现异步加载:
tsx
import { Suspense } from 'react';
import { useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

function Model() {
  const gltf = useLoader(GLTFLoader, '/model.glb');
  return <primitive object={gltf.scene} />;
}

function App() {
  return (
    <Canvas>
      <Suspense fallback={<LoadingSpinner />}>
        <Model />
      </Suspense>
    </Canvas>
  );
}

function LoadingSpinner() {
  const meshRef = useRef<THREE.Mesh>(null!);
  useFrame((_, delta) => {
    meshRef.current.rotation.z += delta * 2;
  });
  
  return (
    <mesh ref={meshRef}>
      <torusGeometry args={[1, 0.2, 16, 32]} />
      <meshBasicMaterial color="white" wireframe />
    </mesh>
  );
}

Dependencies

依赖项

json
{
  "dependencies": {
    "@react-three/fiber": "^8.15.0",
    "three": "^0.160.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/three": "^0.160.0"
  }
}
json
{
  "dependencies": {
    "@react-three/fiber": "^8.15.0",
    "three": "^0.160.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/three": "^0.160.0"
  }
}

File Structure

文件结构

r3f-fundamentals/
├── SKILL.md
├── references/
│   ├── canvas-props.md       # Complete Canvas prop reference
│   ├── hooks-api.md          # useThree, useFrame, useLoader
│   └── event-system.md       # Event handling deep-dive
└── scripts/
    ├── templates/
    │   ├── basic-scene.tsx   # Minimal starter
    │   ├── lit-scene.tsx     # With proper lighting
    │   └── interactive.tsx   # With events and animation
    └── utils/
        └── canvas-config.ts  # Preset configurations
r3f-fundamentals/
├── SKILL.md
├── references/
│   ├── canvas-props.md       # 完整Canvas属性参考
│   ├── hooks-api.md          # useThree、useFrame、useLoader
│   └── event-system.md       # 事件处理深度解析
└── scripts/
    ├── templates/
    │   ├── basic-scene.tsx   # 最小化启动模板
    │   ├── lit-scene.tsx     # 带完整灯光的模板
    │   └── interactive.tsx   # 带事件和动画的模板
    └── utils/
        └── canvas-config.ts  # 预设配置

Reference

参考文档

  • references/canvas-props.md
    — Complete Canvas configuration options
  • references/hooks-api.md
    — All R3F hooks with examples
  • references/event-system.md
    — Pointer events and raycasting
  • references/canvas-props.md
    — 完整Canvas配置选项
  • references/hooks-api.md
    — 所有R3F钩子及示例
  • references/event-system.md
    — 指针事件与射线检测