Three.js合并多个材质时只应用一个材质

4
我已经搜索过,发现了几个在stackoverflow上的例子,但是这些答案都没有解决我的问题。
以下是我尝试过的方法:
首先,我创建了一个几何桶用于分组,并且创建了一个数组来存储我的材质。
var totalGeom = new THREE.Geometry();
var materials = [];

然后我使用for循环遍历我的数据(strData)并调用addMapMarker3d函数。

  for(var i=0; i<strData.length;i++){
     addMapMarker3d([strData[i].lat,strData[i].lon], strData[i].time, measureColors[i]);
  }

addMapMarker3d函数定义如下:
addMapMarker3d = function(latLng, height, color){
  var max = 600;
  var dist = 25;
  var opacity = (height/max);

  var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));

  //create the material and add it to the materials array
  var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
  this.materials.push(material);

  //create a mesh from the geometry and material.
  var cube = new THREE.Mesh( geometry, material);

  //leaf is a simple lat/lng to pixel position converter
  var actualMarkerPos = leaf.getPoint(latLng);
  //apply the position on a 5000x5000 cartesian plane
  var extent = 5000;
  cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
  cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
  cube.position.setY(height/2);

  //update the matrix and add the cube to the totalGeom bucket
  cube.updateMatrix();
  totalGeom.merge( cube.geometry, cube.matrix);
}

在for循环运行并且所有的立方体被创建之后:
  var mats = new THREE.MeshFaceMaterial(materials)
  var total = new THREE.Mesh(totalGeom, mats);

  world.scene.add(total);

问题

当使用几何合并函数时,我的视口运行速度大大提高,但所有的立方体都具有完全相同的颜色和不透明度。似乎合并正在使用我提供的10k个材质中的单个材质。 有没有办法确保几何体使用数组中提供的材质?我做错了什么吗?

如果我在addMapMarker3d中尝试这样做:

totalGeom.merge( cube.geometry, cube.matrix, materials.length-1);

我收到了"Uncaught TypeError: Cannot read property 'transparent' of undefined"的错误提示,但是没有渲染出任何东西。我不太理解这个问题,因为根据示例,每个几何体都应该与材质数组中的一个材质相对应。
three.js版本为r.70
3个回答

4
以下技术只使用一种材料,但可以保留每个合并对象的独特颜色。我不知道是否可以保留每个合并对象的透明度。
请为每个网格设置其几何体面的颜色: http://jsfiddle.net/looshi/nsknn53p/61/
function makeCube(size, color) {
    var geom = new THREE.BoxGeometry(size, size, size);

    for (var i = 0; i < geom.faces.length; i++) {
        face = geom.faces[i];
        face.color.setHex(color);
    }
    var cube = new THREE.Mesh(geom);
    return cube;
}

接下来,在你要进行网格化的父级几何体中,设置它的材质vertexColors属性。

var parentGeometry = new THREE.Geometry();
var parentMatrial = new THREE.MeshLambertMaterial({
    color: 0xffffff,
    shading: THREE.SmoothShading,
    vertexColors: THREE.VertexColors
});


   // in a loop you could create many objects and merge them
for (var i = 0; i < 1000; i++) {
      cube = makeCube(size, color);
      cube.position.set(x, y, z);
      cube.rotation.set(rotation,rotation,rotation);
      cube.updateMatrix()
      parentGeometry.merge(cube.geometry, cube.matrix);
}
// after you're done creating objects and merging them, add the parent to the scene 
parentMesh = new THREE.Mesh(parentGeometry, parentMatrial);
scene.add(parentMesh);

4

我的原始问题没有得到回答:有没有办法确保几何体使用数组中提供的材质?

答案是肯定的。在合并期间,可以将多个材料应用于单个网格。将材料推送到材料数组后,合并将利用几何体面材质索引。当一个数组被提供给 new THREE.MeshFaceMaterial([materialsArray]) 以创建总网格时,合并将应用多个材料。这解决了语法之谜。只是因为您提供一组材料,并不意味着合并会在迭代方式下使用每种材料,因为自 r71 起,对象已经被合并。面必须告知合并使用材料数组中的哪种材料。

我正在使用这个非渲染场景,并导出最终的obj文件。如果您需要渲染性能,请参阅其他答案以获取一些选项。

对几何体上的面数组进行简单的 for 循环即可告知合并应用哪种材质:

  addMapMarker3d = function(latLng, height, color){
  var max = 600;
  var dist = 25;
  var opacity = (height/max);

  var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));

  //create the material and add it to the materials array
  var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
  this.materials.push(material);

  //set the material index of each face so a merge knows which material to apply
  for ( var i = 0; i < geometry.faces.length; i ++ ) {
    geometry.faces[i].materialIndex = this.materials.length-1;
  }

  //create a mesh from the geometry and material.
  var cube = new THREE.Mesh( geometry, material);

  //leaf is a simple lat/lng to pixel position converter
  var actualMarkerPos = leaf.getPoint(latLng);
  //apply the position on a 5000x5000 cartesian plane
  var extent = 5000;
  cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
  cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
  cube.position.setY(height/2);

  //update the matrix and add the cube to the totalGeom bucket
  cube.updateMatrix();
  totalGeom.merge( cube.geometry, cube.matrix);
}

性能如何?我尝试了类似的东西,但是使用这种技术大约1000个对象的FPS非常慢,但我会再试一次。在我的情况下,保留像您所做的那样的单独材料将是更可取的。 - looshi
1
如果只有几个几何体,那就没问题。例如:定义一个放在闪亮盆中的植物,使用一个哑光透明的PNG图片来代表植物和一个闪亮的盆,然后随意克隆合并后的网格。我的示例使用了360k个立方体。但是只渲染了一个帧用于检查和导出网格,因此只有一个网格。我不需要相机变换。在立方体的每个面上添加一个材质意味着矩阵变换要进行6次计算。但是你可以想象一下,100k个立方体将会产生600k个材质计算。网格合并有其应用场景,但要明智地使用。正如你和@gaitat所提到的,运行时性能有多种选择。 - Radio
@looshi,忘记@你了。请看我的回复。 - Radio

2

你看到性能有所提升,唯一的原因是在合并之后,只剩下了一个材质。如果你想让场景有多个材质,就不应该进行合并。

添加到同一组:

var group = new THREE.Object3D();
group.add (object1);
group.add (object2);
group.add (object3);

好消息是,我可以不用提高FPS就能生存。我想把这个组当作一个单一的网格对象来处理。材料没有被应用的原因是什么? - Radio
1
就像我之前所说的,当你合并时,你只会得到一个材质。但是你可以将所有这些对象都添加到同一组中,以便在变换方面将它们视为一个整体。 - gaitat
啊,我是根据这个答案来的,显然,我发现我不理解:https://dev59.com/-4Xca4cB1Zd3GeqPLKKn - Radio
我明白你为我提供了更好的方式来将网格视为单个组。然而,当我们使用合并时,如果只使用一个材质,为什么要将材质作为数组提供呢?如果只使用一个材质,为什么合并需要偏移索引参数呢?谢谢! - Radio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接