threejs-react-three-fiber

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Three.js React Three Fiber

Three.js React Three Fiber

Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.
适用于 three.js r186。示例使用 WebGLRenderer;WebGPU 和 TSL 一节使用 WebGPURenderer。

Quick Start

快速开始

Targets
@react-three/fiber
9.8 with React 19 and
@react-three/drei
10.7. The Canvas creates and owns a WebGLRenderer, a scene and a camera; three.js objects become JSX elements. The Canvas fills its parent element, so give the parent a height. The component fragments below render inside a Canvas like this one; lit materials assume its lights.
jsx
import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";

function Box() {
  const ref = useRef();
  useFrame((_, delta) => {
    ref.current.rotation.y += delta;
  });
  return (
    <mesh ref={ref} position={[0, 0.5, 0]} castShadow>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="#d9915b" />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas shadows="percentage" camera={{ position: [3, 2, 4], fov: 50 }}>
      <color attach="background" args={["#111827"]} />
      <ambientLight intensity={0.4} />
      <directionalLight position={[3, 4, 2]} intensity={2} castShadow />
      <Box />
      <mesh rotation-x={-Math.PI / 2} receiveShadow>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial color="#20293a" />
      </mesh>
      <OrbitControls makeDefault />
    </Canvas>
  );
}
适用于
@react-three/fiber
9.8、React 19 和
@react-three/drei
10.7。Canvas 会创建并管理一个 WebGLRenderer、一个 scene 和一个 camera;three.js 对象则成为 JSX 元素。Canvas 会填满其父元素,因此必须为父元素设置高度。下面的组件片段会渲染在类似这样的 Canvas 内;受光材质假定 Canvas 中已有灯光。
jsx
import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";

function Box() {
  const ref = useRef();
  useFrame((_, delta) => {
    ref.current.rotation.y += delta;
  });
  return (
    <mesh ref={ref} position={[0, 0.5, 0]} castShadow>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="#d9915b" />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas shadows="percentage" camera={{ position: [3, 2, 4], fov: 50 }}>
      <color attach="background" args={["#111827"]} />
      <ambientLight intensity={0.4} />
      <directionalLight position={[3, 4, 2]} intensity={2} castShadow />
      <Box />
      <mesh rotation-x={-Math.PI / 2} receiveShadow>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial color="#20293a" />
      </mesh>
      <OrbitControls makeDefault />
    </Canvas>
  );
}

Canvas Props

Canvas 属性

