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

如何用react-three-fiber的Instance Mesh批量渲染GLTF模型并自定义颜色

使用Instance Mesh优化GLTF模型批量渲染性能,传递color和windowsTint属性

核心思路

要实现单次绘制调用渲染大量实例,需用THREE.InstancedMesh替代多个独立<mesh>组件。你的模型包含3个不同Mesh(墙体、窗户、金属部件),需为每个Mesh创建对应InstancedMesh,通过实例化属性传递每个实例的color和windowsTint值,同时修改材质以支持读取这些实例属性。


步骤1:扩展材质支持实例化属性

默认Three.js材质不支持读取自定义实例属性,需扩展着色器添加实例化颜色逻辑:

import * as THREE from 'three'
import { useMemo } from 'react'

// 扩展MeshStandardMaterial,支持墙体实例化颜色
const InstanceWallMaterial = () => {
  const material = useMemo(() => {
    const mat = new THREE.MeshStandardMaterial({
      roughness: 0.6,
      metalness: 0.3,
      side: THREE.DoubleSide,
    })
    mat.onBeforeCompile = (shader) => {
      // 顶点着色器添加实例颜色属性
      shader.vertexShader = `
        attribute vec3 instanceColor;
        varying vec3 vInstanceColor;
        ${shader.vertexShader}
      `.replace(
        '#include <color_vertex>',
        `#include <color_vertex>
        vInstanceColor = instanceColor;`
      )
      // 片元着色器应用实例颜色
      shader.fragmentShader = `
        varying vec3 vInstanceColor;
        ${shader.fragmentShader}
      `.replace(
        '#include <color_fragment>',
        `#include <color_fragment>
        diffuseColor.rgb *= vInstanceColor;`
      )
    }
    return mat
  }, [])
  return <primitive object={material} />
}

// 扩展MeshPhongMaterial,支持窗户实例化tint颜色
const InstanceWindowMaterial = () => {
  const material = useMemo(() => {
    const mat = new THREE.MeshPhongMaterial({
      transparent: true,
      opacity: 0.7,
      side: THREE.DoubleSide,
    })
    mat.onBeforeCompile = (shader) => {
      shader.vertexShader = `
        attribute vec3 instanceWindowTint;
        varying vec3 vInstanceWindowTint;
        ${shader.vertexShader}
      `.replace(
        '#include <color_vertex>',
        `#include <color_vertex>
        vInstanceWindowTint = instanceWindowTint;`
      )
      shader.fragmentShader = `
        varying vec3 vInstanceWindowTint;
        ${shader.fragmentShader}
      `.replace(
        '#include <color_fragment>',
        `#include <color_fragment>
        diffuseColor.rgb *= vInstanceWindowTint;`
      )
    }
    return mat
  }, [])
  return <primitive object={material} />
}

步骤2:创建实例化渲染组件

在组件中加载GLTF模型,批量创建InstancedMesh并设置实例属性:

import * as THREE from 'three'
import { useGLTF } from '@react-three/drei'
import { useRef, useEffect } from 'react'
import { GLTF } from 'three-stdlib'

type GLTFResult = GLTF & {
  nodes: {
    Cylinder: THREE.Mesh
    Cylinder_1: THREE.Mesh
    Cylinder_2: THREE.Mesh
  }
  materials: {
    Metal: THREE.MeshStandardMaterial
  }
}

type RoomData = {
  color: string
  windowTint: number
}

