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>
问题根源
- 矩阵更新顺序错误:设置实例矩阵时,先调用了
setMatrixAt再更新dummy的矩阵,导致传入的是旧矩阵数据。 - 实例数量不匹配:给每个
InstancedMesh分配了range*range个实例,但实际只生成了一半数量的有效实例,剩余实例会默认在原点渲染,造成视觉混乱。 - 冗余属性干扰:添加的
instanceTextureIndex属性完全多余,当前使用的MeshBasicMaterial并不支持实例化纹理索引逻辑。 - 相机位置不合理:初始相机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
相关产品推荐
相关产品推荐

