首页 > 解决方案 > 如何从 ThreeJS 中的 BufferGeometry 中获取单击的几何图形的名称属性?

问题描述

我正在开发一个 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
  }
}

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

在此处输入图像描述

这是盒子几何数据的结果

在此处输入图像描述

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

标签: three.jsbuffer-geometry

解决方案


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

为了提高性能,我们不仅可以为您节省 GPU 绘制调用,还可以节省内存,而且我们还可以让您的盒子选择工作。让我们从头开始。看起来我们已经倒退了一步,但随后一切都会一起倒下……

您的几何定义

首先,您不需要唯一标识每个BufferGeometry. Mesh对象可以共享几何!和材料!这相当于节省内存。

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

您的网格定义

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

实例化——用非常简单的术语来说——是一种让 GPU 不仅重新使用几何缓冲区信息的方式,而且还可以在一次滑动中执行所有 1000 个网格的绘制,而不是一次绘制一个。

把它们加起来:

  • 仅使用一个BufferGeometry对象...
  • 还有一种材料...
  • 我们将绘制 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应该返回实例,但需要多一步才能获得您选择的框的“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 );

推荐阅读