jsx
export function App() {
  return (
    <Canvas
      shadows="variance"
      dpr={[1, 2]}
      frameloop="demand"
      flat
      camera={{ position: [0, 2, 6], fov: 45 }}
      gl={{ powerPreference: "high-performance" }}
      onCreated={(state) => state.gl.setClearColor("#111827")}
    >
      {/* scene contents */}
    </Canvas>
  );
}
  • camera
    : props for the default camera, or a camera instance. Without it you get a
    PerspectiveCamera
    with fov 75, near 0.1, far 1000, at z 5 looking at the origin; fiber updates aspect (or left/right/top/bottom with
    orthographic
    ) and the projection matrix on every resize.
    camera={{ manual: true }}
    puts projection entirely in your hands.
  • dpr
    : a number, or
    [min, max]
    clamped against
    window.devicePixelRatio
    ; the range follows display changes.
  • shadows
    :
    true
    enables shadow maps, a string (
    'basic'
    ,
    'percentage'
    ,
    'variance'
    ) picks the filter type, an object merges into the renderer's
    shadowMap
    properties.
    true
    ,
    'soft'
    and the default
    false
    all set PCFSoftShadowMap, which three swaps for PCFShadowMap with a console warning (WebGLRenderer when shadows render, WebGPURenderer on every Canvas);
    'percentage'
    is the same soft filter without the warning.
  • flat
    sets NoToneMapping; without it fiber applies ACESFilmicToneMapping — unlike plain three.js, tone mapping is on by default.
    linear
    switches the output to LinearSRGBColorSpace; the default is sRGB.
    legacy
    disables three's ColorManagement.
  • gl
    : props for the default
    new WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" })
    , a renderer instance, or a factory function (the WebGPU path below).
  • frameloop
    :
    'always'
    renders every frame,
    'demand'
    renders only after React changes or
    invalidate()
    ,
    'never'
    hands the loop over (call the module-level
    advance(timestamp)
    export with a time in seconds;
    useFrame
    's delta is the difference between successive timestamps, so milliseconds make it 1000 times too large).
  • events
    replaces the event manager (the default maps DOM pointer events to raycasts);
    eventSource
    (an element pointer events attach to, default the Canvas wrapper) and
    eventPrefix
    (
    'offset'
    by default;
    'page'
    ,
    'client'
    ,
    'layer'
    ,
    'screen'
    when the page scrolls or transforms the canvas) tune pointer mapping.
    onCreated
    runs once the renderer exists;
    onPointerMissed
    fires on clicks that hit nothing.
jsx
export function App() {
  return (
    <Canvas
      shadows="variance"
      dpr={[1, 2]}
      frameloop="demand"
      flat
      camera={{ position: [0, 2, 6], fov: 45 }}
      gl={{ powerPreference: "high-performance" }}
      onCreated={(state) => state.gl.setClearColor("#111827")}
    >
      {/* scene contents */}
    </Canvas>
  );
}
  • camera
    :默认 camera 的 props,或一个 camera 实例。不传入时,会得到一个
    PerspectiveCamera
    ,其 fov 为 75、near 为 0.1、far 为 1000,位于 z 5 并朝向原点;fiber 会在每次 resize 时更新 aspect(使用
    orthographic
    时则更新 left/right/top/bottom)以及 projection matrix。
    camera={{ manual: true }}
    表示 projection 完全由你控制。
  • dpr
    :一个数字,或
    [min, max]
    形式的范围,后者会根据
    window.devicePixelRatio
    进行限制;该范围会随显示器变化而调整。
  • shadows
    :
    true
    会启用 shadow maps;字符串(
    'basic'
    、
    'percentage'
    、
    'variance'
    )用于选择 filter 类型;对象则会合并到 renderer 的
    shadowMap
    properties 中。
    true
    、
    'soft'
    和默认的
    false
    都会设置 PCFSoftShadowMap,而 three 会将其替换为 PCFShadowMap 并输出 console warning(WebGLRenderer 仅在渲染阴影时警告,WebGPURenderer 则在每个 Canvas 上警告);
    'percentage'
    使用相同的软阴影 filter,但不会产生警告。
  • flat
    :设置 NoToneMapping;不设置时,fiber 会应用 ACESFilmicToneMapping——与原生 three.js 不同,色调映射默认开启。
    linear
    会将输出切换为 LinearSRGBColorSpace;默认为 sRGB。
    legacy
    会禁用 three 的 ColorManagement。
  • gl
    :用于默认的
    new WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" })
    的 props、一个 renderer 实例,或一个 factory function(见下方的 WebGPU 路径)。
  • frameloop
    :
    'always'
    每帧都渲染;
    'demand'
    仅在 React 发生变化或调用
    invalidate()
    后渲染;
    'never'
    会交出该循环的控制权(调用模块级导出的
    advance(timestamp)
    ,并传入以秒为单位的时间;
    useFrame
    的 delta 是相邻时间戳之差,因此传入毫秒会使其放大 1000 倍)。
  • events
    :替换 event manager(默认实现会将 DOM pointer events 映射到 raycasts);
    eventSource
    (pointer events 附加到的元素,默认为 Canvas wrapper)和
    eventPrefix
    (默认值为
    'offset'
    ;当页面滚动或对 canvas 做 transform 时,可使用
    'page'
    、
    'client'
    、
    'layer'
    、
    'screen'
    )用于调整 pointer 映射。
    onCreated
    会在 renderer 创建后运行一次;
    onPointerMissed
    会在点击未命中任何对象时触发。

The Scene Graph as JSX

将场景图表示为 JSX

Every export of the three namespace is an element: lowercase tag, capitalized class (
<mesh>
→
THREE.Mesh
,
<meshStandardMaterial>
→ its material). Props apply to the instance; shorthand values work anywhere three has a
.set()
:
position={[1, 2, 3]}
,
position={new THREE.Vector3(1, 2, 3)}
,
position={5}
(setScalar),
color="#d9915b"
. Dashed props pierce (
rotation-x
,
material-color
,
geometry-attributes-position
);
attach="material-0"
attaches into an array index.
args
is the constructor argument list; changing it rebuilds the object from scratch. Geometries and materials attach to their parent mesh automatically — no
attach
needed.
jsx
function Torus() {
  return (
    <mesh position={[0, 1, 0]}>
      <torusKnotGeometry args={[0.7, 0.25, 128, 24]} />
      <meshStandardMaterial
        color="#42699c"
        roughness={0.2}
        metalness={0.8}
        onUpdate={(self) => console.log(self.type)}
      />
    </mesh>
  );
}
Mount an object you already hold with
<primitive object={obj} />
; one object per primitive (fiber keeps its per-object state on the object), and clone for copies. Fiber never disposes primitives — their lifetime is yours.
dispose={null}
on any element opts it out of unmount disposal. Register classes outside the three namespace with
extend
:
jsx
import { extend } from "@react-three/fiber";
import { Water } from "three/addons/objects/Water.js";

extend({ Water });
// then render <water args={[geometry]} /> anywhere in the Canvas
In TypeScript the tags
line
,
audio
,
source
and
path
collide with DOM types; write
threeLine
,
threeAudio
,
threeSource
,
threePath
(fiber strips the prefix and resolves the three class).
three namespace 的每个导出项都会成为一个 element:小写标签对应首字母大写的 class(
<mesh>
→
THREE.Mesh
,
<meshStandardMaterial>
→ 对应的 material)。Props 会应用到实例;在 three 提供
.set()
的位置均可使用简写值,例如
position={[1, 2, 3]}
、
position={new THREE.Vector3(1, 2, 3)}
、
position={5}
(setScalar)、
color="#d9915b"
。带连字符的 props 会向下穿透(
rotation-x
、
material-color
、
geometry-attributes-position
);
attach="material-0"
可将对象附加到数组中的指定索引。
args
是 constructor argument list;修改它会从头重新构建对象。Geometries 和 materials 会自动附加到父 mesh,无需使用
attach
。
jsx
function Torus() {
  return (
    <mesh position={[0, 1, 0]}>
      <torusKnotGeometry args={[0.7, 0.25, 128, 24]} />
      <meshStandardMaterial
        color="#42699c"
        roughness={0.2}
        metalness={0.8}
        onUpdate={(self) => console.log(self.type)}
      />
    </mesh>
  );
}
可通过
<primitive object={obj} />
挂载已经持有的对象;每个 primitive 只能对应一个对象(fiber 会将每个对象的状态保存在该对象上),如需副本则应进行 clone。Fiber 永远不会 dispose primitives——其生命周期由你管理。在任意 element 上设置
dispose={null}
,即可使其免于在 unmount 时被 dispose。可使用
extend
注册 three namespace 之外的 classes:
jsx
import { extend } from "@react-three/fiber";
import { Water } from "three/addons/objects/Water.js";

extend({ Water });
// then render <water args={[geometry]} /> anywhere in the Canvas
在 TypeScript 中,标签
line
、
audio
、
source
和
path
会与 DOM types 冲突;应写成
threeLine
、
threeAudio
、
threeSource
、
threePath
(fiber 会移除前缀并解析对应的 three class)。

Hooks

Hooks

useFrame(callback, renderPriority?)
subscribes to the render loop; it runs before the scene renders:
jsx
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";

function Spinner({ radius = 2 }) {
  const ref = useRef();
  useFrame((state, delta) => {
    ref.current.rotation.y += delta;
    ref.current.position.x = Math.sin(state.clock.elapsedTime) * radius;
  });
  return (
    <mesh ref={ref}>
      <sphereGeometry args={[0.4, 32, 16]} />
      <meshBasicMaterial color="#e8b4a0" />
    </mesh>
  );
}
Subscribers with a positive
renderPriority
take rendering into their own hands — the Canvas stops rendering, and your callback must call
state.gl.render(state.scene, state.camera)
itself. At default priority, calling
gl.render
yourself double-renders.
useThree(selector?)
reads Canvas state reactively; with no selector it returns the whole state (
gl
,
camera
,
scene
,
raycaster
,
pointer
,
size
,
viewport
,
invalidate
,
setDpr
,
setFrameloop
,
controls
,
performance
):
jsx
import { useThree } from "@react-three/fiber";

const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);
Inside
useFrame
use the
state
argument instead — it is the same state without a re-render.
state.clock
is a
THREE.Clock
that fiber 9.8 creates, so every Canvas logs three's Clock deprecation warning; it does not come from your code.
useLoader(ClassOrInstance, url, extensions?, onProgress?)
suspends until any three loader resolves and caches by loader and URL; its caller must sit under a Suspense boundary. The extensions callback runs on the one loader instance fiber keeps per class, so a setting it makes (a path, a decoder) applies to every later
useLoader
call with that class.
useGraph(object)
builds named lookup maps (
{ nodes, materials, meshes }
) from an object tree:
jsx
import { useGraph, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

function Model({ url }) {
  const gltf = useLoader(GLTFLoader, url, (loader) => loader.setPath("assets/"));
  const { nodes } = useGraph(gltf.scene);
  return <primitive object={gltf.scene} />;
}
useLoader.preload(Class, url)
warms the cache outside React;
useLoader.clear(Class, url)
drops an entry.
useFrame(callback, renderPriority?)
用于订阅 render loop;它会在 scene 渲染之前运行:
jsx
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";

function Spinner({ radius = 2 }) {
  const ref = useRef();
  useFrame((state, delta) => {
    ref.current.rotation.y += delta;
    ref.current.position.x = Math.sin(state.clock.elapsedTime) * radius;
  });
  return (
    <mesh ref={ref}>
      <sphereGeometry args={[0.4, 32, 16]} />
      <meshBasicMaterial color="#e8b4a0" />
    </mesh>
  );
}
具有正数
renderPriority
的订阅者将自行接管渲染——Canvas 会停止渲染,你的 callback 必须自行调用
state.gl.render(state.scene, state.camera)
。在默认 priority 下自行调用
gl.render
会导致双重渲染。
useThree(selector?)
可响应式地读取 Canvas state;不传 selector 时会返回完整 state(
gl
、
camera
、
scene
、
raycaster
、
pointer
、
size
、
viewport
、
invalidate
、
setDpr
、
setFrameloop
、
controls
、
performance
):
jsx
import { useThree } from "@react-three/fiber";

const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);
在
useFrame
内应使用
state
参数,而不是
useThree
——它提供的是同一份 state,但不会触发 re-render。
state.clock
是 fiber 9.8 创建的
THREE.Clock
,因此每个 Canvas 都会输出 three 的 Clock deprecation warning;这并非来自你的代码。
useLoader(ClassOrInstance, url, extensions?, onProgress?)
会 suspend,直到相应的 three loader 完成加载,并按 loader 和 URL 缓存结果;调用者必须位于 Suspense boundary 之下。Extensions callback 会在 fiber 为每个 class 保留的同一个 loader 实例上运行,因此它所做的设置(例如 path 或 decoder)会应用到该 class 后续的所有
useLoader
调用。
useGraph(object)
会从对象树构建命名 lookup maps(
{ nodes, materials, meshes }
):
jsx
import { useGraph, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

function Model({ url }) {
  const gltf = useLoader(GLTFLoader, url, (loader) => loader.setPath("assets/"));
  const { nodes } = useGraph(gltf.scene);
  return <primitive object={gltf.scene} />;
}
useLoader.preload(Class, url)
可在 React 之外预热 cache;
useLoader.clear(Class, url)
会移除对应条目。

Pointer Events

Pointer Events

Objects with handlers are raycast on each pointer event (pointer moves test only objects with move, over, out, enter or leave handlers); handlers fire nearest first, bubbling from the hit object up its parents. The event carries the intersection plus helpers:
object
,
eventObject
(the object that registered the handler),
point
,
distance
,
face
,
instanceId
,
intersections
,
ray
,
camera
,
unprojectedPoint
,
delta
(pixels travelled since pointerdown, for click-like events),
nativeEvent
. Handlers:
onClick
,
onDoubleClick
,
onContextMenu
,
onWheel
,
onPointerUp
,
onPointerDown
,
onPointerMove
,
onPointerOver
,
onPointerOut
,
onPointerEnter
,
onPointerLeave
,
onPointerMissed
(
onPointerCancel
and
onLostPointerCapture
are typed but fiber never calls them on objects).
jsx
import { useState } from "react";

function Pickable() {
  const [hovered, setHovered] = useState(false);
  return (
    <mesh
      onClick={(event) => {
        event.stopPropagation(); // underlying objects along the ray no longer receive it
        console.log(event.object.name, event.distance, event.point, event.instanceId);
      }}
      onPointerOver={(event) => {
        event.stopPropagation();
        setHovered(true);
      }}
      onPointerOut={() => setHovered(false)}
      onPointerDown={(event) => {
        event.target.setPointerCapture(event.pointerId); // this pointer's events reach the mesh off its surface until release
      }}
      onPointerUp={(event) => {
        event.target.releasePointerCapture(event.pointerId);
      }}
      onPointerMissed={() => console.log("clicked elsewhere")}
    >
      <sphereGeometry args={[0.75, 32, 16]} />
      <meshStandardMaterial color={hovered ? "#e8b4a0" : "#42699c"} />
    </mesh>
  );
}
Pointer events work under any renderer: the default raycaster tests CPU geometry in
intersectObject(object, true)
. Pass
raycaster={{ params: { Line: { threshold: 0.1 } } }}
to tune it.
每次 pointer event 都会对带 event handler 的对象执行 raycast(pointer move 仅检测带有 move、over、out、enter 或 leave handler 的对象);handlers 会优先在最近命中的对象上触发,并从命中对象沿父级向上冒泡。Event 包含 intersection 及以下辅助信息:
object
、
eventObject
(注册 handler 的对象)、
point
、
distance
、
face
、
instanceId
、
intersections
、
ray
、
camera
、
unprojectedPoint
、
delta
(自 pointerdown 起移动的像素数,适用于类似 click 的事件)、
nativeEvent
。Handlers 包括:
onClick
、
onDoubleClick
、
onContextMenu
、
onWheel
、
onPointerUp
、
onPointerDown
、
onPointerMove
、
onPointerOver
、
onPointerOut
、
onPointerEnter
、
onPointerLeave
、
onPointerMissed
(
onPointerCancel
和
onLostPointerCapture
已有类型定义,但 fiber 从不会在对象上调用它们)。
jsx
import { useState } from "react";

function Pickable() {
  const [hovered, setHovered] = useState(false);
  return (
    <mesh
      onClick={(event) => {
        event.stopPropagation(); // underlying objects along the ray no longer receive it
        console.log(event.object.name, event.distance, event.point, event.instanceId);
      }}
      onPointerOver={(event) => {
        event.stopPropagation();
        setHovered(true);
      }}
      onPointerOut={() => setHovered(false)}
      onPointerDown={(event) => {
        event.target.setPointerCapture(event.pointerId); // this pointer's events reach the mesh off its surface until release
      }}
      onPointerUp={(event) => {
        event.target.releasePointerCapture(event.pointerId);
      }}
      onPointerMissed={() => console.log("clicked elsewhere")}
    >
      <sphereGeometry args={[0.75, 32, 16]} />
      <meshStandardMaterial color={hovered ? "#e8b4a0" : "#42699c"} />
    </mesh>
  );
}
Pointer events 可用于任何 renderer:默认 raycaster 会在
intersectObject(object, true)
中检测 CPU geometry。可传入
raycaster={{ params: { Line: { threshold: 0.1 } } }}
对其进行调整。

Loading Assets

加载资源

Loading hooks suspend; put the boundary above them and the fallback shows while they load.
jsx
import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF, useProgress, Environment } from "@react-three/drei";

