ThreeJS 如何从 BufferGeometry 中获取被点击几何体的名称属性?

How To Get Clicked Geometry’s Name Attribute From BufferGeometry in ThreeJS?

我正在开发一个 VueJs 应用程序,并在场景中添加了 1000 个盒子模型。虽然我在单击盒子模型时设置了每个几何体的名称属性,但我无法获取盒子几何体的名称。我认为这是由应用合并缓冲区几何引起的。是否可以通过某种方式点击获取名称属性?

这是我的代码;

addCubeToScene() {
  this.oDracoLoader = new DRACOLoader();
  this.oDracoLoader.setDecoderPath("./draco/");
  this.oGltfLoader = new GLTFLoader();
  this.oGltfLoader.setDRACOLoader(this.oDracoLoader);
  this.oDracoLoader.preload();
  this.oGltfLoader.load(BoxModel, function(oObject) {
    oObject.scene.traverse(function(oObject) {
      if (oObject.isMesh) {
        var oObjectGeometry = oObject.geometry;
        for (var i = 0; i < 1000; i++) {
          var oGeometry = oObjectGeometry.clone();
          oGeometry.applyMatrix4(
            new THREE.Matrix4().makeTranslation(
              Math.random() * 3000,
              Math.random() * 5000,
              Math.random() * 1000
            )
          );
          oGeometry.name = "Box-" + i;
          oCubes.push(oGeometry);
        }
        var oGeometriesCubes = BufferGeometryUtils.mergeBufferGeometries(
          oCubes
        );
        oGeometriesCubes.computeBoundingSphere();
        const oTexture = new THREE.TextureLoader().load(BoxTexture);
        oTexture.magFilter = THREE.NearestFilter;
        var oMesh = new THREE.Mesh(
          oGeometriesCubes,
          new THREE.MeshLambertMaterial({
            map: oTexture,
            side: THREE.DoubleSide,
          })
          // new THREE.MeshNormalMaterial()
        );
        oMesh.scale.set(0.5, 0.5, 0.5);
        oThis.scene.add(oMesh);
        oThis.renderScene();
      }
    });
  });
}

onMouseClick(oEvent) {
  oEvent.preventDefault();
  oEvent.stopPropagation();
  oMouse.x =
    (oEvent.offsetX / this.renderer.domElement.clientWidth) * 2 - 1;
  oMouse.y =
    -(oEvent.offsetY / this.renderer.domElement.clientHeight) * 2 + 1;
  oRaycaster.setFromCamera(oMouse, this.camera);
  var oIntersects = oRaycaster.intersectObjects(this.scene.children, true);
  if (oIntersects[0] && oIntersects[0].object) {
    var oObject = oIntersects[0].object;
    console.log(oObject); // I would like to get clicked geometry's name from this object
  }
}

这是代表盒子模型视图的图像;

这是盒子几何数据的结果

当我合并所有几何图形时,我看不到任何名称属性

我假设您正在尝试合并几何图形以提高渲染性能。虽然您确实设法减少了 GPU 必须进行的绘制调用次数,但您也删除了不同 BufferGeometry 对象的所有唯一性。您的几何图形现在由一个大缓冲区表示(调试 oGeometriesCubes.attributes.position.array 以查看)。

为了提高性能,我们不仅可以为您节省 GPU 绘图调用,还可以节省内存,我们也可以让您的选框正常工作。让我们从头开始。看起来我们已经倒退了一步,但它会一起倒下......

您的几何定义

首先,您不需要唯一标识每个 BufferGeometryMesh 对象可以共享几何体!和 materials!这相当于节省了内存。

拥有唯一的 Mesh 对象也意味着您可以将 name 和翻译信息放在 Mesh 级别。在几何级别上翻译 1000 次是 昂贵的,即使您像您一样预先进行翻译。相信 GPU 可以从网格级别非常快速地完成这些转换。这实际上是 GPU 的工作。

您的网格定义

现在,我们仍在查看 1000 个网格,这意味着 1000 个绘制调用,对吗?这就是 InstancedMesh 发挥作用的地方。

Instancing——用非常简单的术语来说——是一种让 GPU 不仅可以重新使用几何缓冲区信息,还可以执行所有 1000 个图形的方法一次滑动即可生成网格,而不是一次滑动一次。

总结一下:

  • 仅使用一个 BufferGeometry 对象...
  • 还有一个material...
  • 我们将绘制 1000 个实例...
  • 在一次绘制调用中。

准备好了吗?让我们开始吧。

密码

let geometry = yourGeometryFromGLTF;
// load your texture here...
let material = new new THREE.MeshLambertMaterial({
  map: oTexture,
  side: THREE.DoubleSide,
})

let iMesh = new THREE.InstancedMesh( geometry, material, 1000 );

let translateMatrix = new THREE.Matrix4();
let scaleMatrix = new THREE.Matrix4().makeScale( 0.5, 0.5, 0.5 );
let finalMatrix = new THREE.Matrix4();

for( let i = 0; i < 1000; ++i ){ 
 ​
  ​translateMatrix.makeTranslation(
    ​Math.random() * 1500,
    ​Math.random() * 2500,
    ​Math.random() * 500
 ​ );

  finalMatrix.multiplyMatrices( translateMatrix, scaleMatrix );
 ​ 
  iMesh.setMatrixAt( i, finalMatrix );

}

imesh.instanceMatrix.needsUpdate = true; // IMPORTANT
scene.add( iMesh );

现在您的 Raycaster 应该 return 实例,但是需要多一步才能获得您选择的框的“ID”。

let intersects = raycaster.intersectObjects(scene);
if(intersects.length > 0){
  let boxName = `Box-${ intersects[0].instanceId }`;
}

instanceId 将是您的 InstancedMesh 中框实例的索引。您甚至可以使用它来获取有关该实例的更多信息,例如它的转换矩阵:

let iMatrix = new THREE.Matrix4();
iMesh.getMatrixAt( intersects.instanceId, iMatrix );