微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在THREE.js中的大型BufferGeometry上优化变化的材质

如何解决如何在THREE.js中的大型BufferGeometry上优化变化的材质

要求

通过webgl渲染非常大的几何图形(> 100万个三角形),并通过用户交互更改某些三角形的颜色。应保持60fps。无法简化几何图形,应按原样使用所有三角形进行渲染。

问题

渲染周期花费太多时间,有时长达100ms。

我尝试过的事情

我尝试通过THREE.js缓冲的几何体渲染场景,将共享相同颜色的三角形分组(只要它们的索引连续),然后通过materialIndex组中的材料进行重用。

//Declare three.js variables
let camera,scene,renderer,mesh

//assign three.js objects to each variable
function init() {

  //camera
  camera = new THREE.PerspectiveCamera(50,window.innerWidth / window.innerHeight);
  camera.position.z = 2000;
  //scene
  scene = new THREE.Scene();
  //renderer
  renderer = new THREE.Webglrenderer({
    antialias: true
  });
  //set the size of the renderer
  renderer.setSize(window.innerWidth,window.innerHeight);

  //add the renderer to the html document body
  document.querySelector('.webgl').appendChild(renderer.domElement);
}


function addMesh() {
  const bufferGeometries = []
  for (var i = 0; i < 50000; i++) {
    var geo = new THREE.BoxGeometry(15,15,15)
    geo.applyMatrix4(new THREE.Matrix4().makeTranslation(Math.random() * 1500 - 500,Math.random() * 1500 - 500,0));
    geo.rotateX(Math.random() * 1)
    geo.rotateY(Math.random() * 1)
    bufferGeometries.push(new THREE.BufferGeometry().fromGeometry(geo))
  }
  const mergedBufferGeometries = mergeBufferGeometries(bufferGeometries,true)
  mesh = new THREE.Mesh(mergedBufferGeometries,new THREE.MeshnormalMaterial());
  
  mesh.material = [0,1,2,3,4,5,6,7,8,9].map((value) => new THREE.MeshphongMaterial({
    emissive: new THREE.Color(`hsl(${value * i},100%,50%)`),side: THREE.DoubleSide,polygonOffset: true,polygonOffsetFactor: 1,polygonOffsetUnits: 1,transparent: false,depthWrite: false
  }))
  mesh.geometry.groups = mesh.geometry.groups.map(group => ({
    ...group,materialIndex: 0
  })) // assign the first material too all the groups
  scene.add(mesh);
}

window.animate = function() {
  mesh.geometry.groups = mesh.geometry.groups.map((group,i) =>
    i < 100 ? // assign random materials to the first 1,00 items
    ({
      ...group,materialIndex: Math.floor(Math.random() * 10)
    }) :
    group
  )


  performance.mark('a');
  render()
  performance.measure('duration','a')
  const entries = performance.getEntriesByType("measure")
  document.getElementById('time').innerText = Math.round(entries[0].duration)
  performance.clearMeasures()
  performance.clearMarks()
}

function render() {
  //render the scene
  renderer.render(scene,camera);
}

init();
addMesh();
render();
window.animate();// assign random colors one first time

// below is copied from https://github.com/mrdoob/three.js/blob/dev/examples/js/utils/BufferGeometryUtils.js
function mergeBufferAttributes(attributes) {

  var TypedArray;
  var itemSize;
  var normalized;
  var arrayLength = 0;

  for (var i = 0; i < attributes.length; ++i) {

    var attribute = attributes[i];

    if (attribute.isInterleavedBufferAttribute) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferAttributes() Failed. InterleavedBufferAttributes are not supported.');
      return null;

    }

    if (TypedArray === undefined) TypedArray = attribute.array.constructor;
    if (TypedArray !== attribute.array.constructor) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferAttributes() Failed. BufferAttribute.array must be of consistent array types across matching attributes.');
      return null;

    }

    if (itemSize === undefined) itemSize = attribute.itemSize;
    if (itemSize !== attribute.itemSize) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferAttributes() Failed. BufferAttribute.itemSize must be consistent across matching attributes.');
      return null;

    }

    if (normalized === undefined) normalized = attribute.normalized;
    if (normalized !== attribute.normalized) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferAttributes() Failed. BufferAttribute.normalized must be consistent across matching attributes.');
      return null;

    }

    arrayLength += attribute.array.length;

  }

  var array = new TypedArray(arrayLength);
  var offset = 0;

  for (var i = 0; i < attributes.length; ++i) {

    array.set(attributes[i].array,offset);

    offset += attributes[i].array.length;

  }

  return new THREE.BufferAttribute(array,itemSize,normalized);

}


