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

如何用单个Mesh创建2x1x2网格BoxGeometry并正确定位?

合并多个BoxGeometry为单个Mesh时位置重叠的解决办法

问题现象

尝试创建2x1x2的盒子网格,通过单个Mesh渲染所有盒子,但所有盒子重叠显示;修改位置数据时仅导致盒子变大,无法正确平移到目标坐标。

错误原因

  1. 顶点未平移:默认BoxGeometry的中心在原点,直接合并所有盒子的顶点数据时,所有盒子都处于同一位置,导致重叠。
  2. 位置修改方式错误:之前的代码将每个顶点的每个分量都加固定值,相当于把盒子整体放大并偏移,而非平移整个盒子。
  3. 索引未偏移:每个BoxGeometry的索引都是从0开始,合并时直接拼接会导致索引混乱,无法正确渲染多个独立的盒子。

解决方案

  1. 为每个盒子计算目标位置(根据网格坐标生成Vector3)。
  2. 对每个盒子的顶点进行平移变换:每个顶点的x/y/z分量分别加上目标位置的对应分量,实现盒子整体移动。
  3. 合并索引时,将当前盒子的索引加上之前所有顶点的总数,避免索引冲突。

修改后的代码示例

1. 修正Block类的getGeometryData方法

让方法接收目标位置参数,对顶点进行平移:

public getGeometryData(position: THREE.Vector3): { positions: number[], indices: number[] } {
  const boxGeo = new THREE.BoxGeometry();
  const positionArray = boxGeo.getAttribute('position').array as number[];
  const translatedPositions: number[] = [];

  // 遍历每个顶点(每3个元素为一个顶点的x/y/z)
  for (let i = 0; i < positionArray.length; i += 3) {
    translatedPositions.push(
      positionArray[i] + position.x,
      positionArray[i + 1] + position.y,
      positionArray[i + 2] + position.z
    );
  }

  return {
    positions: translatedPositions,
    indices: boxGeo.index?.array as number[]
  };
}

2. 修正数据收集循环

处理索引偏移,确保每个盒子的索引指向正确的顶点:

const positions: Array<number> = [];
const indices: Array<number> = [];
let vertexOffset = 0; // 记录已添加的顶点总数,用于偏移索引

this.blocks.forEach((row, rowIndex) => {
  row.forEach((block, blockIndex) => {
    // 根据行列计算盒子位置,示例:x=blockIndex,y=0,z=rowIndex
    const blockPosition = new THREE.Vector3(blockIndex, 0, rowIndex);
    const data = block.getGeometryData(blockPosition);
    
    positions.push(...data.positions);
    
    // 偏移当前盒子的索引,避免和前面的盒子索引冲突
    const offsetIndices = data.indices.map(index => index + vertexOffset);
    indices.push(...offsetIndices);
    
    // 更新顶点偏移量:每个BoxGeometry有24个顶点(positions.length / 3)
    vertexOffset += data.positions.length / 3;
  });
});

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setIndex(indices);
geometry.computeBoundingBox(); // 可选,优化视锥体剔除
geometry.computeVertexNormals(); // 若使用需要法线的材质(如MeshStandardMaterial),需计算法线

this.mesh = new THREE.Mesh(geometry, this.material);
this.object.add(this.mesh);

3. 修正后的可复现代码

const scene = new THREE.Scene();
scene.background = new THREE.Color('lightblue');

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(() => {
  animate();
});

let camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);

document.body.appendChild(renderer.domElement);

const animate = () => {
  renderer.render(scene, camera);
};

const positions = [];
const indices = [];
let vertexOffset = 0;

// 创建3x1x3的盒子网格
for (let z = 0; z < 3; z++) { // z轴方向
  for (let x = 0; x < 3; x++) { // x轴方向
    const boxGeo = new THREE.BoxGeometry();
    const positionArray = boxGeo.getAttribute('position').array;
    const translatedPositions = [];
    
    // 计算当前盒子的位置,每个盒子间距1(适配默认BoxGeometry边长1)
    const targetPos = new THREE.Vector3(x, 0, z);
    
    for (let i = 0; i < positionArray.length; i += 3) {
      translatedPositions.push(
        positionArray[i] + targetPos.x,
        positionArray[i + 1] + targetPos.y,
        positionArray[i + 2] + targetPos.z
      );
    }
    
    positions.push(...translatedPositions);
    
    // 偏移索引
    const offsetIndices = boxGeo.index.array.map(idx => idx + vertexOffset);
    indices.push(...offsetIndices);
    
    vertexOffset += translatedPositions.length / 3;
  }
}

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setIndex(indices);
geometry.computeVertexNormals(); // 计算法线,让光照生效

const material = new THREE.MeshStandardMaterial({ color: 'green' });
material.transparent = true;
material.opacity = 0.8;

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 添加光源提升显示效果
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);

const sphereGeo = new THREE.SphereGeometry(.2);
const sphereMesh = new THREE.Mesh(sphereGeo);
scene.add(sphereMesh);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 05:02:12