function Model({ url }) {
  const { scene, animations, nodes, materials } = useGLTF(url);
  return <primitive object={scene} />;
}

function Hud() {
  const { active, progress, errors } = useProgress(); // tracks three's DefaultLoadingManager
  return <div>{active ? `${progress.toFixed(0)}%` : errors.length > 0 ? "failed" : "done"}</div>;
}

export function App() {
  return (
    <>
      <Hud />
      <Canvas camera={{ position: [3, 2, 4], fov: 50 }}>
        <Suspense fallback={null}>
          <Environment preset="city" />
          <Model url="model.glb" />
        </Suspense>
      </Canvas>
    </>
  );
}
  • useGLTF(url)
    loads through the GLTFLoader drei ships with (three-stdlib), with Draco and meshopt enabled by default; the Draco decoder comes from a pinned gstatic URL (
    useGLTF.setDecoderPath
    retargets it,
    useGLTF(url, false)
    disables it).
    useGLTF.preload(url)
    warms the cache. The result carries
    scene
    ,
    animations
    ,
    cameras
    plus
    nodes
    /
    materials
    /
    meshes
    lookup maps. For several copies of a skinned model, clone it once per component with
    useMemo(() => SkeletonUtils.clone(scene), [scene])
    (
    import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js"
    ) and mount each clone in its own primitive.
  • useTexture(input)
    loads one URL, an array (
    const [a, b] = useTexture([x, y])
    ) or a keyed object; fiber sets sRGB color space on LDR (RGBA8) color maps assigned through props —
    map
    ,
    emissiveMap
    ,
    envMap
    among them — so color maps work without manual
    colorSpace
    , and scalar maps keep NoColorSpace.
  • <Preload all />
    compiles every material once, when it mounts (with
    all
    , hidden objects' too), and renders the scene into a cube target to upload textures, trading a one-time stall for no first-view hitches; put it inside the Suspense boundary after the assets, or it runs before they load.
  • useProgress
    is a plain store, so the Hud above reads it outside the Canvas in the DOM.
加载类 hooks 会触发 suspend;应将 boundary 放在它们上方,加载期间会显示 fallback。
jsx
import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF, useProgress, Environment } from "@react-three/drei";

function Model({ url }) {
  const { scene, animations, nodes, materials } = useGLTF(url);
  return <primitive object={scene} />;
}

function Hud() {
  const { active, progress, errors } = useProgress(); // tracks three's DefaultLoadingManager
  return <div>{active ? `${progress.toFixed(0)}%` : errors.length > 0 ? "failed" : "done"}</div>;
}

