如何用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
相关产品推荐
相关产品推荐

