threejs-react-three-fiber
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThree.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 9.8 with React 19 and 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.
@react-three/fiber@react-three/dreijsx
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>
);
}适用于 9.8、React 19 和 10.7。Canvas 会创建并管理一个 WebGLRenderer、一个 scene 和一个 camera;three.js 对象则成为 JSX 元素。Canvas 会填满其父元素,因此必须为父元素设置高度。下面的组件片段会渲染在类似这样的 Canvas 内;受光材质假定 Canvas 中已有灯光。
@react-three/fiber@react-three/dreijsx
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>
);
}- : props for the default camera, or a camera instance. Without it you get a
camerawith fov 75, near 0.1, far 1000, at z 5 looking at the origin; fiber updates aspect (or left/right/top/bottom withPerspectiveCamera) and the projection matrix on every resize.orthographicputs projection entirely in your hands.camera={{ manual: true }} - : a number, or
dprclamped against[min, max]; the range follows display changes.window.devicePixelRatio - :
shadowsenables shadow maps, a string (true,'basic','percentage') picks the filter type, an object merges into the renderer's'variance'properties.shadowMap,trueand the default'soft'all set PCFSoftShadowMap, which three swaps for PCFShadowMap with a console warning (WebGLRenderer when shadows render, WebGPURenderer on every Canvas);falseis the same soft filter without the warning.'percentage' - sets NoToneMapping; without it fiber applies ACESFilmicToneMapping — unlike plain three.js, tone mapping is on by default.
flatswitches the output to LinearSRGBColorSpace; the default is sRGB.lineardisables three's ColorManagement.legacy - : props for the default
gl, a renderer instance, or a factory function (the WebGPU path below).new WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" }) - :
framelooprenders every frame,'always'renders only after React changes or'demand',invalidate()hands the loop over (call the module-level'never'export with a time in seconds;advance(timestamp)'s delta is the difference between successive timestamps, so milliseconds make it 1000 times too large).useFrame - replaces the event manager (the default maps DOM pointer events to raycasts);
events(an element pointer events attach to, default the Canvas wrapper) andeventSource(eventPrefixby default;'offset','page','client','layer'when the page scrolls or transforms the canvas) tune pointer mapping.'screen'runs once the renderer exists;onCreatedfires on clicks that hit nothing.onPointerMissed
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,或一个 camera 实例。不传入时,会得到一个
camera,其 fov 为 75、near 为 0.1、far 为 1000,位于 z 5 并朝向原点;fiber 会在每次 resize 时更新 aspect(使用PerspectiveCamera时则更新 left/right/top/bottom)以及 projection matrix。orthographic表示 projection 完全由你控制。camera={{ manual: true }} - :一个数字,或
dpr形式的范围,后者会根据[min, max]进行限制;该范围会随显示器变化而调整。window.devicePixelRatio - :
shadows会启用 shadow maps;字符串(true、'basic'、'percentage')用于选择 filter 类型;对象则会合并到 renderer 的'variance'properties 中。shadowMap、true和默认的'soft'都会设置 PCFSoftShadowMap,而 three 会将其替换为 PCFShadowMap 并输出 console warning(WebGLRenderer 仅在渲染阴影时警告,WebGPURenderer 则在每个 Canvas 上警告);false使用相同的软阴影 filter,但不会产生警告。'percentage' - :设置 NoToneMapping;不设置时,fiber 会应用 ACESFilmicToneMapping——与原生 three.js 不同,色调映射默认开启。
flat会将输出切换为 LinearSRGBColorSpace;默认为 sRGB。linear会禁用 three 的 ColorManagement。legacy - :用于默认的
gl的 props、一个 renderer 实例,或一个 factory function(见下方的 WebGPU 路径)。new WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" }) - :
frameloop每帧都渲染;'always'仅在 React 发生变化或调用'demand'后渲染;invalidate()会交出该循环的控制权(调用模块级导出的'never',并传入以秒为单位的时间;advance(timestamp)的 delta 是相邻时间戳之差,因此传入毫秒会使其放大 1000 倍)。useFrame - :替换 event manager(默认实现会将 DOM pointer events 映射到 raycasts);
events(pointer events 附加到的元素,默认为 Canvas wrapper)和eventSource(默认值为eventPrefix;当页面滚动或对 canvas 做 transform 时,可使用'offset'、'page'、'client'、'layer')用于调整 pointer 映射。'screen'会在 renderer 创建后运行一次;onCreated会在点击未命中任何对象时触发。onPointerMissed
The Scene Graph as JSX
将场景图表示为 JSX
Every export of the three namespace is an element: lowercase tag, capitalized class ( → , → its material). Props apply to the instance; shorthand values work anywhere three has a : , , (setScalar), . Dashed props pierce (, , ); attaches into an array index.
<mesh>THREE.Mesh<meshStandardMaterial>.set()position={[1, 2, 3]}position={new THREE.Vector3(1, 2, 3)}position={5}color="#d9915b"rotation-xmaterial-colorgeometry-attributes-positionattach="material-0"argsattachjsx
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 ; 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. on any element opts it out of unmount disposal. Register classes outside the three namespace with :
<primitive object={obj} />dispose={null}extendjsx
import { extend } from "@react-three/fiber";
import { Water } from "three/addons/objects/Water.js";
extend({ Water });
// then render <water args={[geometry]} /> anywhere in the CanvasIn TypeScript the tags , , and collide with DOM types; write , , , (fiber strips the prefix and resolves the three class).
lineaudiosourcepaththreeLinethreeAudiothreeSourcethreePaththree namespace 的每个导出项都会成为一个 element:小写标签对应首字母大写的 class( → , → 对应的 material)。Props 会应用到实例;在 three 提供 的位置均可使用简写值,例如 、、(setScalar)、。带连字符的 props 会向下穿透(、、); 可将对象附加到数组中的指定索引。
<mesh>THREE.Mesh<meshStandardMaterial>.set()position={[1, 2, 3]}position={new THREE.Vector3(1, 2, 3)}position={5}color="#d9915b"rotation-xmaterial-colorgeometry-attributes-positionattach="material-0"argsattachjsx
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 只能对应一个对象(fiber 会将每个对象的状态保存在该对象上),如需副本则应进行 clone。Fiber 永远不会 dispose primitives——其生命周期由你管理。在任意 element 上设置 ,即可使其免于在 unmount 时被 dispose。可使用 注册 three namespace 之外的 classes:
<primitive object={obj} />dispose={null}extendjsx
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 中,标签 、、 和 会与 DOM types 冲突;应写成 、、、(fiber 会移除前缀并解析对应的 three class)。
lineaudiosourcepaththreeLinethreeAudiothreeSourcethreePathHooks
Hooks
useFrame(callback, renderPriority?)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 take rendering into their own hands — the Canvas stops rendering, and your callback must call itself. At default priority, calling yourself double-renders.
renderPrioritystate.gl.render(state.scene, state.camera)gl.renderuseThree(selector?)glcamerasceneraycasterpointersizeviewportinvalidatesetDprsetFrameloopcontrolsperformancejsx
import { useThree } from "@react-three/fiber";
const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);Inside use the argument instead — it is the same state without a re-render. is a that fiber 9.8 creates, so every Canvas logs three's Clock deprecation warning; it does not come from your code.
useFramestatestate.clockTHREE.ClockuseLoader(ClassOrInstance, url, extensions?, onProgress?)useLoaderuseGraph(object){ 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)useLoader.clear(Class, url)useFrame(callback, renderPriority?)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>
);
}具有正数 的订阅者将自行接管渲染——Canvas 会停止渲染,你的 callback 必须自行调用 。在默认 priority 下自行调用 会导致双重渲染。
renderPrioritystate.gl.render(state.scene, state.camera)gl.renderuseThree(selector?)glcamerasceneraycasterpointersizeviewportinvalidatesetDprsetFrameloopcontrolsperformancejsx
import { useThree } from "@react-three/fiber";
const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);在 内应使用 参数,而不是 ——它提供的是同一份 state,但不会触发 re-render。 是 fiber 9.8 创建的 ,因此每个 Canvas 都会输出 three 的 Clock deprecation warning;这并非来自你的代码。
useFramestateuseThreestate.clockTHREE.ClockuseLoader(ClassOrInstance, url, extensions?, onProgress?)useLoaderuseGraph(object){ 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)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: , (the object that registered the handler), , , , , , , , , (pixels travelled since pointerdown, for click-like events), . Handlers: , , , , , , , , , , , ( and are typed but fiber never calls them on objects).
objecteventObjectpointdistancefaceinstanceIdintersectionsraycameraunprojectedPointdeltanativeEventonClickonDoubleClickonContextMenuonWheelonPointerUponPointerDownonPointerMoveonPointerOveronPointerOutonPointerEnteronPointerLeaveonPointerMissedonPointerCancelonLostPointerCapturejsx
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 . Pass to tune it.
intersectObject(object, true)raycaster={{ params: { Line: { threshold: 0.1 } } }}每次 pointer event 都会对带 event handler 的对象执行 raycast(pointer move 仅检测带有 move、over、out、enter 或 leave handler 的对象);handlers 会优先在最近命中的对象上触发,并从命中对象沿父级向上冒泡。Event 包含 intersection 及以下辅助信息:、(注册 handler 的对象)、、、、、、、、、(自 pointerdown 起移动的像素数,适用于类似 click 的事件)、。Handlers 包括:、、、、、、、、、、、( 和 已有类型定义,但 fiber 从不会在对象上调用它们)。
objecteventObjectpointdistancefaceinstanceIdintersectionsraycameraunprojectedPointdeltanativeEventonClickonDoubleClickonContextMenuonWheelonPointerUponPointerDownonPointerMoveonPointerOveronPointerOutonPointerEnteronPointerLeaveonPointerMissedonPointerCancelonLostPointerCapturejsx
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 会在 中检测 CPU geometry。可传入 对其进行调整。
intersectObject(object, true)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>
</>
);
}- 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(url)retargets it,useGLTF.setDecoderPathdisables it).useGLTF(url, false)warms the cache. The result carriesuseGLTF.preload(url),scene,animationspluscameras/nodes/materialslookup maps. For several copies of a skinned model, clone it once per component withmeshes(useMemo(() => SkeletonUtils.clone(scene), [scene])) and mount each clone in its own primitive.import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js" - loads one URL, an array (
useTexture(input)) or a keyed object; fiber sets sRGB color space on LDR (RGBA8) color maps assigned through props —const [a, b] = useTexture([x, y]),map,emissiveMapamong them — so color maps work without manualenvMap, and scalar maps keep NoColorSpace.colorSpace - compiles every material once, when it mounts (with
<Preload 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.all - is a plain store, so the Hud above reads it outside the Canvas in the DOM.
useProgress
加载类 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>
</>
);
}- 通过 drei 附带的 GLTFLoader(three-stdlib)加载,默认启用 Draco 和 meshopt;Draco decoder 来自一个固定版本的 gstatic URL(
useGLTF(url)可重定向该地址,useGLTF.setDecoderPath可禁用它)。useGLTF(url, false)可预热 cache。结果包含useGLTF.preload(url)、scene、animations,以及cameras/nodes/materialslookup maps。对于同一个 skinned model 的多个副本,应为每个 component 使用meshesclone 一次(useMemo(() => SkeletonUtils.clone(scene), [scene])),并将每个 clone 挂载到各自的 primitive 中。import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js" - 可加载单个 URL、数组(
useTexture(input))或带 key 的 object;fiber 会为通过 props 分配的 LDR(RGBA8)color maps 设置 sRGB color space——包括const [a, b] = useTexture([x, y])、map、emissiveMap等——因此 color maps 无需手动设置envMap,而 scalar maps 会保持 NoColorSpace。colorSpace - 会在挂载时将每个 material 编译一次(使用
<Preload all />时也包括隐藏对象),并将 scene 渲染到 cube target 中以上传 textures;这会用一次性的卡顿换取首次查看时不再出现卡顿。应将其放在 assets 之后的 Suspense boundary 内,否则它会在资源加载完成前运行。all - 是一个普通 store,因此上面的 Hud 可在 Canvas 外的 DOM 中读取它。
useProgress
drei Helpers
drei Helpers
drei ships many more helpers than these; only this subset is verified here, and every helper imports from .
@react-three/dreiControls: 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 on change (so it works with ) and turns damping on by default. stores it in (Bounds and other helpers pick it up); drops the adaptive-performance value while dragging. Props pass straight through to the controls instance:
OrbitControlsinvalidate()frameloop="demand"makeDefaultstate.controlsregressjsx
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}
/>CameraControlsfitToBoxScene 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>
);
}- 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
ContactShadowsto bake it once for still scenes.frames={1} - is a card of light — a mesh with a basic material,
Lightformerform,circle,ringorrect— meant to sit inside anboxchildren scene that bakes into reflections.<Environment> - renders SDF text (troika) with layout props
Text,maxWidth,textAlign,lineHeight,letterSpacing; it suspends while the font loads.characters - projects a DOM node into the scene;
Htmlrenders it as a 3D-rotated overlay instead of a screen-facing one.transform(occludeortrue) hides it while geometry blocks the ray to its anchor, so never anchor it inside a mesh;"raycast"hides it by depth through a ShaderMaterial (WebGL only). With fiber 9.8, drei 10.7 and React 19.2, anocclude="blending"that mounts in the same commit as a default (WebGLRenderer) Canvas comes up empty, and so does one that remounts with its Canvas; passHtmla ref to a DOM element laid over the canvas, which keeps its container fixed, as the Dressing does:portalafter the Canvas, inside a<div ref={overlay} style={{ position: "absolute", inset: 0, pointerEvents: "none" }} />wrapper.position: relative - recenters children on their bounding box (
Center/top/bottom/left/right/frontalign to an edge,backmeasures per-object bounds instead of every vertex,precise={false}reports the box). It measures vertex positions, so it misplaces dreionCentered, whose geometry is a unit quad; place Text withText,anchorXandanchorYinstead.position - fits and frames the camera around its children (
Bounds,fitadjusts near/far,cliprefits on resize);observereturns its imperative API (useBounds(),refresh,reset,moveTo,lookAt,fit).clip
drei 提供的 helpers 远不止以下这些;这里仅验证了这一子集,而且每个 helper 都从 导入。
@react-three/dreiControls: 会构造 drei 内置的 controls class,将其连接到 event source(默认是 Canvas wrapper),在用户 callbacks 之前每帧更新它,在发生变化时调用 (因此可配合 使用),并默认开启 damping。 会将其存储在 中(Bounds 和其他 helpers 会读取它); 会在拖动期间降低 adaptive-performance 值。Props 会直接传递给 controls instance:
OrbitControlsinvalidate()frameloop="demand"makeDefaultstate.controlsregressjsx
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}
/>CameraControlsfitToBoxScene 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>
);
}- 会从下方将 scene 渲染到 depth target 中并对其模糊处理——用于在地面附近的对象下方创建柔和的接触阴影平面,无需配置 shadow maps;设置
ContactShadows可为静态 scene 烘焙一次。frames={1} - 是一种卡片状光源——一个使用 basic material 的 mesh,
Lightformer可为form、circle、ring或rect——适合放在box的 children scene 中并烘焙到 reflections 中。<Environment> - 会渲染 SDF text(troika),支持
Text、maxWidth、textAlign、lineHeight、letterSpacing等 layout props;加载字体期间会 suspend。characters - 会将 DOM node 投射到 scene 中;
Html会将其渲染为经过 3D rotation 的 overlay,而不是始终朝向屏幕。transform(occlude或true)会在 geometry 阻挡其锚点的 ray 时隐藏它,因此绝不要将它锚定在 mesh 内部;"raycast"会通过 ShaderMaterial 按深度隐藏(仅支持 WebGL)。在 fiber 9.8、drei 10.7 和 React 19.2 中,与默认 WebGLRenderer Canvas 在同一次 commit 中挂载的occlude="blending"会显示为空,随 Canvas 重新挂载的Html也会如此;应向Html传入一个指向 Canvas 上方 DOM element 的 ref,这会保持其 container 固定,如 Dressing 中所示:在 Canvas 之后、位于portalwrapper 内放置position: relative。<div ref={overlay} style={{ position: "absolute", inset: 0, pointerEvents: "none" }} /> - 会根据 children 的 bounding box 重新居中(
Center/top/bottom/left/right/front用于对齐指定边缘,back按每个对象的 bounds 测量,而不是检查每个 vertex,precise={false}会返回该 box)。它通过 vertex positions 进行测量,因此会错误放置 dreionCentered,因为后者的 geometry 是一个 unit quad;应改用Text、anchorX和anchorY放置 Text。position - 会围绕 children 调整 camera,使其适合取景(
Bounds、fit调整 near/far,clip在 resize 时重新适配);observe返回其 imperative API(useBounds()、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 in — mutate objects through refs, as the Quick Start does; a store or covers the rare case the DOM must follow per-frame values.
setStateuseFrameflushSyncMany copies of one shape share one draw call through ; each is a declarative transform (and optional ) that composes into the instanced matrix every frame:
InstancesInstancecolorjsx
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>
);
}DetailedLODdistances[i]hysteresisjsx
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 drop to (default 0.5) while dragging and restore it after 200 ms; sets the pixel ratio to that value times the first ratio the Canvas resolved, so every drag ends at that first ratio. samples real frame rates and reports through callbacks — it does not touch the pixel ratio itself. Drive the Canvas prop from its callbacks through React state, because the Canvas re-applies that prop whenever it re-renders, which undoes a call made from inside it; keep out of that Canvas, or the next drag restores the first ratio over a lowered :
regressperformance.currentminAdaptiveDprPerformanceMonitordprsetDprAdaptiveDprdprjsx
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 , prefer KTX2 textures, merge or instance geometry, dispose what you unmount — see .
dprthreejs-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。绝不要在 中调用 ——应像快速开始示例那样通过 refs 修改对象;在极少数 DOM 必须跟随每帧数值的情况下,可以使用 store 或 。
useFramesetStateflushSync同一 shape 的多个副本可通过 共享一个 draw call;每个 都是一个 declarative transform(还可包含可选的 ),每帧都会组合到 instanced matrix 中:
InstancesInstancecolorjsx
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>
);
}DetailedLODdistances[i]hysteresisjsx
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>自适应分辨率:带 的 controls 在拖动期间会将 降至 (默认 0.5),并在 200 ms 后恢复; 会将 pixel ratio 设置为该值乘以 Canvas 首次解析出的 ratio,因此每次拖动结束时都会回到首次 ratio。 会采样实际 frame rate,并通过 callbacks 报告结果,但它本身不会修改 pixel ratio。应通过 React state 根据这些 callbacks 驱动 Canvas 的 prop,因为 Canvas 每次 re-render 时都会重新应用该 prop,从而撤销从内部调用 的效果;应避免在该 Canvas 中使用 ,否则下一次拖动会将首次 ratio 恢复到已经降低的 上:
regressperformance.currentminAdaptiveDprPerformanceMonitordprsetDprAdaptiveDprdprjsx
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 专用工具外:限制 、优先使用 KTX2 textures、合并或实例化 geometry,以及 dispose 已 unmount 的资源——参见 。
dprthreejs-performanceDisposal
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 props or a factory. Objects passed in through props (, ) and primitives are never disposed, and 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 , and stay cached for remounts; the hook's only drops the cache entry, so dispose the geometries, materials and textures you no longer need yourself. The module-level export calls 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.
glgeometry={...}map={...}dispose={null}useLoaderuseGLTFuseTexturecleardispose(object)dispose()Fiber 会在 unmount 时 dispose 它所创建的对象(安排在 idle time 执行):每个 JSX element 对应的对象、作为 JSX children 声明的 geometries、materials 和 textures,以及由 props 或 factory 创建的 renderer。通过 props 传入的对象(、)和 primitives 永远不会被 dispose; 会使某个 element 及其 children 免于被 dispose。传入的 renderer 实例不会被 dispose,但 fiber 会在 unmount 时强制使其丢失 context,因此无法再次使用。通过 、 和 加载的资源会保留在 cache 中,以便 remount;hook 的 只会移除 cache entry,因此仍需自行 dispose 不再需要的 geometries、materials 和 textures。模块级导出的 会在对象及其自身具有 方法的 properties 上调用 (例如 mesh 的 geometry 和 material);它不会遍历 children,因此需要自行遍历整棵树。
glgeometry={...}map={...}dispose={null}useLoaderuseGLTFuseTexturecleardispose(object)dispose()dispose()WebGPU and TSL
WebGPU 和 TSL
The 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 namespace in with before using node-material elements. TSL nodes from pass through props the same way, but a material compiles its nodes once: build nodes once (module scope or ) and animate through values, because a new node passed on a later render is assigned without a recompile until you set :
glthree/webgpuextendthree/tsluseMemouniform()material.needsUpdate = truejsx
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 ) and sRGB output. WebGPURenderer tone-maps the whole frame, background included, so the same renders darker than under WebGLRenderer unless the Canvas is .
flat<color attach="background">flatChecked by rendering under WebGPURenderer on its WebGPU backend and with (fiber 9.8.2, drei 10.7.9). Working on both backends: the scene graph, hooks, events (the raycaster tests CPU geometry, so displacement is invisible to it), (loaded materials convert to node materials), , , , , , , , , without , and with , or files; , and call no renderer API (read in their source).
forceWebGL: truepositionNodeuseGLTFuseTexturePreloadCenterBoundsInstancesDetailedOrbitControlsCameraControlsHtmlocclude="blending"Environmentpresetmap.hdruseProgressAdaptiveDprPerformanceMonitorNot WebGPU-safe: draws one untextured quad instead of glyphs (troika injects its SDF shader through ), (an depth material and blur s, which three reports as not compatible), with , with or with / gainmap (both draw through ShaderMaterials; read in the source), and 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 (, ) behave exactly as in — write code, not GLSL.
TextonBeforeCompileContactShadowsonBeforeCompileShaderMaterialHtmlocclude="blending"Environmentground.jpg.webpfilesEnvironmentcolorNodepositionNodethreejs-shadersFnglextendthree/webgputhree/tsluseMemouniform()material.needsUpdatetruejsx
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(使用 时为 NoToneMapping)和 sRGB output。WebGPURenderer 会对整个 frame(包括 background)进行 tone mapping,因此除非 Canvas 设置了 ,相同的 在 WebGPURenderer 下会比在 WebGLRenderer 下显得更暗。
flatflat<color attach="background">已在 WebGPURenderer 的 WebGPU backend 以及 模式下完成渲染验证(fiber 9.8.2、drei 10.7.9)。两种 backend 均可正常工作:scene graph、hooks、events(raycaster 检测的是 CPU geometry,因此它无法发现 displacement)、(已加载的 materials 会转换为 node materials)、、、、、、、、、不使用 的 ,以及使用 、 或 files 的 ;、 和 不调用任何 renderer API(其源码中仅为读取操作)。
forceWebGL: truepositionNodeuseGLTFuseTexturePreloadCenterBoundsInstancesDetailedOrbitControlsCameraControlsocclude="blending"Htmlpresetmap.hdrEnvironmentuseProgressAdaptiveDprPerformanceMonitor不兼容 WebGPU 的功能包括: 会绘制一个没有 texture 的 quad,而不是 glyphs(troika 通过 注入其 SDF shader);(使用 depth material 和 blur s,three 报告称其不兼容);带 的 ;带 或使用 / gainmap 的 (两者均通过 ShaderMaterials 绘制,源码中可确认这一点);以及带 children 的 ,其 children 会通过 cube camera 烘焙,在 WebGPU backend 上可生成 reflections,但在 WebGL 2 fallback 上会保持黑色且不报错。TSL material slots(、)的行为与 中完全一致——请编写 code,而不是 GLSL。
TextonBeforeCompileContactShadowsonBeforeCompileShaderMaterialocclude="blending"Htmlground.jpg.webpfilesEnvironmentEnvironmentcolorNodepositionNodethreejs-shadersFnCommon Mistakes
常见错误
- →
requestAnimationFrame(tick): the Canvas already runs the frame loop; a separate loop races it and bypasses its effects.useFrame((state, delta) => { ... }) - →
state.clock.getElapsedTime(): getElapsedTime() calls getDelta() internally, which shortens the delta fiber measures for the next frame.state.clock.elapsedTime - →
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js":import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"resolves both under bundlers and under the import maps three.js documents, while thethree/addons/path fails under those import maps.examples/jsm - →
useFrame(() => { ref.current.rotation.y += 0.01; }): the second argument is the frame delta; a fixed increment runs at a different speed on every refresh rate.useFrame((_, delta) => { ref.current.rotation.y += delta; }) - →
useFrame((_, delta) => setRotation((r) => r + delta)): state set in the frame loop re-renders React each frame; mutate objects through refs.useFrame((_, delta) => { ref.current.rotation.y += delta; }) - →
useFrame((state, delta) => { ref.current.rotation.y += delta; }, 1): a positive render priority makes the Canvas stop rendering, and the scene goes black unless your callback callsuseFrame((_, delta) => { ref.current.rotation.y += delta; })itself.state.gl.render(state.scene, state.camera) - →
<mesh position={{ x: 1, y: 0, z: 0 }}>:<mesh position={[1, 0, 0]}>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 (position,fromArray,setScalar).copy - →
useEffect(() => { new GLTFLoader().load(url, (gltf) => setScene(gltf.scene)); }, []): 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.const gltf = useGLTF(url) - →
useGLTF(url).scene.clone(): 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.useMemo(() => SkeletonUtils.clone(scene), [scene]) - →
scene.add(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).<primitive object={gltf.scene} /> - →
<water args={[geometry]} />: elements resolve against the three namespace only; addon classes must be registered first.extend({ Water }); <water args={[geometry]} /> - →
<line>: the bare tag collides with the SVG line element in TypeScript; fiber strips the leading<threeLine>and builds THREE.Line.three - →
<Canvas shadows>: the boolean selects PCFSoftShadowMap, deprecated in r182 — three falls back to PCF with a console warning; the string picks the type with no warning.<Canvas shadows="percentage"> - →
<directionalLight intensity={2} />: a mesh's castShadow only draws shadows when a light also casts and the Canvas shadows flag is set.<directionalLight castShadow intensity={2} /> - →
ref.current.position.x += 1: under frameloop="demand" a bare mutation never repaints, so callref.current.position.x += 1; invalidate();frominvalidateafter imperative changes, while React prop changes invalidate themselves.useThree((state) => state.invalidate) - →
gl={new THREE.WebGPURenderer()}: 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.gl={async (props) => { const renderer = new THREE.WebGPURenderer(props); await renderer.init(); return renderer; }} - →
new OrbitControls(camera, renderer.domElement): the drei component wires per-frame updates, invalidation and disposal; the raw class stalls with damping on and never invalidates a demand loop.<OrbitControls makeDefault /> - →
<Environment><Lightformer /></Environment>: 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<Environment files="env.hdr" />files and presets only assign a texture and work on both backends..hdr
- →
requestAnimationFrame(tick):Canvas 已经运行 frame loop;额外的 loop 会与其竞争,并绕过它产生的效果。useFrame((state, delta) => { ... }) - →
state.clock.getElapsedTime():getElapsedTime() 会在内部调用 getDelta(),从而缩短 fiber 为下一帧测得的 delta。state.clock.elapsedTime - →
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js":import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"在 bundlers 和 three.js 文档中的 import maps 下均可解析,而three/addons/路径在这些 import maps 下会解析失败。examples/jsm - →
useFrame(() => { ref.current.rotation.y += 0.01; }):第二个参数是 frame delta;固定增量会在不同 refresh rate 下以不同速度运行。useFrame((_, delta) => { ref.current.rotation.y += delta; }) - →
useFrame((_, delta) => setRotation((r) => r + delta)):在 frame loop 中设置 state 会让 React 每帧 re-render;应通过 refs 修改对象。useFrame((_, delta) => { ref.current.rotation.y += delta; }) - →
useFrame((state, delta) => { ref.current.rotation.y += delta; }, 1):正数 render priority 会使 Canvas 停止渲染,除非 callback 自行调用useFrame((_, delta) => { ref.current.rotation.y += delta; }),否则 scene 会变黑。state.gl.render(state.scene, state.camera) - →
<mesh position={{ x: 1, y: 0, z: 0 }}>:<mesh position={[1, 0, 0]}>是保存 Vector3 的只读 property,因此 plain object 会抛出“Cannot assign to read only property”并导致 Canvas 失败;而 arrays、numbers 和 Vector3s 会被写入现有的 Vector3(position、fromArray、setScalar)。copy - →
useEffect(() => { new GLTFLoader().load(url, (gltf) => setScene(gltf.scene)); }, []):useGLTF 会 suspend、按 URL 缓存解析后的 model 以供 remount 和 preloading 使用,并接入 Draco 与 meshopt decoding;而 effect 版本每次 mount 都会重新获取和解析,且没有 Draco decoder。const gltf = useGLTF(url) - →
useGLTF(url).scene.clone():Object3D.clone() 会共享同一个 skeleton,因此副本仍绑定到原始模型的 bones;SkeletonUtils 会重新绑定每个副本,而 useMemo 可确保每个 component 只保留一个 clone,而不是每次 render 都创建一个。useMemo(() => SkeletonUtils.clone(scene), [scene]) - →
scene.add(gltf.scene):以命令式方式添加的对象不会获得 fiber event props,并且在 component unmount 后仍会留在 scene 中;primitive 则可使用 event props,并会随 component 添加和移除(其 disposal 仍由你负责)。<primitive object={gltf.scene} /> - →
<water args={[geometry]} />:elements 只会在 three namespace 中解析;addon classes 必须先注册。extend({ Water }); <water args={[geometry]} /> - →
<line>:在 TypeScript 中,裸标签会与 SVG line element 冲突;fiber 会移除开头的<threeLine>并构建 THREE.Line。three - →
<Canvas shadows>:boolean 会选择 PCFSoftShadowMap,而它已在 r182 中弃用——three 会回退到 PCF 并输出 console warning;string 可选择类型且不会产生警告。<Canvas shadows="percentage"> - →
<directionalLight intensity={2} />:只有当 light 也会投射阴影且 Canvas 设置了 shadows flag 时,mesh 的 castShadow 才会绘制阴影。<directionalLight castShadow intensity={2} /> - →
ref.current.position.x += 1:在ref.current.position.x += 1; invalidate();下,单纯的 mutation 不会触发重绘,因此命令式修改后应调用frameloop="demand"获得的 invalidate,而 React prop changes 会自动 invalidate。useThree((state) => state.invalidate) - →
gl={new THREE.WebGPURenderer()}:未使用 fiber 的 canvas 创建的 renderer 会绘制到其自己的、永远不会显示的 canvas 中,并且在 init() resolved 前调用 render() 会抛出错误;factory 则会获得 Canvas 的 canvas,fiber 只会在其 resolved 后开始渲染。gl={async (props) => { const renderer = new THREE.WebGPURenderer(props); await renderer.init(); return renderer; }} - →
new OrbitControls(camera, renderer.domElement):drei component 会接入每帧更新、invalidation 和 disposal;原始 class 在开启 damping 后可能停滞,并且永远不会 invalidate demand loop。<OrbitControls makeDefault /> - →
<Environment><Lightformer /></Environment>:在 WebGPURenderer 下,带 children 的烘焙会通过 cube camera 完成,在 WebGL 2 fallback backend 上 reflections 会变成黑色且不报错;而<Environment files="env.hdr" />files 和 presets 仅分配 texture,可在两种 backend 上正常工作。.hdr
See Also
参见
- - the renderer, scene and camera concepts the Canvas manages for you
threejs-fundamentals - - loader configuration, compression and disposal in depth
threejs-loaders - - raycasting and controls beyond fiber's pointer events
threejs-interaction - - TSL node materials and slots used inside fiber elements
threejs-shaders - - profiling and optimization beyond fiber's adaptive tools
threejs-performance - - gltfjsx components, and compressing models before useGLTF loads them
threejs-asset-pipeline
- - Canvas 为你管理的 renderer、scene 和 camera 概念
threejs-fundamentals - - 更深入的 loader 配置、压缩和 disposal
threejs-loaders - - fiber pointer events 之外的 raycasting 和 controls
threejs-interaction - - fiber elements 中使用的 TSL node materials 和 slots
threejs-shaders - - fiber 自适应工具之外的 profiling 和 optimization
threejs-performance - - gltfjsx components,以及在 useGLTF 加载模型前对其进行压缩
threejs-asset-pipeline