export function App() {
  return (
    <>
      <Hud />
      <Canvas camera={{ position: [3, 2, 4], fov: 50 }}>
        <Suspense fallback={null}>
          <Environment preset="city" />
          <Model url="model.glb" />
        </Suspense>
      </Canvas>
    </>
  );
}
  • useGLTF(url)
    通过 drei 附带的 GLTFLoader(three-stdlib)加载,默认启用 Draco 和 meshopt;Draco decoder 来自一个固定版本的 gstatic URL(
    useGLTF.setDecoderPath
    可重定向该地址,
    useGLTF(url, false)
    可禁用它)。
    useGLTF.preload(url)
    可预热 cache。结果包含
    scene
    、
    animations
    、
    cameras
    ,以及
    nodes
    /
    materials
    /
    meshes
    lookup maps。对于同一个 skinned model 的多个副本,应为每个 component 使用
    useMemo(() => SkeletonUtils.clone(scene), [scene])
    clone 一次(
    import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js"
    ),并将每个 clone 挂载到各自的 primitive 中。
  • useTexture(input)
    可加载单个 URL、数组(
    const [a, b] = useTexture([x, y])
    )或带 key 的 object;fiber 会为通过 props 分配的 LDR(RGBA8)color maps 设置 sRGB color space——包括
    map
    、
    emissiveMap
    、
    envMap
    等——因此 color maps 无需手动设置
    colorSpace
    ,而 scalar maps 会保持 NoColorSpace。
  • <Preload all />
    会在挂载时将每个 material 编译一次(使用
    all
    时也包括隐藏对象),并将 scene 渲染到 cube target 中以上传 textures;这会用一次性的卡顿换取首次查看时不再出现卡顿。应将其放在 assets 之后的 Suspense boundary 内,否则它会在资源加载完成前运行。
  • useProgress
    是一个普通 store,因此上面的 Hud 可在 Canvas 外的 DOM 中读取它。

drei Helpers

drei Helpers

drei ships many more helpers than these; only this subset is verified here, and every helper imports from
@react-three/drei
.
Controls:
OrbitControls
constructs the controls class drei bundles, connects it to the event source (the Canvas wrapper by default), updates it every frame before user callbacks, calls
invalidate()
on change (so it works with
frameloop="demand"
) and turns damping on by default.
makeDefault
stores it in
state.controls
(Bounds and other helpers pick it up);
regress
drops the adaptive-performance value while dragging. Props pass straight through to the controls instance:
jsx
import { OrbitControls } from "@react-three/drei";

<OrbitControls
  makeDefault
  enableDamping
  dampingFactor={0.1}
  target={[0, 1, 0]}
  minDistance={2}
  maxDistance={12}
  maxPolarAngle={Math.PI / 2}
  autoRotate
  autoRotateSpeed={0.5}
/>
CameraControls
wraps the camera-controls library the same way (ref methods like
fitToBox
come from that library, and it updates with the frame delta).
Scene dressing:
jsx
import {
  ContactShadows, Environment, Lightformer, Text, Html, Center, Bounds,
} from "@react-three/drei";

export function Dressing({ overlay }) { // overlay: a ref to a DOM element laid over the Canvas
  return (
    <group>
      <Environment resolution={256}>
        <Lightformer form="rect" intensity={2} position={[0, 4, 3]} scale={[8, 2, 1]} />
      </Environment>
      <ContactShadows position={[0, -1, 0]} opacity={0.6} scale={12} blur={2.4} far={4} />
      <Bounds fit clip observe margin={1.5}>
        <mesh>
          <sphereGeometry args={[1, 32, 16]} />
          <meshStandardMaterial metalness={0.9} roughness={0.2} />
        </mesh>
        <Center top position={[2.5, -1, 0]}>
          <mesh>
            <boxGeometry args={[1, 0.6, 1]} />
            <meshStandardMaterial />
          </mesh>
        </Center>
      </Bounds>
      <Text position={[0.75, 1.1, 0]} fontSize={0.4} anchorX="center" anchorY="bottom" color="#e8b4a0" outlineWidth={0.02}>
        three.js
      </Text>
      <Html portal={overlay} position={[2.5, 0.2, 0]} center distanceFactor={8} occlude="raycast">
        <div>dom overlay anchored to the scene</div>
      </Html>
    </group>
  );
}
  • ContactShadows
    renders the scene from below into a depth target and blurs it — a soft contact shadow plane under objects near the floor, with no shadow-map setup; set
    frames={1}
    to bake it once for still scenes.
  • Lightformer
    is a card of light — a mesh with a basic material,
    form
    circle
    ,
    ring
    ,
    rect
    or
    box
    — meant to sit inside an
    <Environment>
    children scene that bakes into reflections.
  • Text
    renders SDF text (troika) with layout props
    maxWidth
    ,
    textAlign
    ,
    lineHeight
    ,
    letterSpacing
    ,
    characters
    ; it suspends while the font loads.
  • Html
    projects a DOM node into the scene;
    transform
    renders it as a 3D-rotated overlay instead of a screen-facing one.
    occlude
    (
    true
    or
    "raycast"
    ) hides it while geometry blocks the ray to its anchor, so never anchor it inside a mesh;
    occlude="blending"
    hides it by depth through a ShaderMaterial (WebGL only). With fiber 9.8, drei 10.7 and React 19.2, an
    Html
    that mounts in the same commit as a default (WebGLRenderer) Canvas comes up empty, and so does one that remounts with its Canvas; pass
    portal
    a ref to a DOM element laid over the canvas, which keeps its container fixed, as the Dressing does:
    <div ref={overlay} style={{ position: "absolute", inset: 0, pointerEvents: "none" }} />
    after the Canvas, inside a
    position: relative
    wrapper.
  • Center
    recenters children on their bounding box (
    top
    /
    bottom
    /
    left
    /
    right
    /
    front
    /
    back
    align to an edge,
    precise={false}
    measures per-object bounds instead of every vertex,
    onCentered
    reports the box). It measures vertex positions, so it misplaces drei
    Text
    , whose geometry is a unit quad; place Text with
    anchorX
    ,
    anchorY
    and
    position
    instead.
  • Bounds
    fits and frames the camera around its children (
    fit
    ,
    clip
    adjusts near/far,
    observe
    refits on resize);
    useBounds()
    returns its imperative API (
    refresh
    ,
    reset
    ,
    moveTo
    ,
    lookAt
    ,
    fit
    ,
    clip
    ).
drei 提供的 helpers 远不止以下这些;这里仅验证了这一子集,而且每个 helper 都从
@react-three/drei
导入。
Controls:
OrbitControls
会构造 drei 内置的 controls class,将其连接到 event source(默认是 Canvas wrapper),在用户 callbacks 之前每帧更新它,在发生变化时调用
invalidate()
(因此可配合
frameloop="demand"
使用),并默认开启 damping。
makeDefault
会将其存储在
state.controls
中(Bounds 和其他 helpers 会读取它);
regress
会在拖动期间降低 adaptive-performance 值。Props 会直接传递给 controls instance:
jsx
import { OrbitControls } from "@react-three/drei";

<OrbitControls
  makeDefault
  enableDamping
  dampingFactor={0.1}
  target={[0, 1, 0]}
  minDistance={2}
  maxDistance={12}
  maxPolarAngle={Math.PI / 2}
  autoRotate
  autoRotateSpeed={0.5}
/>
CameraControls
以相同方式封装 camera-controls library(例如
fitToBox
等 ref methods 直接来自该 library,并会使用 frame delta 更新)。
Scene dressing:
jsx
import {
  ContactShadows, Environment, Lightformer, Text, Html, Center, Bounds,
} from "@react-three/drei";

