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

THREE.js中InstancedMesh错位问题排查求助

Three.js InstancedMesh 放置问题排查与解决

我正在编写Minecraft风格的地形生成代码,目标是在特定Y层级生成水和石块作为沙盒模型,但代码中的两个InstancedMesh没有被正确放置。尝试调整循环参数后毫无效果,作为Three.js和JavaScript新手,想知道哪里出了问题。

场景截图

原代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My first three.js app</title>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <script type="module">
  import * as THREE from 'https://unpkg.com/three/build/three.module.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
scene.background = new THREE.Color("lightblue");

const range = 4; // WIDTH AND LENGTH OF AREA


// MESSY ARRAYS THAT IM GOING TO FIX LATER
const textures = [
  new THREE.TextureLoader().load('StoneBlock.jpg'),
  new THREE.TextureLoader().load('water.jpg')
];

const materials = [
  new THREE.MeshBasicMaterial({ map: textures[0] }),
  new THREE.MeshBasicMaterial({ map: textures[1] })
];


const meshes = [
  new THREE.InstancedMesh(new THREE.BoxGeometry(1, 1, 1), materials[0], range * range),
  new THREE.InstancedMesh(new THREE.BoxGeometry(1, 1, 1), materials[1], range * range)
];

// ADDING MESHES, WOULD BE GREAT IF SOMEONE COULD TELL ME IF THIS IS GOING TO WEAKEN PREFORMANCE WHEN IM ADDING MORE, SAY 100 x 100 
for (const mesh of meshes) {
  scene.add(mesh);
}

const waterDummy = new THREE.Mesh();
const stoneDummy = new THREE.Mesh();

for (let j = 0; j < range; j++) { // FOR LOOP CREATING A SQUARE OF BOXES
  for (let i = 0; i < range; i++) {
    const random = Math.floor(Math.random() * 2);
    
    if (i < (range / 2)) {
      console.log("water placed")
      waterDummy.position.x = i + 0.5;
      waterDummy.position.y = 1;
      waterDummy.position.z = j + 0.5;
      meshes[1].setMatrixAt(i + j * range, waterDummy.matrix); 
    } else {
      console.log("stone placed")
      stoneDummy.position.x = i + 0.5;
      stoneDummy.position.y = 2;
      stoneDummy.position.z = j + 0.5;
      meshes[0].setMatrixAt(i + j * range, stoneDummy.matrix); 
    }

    waterDummy.updateMatrix();
    stoneDummy.updateMatrix();
  }
}
// UPDATING BUFFERS
meshes[0].geometry.setAttribute('instanceTextureIndex', new THREE.InstancedBufferAttribute(new Float32Array(range * range).fill(0), 1));
meshes[0].geometry.getAttribute('instanceTextureIndex').needsUpdate = true;

meshes[1].geometry.setAttribute('instanceTextureIndex', new THREE.InstancedBufferAttribute(new Float32Array(range * range).fill(1), 1));
meshes[1].geometry.getAttribute('instanceTextureIndex').needsUpdate = true;

camera.position.z = 9;
camera.position.x = 50;
camera.position.y = 5;

function animate() {
  requestAnimationFrame(animate);

  renderer.render(scene, camera);
}

animate();

// JUST MOVEMENT AROUND THE SCENE

document.addEventListener('keydown', (event) => {
  if (event.code == "KeyD") {
    camera.position.x += 1;
  }
  if (event.code == "KeyA") {
    camera.position.x -= 1;
  }
  if (event.code == "KeyS") {
    camera.position.z += 1;
  }
  if (event.code == "KeyW") {
    camera.position.z -= 1;
  }
});
    </script>
  </body>
</html>

问题根源

  1. 矩阵更新顺序错误:设置实例矩阵时,先调用了setMatrixAt再更新dummy的矩阵,导致传入的是旧矩阵数据。
  2. 实例数量不匹配:给每个InstancedMesh分配了range*range个实例,但实际只生成了一半数量的有效实例,剩余实例会默认在原点渲染,造成视觉混乱。
  3. 冗余属性干扰:添加的instanceTextureIndex属性完全多余,当前使用的MeshBasicMaterial并不支持实例化纹理索引逻辑。
  4. 相机位置不合理:初始相机x轴位置设为50,远超地形生成范围(x轴0.5~4.5),可能导致一开始看不到物体。

修复后的代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My first three.js app</title>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <script type="module">
  import * as THREE from 'https://unpkg.com/three/build/three.module.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
scene.background = new THREE.Color("lightblue");

const range = 4; // WIDTH AND LENGTH OF AREA

const textures = [
  new THREE.TextureLoader().load('StoneBlock.jpg'),
  new THREE.TextureLoader().load('water.jpg')
];

const materials = [
  new THREE.MeshBasicMaterial({ map: textures[0] }),
  new THREE.MeshBasicMaterial({ map: textures[1] })
];

// 按实际需要分配实例数量
const waterCount = (range / 2) * range;
const stoneCount = (range / 2) * range;

const meshes = [
  new THREE.InstancedMesh(new THREE.BoxGeometry(1, 1, 1), materials[0], stoneCount),
  new THREE.InstancedMesh(new THREE.BoxGeometry(1, 1, 1), materials[1], waterCount)
];

for (const mesh of meshes) {
  scene.add(mesh);
}

const waterDummy = new THREE.Mesh();
const stoneDummy = new THREE.Mesh();

let waterIndex = 0;
let stoneIndex = 0;

for (let j = 0; j < range; j++) {
  for (let i = 0; i < range; i++) {
    if (i < (range / 2)) {
      console.log("water placed")
      waterDummy.position.set(i + 0.5, 1, j + 0.5);
      waterDummy.updateMatrix(); // 先更新矩阵
      meshes[1].setMatrixAt(waterIndex, waterDummy.matrix); // 再绑定到实例
      waterIndex++;
    } else {
      console.log("stone placed")
      stoneDummy.position.set(i + 0.5, 2, j + 0.5);
      stoneDummy.updateMatrix();
      meshes[0].setMatrixAt(stoneIndex, stoneDummy.matrix);
      stoneIndex++;
    }
  }
}

// 通知实例网格矩阵已更新
meshes[0].instanceMatrix.needsUpdate = true;
meshes[1].instanceMatrix.needsUpdate = true;

// 调整相机位置到可见范围
camera.position.z = 9;
camera.position.x = 2;
camera.position.y = 5;
camera.lookAt(2, 1.5, 2); // 看向地形中心

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

document.addEventListener('keydown', (event) => {
  if (event.code == "KeyD") camera.position.x += 1;
  if (event.code == "KeyA") camera.position.x -= 1;
  if (event.code == "KeyS") camera.position.z += 1;
  if (event.code == "KeyW") camera.position.z -= 1;
});
    </script>
  </body>
</html>

额外说明

  • 性能问题:InstancedMesh是Three.js中批量渲染大量重复物体的最优方案,即使100x100的规模也能保持良好性能,远优于单独创建成千上万个Mesh。
  • 纹理加载:确保StoneBlock.jpg和water.jpg的文件路径正确,否则材质会显示黑色。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 23:32:02