You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R3F/three.js中高效实例化组合几何体并控制部件属性?

解决方案

一、高性能批量创建组合几何体

直接合并几何体(mergeGeometries)无法满足批量创建的性能需求,也没法单独控制部件属性。正确的做法是用多InstancedMesh组合:把每个部件(两个Box、一个Sphere)分别做成独立的InstancedMesh,让它们的实例一一对应,视觉上形成组合体,同时利用实例化的性能优势。

实现步骤

  1. 为每个部件创建InstancedMesh,设置相同的实例数量;
  2. 为每个实例生成统一的位置、旋转矩阵,保证三个部件的第n个实例对应同一个组合体;
  3. 一次性把所有实例的矩阵数据传递给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中实现动态调整。

实现步骤

  1. 为每个InstancedMesh的几何体添加自定义实例属性(比如instanceScale),存储每个实例的缩放参数;
  2. 自定义Shader材质,在顶点着色器中读取实例属性,对顶点进行缩放;
  3. 按需修改指定实例的属性值,实现单个部件的个性化控制。

代码示例

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 22:30:53