export function Dressing({ overlay }) { // overlay: a ref to a DOM element laid over the Canvas
  return (
    <group>
      <Environment resolution={256}>
        <Lightformer form="rect" intensity={2} position={[0, 4, 3]} scale={[8, 2, 1]} />
      </Environment>
      <ContactShadows position={[0, -1, 0]} opacity={0.6} scale={12} blur={2.4} far={4} />
      <Bounds fit clip observe margin={1.5}>
        <mesh>
          <sphereGeometry args={[1, 32, 16]} />
          <meshStandardMaterial metalness={0.9} roughness={0.2} />
        </mesh>
        <Center top position={[2.5, -1, 0]}>
          <mesh>
            <boxGeometry args={[1, 0.6, 1]} />
            <meshStandardMaterial />
          </mesh>
        </Center>
      </Bounds>
      <Text position={[0.75, 1.1, 0]} fontSize={0.4} anchorX="center" anchorY="bottom" color="#e8b4a0" outlineWidth={0.02}>
        three.js
      </Text>
      <Html portal={overlay} position={[2.5, 0.2, 0]} center distanceFactor={8} occlude="raycast">
        <div>dom overlay anchored to the scene</div>
      </Html>
    </group>
  );
}
  • ContactShadows
    会从下方将 scene 渲染到 depth target 中并对其模糊处理——用于在地面附近的对象下方创建柔和的接触阴影平面,无需配置 shadow maps;设置
    frames={1}
    可为静态 scene 烘焙一次。
  • Lightformer
    是一种卡片状光源——一个使用 basic material 的 mesh,
    form
    可为
    circle
    、
    ring
    、
    rect
    或
    box
    ——适合放在
    <Environment>
    的 children scene 中并烘焙到 reflections 中。
  • Text
    会渲染 SDF text(troika),支持
    maxWidth
    、
    textAlign
    、
    lineHeight
    、
    letterSpacing
    、
    characters
    等 layout props;加载字体期间会 suspend。
  • Html
    会将 DOM node 投射到 scene 中;
    transform
    会将其渲染为经过 3D rotation 的 overlay,而不是始终朝向屏幕。
    occlude
    (
    true
    或
    "raycast"
    )会在 geometry 阻挡其锚点的 ray 时隐藏它,因此绝不要将它锚定在 mesh 内部;
    occlude="blending"
    会通过 ShaderMaterial 按深度隐藏(仅支持 WebGL)。在 fiber 9.8、drei 10.7 和 React 19.2 中,与默认 WebGLRenderer Canvas 在同一次 commit 中挂载的
    Html
    会显示为空,随 Canvas 重新挂载的
    Html
    也会如此;应向
    portal
    传入一个指向 Canvas 上方 DOM element 的 ref,这会保持其 container 固定,如 Dressing 中所示:在 Canvas 之后、位于
    position: relative
    wrapper 内放置
    <div ref={overlay} style={{ position: "absolute", inset: 0, pointerEvents: "none" }} />
    。
  • Center
    会根据 children 的 bounding box 重新居中(
    top
    /
    bottom
    /
    left
    /
    right
    /
    front
    /
    back
    用于对齐指定边缘,
    precise={false}
    按每个对象的 bounds 测量,而不是检查每个 vertex,
    onCentered
    会返回该 box)。它通过 vertex positions 进行测量,因此会错误放置 drei
    Text
    ,因为后者的 geometry 是一个 unit quad;应改用
    anchorX
    、
    anchorY
    和
    position
    放置 Text。
  • Bounds
    会围绕 children 调整 camera,使其适合取景(
    fit
    、
    clip
    调整 near/far,
    observe
    在 resize 时重新适配);
    useBounds()
    返回其 imperative API(
    refresh
    、
    reset
    、
    moveTo
    、
    lookAt
    、
    fit
    、
    clip
    )。

Performance

性能

Render on demand: nothing changes, nothing renders.
jsx
import { useEffect, useRef } from "react";
import { useThree } from "@react-three/fiber";

function OnDemand() {
  const ref = useRef();
  const invalidate = useThree((state) => state.invalidate);
  // under <Canvas frameloop="demand"> imperative changes are invisible to React — request a frame for them
  useEffect(() => {
    const id = setInterval(() => {
      ref.current.rotation.y += 0.5;
      invalidate();
    }, 100);
    return () => clearInterval(id);
  }, [invalidate]);
  return (
    <mesh ref={ref}>
      <boxGeometry args={[1, 1, 1]} />
      <meshNormalMaterial />
    </mesh>
  );
}
React changes (props, state) invalidate automatically. Never call
setState
in
useFrame
— mutate objects through refs, as the Quick Start does; a store or
flushSync
covers the rare case the DOM must follow per-frame values.
Many copies of one shape share one draw call through
Instances
; each
Instance
is a declarative transform (and optional
color
) that composes into the instanced matrix every frame:
jsx
import { Instance, Instances } from "@react-three/drei";

function Crowd({ count = 200 }) {
  return (
    <Instances limit={count} castShadow>
      <capsuleGeometry args={[0.25, 0.8, 4, 12]} />
      <meshStandardMaterial />
      {Array.from({ length: count }, (_, i) => (
        <Instance
          key={i}
          position={[(i % 15) - 7, 0.65, Math.floor(i / 15) - 7]}
          color={`hsl(${(i * 37) % 360}, 60%, 55%)`}
        />
      ))}
    </Instances>
  );
}
Detailed
picks one of its children by camera distance (three's
LOD
), for cheap far-field geometry: child i shows from
distances[i]
on (the first entry is 0), and
hysteresis
is a fraction of that distance that delays the switch back to finer detail:
jsx
import { Detailed } from "@react-three/drei";

<Detailed distances={[0, 15, 40]} hysteresis={0.1}>
  <mesh><sphereGeometry args={[1, 64, 32]} /><meshStandardMaterial /></mesh>
  <mesh><sphereGeometry args={[1, 16, 8]} /><meshStandardMaterial /></mesh>
  <mesh><sphereGeometry args={[1, 8, 4]} /><meshStandardMaterial /></mesh>
</Detailed>
Adaptive resolution: controls with
regress
drop
performance.current
to
min
(default 0.5) while dragging and restore it after 200 ms;
AdaptiveDpr
sets the pixel ratio to that value times the first ratio the Canvas resolved, so every drag ends at that first ratio.
PerformanceMonitor
samples real frame rates and reports through callbacks — it does not touch the pixel ratio itself. Drive the Canvas
dpr
prop from its callbacks through React state, because the Canvas re-applies that prop whenever it re-renders, which undoes a
setDpr
call made from inside it; keep
AdaptiveDpr
out of that Canvas, or the next drag restores the first ratio over a lowered
dpr
:
jsx
import { useState } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, AdaptiveDpr, PerformanceMonitor } from "@react-three/drei";

// lower the resolution while the controls move
export function DragTunedApp() {
  return (
    <Canvas dpr={[1, 2]}>
      <OrbitControls regress />
      <AdaptiveDpr pixelated />
      {/* scene contents */}
    </Canvas>
  );
}

// let the measured frame rate pick the resolution through the dpr prop
export function MonitoredApp() {
  const [dpr, setDpr] = useState([1, 2]);
  return (
    <Canvas dpr={dpr}>
      <OrbitControls />
      <PerformanceMonitor
        onDecline={() => setDpr(1)}
        onIncline={() => setDpr([1, 2])}
        flipflops={3}
        onFallback={() => setDpr(1)}
      />
      {/* scene contents */}
    </Canvas>
  );
}
Beyond fiber-specific tools: cap
dpr
, prefer KTX2 textures, merge or instance geometry, dispose what you unmount — see
threejs-performance
.
按需渲染:没有变化,就不会渲染。
jsx
import { useEffect, useRef } from "react";
import { useThree } from "@react-three/fiber";

