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

React Three Fiber使用instanced mesh优化类Minecraft超大可点击地图

Instancing 运行逻辑
  • 常规渲染流程中,每一个独立Mesh对应一次GPU Draw Call,CPU需要逐一向GPU传递模型的几何体、材质、位置、旋转、缩放、自定义属性等参数,物体数量过万后CPU会成为性能瓶颈,直接导致帧率暴跌
  • InstancedMesh是Three.js对GPU实例化渲染的封装:所有复用同一个几何体、同一个材质的重复物体,仅需要1次Draw Call即可完成全部渲染
  • 运行时CPU仅需要一次性向GPU传递所有实例的变换矩阵(位置/旋转/缩放)、自定义属性(颜色、特殊标记等),GPU侧并行完成所有实例的计算渲染,同性能下可承载的物体数量可提升几十至上百倍
  • React Three Fiber(R3F)生态中已经完整封装了实例化能力,支持直接通过<instancedMesh>组件实现,且原生支持实例级点击交互,可通过点击事件的instanceId属性定位到具体点击的是哪个实例,完全满足方块点击交互的需求
项目落地改造步骤
  1. 预转换布局数据:原代码中每次循环都用filter遍历layout数组查询坐标对应的配置,时间复杂度O(n²),先将layout转换为哈希表,查询复杂度降到O(1)
  2. 按类型分组创建InstancedMesh:普通地面方块、树木、岩石三类元素分别创建独立的InstancedMesh(因为三者的几何体、材质不同,不能合并到同一个实例化网格)
  3. 预计算所有实例的参数:提前生成所有方块的坐标、颜色、是否带树、是否带岩石的配置,统一写入对应的InstancedMesh的实例属性中
  4. 适配交互逻辑:给地面InstancedMesh绑定点击事件,通过event.instanceId匹配对应方块的配置,触发原有的selectBox逻辑
改造后代码示例
import React, { Suspense, useRef, useMemo, useEffect } from "react";
import { BoxGeometry, MeshLambertMaterial, Object3D, Color } from "three";

export default function Ground({
  layout,
  selectBox,
  selectedBox,
  position,
}) {
  const size = 20;
  const totalBoxCount = size * size;
  const groundMeshRef = useRef(null);
  // 预存所有方块的配置数据
  const boxConfigs = useMemo(() => {
    const configs = [];
    // 先转换layout为哈希表优化查询
    const layoutMap = new Map(layout.map(item => [`${item.x},${item.y}`, item.color]));
    let id = 0;
    let hasTree = Math.random() > 0.99;
    for (let i = -(size / 2); i < size / 2; i++) {
      for (let j = -(size / 2); j < size / 2; j++) {
        const color = layoutMap.get(`${i},${j}`) ?? 0x61892f;
        const coords = { x: j * 5, y: 0, z: i * 5 };
        const hasStone = Math.random() < 0.2;
        configs.push({ id, coords, color, hasTree, hasStone });
        id++;
        if (hasTree) hasTree = Math.random() > 0.5;
      }
      if (Math.random() > 0.99) hasTree = true;
    }
    return configs;
  }, [layout]);

  // 初始化实例矩阵和颜色
  useEffect(() => {
    if (!groundMeshRef.current) return;
    const mesh = groundMeshRef.current;
    const dummy = new Object3D();
    boxConfigs.forEach((config, index) => {
      dummy.position.set(config.coords.x, config.coords.y, config.coords.z);
      dummy.updateMatrix();
      mesh.setMatrixAt(index, dummy.matrix);
      mesh.setColorAt(index, new Color(config.color));
    });
    mesh.instanceMatrix.needsUpdate = true;
    mesh.instanceColor.needsUpdate = true;
  }, [boxConfigs]);

  // 点击交互处理
  const handleBoxClick = (e) => {
    e.stopPropagation();
    const clickedBox = boxConfigs[e.instanceId];
    selectBox(clickedBox);
  };

  return (
    <group position={position ?? [0, 0, 0]}>
      <Suspense fallback={null}>
        {/* 地面方块实例化网格 */}
        <instancedMesh
          ref={groundMeshRef}
          args={[new BoxGeometry(5, 1, 5), new MeshLambertMaterial({ vertexColors: true }), totalBoxCount]}
          onClick={handleBoxClick}
        />
        {/* 树木、岩石可参照上方逻辑分别创建对应的instancedMesh,仅渲染hasTree/hasStone为true的实例即可 */}
      </Suspense>
    </group>
  );
}
额外优化提示
  • 如果方块类型较多(比如草地、泥土、石头等不同材质),按材质类型拆分多个InstancedMesh即可,不要把不同材质的方块塞到同一个实例化网格里
  • 渲染距离较大时可以配合视锥剔除,动态更新InstancedMesh的实例数组,仅保留视野范围内的实例
  • 树木、岩石等装饰元素如果是独立模型,单独创建对应的InstancedMesh即可,每类装饰仅占用1次Draw Call,性能开销极低
  • 若需要实现选中高亮效果,可以单独维护选中实例的ID,在渲染循环中更新对应实例的颜色或缩放即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 03:06:04