如何用单个Mesh创建2x1x2网格BoxGeometry并正确定位?
合并多个BoxGeometry为单个Mesh时位置重叠的解决办法
问题现象
尝试创建2x1x2的盒子网格,通过单个Mesh渲染所有盒子,但所有盒子重叠显示;修改位置数据时仅导致盒子变大,无法正确平移到目标坐标。
错误原因
- 顶点未平移:默认
BoxGeometry的中心在原点,直接合并所有盒子的顶点数据时,所有盒子都处于同一位置,导致重叠。 - 位置修改方式错误:之前的代码将每个顶点的每个分量都加固定值,相当于把盒子整体放大并偏移,而非平移整个盒子。
- 索引未偏移:每个
BoxGeometry的索引都是从0开始,合并时直接拼接会导致索引混乱,无法正确渲染多个独立的盒子。
解决方案
- 为每个盒子计算目标位置(根据网格坐标生成
Vector3)。 - 对每个盒子的顶点进行平移变换:每个顶点的x/y/z分量分别加上目标位置的对应分量,实现盒子整体移动。
- 合并索引时,将当前盒子的索引加上之前所有顶点的总数,避免索引冲突。
修改后的代码示例
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
相关产品推荐
相关产品推荐