function OnDemand() {
  const ref = useRef();
  const invalidate = useThree((state) => state.invalidate);
  // under <Canvas frameloop="demand"> imperative changes are invisible to React — request a frame for them
  useEffect(() => {
    const id = setInterval(() => {
      ref.current.rotation.y += 0.5;
      invalidate();
    }, 100);
    return () => clearInterval(id);
  }, [invalidate]);
  return (
    <mesh ref={ref}>
      <boxGeometry args={[1, 1, 1]} />
      <meshNormalMaterial />
    </mesh>
  );
}
React changes(props、state)会自动触发 invalidate。绝不要在
useFrame
中调用
setState
——应像快速开始示例那样通过 refs 修改对象;在极少数 DOM 必须跟随每帧数值的情况下,可以使用 store 或
flushSync
。
同一 shape 的多个副本可通过
Instances
共享一个 draw call;每个
Instance
都是一个 declarative transform(还可包含可选的
color
),每帧都会组合到 instanced matrix 中:
jsx
import { Instance, Instances } from "@react-three/drei";

function Crowd({ count = 200 }) {
  return (
    <Instances limit={count} castShadow>
      <capsuleGeometry args={[0.25, 0.8, 4, 12]} />
      <meshStandardMaterial />
      {Array.from({ length: count }, (_, i) => (
        <Instance
          key={i}
          position={[(i % 15) - 7, 0.65, Math.floor(i / 15) - 7]}
          color={`hsl(${(i * 37) % 360}, 60%, 55%)`}
        />
      ))}
    </Instances>
  );
}
Detailed
会根据 camera distance 选择一个 child(three 的
LOD
),以提供低成本的远景 geometry:child i 从
distances[i]
开始显示(第一个条目为 0),
hysteresis
是该距离的一个比例值,可延迟切换回更高精度的细节:
jsx
import { Detailed } from "@react-three/drei";

<Detailed distances={[0, 15, 40]} hysteresis={0.1}>
  <mesh><sphereGeometry args={[1, 64, 32]} /><meshStandardMaterial /></mesh>
  <mesh><sphereGeometry args={[1, 16, 8]} /><meshStandardMaterial /></mesh>
  <mesh><sphereGeometry args={[1, 8, 4]} /><meshStandardMaterial /></mesh>
</Detailed>
自适应分辨率:带
regress
的 controls 在拖动期间会将
performance.current
降至
min
(默认 0.5),并在 200 ms 后恢复;
AdaptiveDpr
会将 pixel ratio 设置为该值乘以 Canvas 首次解析出的 ratio,因此每次拖动结束时都会回到首次 ratio。
PerformanceMonitor
会采样实际 frame rate,并通过 callbacks 报告结果,但它本身不会修改 pixel ratio。应通过 React state 根据这些 callbacks 驱动 Canvas 的
dpr
prop,因为 Canvas 每次 re-render 时都会重新应用该 prop,从而撤销从内部调用
setDpr
的效果;应避免在该 Canvas 中使用
AdaptiveDpr
,否则下一次拖动会将首次 ratio 恢复到已经降低的
dpr
上:
jsx
import { useState } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, AdaptiveDpr, PerformanceMonitor } from "@react-three/drei";

// lower the resolution while the controls move
export function DragTunedApp() {
  return (
    <Canvas dpr={[1, 2]}>
      <OrbitControls regress />
      <AdaptiveDpr pixelated />
      {/* scene contents */}
    </Canvas>
  );
}

// let the measured frame rate pick the resolution through the dpr prop
export function MonitoredApp() {
  const [dpr, setDpr] = useState([1, 2]);
  return (
    <Canvas dpr={dpr}>
      <OrbitControls />
      <PerformanceMonitor
        onDecline={() => setDpr(1)}
        onIncline={() => setDpr([1, 2])}
        flipflops={3}
        onFallback={() => setDpr(1)}
      />
      {/* scene contents */}
    </Canvas>
  );
}
除 fiber 专用工具外:限制
dpr
、优先使用 KTX2 textures、合并或实例化 geometry,以及 dispose 已 unmount 的资源——参见
threejs-performance
。

Disposal

Disposal

Fiber disposes what it creates when it unmounts (scheduled for idle time): the object behind every JSX element, geometries, materials and textures declared as JSX children included, and the renderer it built from
gl
props or a factory. Objects passed in through props (
geometry={...}
,
map={...}
) and primitives are never disposed, and
dispose={null}
exempts an element and its children. A renderer instance you passed in is not disposed, but fiber forces its context loss on unmount, so it cannot be reused. Assets loaded through
useLoader
,
useGLTF
and
useTexture
stay cached for remounts; the hook's
clear
only drops the cache entry, so dispose the geometries, materials and textures you no longer need yourself. The module-level
dispose(object)
export calls
dispose()
on the object and on each of its own properties that has one (a mesh's geometry and material); it does not visit children, so traverse a tree yourself.
Fiber 会在 unmount 时 dispose 它所创建的对象(安排在 idle time 执行):每个 JSX element 对应的对象、作为 JSX children 声明的 geometries、materials 和 textures,以及由
gl
props 或 factory 创建的 renderer。通过 props 传入的对象(
geometry={...}
、
map={...}
)和 primitives 永远不会被 dispose;
dispose={null}
会使某个 element 及其 children 免于被 dispose。传入的 renderer 实例不会被 dispose,但 fiber 会在 unmount 时强制使其丢失 context,因此无法再次使用。通过
useLoader
、
useGLTF
和
useTexture
加载的资源会保留在 cache 中,以便 remount;hook 的
clear
只会移除 cache entry,因此仍需自行 dispose 不再需要的 geometries、materials 和 textures。模块级导出的
dispose(object)
会在对象及其自身具有
dispose()
方法的 properties 上调用
dispose()
(例如 mesh 的 geometry 和 material);它不会遍历 children,因此需要自行遍历整棵树。

WebGPU and TSL

WebGPU 和 TSL

The
gl
prop takes an async factory: construct the WebGPURenderer with the props fiber hands you, await its initialization, return it — fiber waits for the promise inside the Canvas and renders the children once it resolves (a Suspense fallback around the Canvas does not show meanwhile). Because the Canvas seeds its element catalogue from the plain three namespace (which has no node materials), merge the
three/webgpu
namespace in with
extend
before using node-material elements. TSL nodes from
three/tsl
pass through props the same way, but a material compiles its nodes once: build nodes once (module scope or
useMemo
) and animate through
uniform()
values, because a new node passed on a later render is assigned without a recompile until you set
material.needsUpdate = true
:
jsx
import * as THREE from "three/webgpu";
import { color } from "three/tsl";
import { Canvas, extend, useFrame } from "@react-three/fiber";
import { useRef } from "react";

extend(THREE); // node materials and WebGPU classes join the element catalogue

function Knot() {
  const ref = useRef();
  useFrame((_, delta) => {
    ref.current.rotation.y += delta;
  });
  return (
    <mesh ref={ref}>
      <torusKnotGeometry args={[0.8, 0.3, 128, 24]} />
      <meshBasicNodeMaterial colorNode={color(0x42699c)} />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas
      camera={{ position: [3, 2, 4], fov: 50 }}
      gl={async (props) => {
        const renderer = new THREE.WebGPURenderer(props);
        await renderer.init();
        return renderer;
      }}
    >
      <color attach="background" args={["#111827"]} />
      <Knot />
    </Canvas>
  );
}
Fiber applies its color defaults to a factory's renderer too: ACESFilmicToneMapping (NoToneMapping with
flat
) and sRGB output. WebGPURenderer tone-maps the whole frame, background included, so the same
<color attach="background">
renders darker than under WebGLRenderer unless the Canvas is
flat
.
Checked by rendering under WebGPURenderer on its WebGPU backend and with
forceWebGL: true
(fiber 9.8.2, drei 10.7.9). Working on both backends: the scene graph, hooks, events (the raycaster tests CPU geometry, so
positionNode
displacement is invisible to it),
useGLTF
(loaded materials convert to node materials),
useTexture
,
Preload
,
Center
,
Bounds
,
Instances
,
Detailed
,
OrbitControls
,
CameraControls
,
Html
without
occlude="blending"
, and
Environment
with
preset
,
map
or
.hdr
files;
useProgress
,
AdaptiveDpr
and
PerformanceMonitor
call no renderer API (read in their source).
Not WebGPU-safe:
Text
draws one untextured quad instead of glyphs (troika injects its SDF shader through
onBeforeCompile
),
ContactShadows
(an
onBeforeCompile
depth material and blur
ShaderMaterial
s, which three reports as not compatible),
Html
with
occlude="blending"
,
Environment
with
ground
or with
.jpg
/
.webp
gainmap
files
(both draw through ShaderMaterials; read in the source), and
Environment
with children, which bakes through a cube camera that gives reflections on the WebGPU backend but leaves them black on the WebGL 2 fallback, with no error. TSL material slots (
colorNode
,
positionNode
) behave exactly as in
threejs-shaders
— write
Fn
code, not GLSL.
gl
prop 可以接受一个 async factory:使用 fiber 提供的 props 构造 WebGPURenderer,等待其初始化完成,然后返回它——fiber 会等待 Canvas 内的 promise,并在 promise resolved 后渲染 children(此时,在 Canvas 外包裹的 Suspense fallback 不会显示)。由于 Canvas 的 element catalogue 最初基于普通 three namespace 初始化,而该 namespace 不包含 node materials,因此在使用 node-material elements 前,必须通过
extend
合并
three/webgpu
namespace。
three/tsl
中的 TSL nodes 同样通过 props 传递,但 material 只会将其 nodes 编译一次:应只构建一次 nodes(放在 module scope 或使用
useMemo
),并通过
uniform()
values 实现动画,因为后续 render 中传入的新 node 只会赋值给 material,而不会重新编译,除非将
material.needsUpdate
设置为
true
:
jsx
import * as THREE from "three/webgpu";
import { color } from "three/tsl";
import { Canvas, extend, useFrame } from "@react-three/fiber";
import { useRef } from "react";

