如何在R3F/three.js中高效实例化组合几何体并控制部件属性?
解决方案
一、高性能批量创建组合几何体
直接合并几何体(mergeGeometries)无法满足批量创建的性能需求,也没法单独控制部件属性。正确的做法是用多InstancedMesh组合:把每个部件(两个Box、一个Sphere)分别做成独立的InstancedMesh,让它们的实例一一对应,视觉上形成组合体,同时利用实例化的性能优势。
实现步骤
- 为每个部件创建
InstancedMesh,设置相同的实例数量; - 为每个实例生成统一的位置、旋转矩阵,保证三个部件的第n个实例对应同一个组合体;
- 一次性把所有实例的矩阵数据传递给GPU,大幅减少绘制调用。
代码示例
import { useRef, useEffect } from 'react' import { InstancedMesh, BoxGeometry, SphereGeometry, MeshStandardMaterial } from '@react-three/fiber' export default function CombinedInstances() { const box1Ref = useRef() const box2Ref = useRef() const sphereRef = useRef() const instanceCount = 1000 // 批量创建的数量 useEffect(() => { const matrix = new THREE.Matrix4() // 为每个实例生成统一的变换矩阵 for (let i = 0; i < instanceCount; i++) { // 随机分散排列 matrix.setPosition( (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20 ) // 同步三个部件的实例矩阵 box1Ref.current.setMatrixAt(i, matrix) box2Ref.current.setMatrixAt(i, matrix) sphereRef.current.setMatrixAt(i, matrix) } // 标记矩阵需要更新 box1Ref.current.instanceMatrix.needsUpdate = true box2Ref.current.instanceMatrix.needsUpdate = true sphereRef.current.instanceMatrix.needsUpdate = true }, []) return ( <> <InstancedMesh ref={box1Ref} args={[new BoxGeometry(1,1,1), new MeshStandardMaterial({color: 'red'}), instanceCount]} /> <InstancedMesh ref={box2Ref} args={[new BoxGeometry(1,1,1), new MeshStandardMaterial({color: 'blue'}), instanceCount]} /> <InstancedMesh ref={sphereRef} args={[new SphereGeometry(0.5), new MeshStandardMaterial({color: 'green'}), instanceCount]} /> </> ) }
二、控制单个实例的部件属性
要单独修改某个实例的部件(比如让第5个实例的第一个Box变大),需要通过自定义实例属性传递参数,在Shader中实现动态调整。
实现步骤
- 为每个
InstancedMesh的几何体添加自定义实例属性(比如instanceScale),存储每个实例的缩放参数; - 自定义Shader材质,在顶点着色器中读取实例属性,对顶点进行缩放;
- 按需修改指定实例的属性值,实现单个部件的个性化控制。
代码示例
import { useRef, useEffect } from 'react' import { InstancedMesh, BoxGeometry, SphereGeometry, extend } from '@react-three/fiber' import { shaderMaterial } from '@react-three/drei' // 自定义支持实例缩放的材质 const ScaledInstanceMaterial = shaderMaterial( { color: '#ffffff', metalness: 0.1, roughness: 0.8 }, // 顶点着色器:读取instanceScale属性缩放顶点 ` attribute vec3 instanceScale; varying vec3 vNormal; void main() { vNormal = normal; vec3 scaledPosition = position * instanceScale; gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(scaledPosition, 1.0); } `, // 片段着色器保持标准材质效果 ` varying vec3 vNormal; uniform vec3 color; uniform float metalness; uniform float roughness; void main() { vec3 normal = normalize(vNormal); vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * color; gl_FragColor = vec4(diffuse, 1.0); } ` ) extend({ ScaledInstanceMaterial }) export default function ControllableCombinedInstances() { const box1Ref = useRef() const box2Ref = useRef() const sphereRef = useRef() const instanceCount = 1000 useEffect(() => { const matrix = new THREE.Matrix4() // 创建实例缩放属性数组 const box1Scales = new Float32Array(instanceCount * 3) const box2Scales = new Float32Array(instanceCount * 3) const sphereScales = new Float32Array(instanceCount * 3) for (let i = 0; i < instanceCount; i++) { // 同步基础变换矩阵 matrix.setPosition( (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20 ) box1Ref.current.setMatrixAt(i, matrix) box2Ref.current.setMatrixAt(i, matrix) sphereRef.current.setMatrixAt(i, matrix) // 自定义单个实例的缩放:让第5个实例的box1放大2倍 const box1Scale = i === 4 ? [2, 2, 2] : [1, 1, 1] const box2Scale = [1, 1, 1] const sphereScale = [1, 1, 1] // 写入属性数组 box1Scales.set(box1Scale, i * 3) box2Scales.set(box2Scale, i * 3) sphereScales.set(sphereScale, i * 3) } // 为几何体添加实例缩放属性 box1Ref.current.geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(box1Scales, 3)) box2Ref.current.geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(box2Scales, 3)) sphereRef.current.geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(sphereScales, 3)) // 更新实例矩阵 box1Ref.current.instanceMatrix.needsUpdate = true box2Ref.current.instanceMatrix.needsUpdate = true sphereRef.current.instanceMatrix.needsUpdate = true }, []) return ( <> <InstancedMesh ref={box1Ref} args={[new BoxGeometry(1,1,1), null, instanceCount]}> <scaledInstanceMaterial color="red" /> </InstancedMesh> <InstancedMesh ref={box2Ref} args={[new BoxGeometry(1,1,1), null, instanceCount]}> <scaledInstanceMaterial color="blue" /> </InstancedMesh> <InstancedMesh ref={sphereRef} args={[new SphereGeometry(0.5), null, instanceCount]}> <scaledInstanceMaterial color="green" /> </InstancedMesh> </> ) }
关键说明
- 不要使用
mergeGeometries:合并后的几何体是一个整体,无法单独控制部件,也无法利用实例化的性能优势; - 多
InstancedMesh组合:每个部件独立实例化,既保证性能,又能单独控制每个部件的实例参数; - 自定义实例属性:除了缩放,还可以扩展颜色、旋转偏移等属性,通过
InstancedBufferAttribute传递到Shader,实现更复杂的个性化控制。
内容的提问来源于stack exchange,提问作者TheKalashnikov
相关产品推荐
相关产品推荐