const RoomsInstance = ({ rooms }: { rooms: RoomData[] }) => {
  const { nodes } = useGLTF('./3d/room.gltf') as GLTFResult
  const wallInstRef = useRef<THREE.InstancedMesh>(null)
  const windowInstRef = useRef<THREE.InstancedMesh>(null)
  const metalInstRef = useRef<THREE.InstancedMesh>(null)
  const dummyObj = useRef(new THREE.Object3D())

  useEffect(() => {
    if (!wallInstRef.current || !windowInstRef.current || !metalInstRef.current) return

    const count = rooms.length
    // 设置实例数量
    wallInstRef.current.count = count
    windowInstRef.current.count = count
    metalInstRef.current.count = count

    // 初始化墙体实例颜色属性
    const wallColors = new THREE.InstancedBufferAttribute(new Float32Array(count * 3), 3)
    wallInstRef.current.geometry.setAttribute('instanceColor', wallColors)

    // 初始化窗户实例tint属性
    const windowTints = new THREE.InstancedBufferAttribute(new Float32Array(count * 3), 3)
    windowInstRef.current.geometry.setAttribute('instanceWindowTint', windowTints)

    // 批量设置每个实例的矩阵和属性
    rooms.forEach((room, idx) => {
      // 设置位置与旋转
      dummyObj.current.position.set(0, idx * 2, 0)
      dummyObj.current.rotation.set(0, Math.PI * 0.08 * idx, 0)
      dummyObj.current.updateMatrix()

      // 墙体实例:设置矩阵+颜色
      wallInstRef.current.setMatrixAt(idx, dummyObj.current.matrix)
      const color = new THREE.Color(room.color)
      wallColors.setXYZ(idx, color.r, color.g, color.b)

      // 窗户实例:设置矩阵+tint值(转为灰度RGB)
      windowInstRef.current.setMatrixAt(idx, dummyObj.current.matrix)
      const tintVal = room.windowTint
      windowTints.setXYZ(idx, tintVal, tintVal, tintVal)

      // 金属部件实例:仅设置矩阵(颜色固定)
      metalInstRef.current.setMatrixAt(idx, dummyObj.current.matrix)
    })

    // 标记属性需要更新
    wallInstRef.current.instanceMatrix.needsUpdate = true
    wallInstRef.current.geometry.attributes.instanceColor.needsUpdate = true
    windowInstRef.current.instanceMatrix.needsUpdate = true
    windowInstRef.current.geometry.attributes.instanceWindowTint.needsUpdate = true
    metalInstRef.current.instanceMatrix.needsUpdate = true
  }, [rooms, nodes])

  return (
    <group dispose={null}>
      {/* 墙体实例化Mesh */}
      <instancedMesh
        ref={wallInstRef}
        castShadow
        receiveShadow
        geometry={nodes.Cylinder.geometry}
      >
        <InstanceWallMaterial />
      </instancedMesh>

      {/* 窗户实例化Mesh */}
      <instancedMesh
        ref={windowInstRef}
        castShadow
        receiveShadow
        geometry={nodes.Cylinder_1.geometry}
      >
        <InstanceWindowMaterial />
      </instancedMesh>

      {/* 金属部件实例化Mesh */}
      <instancedMesh
        ref={metalInstRef}
        castShadow
        receiveShadow
        geometry={nodes.Cylinder_2.geometry}
      >
        <meshStandardMaterial
          metalness={0.8}
          roughness={0.4}
          color='#888888'
        />
      </instancedMesh>
    </group>
  )
}

useGLTF.preload('./3d/room.gltf')
export default RoomsInstance

步骤3:在App中使用实例化组件

替换原有的map渲染逻辑,直接传入房间数据数组:

// App.tsx
import { Canvas } from '@react-three/fiber'
import RoomsInstance from './RoomsInstance'

const rooms = [
  { color: '#f7f7f7', windowTint: 0.9 },
  // ... 更多10万级实例数据
]

function App() {
  return (
    <Canvas>
      {/* 其他场景元素 */}
      <RoomsInstance rooms={rooms} />
    </Canvas>
  )
}

export default App

关键注意事项

  • 所有实例必须使用同一材质(或同类型扩展材质),否则无法合并绘制调用。
  • 10万级实例需确保模型几何精简,可开启frustumCulled(默认开启)剔除视锥体外实例。
  • 实例属性更新后必须标记needsUpdate = true,否则Three.js不会重新渲染。

内容的提问来源于stack exchange,提问作者Paxiston

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 07:30:52