extend(THREE); // node materials and WebGPU classes join the element catalogue

function Knot() {
  const ref = useRef();
  useFrame((_, delta) => {
    ref.current.rotation.y += delta;
  });
  return (
    <mesh ref={ref}>
      <torusKnotGeometry args={[0.8, 0.3, 128, 24]} />
      <meshBasicNodeMaterial colorNode={color(0x42699c)} />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas
      camera={{ position: [3, 2, 4], fov: 50 }}
      gl={async (props) => {
        const renderer = new THREE.WebGPURenderer(props);
        await renderer.init();
        return renderer;
      }}
    >
      <color attach="background" args={["#111827"]} />
      <Knot />
    </Canvas>
  );
}
Fiber 也会将颜色默认值应用到 factory 创建的 renderer:ACESFilmicToneMapping(使用
flat
时为 NoToneMapping)和 sRGB output。WebGPURenderer 会对整个 frame(包括 background)进行 tone mapping,因此除非 Canvas 设置了
flat
,相同的
<color attach="background">
在 WebGPURenderer 下会比在 WebGLRenderer 下显得更暗。
已在 WebGPURenderer 的 WebGPU backend 以及
forceWebGL: true
模式下完成渲染验证(fiber 9.8.2、drei 10.7.9)。两种 backend 均可正常工作:scene graph、hooks、events(raycaster 检测的是 CPU geometry,因此它无法发现
positionNode
displacement)、
useGLTF
(已加载的 materials 会转换为 node materials)、
useTexture
、
Preload
、
Center
、
Bounds
、
Instances
、
Detailed
、
OrbitControls
、
CameraControls
、不使用
occlude="blending"
的
Html
,以及使用
preset
、
map
或
.hdr
files 的
Environment
;
useProgress
、
AdaptiveDpr
和
PerformanceMonitor
不调用任何 renderer API(其源码中仅为读取操作)。
不兼容 WebGPU 的功能包括:
Text
会绘制一个没有 texture 的 quad,而不是 glyphs(troika 通过
onBeforeCompile
注入其 SDF shader);
ContactShadows
(使用
onBeforeCompile
depth material 和 blur
ShaderMaterial
s,three 报告称其不兼容);带
occlude="blending"
的
Html
;带
ground
或使用
.jpg
/
.webp
gainmap
files
的
Environment
(两者均通过 ShaderMaterials 绘制,源码中可确认这一点);以及带 children 的
Environment
,其 children 会通过 cube camera 烘焙,在 WebGPU backend 上可生成 reflections,但在 WebGL 2 fallback 上会保持黑色且不报错。TSL material slots(
colorNode
、
positionNode
)的行为与
threejs-shaders
中完全一致——请编写
Fn
code,而不是 GLSL。

Common Mistakes