function mergeBufferGeometries(geometries,useGroups) {

  var isIndexed = geometries[0].index !== null;

  var attributesUsed = new Set(Object.keys(geometries[0].attributes));
  var morphAttributesUsed = new Set(Object.keys(geometries[0].morphAttributes));

  var attributes = {};
  var morphAttributes = {};

  var morphTargetsRelative = geometries[0].morphTargetsRelative;

  var mergedGeometry = new THREE.BufferGeometry();

  var offset = 0;

  for (var i = 0; i < geometries.length; ++i) {

    var geometry = geometries[i];
    var attributesCount = 0;

    // ensure that all geometries are indexed,or none

    if (isIndexed !== (geometry.index !== null)) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries,or in none of them.');
      return null;

    }

    // gather attributes,exit early if they're different

    for (var name in geometry.attributes) {

      if (!attributesUsed.has(name)) {

        console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries,or in none of them.');
        return null;

      }

      if (attributes[name] === undefined) attributes[name] = [];

      attributes[name].push(geometry.attributes[name]);

      attributesCount++;

    }

    // ensure geometries have the same number of attributes

    if (attributesCount !== attributesUsed.size) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.');
      return null;

    }

    // gather morph attributes,exit early if they're different

    if (morphTargetsRelative !== geometry.morphTargetsRelative) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.');
      return null;

    }

    for (var name in geometry.morphAttributes) {

      if (!morphAttributesUsed.has(name)) {

        console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '.  .morphAttributes must be consistent throughout all geometries.');
        return null;

      }

      if (morphAttributes[name] === undefined) morphAttributes[name] = [];

      morphAttributes[name].push(geometry.morphAttributes[name]);

    }

    // gather .userData

    mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];
    mergedGeometry.userData.mergedUserData.push(geometry.userData);

    if (useGroups) {

      var count;

      if (isIndexed) {

        count = geometry.index.count;

      } else if (geometry.attributes.position !== undefined) {

        count = geometry.attributes.position.count;

      } else {

        console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute');
        return null;

      }

      mergedGeometry.addGroup(offset,count,i);

      offset += count;

    }

  }

  // merge indices

  if (isIndexed) {

    var indexOffset = 0;
    var mergedindex = [];

    for (var i = 0; i < geometries.length; ++i) {

      var index = geometries[i].index;

      for (var j = 0; j < index.count; ++j) {

        mergedindex.push(index.getX(j) + indexOffset);

      }

      indexOffset += geometries[i].attributes.position.count;

    }

    mergedGeometry.setIndex(mergedindex);

  }

  // merge attributes

  for (var name in attributes) {

    var mergedAttribute = mergeBufferAttributes(attributes[name]);

    if (!mergedAttribute) {

      console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed while trying to merge the ' + name + ' attribute.');
      return null;

    }

    mergedGeometry.setAttribute(name,mergedAttribute);

  }

  // merge morph attributes

  for (var name in morphAttributes) {

    var numMorphTargets = morphAttributes[name][0].length;

    if (numMorphTargets === 0) break;

    mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
    mergedGeometry.morphAttributes[name] = [];

    for (var i = 0; i < numMorphTargets; ++i) {

      var morphAttributesToMerge = [];

      for (var j = 0; j < morphAttributes[name].length; ++j) {

        morphAttributesToMerge.push(morphAttributes[name][j][i]);

      }

      var mergedMorphAttribute = mergeBufferAttributes(morphAttributesToMerge);

      if (!mergedMorphAttribute) {

        console.error('THREE.BufferGeometryUtils: .mergeBufferGeometries() Failed while trying to merge the ' + name + ' morphAttribute.');
        return null;

      }

      mergedGeometry.morphAttributes[name].push(mergedMorphAttribute);

    }

  }

  return mergedGeometry;

}
html,body {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
}

.webgl {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r119/three.min.js"></script>
<div class="webgl"></div>
<button onClick='window.animate()'>refresh colors</button>
<div style='color: white'>requied time <span id='time'>ms</span></div>

理想的解决方

仅渲染 所需的颜色变化的三角形/像素。我尝试阅读THREE.js文档以进行部分渲染,但无济于事。我只发现setDrawRange仅呈现某些索引,但实际上“放弃”了我不想要的其余索引。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。