r3f-geometry
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseR3F Geometry
R3F 几何体
Geometry defines the shape of 3D objects via vertices, faces, normals, and UVs stored in buffer attributes.
几何体通过存储在缓冲区属性中的顶点、面、法线和UV来定义3D对象的形状。
Quick Start
快速开始
tsx
// Built-in geometry
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial />
</mesh>
// Custom geometry
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={3}
array={new Float32Array([0, 0, 0, 1, 0, 0, 0.5, 1, 0])}
itemSize={3}
/>
</bufferGeometry>
<meshBasicMaterial side={THREE.DoubleSide} />
</mesh>tsx
// Built-in geometry
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial />
</mesh>
// Custom geometry
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={3}
array={new Float32Array([0, 0, 0, 1, 0, 0, 0.5, 1, 0])}
itemSize={3}
/>
</bufferGeometry>
<meshBasicMaterial side={THREE.DoubleSide} />
</mesh>Built-in Geometries
内置几何体
All geometries accept array matching constructor parameters:
argstsx
// Box: [width, height, depth, widthSegments?, heightSegments?, depthSegments?]
<boxGeometry args={[1, 2, 1, 1, 2, 1]} />
// Sphere: [radius, widthSegments, heightSegments, phiStart?, phiLength?, thetaStart?, thetaLength?]
<sphereGeometry args={[1, 32, 32]} />
// Plane: [width, height, widthSegments?, heightSegments?]
<planeGeometry args={[10, 10, 10, 10]} />
// Cylinder: [radiusTop, radiusBottom, height, radialSegments?, heightSegments?, openEnded?]
<cylinderGeometry args={[0.5, 0.5, 2, 32]} />
// Cone: [radius, height, radialSegments?, heightSegments?, openEnded?]
<coneGeometry args={[1, 2, 32]} />
// Torus: [radius, tube, radialSegments, tubularSegments, arc?]
<torusGeometry args={[1, 0.3, 16, 100]} />
// TorusKnot: [radius, tube, tubularSegments, radialSegments, p?, q?]
<torusKnotGeometry args={[1, 0.3, 100, 16]} />
// Ring: [innerRadius, outerRadius, thetaSegments?, phiSegments?]
<ringGeometry args={[0.5, 1, 32]} />
// Circle: [radius, segments?, thetaStart?, thetaLength?]
<circleGeometry args={[1, 32]} />
// Dodecahedron/Icosahedron/Octahedron/Tetrahedron: [radius, detail?]
<icosahedronGeometry args={[1, 0]} />所有几何体都接受与构造函数参数匹配的数组:
argstsx
// Box: [width, height, depth, widthSegments?, heightSegments?, depthSegments?]
<boxGeometry args={[1, 2, 1, 1, 2, 1]} />
// Sphere: [radius, widthSegments, heightSegments, phiStart?, phiLength?, thetaStart?, thetaLength?]
<sphereGeometry args={[1, 32, 32]} />
// Plane: [width, height, widthSegments?, heightSegments?]
<planeGeometry args={[10, 10, 10, 10]} />
// Cylinder: [radiusTop, radiusBottom, height, radialSegments?, heightSegments?, openEnded?]
<cylinderGeometry args={[0.5, 0.5, 2, 32]} />
// Cone: [radius, height, radialSegments?, heightSegments?, openEnded?]
<coneGeometry args={[1, 2, 32]} />
// Torus: [radius, tube, radialSegments, tubularSegments, arc?]
<torusGeometry args={[1, 0.3, 16, 100]} />
// TorusKnot: [radius, tube, tubularSegments, radialSegments, p?, q?]
<torusKnotGeometry args={[1, 0.3, 100, 16]} />
// Ring: [innerRadius, outerRadius, thetaSegments?, phiSegments?]
<ringGeometry args={[0.5, 1, 32]} />
// Circle: [radius, segments?, thetaStart?, thetaLength?]
<circleGeometry args={[1, 32]} />
// Dodecahedron/Icosahedron/Octahedron/Tetrahedron: [radius, detail?]
<icosahedronGeometry args={[1, 0]} />Buffer Attributes
缓冲区属性
Geometry data lives in typed arrays attached as attributes:
| Attribute | ItemSize | Purpose |
|---|---|---|
| 3 | Vertex positions (x, y, z) |
| 3 | Surface normals for lighting |
| 2 | Texture coordinates (u, v) |
| 3 | Per-vertex colors (r, g, b) |
| 1 | Triangle indices (optional) |
几何体数据存储在作为属性附加的类型化数组中:
| 属性 | 元素数量 | 用途 |
|---|---|---|
| 3 | 顶点位置(x, y, z) |
| 3 | 用于光照的表面法线 |
| 2 | 纹理坐标(u, v) |
| 3 | 逐顶点颜色(r, g, b) |
| 1 | 三角形索引(可选) |
Custom Geometry from Scratch
从零开始创建自定义几何体
tsx
import { useMemo } from 'react';
import * as THREE from 'three';
function Triangle() {
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// 3 vertices × 3 components (x, y, z)
const positions = new Float32Array([
-1, -1, 0, // vertex 0
1, -1, 0, // vertex 1
0, 1, 0 // vertex 2
]);
// 3 vertices × 3 components (nx, ny, nz)
const normals = new Float32Array([
0, 0, 1,
0, 0, 1,
0, 0, 1
]);
// 3 vertices × 2 components (u, v)
const uvs = new Float32Array([
0, 0,
1, 0,
0.5, 1
]);
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
geo.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
return geo;
}, []);
return (
<mesh geometry={geometry}>
<meshStandardMaterial side={THREE.DoubleSide} />
</mesh>
);
}tsx
import { useMemo } from 'react';
import * as THREE from 'three';
function Triangle() {
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// 3 vertices × 3 components (x, y, z)
const positions = new Float32Array([
-1, -1, 0, // vertex 0
1, -1, 0, // vertex 1
0, 1, 0 // vertex 2
]);
// 3 vertices × 3 components (nx, ny, nz)
const normals = new Float32Array([
0, 0, 1,
0, 0, 1,
0, 0, 1
]);
// 3 vertices × 2 components (u, v)
const uvs = new Float32Array([
0, 0,
1, 0,
0.5, 1
]);
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
geo.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
return geo;
}, []);
return (
<mesh geometry={geometry}>
<meshStandardMaterial side={THREE.DoubleSide} />
</mesh>
);
}Declarative Buffer Attributes
声明式缓冲区属性
tsx
function Triangle() {
const positions = useMemo(() =>
new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0]),
[]);
return (
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={3}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<meshBasicMaterial side={THREE.DoubleSide} />
</mesh>
);
}tsx
function Triangle() {
const positions = useMemo(() =>
new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0]),
[]);
return (
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={3}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<meshBasicMaterial side={THREE.DoubleSide} />
</mesh>
);
}Indexed Geometry
索引化几何体
Use indices to share vertices between triangles:
tsx
function Quad() {
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// 4 unique vertices
const positions = new Float32Array([
-1, -1, 0, // 0: bottom-left
1, -1, 0, // 1: bottom-right
1, 1, 0, // 2: top-right
-1, 1, 0 // 3: top-left
]);
// 2 triangles, 6 indices
const indices = new Uint16Array([
0, 1, 2, // first triangle
0, 2, 3 // second triangle
]);
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setIndex(new THREE.BufferAttribute(indices, 1));
geo.computeVertexNormals();
return geo;
}, []);
return (
<mesh geometry={geometry}>
<meshStandardMaterial side={THREE.DoubleSide} />
</mesh>
);
}使用索引在多个三角形之间共享顶点:
tsx
function Quad() {
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// 4 unique vertices
const positions = new Float32Array([
-1, -1, 0, // 0: bottom-left
1, -1, 0, // 1: bottom-right
1, 1, 0, // 2: top-right
-1, 1, 0 // 3: top-left
]);
// 2 triangles, 6 indices
const indices = new Uint16Array([
0, 1, 2, // first triangle
0, 2, 3 // second triangle
]);
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setIndex(new THREE.BufferAttribute(indices, 1));
geo.computeVertexNormals();
return geo;
}, []);
return (
<mesh geometry={geometry}>
<meshStandardMaterial side={THREE.DoubleSide} />
</mesh>
);
}Dynamic Geometry Updates
动态几何体更新
tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function WavingPlane() {
const geometryRef = useRef<THREE.BufferGeometry>(null!);
useFrame(({ clock }) => {
const positions = geometryRef.current.attributes.position;
const time = clock.elapsedTime;
for (let i = 0; i < positions.count; i++) {
const x = positions.getX(i);
const y = positions.getY(i);
const z = Math.sin(x * 2 + time) * Math.cos(y * 2 + time) * 0.5;
positions.setZ(i, z);
}
positions.needsUpdate = true; // Critical!
geometryRef.current.computeVertexNormals();
});
return (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry ref={geometryRef} args={[10, 10, 50, 50]} />
<meshStandardMaterial color="royalblue" side={THREE.DoubleSide} />
</mesh>
);
}tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function WavingPlane() {
const geometryRef = useRef<THREE.BufferGeometry>(null!);
useFrame(({ clock }) => {
const positions = geometryRef.current.attributes.position;
const time = clock.elapsedTime;
for (let i = 0; i < positions.count; i++) {
const x = positions.getX(i);
const y = positions.getY(i);
const z = Math.sin(x * 2 + time) * Math.cos(y * 2 + time) * 0.5;
positions.setZ(i, z);
}
positions.needsUpdate = true; // Critical!
geometryRef.current.computeVertexNormals();
});
return (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry ref={geometryRef} args={[10, 10, 50, 50]} />
<meshStandardMaterial color="royalblue" side={THREE.DoubleSide} />
</mesh>
);
}Instanced Mesh
实例化网格
Render thousands of identical meshes with different transforms in a single draw call:
tsx
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function Particles({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
// Pre-allocate transformation objects
const dummy = useMemo(() => new THREE.Object3D(), []);
// Initialize instance matrices
useEffect(() => {
for (let i = 0; i < count; i++) {
dummy.position.set(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
dummy.scale.setScalar(0.1 + Math.random() * 0.2);
dummy.updateMatrix();
meshRef.current.setMatrixAt(i, dummy.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
}, [count, dummy]);
// Animate instances
useFrame(({ clock }) => {
for (let i = 0; i < count; i++) {
meshRef.current.getMatrixAt(i, dummy.matrix);
dummy.matrix.decompose(dummy.position, dummy.quaternion, dummy.scale);
dummy.rotation.x += 0.01;
dummy.rotation.y += 0.01;
dummy.updateMatrix();
meshRef.current.setMatrixAt(i, dummy.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</instancedMesh>
);
}通过单次绘制调用渲染数千个相同的网格(可应用不同变换):
tsx
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function Particles({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
// Pre-allocate transformation objects
const dummy = useMemo(() => new THREE.Object3D(), []);
// Initialize instance matrices
useEffect(() => {
for (let i = 0; i < count; i++) {
dummy.position.set(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
dummy.scale.setScalar(0.1 + Math.random() * 0.2);
dummy.updateMatrix();
meshRef.current.setMatrixAt(i, dummy.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
}, [count, dummy]);
// Animate instances
useFrame(({ clock }) => {
for (let i = 0; i < count; i++) {
meshRef.current.getMatrixAt(i, dummy.matrix);
dummy.matrix.decompose(dummy.position, dummy.quaternion, dummy.scale);
dummy.rotation.x += 0.01;
dummy.rotation.y += 0.01;
dummy.updateMatrix();
meshRef.current.setMatrixAt(i, dummy.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</instancedMesh>
);
}Instance Colors
实例颜色
tsx
function ColoredInstances({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
useEffect(() => {
const color = new THREE.Color();
for (let i = 0; i < count; i++) {
color.setHSL(i / count, 1, 0.5);
meshRef.current.setColorAt(i, color);
}
meshRef.current.instanceColor!.needsUpdate = true;
}, [count]);
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial />
</instancedMesh>
);
}tsx
function ColoredInstances({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
useEffect(() => {
const color = new THREE.Color();
for (let i = 0; i < count; i++) {
color.setHSL(i / count, 1, 0.5);
meshRef.current.setColorAt(i, color);
}
meshRef.current.instanceColor!.needsUpdate = true;
}, [count]);
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial />
</instancedMesh>
);
}Instance Attributes (Custom Data)
实例属性(自定义数据)
tsx
function CustomInstanceData({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
// Custom per-instance data
const speeds = useMemo(() => {
const arr = new Float32Array(count);
for (let i = 0; i < count; i++) {
arr[i] = 0.5 + Math.random();
}
return arr;
}, [count]);
useEffect(() => {
// Attach as instanced buffer attribute
meshRef.current.geometry.setAttribute(
'aSpeed',
new THREE.InstancedBufferAttribute(speeds, 1)
);
}, [speeds]);
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<boxGeometry />
<shaderMaterial
vertexShader={`
attribute float aSpeed;
varying float vSpeed;
void main() {
vSpeed = aSpeed;
gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
}
`}
fragmentShader={`
varying float vSpeed;
void main() {
gl_FragColor = vec4(vSpeed, 0.5, 1.0 - vSpeed, 1.0);
}
`}
/>
</instancedMesh>
);
}tsx
function CustomInstanceData({ count = 1000 }) {
const meshRef = useRef<THREE.InstancedMesh>(null!);
// Custom per-instance data
const speeds = useMemo(() => {
const arr = new Float32Array(count);
for (let i = 0; i < count; i++) {
arr[i] = 0.5 + Math.random();
}
return arr;
}, [count]);
useEffect(() => {
// Attach as instanced buffer attribute
meshRef.current.geometry.setAttribute(
'aSpeed',
new THREE.InstancedBufferAttribute(speeds, 1)
);
}, [speeds]);
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<boxGeometry />
<shaderMaterial
vertexShader={`
attribute float aSpeed;
varying float vSpeed;
void main() {
vSpeed = aSpeed;
gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
}
`}
fragmentShader={`
varying float vSpeed;
void main() {
gl_FragColor = vec4(vSpeed, 0.5, 1.0 - vSpeed, 1.0);
}
`}
/>
</instancedMesh>
);
}Geometry Utilities
几何体工具
Compute Normals
计算法线
tsx
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// ... set positions
geo.computeVertexNormals(); // Auto-calculate smooth normals
return geo;
}, []);tsx
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry();
// ... set positions
geo.computeVertexNormals(); // Auto-calculate smooth normals
return geo;
}, []);Compute Bounding Box/Sphere
计算边界框/球体
tsx
useEffect(() => {
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
console.log(geometry.boundingBox); // THREE.Box3
console.log(geometry.boundingSphere); // THREE.Sphere
}, [geometry]);tsx
useEffect(() => {
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
console.log(geometry.boundingBox); // THREE.Box3
console.log(geometry.boundingSphere); // THREE.Sphere
}, [geometry]);Center Geometry
居中几何体
tsx
const geometry = useMemo(() => {
const geo = new THREE.BoxGeometry(2, 3, 1);
geo.center(); // Move to origin
return geo;
}, []);tsx
const geometry = useMemo(() => {
const geo = new THREE.BoxGeometry(2, 3, 1);
geo.center(); // Move to origin
return geo;
}, []);Merge Geometries
合并几何体
tsx
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
const merged = useMemo(() => {
const box = new THREE.BoxGeometry(1, 1, 1);
const sphere = new THREE.SphereGeometry(0.5, 16, 16);
sphere.translate(0, 1, 0);
return mergeGeometries([box, sphere]);
}, []);tsx
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
const merged = useMemo(() => {
const box = new THREE.BoxGeometry(1, 1, 1);
const sphere = new THREE.SphereGeometry(0.5, 16, 16);
sphere.translate(0, 1, 0);
return mergeGeometries([box, sphere]);
}, []);Performance Tips
性能优化技巧
| Technique | When to Use | Impact |
|---|---|---|
| Instancing | 100+ identical meshes | Massive |
| Indexed geometry | Shared vertices | Moderate |
| Lower segments | Non-hero geometry | Moderate |
| Merge geometries | Static scene | Moderate |
| Dispose unused | Dynamic loading | Memory |
| 技巧 | 使用场景 | 影响 |
|---|---|---|
| 实例化 | 100+个相同网格 | 极大提升 |
| 索引化几何体 | 共享顶点 | 中等提升 |
| 减少分段数 | 非核心几何体 | 中等提升 |
| 合并几何体 | 静态场景 | 中等提升 |
| 释放未使用资源 | 动态加载场景 | 优化内存 |
Disposal
资源释放
tsx
useEffect(() => {
return () => {
geometry.dispose(); // Clean up GPU memory
};
}, [geometry]);tsx
useEffect(() => {
return () => {
geometry.dispose(); // Clean up GPU memory
};
}, [geometry]);File Structure
文件结构
r3f-geometry/
├── SKILL.md
├── references/
│ ├── buffer-attributes.md # Deep-dive on attribute types
│ ├── instancing-patterns.md # Advanced instancing
│ └── procedural-shapes.md # Algorithmic geometry
└── scripts/
├── procedural/
│ ├── grid.ts # Grid mesh generator
│ ├── terrain.ts # Heightmap terrain
│ └── tube.ts # Custom tube geometry
└── utils/
├── geometry-utils.ts # Merge, center, clone
└── instancing.ts # Instance helpersr3f-geometry/
├── SKILL.md
├── references/
│ ├── buffer-attributes.md # Deep-dive on attribute types
│ ├── instancing-patterns.md # Advanced instancing
│ └── procedural-shapes.md # Algorithmic geometry
└── scripts/
├── procedural/
│ ├── grid.ts # Grid mesh generator
│ ├── terrain.ts # Heightmap terrain
│ └── tube.ts # Custom tube geometry
└── utils/
├── geometry-utils.ts # Merge, center, clone
└── instancing.ts # Instance helpersReference
参考资料
- — All attribute types and usage
references/buffer-attributes.md - — Advanced instancing techniques
references/instancing-patterns.md - — Generating geometry algorithmically
references/procedural-shapes.md
- — 所有属性类型及用法
references/buffer-attributes.md - — 高级实例化技巧
references/instancing-patterns.md - — 程序化生成几何体
references/procedural-shapes.md