常见错误

  • requestAnimationFrame(tick)
    →
    useFrame((state, delta) => { ... })
    : the Canvas already runs the frame loop; a separate loop races it and bypasses its effects.
  • state.clock.getElapsedTime()
    →
    state.clock.elapsedTime
    : getElapsedTime() calls getDelta() internally, which shortens the delta fiber measures for the next frame.
  • import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
    →
    import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"
    :
    three/addons/
    resolves both under bundlers and under the import maps three.js documents, while the
    examples/jsm
    path fails under those import maps.
  • useFrame(() => { ref.current.rotation.y += 0.01; })
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    : the second argument is the frame delta; a fixed increment runs at a different speed on every refresh rate.
  • useFrame((_, delta) => setRotation((r) => r + delta))
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    : state set in the frame loop re-renders React each frame; mutate objects through refs.
  • useFrame((state, delta) => { ref.current.rotation.y += delta; }, 1)
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    : a positive render priority makes the Canvas stop rendering, and the scene goes black unless your callback calls
    state.gl.render(state.scene, state.camera)
    itself.
  • <mesh position={{ x: 1, y: 0, z: 0 }}>
    →
    <mesh position={[1, 0, 0]}>
    :
    position
    is a read-only property holding a Vector3, so a plain object throws "Cannot assign to read only property" and the Canvas fails, while arrays, numbers and Vector3s are written into the existing Vector3 (
    fromArray
    ,
    setScalar
    ,
    copy
    ).
  • useEffect(() => { new GLTFLoader().load(url, (gltf) => setScene(gltf.scene)); }, [])
    →
    const gltf = useGLTF(url)
    : useGLTF suspends, caches the parsed model by URL for remounts and preloading, and wires Draco and meshopt decoding, while the effect version refetches and reparses on every mount and has no Draco decoder.
  • useGLTF(url).scene.clone()
    →
    useMemo(() => SkeletonUtils.clone(scene), [scene])
    : Object3D.clone() shares one skeleton, so copies stay bound to the original's bones, while SkeletonUtils rebinds each copy and useMemo keeps one clone per component instead of one per render.
  • scene.add(gltf.scene)
    →
    <primitive object={gltf.scene} />
    : an imperatively added object takes no fiber event props and stays in the scene after the component unmounts, while a primitive takes event props and is added and removed with the component (its disposal stays yours).
  • <water args={[geometry]} />
    →
    extend({ Water }); <water args={[geometry]} />
    : elements resolve against the three namespace only; addon classes must be registered first.
  • <line>
    →
    <threeLine>
    : the bare tag collides with the SVG line element in TypeScript; fiber strips the leading
    three
    and builds THREE.Line.
  • <Canvas shadows>
    →
    <Canvas shadows="percentage">
    : the boolean selects PCFSoftShadowMap, deprecated in r182 — three falls back to PCF with a console warning; the string picks the type with no warning.
  • <directionalLight intensity={2} />
    →
    <directionalLight castShadow intensity={2} />
    : a mesh's castShadow only draws shadows when a light also casts and the Canvas shadows flag is set.
  • ref.current.position.x += 1
    →
    ref.current.position.x += 1; invalidate();
    : under frameloop="demand" a bare mutation never repaints, so call
    invalidate
    from
    useThree((state) => state.invalidate)
    after imperative changes, while React prop changes invalidate themselves.
  • gl={new THREE.WebGPURenderer()}
    →
    gl={async (props) => { const renderer = new THREE.WebGPURenderer(props); await renderer.init(); return renderer; }}
    : a renderer built without fiber's canvas draws into a canvas of its own that is never shown, and its render() throws until init() resolves, while the factory gets the Canvas's canvas and fiber renders only after it resolves.
  • new OrbitControls(camera, renderer.domElement)
    →
    <OrbitControls makeDefault />
    : the drei component wires per-frame updates, invalidation and disposal; the raw class stalls with damping on and never invalidates a demand loop.
  • <Environment><Lightformer /></Environment>
    →
    <Environment files="env.hdr" />
    : under WebGPURenderer the children form bakes through a cube camera whose reflections come out black on the WebGL 2 fallback backend with no error, while
    .hdr
    files and presets only assign a texture and work on both backends.
  • requestAnimationFrame(tick)
    →
    useFrame((state, delta) => { ... })
    :Canvas 已经运行 frame loop;额外的 loop 会与其竞争,并绕过它产生的效果。
  • state.clock.getElapsedTime()
    →
    state.clock.elapsedTime
    :getElapsedTime() 会在内部调用 getDelta(),从而缩短 fiber 为下一帧测得的 delta。
  • import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
    →
    import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"
    :
    three/addons/
    在 bundlers 和 three.js 文档中的 import maps 下均可解析,而
    examples/jsm
    路径在这些 import maps 下会解析失败。
  • useFrame(() => { ref.current.rotation.y += 0.01; })
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    :第二个参数是 frame delta;固定增量会在不同 refresh rate 下以不同速度运行。
  • useFrame((_, delta) => setRotation((r) => r + delta))
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    :在 frame loop 中设置 state 会让 React 每帧 re-render;应通过 refs 修改对象。
  • useFrame((state, delta) => { ref.current.rotation.y += delta; }, 1)
    →
    useFrame((_, delta) => { ref.current.rotation.y += delta; })
    :正数 render priority 会使 Canvas 停止渲染,除非 callback 自行调用
    state.gl.render(state.scene, state.camera)
    ,否则 scene 会变黑。
  • <mesh position={{ x: 1, y: 0, z: 0 }}>
    →
    <mesh position={[1, 0, 0]}>
    :
    position
    是保存 Vector3 的只读 property,因此 plain object 会抛出“Cannot assign to read only property”并导致 Canvas 失败;而 arrays、numbers 和 Vector3s 会被写入现有的 Vector3(
    fromArray
    、
    setScalar
    、
    copy
    )。
  • useEffect(() => { new GLTFLoader().load(url, (gltf) => setScene(gltf.scene)); }, [])
    →
    const gltf = useGLTF(url)
    :useGLTF 会 suspend、按 URL 缓存解析后的 model 以供 remount 和 preloading 使用,并接入 Draco 与 meshopt decoding;而 effect 版本每次 mount 都会重新获取和解析,且没有 Draco decoder。
  • useGLTF(url).scene.clone()
    →
    useMemo(() => SkeletonUtils.clone(scene), [scene])
    :Object3D.clone() 会共享同一个 skeleton,因此副本仍绑定到原始模型的 bones;SkeletonUtils 会重新绑定每个副本,而 useMemo 可确保每个 component 只保留一个 clone,而不是每次 render 都创建一个。
  • scene.add(gltf.scene)
    →
    <primitive object={gltf.scene} />
    :以命令式方式添加的对象不会获得 fiber event props,并且在 component unmount 后仍会留在 scene 中;primitive 则可使用 event props,并会随 component 添加和移除(其 disposal 仍由你负责)。
  • <water args={[geometry]} />
    →
    extend({ Water }); <water args={[geometry]} />
    :elements 只会在 three namespace 中解析;addon classes 必须先注册。
  • <line>
    →
    <threeLine>
    :在 TypeScript 中,裸标签会与 SVG line element 冲突;fiber 会移除开头的
    three
    并构建 THREE.Line。
  • <Canvas shadows>
    →
    <Canvas shadows="percentage">
    :boolean 会选择 PCFSoftShadowMap,而它已在 r182 中弃用——three 会回退到 PCF 并输出 console warning;string 可选择类型且不会产生警告。
  • <directionalLight intensity={2} />
    →
    <directionalLight castShadow intensity={2} />
    :只有当 light 也会投射阴影且 Canvas 设置了 shadows flag 时,mesh 的 castShadow 才会绘制阴影。
  • ref.current.position.x += 1
    →
    ref.current.position.x += 1; invalidate();
    :在
    frameloop="demand"
    下,单纯的 mutation 不会触发重绘,因此命令式修改后应调用
    useThree((state) => state.invalidate)
    获得的 invalidate,而 React prop changes 会自动 invalidate。
  • gl={new THREE.WebGPURenderer()}
    →
    gl={async (props) => { const renderer = new THREE.WebGPURenderer(props); await renderer.init(); return renderer; }}
    :未使用 fiber 的 canvas 创建的 renderer 会绘制到其自己的、永远不会显示的 canvas 中,并且在 init() resolved 前调用 render() 会抛出错误;factory 则会获得 Canvas 的 canvas,fiber 只会在其 resolved 后开始渲染。
  • new OrbitControls(camera, renderer.domElement)
    →
    <OrbitControls makeDefault />
    :drei component 会接入每帧更新、invalidation 和 disposal;原始 class 在开启 damping 后可能停滞,并且永远不会 invalidate demand loop。
  • <Environment><Lightformer /></Environment>
    →
    <Environment files="env.hdr" />
    :在 WebGPURenderer 下,带 children 的烘焙会通过 cube camera 完成,在 WebGL 2 fallback backend 上 reflections 会变成黑色且不报错;而
    .hdr
    files 和 presets 仅分配 texture,可在两种 backend 上正常工作。

See Also

参见

  • threejs-fundamentals
    - the renderer, scene and camera concepts the Canvas manages for you
  • threejs-loaders
    - loader configuration, compression and disposal in depth
  • threejs-interaction
    - raycasting and controls beyond fiber's pointer events
  • threejs-shaders
    - TSL node materials and slots used inside fiber elements
  • threejs-performance
    - profiling and optimization beyond fiber's adaptive tools
  • threejs-asset-pipeline
    - gltfjsx components, and compressing models before useGLTF loads them
  • threejs-fundamentals
    - Canvas 为你管理的 renderer、scene 和 camera 概念
  • threejs-loaders
    - 更深入的 loader 配置、压缩和 disposal
  • threejs-interaction
    - fiber pointer events 之外的 raycasting 和 controls
  • threejs-shaders
    - fiber elements 中使用的 TSL node materials 和 slots
  • threejs-performance
    - fiber 自适应工具之外的 profiling 和 optimization
  • threejs-asset-pipeline
    - gltfjsx components,以及在 useGLTF 加载模型前对其进行压缩