React Three Fiber 如何为单个 mesh 通过 attach 分配多个材质?
2026/9/10 17:22:23 网站建设 项目流程

React Three Fiber 如何为单个 mesh 通过 attach 分配多个材质?

【免费下载链接】react-three-fiber🇨🇭 A React renderer for Three.js项目地址: https://gitcode.com/GitHub_Trending/re/react-three-fiber

在 React Three Fiber 中,一个mesh默认只能挂一个材质:所有继承自THREE.Material的子组件会被自动推断为attach="material",也就是说第二个<meshBasicMaterial>会直接覆盖第一个,而不是追加。要让同一个 mesh 使用多个材质(multi-material),需要利用attach的数组下标语法attach="material-N",把每个材质显式绑定到mesh.material数组的指定位置。本文基于仓库文档 docs/API/objects.mdx 和官方示例 example/src/demos/MultiMaterial.tsx,给出写法、动态增删材质的处理方式,以及在示例应用中核对结果的方法。

attach 与数组下标:多个材质为什么不能直接并列

attach用来把子对象绑定到父对象的某个属性上,子对象卸载时会自动从父对象上摘除。单个材质和几何体可以省略attach

<mesh> <meshBasicMaterial attach="material" /> <boxGeometry attach="geometry" /> </mesh>

文档说明所有继承THREE.Material的对象自动获得attach="material",所有继承THREE.BufferGeometry的对象自动获得attach="geometry"。但多个材质对应的是父对象上的数组属性,文档给出的数组 attach 语法是attach="a-0"表示绑定到foo.a[0],并且明确要求:"Arrays must have explicit order, for instance multi-materials"(数组必须有显式顺序,例如 multi-materials)。因此多材质必须逐个写死下标:

<mesh> <meshBasicMaterial attach="material-0" color="hotpink" /> <meshBasicMaterial attach="material-1" color="lightgreen" /> </mesh>

attach="material-0"最终把材质写到mesh.material[0]。如果只写attach="material"放多个子组件,后出现的会整体替换先出现的。

基础写法:静态多材质 mesh

官方 demoTestMultiMaterial(example/src/demos/MultiMaterial.tsx)给一个boxGeometrymesh 分配了 6 个材质,是仓库中最完整的参考:

function TestMultiMaterial(props: ThreeElements['mesh']) { const ref = useRef<THREE.Mesh>(null!) const [okay, setOkay] = useState(true) useEffect(() => { const interval = setInterval(() => setOkay((okay) => !okay), 1000) return () => clearInterval(interval) }, []) useEffect(() => { console.log(ref.current.material) }, [okay]) return ( <mesh ref={ref} {...props}> <boxGeometry args={[0.75, 0.75, 0.75]} /> <meshBasicMaterial attach="material-0" color="hotpink" toneMapped={false} /> <meshBasicMaterial attach="material-1" color="lightgreen" toneMapped={false} /> {okay ? ( <meshBasicMaterial attach="material-2" color="lightblue" toneMapped={false} /> ) : ( <meshNormalMaterial attach="material-2" /> )} <meshBasicMaterial attach="material-3" color="pink" toneMapped={false} /> <meshBasicMaterial attach="material-4" color="orange" toneMapped={false} /> <meshBasicMaterial attach="material-5" color="lavender" toneMapped={false} /> </mesh> ) }

注意material-2是条件渲染:okay每秒翻转一次,下标 2 的材质在meshBasicMaterialmeshNormalMaterial之间替换,但始终挂在同一个material-2位置上,其余下标不受影响。

动态数量的材质:用 key 配合索引渲染

当材质数量来自数据而不是写死时,docs/API/objects.mdx 给出的模式是用map配合key和模板字符串下标(colors是你自己的颜色数组):

<mesh> {colors.map((color, index) => <meshBasicMaterial key={index} attach={`material-${index}`} color={color} />)} </mesh>

key={index}保证 React 在列表增删时能正确复用或卸载对应下标的材质组件。

卸载某个下标的材质时会发生什么

同文件的TestMultiDelete演示了条件卸载:{okay && <meshBasicMaterial attach="material-2" ... />},即下标 2 的材质周期性地从 mesh 上摘除再挂回。

下标 attach 的挂载与卸载行为由 packages/fiber/tests/utils.test.ts 的用例 "should create array when using array-index syntax" 断言:对prop-0执行 attach 后,parent.object.prop变为[child](数组是自动创建的);detach 后数组长度保持为 1,被摘除的下标置为undefined。也就是说摘除一个材质不会收缩整个数组,其他下标的材质保持原位,这正是上面 demo 中"只替换/摘除 material-2"能稳定工作的前提。

另外 packages/fiber/tests/renderer.test.tsx 中 "can swap 4 array primitives via attach" 用例验证了下标 attach 的顺序语义:按userData-objects-${i}挂载 4 个对象后,数组顺序与渲染顺序一致;后续即使渲染顺序被反转或打乱,各下标仍绑定到正确的对象。

在示例应用中运行并核对

仓库根目录 package.json 提供examples脚本,即yarn workspace example dev(底层是 Vite)。示例应用依赖 React 19.2.0、three ^0.172.0(见 example/package.json)。

  1. 在仓库根目录安装依赖:
yarn
  1. 启动示例应用:
yarn examples
  1. 浏览器打开 Vite 输出的本地地址,访问路由/demo/MultiMaterial。example/src/App.tsx 中 wouter 的/demo/:name路由会把路径参数映射到 example/src/demos/index.tsx 中注册的同名 demo;页面左侧的圆点面板也可以直接点选MultiMaterial

核对方式由 demo 自身实现:TestMultiMaterialTestMultiDelete组件通过useRef<THREE.Mesh>拿到 mesh 实例,在okay状态翻转时执行console.log(ref.current.material)。打开浏览器控制台,每秒会打印一次mesh.material的当前值——多材质 mesh 上它是一个包含多个材质对象的数组,且下标 2 的内容在两次渲染之间交替变化;画面中对应的盒子 mesh 会呈现多种颜色。

如果不想跑浏览器,仓库根的yarn test(执行jest --coverage)会运行上述 attach 相关断言,可直接核对数组下标挂载与 detach 的行为。

边界与替代写法

  • 顺序必须显式:数组下标 attach 不支持自动排序,下标不连续(如只有material-0material-2)时会得到中间含空位的稀疏数组,空位行为以上述测试为准。
  • attach 函数形式:文档还允许把attach写成(parent, self) => ...并返回清理函数,用于显式parent.add(self)/parent.remove(self)的场景;multi-material 数组场景不需要它,这里仅作了解。
  • 卸载即摘除:任何attach挂上去的对象在 React 卸载时自动从父对象移除,无需手动清理;材质、几何体等对象卸载时 R3F 会调用object.dispose()释放资源,如果材质来自外部缓存且不想自动释放,可以在父容器上设置dispose={null}

写完后如果某个下标的材质不生效,先按文档核对两点:材质子组件是否确实写在<mesh>内部(attach只作用于父对象),以及是否误写成attach="material"(那样会整体替换数组而不是填入下标)。

【免费下载链接】react-three-fiber🇨🇭 A React renderer for Three.js项目地址: https://gitcode.com/GitHub_Trending/re/react-three-fiber

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询