GLTF 创建实例

Ale*_*súa 6 instance three.js gltf

我是 THREEJS 的新手。过去,我使用过 AFRAME、CESIUM、XEOGL 和 BABYLONJS。但最终,由于内存消耗和性能,我意识到制作 CAD 可视化工具的最佳产品是 THREEJS。

BABYLONJS 加载一个大的 GLTF 文件(400MB)需要超过 4 分钟,而 THREEJS 只需要 30 秒。BABYLONJS 占用的内存是 THREEJS 占用的 4 倍。

我知道仍然存在一些问题,无法从 THREEJS 中加载的 GLTF 文件创建实例 (GPU),但我只需要更改每个实例中的位置和旋转,无需设置任何动画。

我尝试过 GLTF1.0 和 GLTF2.0,问题是一样的。当我加载 GLTF 模型时,我得到了一个场景。在这个场景中,我试图从 children 数组中获取 buffergeometry。但是,当我尝试创建一个实例时,它不起作用。我的对象是静态的(根本没有动画)。

有没有办法创建 Object3D 的实例或从它的缓冲区几何?

在 BABYLONJS 中,从加载的 GLTF 文件创建实例非常简单。我真的需要使用实例来节省 RAM 并使用 GPU 而不是 CPU 资源。我的场景需要加载相同对象的多个副本来合成场景。

我在使用 GLFT 加载器时看到的一些问题:

    1. 您必须识别包含有效几何体的所有 object3D。在这个例子中是在第 335 行:

    var geo = data.scene.children[0].children[0].children[0].children[0].geometry

    1. 合并示例不起作用。它在原始示例中也不起作用。
    1. 似乎实例化根本不会提高性能:
  • 10000 个物体,多材质 --> 4FPS

  • 10000 个物体,单一材质--> 4FPS

  • 10000 个对象,实例化 --> 4FPS

  • 10000 个对象,合并 --> 不工作

    1. 选择实例化后,几何图形未正确呈现。

这里有我使用 Duck GLTF 示例的代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>three.js webgl - interactive instances (gpu)</title>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
    />
    <style>
      body {
        font-family: Monospace;
        background-color: #f0f0f0;
        margin: 0px;
        overflow: hidden;
      }
      .info {
        position: absolute;
        background-color: black;
        opacity: 0.8;
        color: white;
        text-align: center;
        top: 0px;
        width: 100%;
      }
      .info a {
        color: #00ffff;
      }
      #notSupported {
        width: 50%;
        margin: auto;
        border: 2px red solid;
        margin-top: 20px;
        padding: 10px;
      }
    </style>
  </head>
  <body>
    <div class="info">
      <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a>
      webgl - gpu picking of geometry instances
      <div id="notSupported" style="display: none">
        Sorry your graphics card + browser does not support hardware instancing
      </div>
      <br /><br />
      <div>
        This demo compares different methods of constructing and rendering many
        instances of a single geometry.
      </div>
      <br />
      <div>
        <div style="display: inline-block">
          <span>number of<br />geometry instances</span>
          <br />
          <select id="instanceCount">
            <option>100</option>
            <option>500</option>
            <option selected>1000</option>
            <option>2000</option>
            <option>3000</option>
            <option>5000</option>
            <option>10000</option>
            <option>20000</option>
            <option>30000</option>
            <option>50000</option>
            <option>100000</option>
          </select>
        </div>
        &nbsp;&nbsp;&nbsp;
        <div style="display: inline-block">
          <span>method of<br />construction/rendering</span>
          <br />
          <select id="method">
            <option>instanced</option>
            <option>merged</option>
            <option selected>singleMaterial</option>
            <option>multiMaterial</option>
          </select>
        </div>
        &nbsp;&nbsp;&nbsp;
        <div style="display: inline-block">
          <span>render continuously<br />(to get fps reading)</span>
          <br />
          <input id="animate" type="checkbox" />
        </div>
        &nbsp;&nbsp;&nbsp;
        <div style="display: inline-block">
          <span
            >use override material<br />(only effects singleMaterial
            method)</span
          >
          <br />
          <input id="override" type="checkbox" checked />
        </div>
        &nbsp;&nbsp;&nbsp;
        <div style="display: inline-block">
          <span>construct anew<br />(to get additional timings)</span>
          <br />
          <button id="construct" type="button">do it</button>
        </div>
      </div>
      <br />
      <div>
        <span>Materials: #<span id="materialCount"></span></span>
        &nbsp;&nbsp;&nbsp;
        <span>Objects: #<span id="objectCount"></span></span>
        &nbsp;&nbsp;&nbsp;
        <span>Drawcalls: #<span id="drawcalls"></span></span>
        &nbsp;&nbsp;&nbsp;
        <span>Construction time: <span id="initTime"></span>&nbsp;ms</span>
        &nbsp;&nbsp;&nbsp;
      </div>
    </div>
    <div id="container"></div>
    <script src="../build/three.js"></script>
    <script src="js/controls/TrackballControls.js"></script>
    <script src="js/libs/stats.min.js"></script>
    <script src="js/loaders/GLTF2Loader.js"></script>
    <script id="vertMerged" type="x-shader/x-vertex">
      #define SHADER_NAME vertMerged
      precision highp float;
      uniform mat4 modelViewMatrix;
      uniform mat4 projectionMatrix;
      attribute vec3 position;
      #ifdef PICKING
      attribute vec3 pickingColor;
      #else
      attribute vec3 color;
      varying vec3 vPosition;
      #endif
      varying vec3 vColor;
      void main()   {
      vec3 positionEye = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
      #ifdef PICKING
      vColor = pickingColor;
      #else
      vColor = color;
      vPosition = positionEye;
      #endif
      gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
      }
    </script>
    <script id="fragMerged" type="x-shader/x-fragment">
      #define SHADER_NAME fragMerged
      #extension GL_OES_standard_derivatives : enable
      precision highp float;
      varying vec3 vColor;
      #ifndef PICKING
      varying vec3 vPosition;
      #endif
      void main()   {
      #ifdef PICKING
      gl_FragColor = vec4( vColor, 1.0 );
      #else
      vec3 fdx = dFdx( vPosition );
      vec3 fdy = dFdy( vPosition );
      vec3 normal = normalize( cross( fdx, fdy ) );
      float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
      gl_FragColor = vec4( diffuse * vColor, 1.0 );
      #endif
      }
    </script>
    <script id="vertInstanced" type="x-shader/x-vertex">
      #define SHADER_NAME vertInstanced
      precision highp float;
      uniform mat4 modelViewMatrix;
      uniform mat4 projectionMatrix;
      attribute vec3 position;
      attribute vec3 mcol0;
      attribute vec3 mcol1;
      attribute vec3 mcol2;
      attribute vec3 mcol3;
      #ifdef PICKING
      attribute vec3 pickingColor;
      #else
      attribute vec3 color;
      varying vec3 vPosition;
      #endif
      varying vec3 vColor;
      void main()   {
      mat4 matrix = mat4(
      vec4( mcol0, 0 ),
      vec4( mcol1, 0 ),
      vec4( mcol2, 0 ),
      vec4( mcol3, 1 )
      );
      vec3 positionEye = ( modelViewMatrix * matrix * vec4( position, 1.0 ) ).xyz;
      #ifdef PICKING
      vColor = pickingColor;
      #else
      vColor = color;
      vPosition = positionEye;
      #endif
      gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
      }
    </script>
    <script id="fragInstanced" type="x-shader/x-fragment">
      #define SHADER_NAME fragInstanced
      #extension GL_OES_standard_derivatives : enable
      precision highp float;
      varying vec3 vColor;
      #ifndef PICKING
      varying vec3 vPosition;
      #endif
      void main()   {
      #ifdef PICKING
      gl_FragColor = vec4( vColor, 1.0 );
      #else
      vec3 fdx = dFdx( vPosition );
      vec3 fdy = dFdy( vPosition );
      vec3 normal = normalize( cross( fdx, fdy ) );
      float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
      gl_FragColor = vec4( diffuse * vColor, 1.0 );
      #endif
      }
    </script>
    <script id="vertMaterial" type="x-shader/x-vertex">
      #define SHADER_NAME vertMaterial
      precision highp float;
      uniform mat4 modelViewMatrix;
      uniform mat4 projectionMatrix;
      attribute vec3 position;
      #ifndef PICKING
      varying vec3 vPosition;
      #endif
      void main()   {
      vec3 positionEye = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
      #ifndef PICKING
      vPosition = positionEye;
      #endif
      gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
      }
    </script>
    <script id="fragMaterial" type="x-shader/x-fragment">
      #define SHADER_NAME fragMaterial
      #extension GL_OES_standard_derivatives : enable
      precision highp float;
      #ifdef PICKING
      uniform vec3 pickingColor;
      #else
      uniform vec3 color;
      varying vec3 vPosition;
      #endif
      void main()   {
      #ifdef PICKING
      gl_FragColor = vec4( pickingColor, 1.0 );
      #else
      vec3 fdx = dFdx( vPosition );
      vec3 fdy = dFdy( vPosition );
      vec3 normal = normalize( cross( fdx, fdy ) );
      float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
      gl_FragColor = vec4( diffuse * color, 1.0 );
      #endif
      }
    </script>
    <script>
      var container, stats;
      var camera, controls, scene, renderer;
      var pickingData, pickingRenderTarget, pickingScene;
      var useOverrideMaterial = true;
      var singleMaterial, singlePickingMaterial;
      var highlightBox;
      var materialList = [];
      var geometryList = [];
      var objectCount = 0;
      var geometrySize;
      var mouse = new THREE.Vector2();
      var scale = 1.03;
      var loader = new THREE.GLTF2Loader();
      var pixelBuffer = new Uint8Array(4);
      var instanceCount, method, doAnimate;
      gui();
      init();
      initMesh();
      if (doAnimate) animate();
      function gui() {
        var instanceCountElm = document.getElementById("instanceCount");
        instanceCount = parseInt(instanceCountElm.value);
        instanceCountElm.addEventListener("change", function () {
          instanceCount = parseInt(instanceCountElm.value);
          initMesh();
        });
        var methodElm = document.getElementById("method");
        method = methodElm.value;
        methodElm.addEventListener("change", function () {
          method = methodElm.value;
          initMesh();
        });
        var animateElm = document.getElementById("animate");
        doAnimate = animateElm.checked;
        animateElm.addEventListener("click", function () {
          doAnimate = animateElm.checked;
          animate();
        });
        var overrideElm = document.getElementById("override");
        useOverrideMaterial = overrideElm.checked;
        overrideElm.addEventListener("click", function () {
          useOverrideMaterial = overrideElm.checked;
          initMesh();
        });
        var constructElm = document.getElementById("construct");
        constructElm.addEventListener("click", function () {
          initMesh();
        });
      }
      function clean() {
        THREE.Cache.clear();
        materialList.forEach(function (m) {
          m.dispose();
        });
        geometryList.forEach(function (g) {
          g.dispose();
        });
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        scene.add(camera);
        scene.add(highlightBox);
        pickingScene = new THREE.Scene();
        pickingData = {};
        materialList = [];
        geometryList = [];
        objectCount = 0;
        singleMaterial = undefined;
        singlePickingMaterial = undefined;
      }
      var randomizeMatrix = (function () {
        var position = new THREE.Vector3();
        var rotation = new THREE.Euler();
        var quaternion = new THREE.Quaternion();
        var scale = new THREE.Vector3();
        return function (matrix) {
          position.x = Math.random() * 40 - 20;
          position.y = Math.random() * 40 - 20;
          position.z = Math.random() * 40 - 20;
          rotation.x = Math.random() * 2 * Math.PI;
          rotation.y = Math.random() * 2 * Math.PI;
          rotation.z = Math.random() * 2 * Math.PI;
          quaternion.setFromEuler(rotation, false);
          scale.x = scale.y = scale.z = 0.001;
          matrix.compose(position, quaternion, scale);
        };
      })();
      function initMesh() {
        clean();
        loader.load("models/gltf/Duck/glTF-Binary/Duck.glb", function (data) {
          console.log(data);
          var geo =
            data.scene.children[0].children[0].children[0].children[0].geometry;
          console.log("geo:");
          console.log(geo);
          geo.computeBoundingBox();
          geometrySize = geo.boundingBox.getSize();
          geometryList.push(geo);
          var start = window.performance.now();
          switch (method) {
            case "merged":
              makeMerged(geo);
              break;
            case "instanced":
              makeInstanced(geo);
              break;
            case "singleMaterial":
              makeSingleMaterial(geo);
              break;
            case "multiMaterial":
              makeMultiMaterial(geo);
              break;
          }
          render();
          var end = window.performance.now();
          document.getElementById("materialCount").innerText =
            materialList.length;
          document.getElementById("objectCount").innerText = objectCount;
          document.getElementById("drawcalls").innerText =
            renderer.info.render.calls;
          document.getElementById("initTime").innerText = (end - start).toFixed(
            2
          );
        });
      }
      function makeMultiMaterial(geo) {
        var vert = document.getElementById("vertMaterial").textContent;
        var frag = document.getElementById("fragMaterial").textContent;
        var material = new THREE.RawShaderMaterial({
          vertexShader: vert,
          fragmentShader: frag,
          uniforms: {
            color: {
              value: new THREE.Color(),
            },
          },
        });
        var pickingMaterial = new THREE.RawShaderMaterial({
          vertexShader: "#define PICKING\n" + vert,
          fragmentShader: "#define PICKING\n" + frag,
          uniforms: {
            pickingColor: {
              value: new THREE.Color(),
            },
          },
        });
        var matrix = new THREE.Matrix4();
        for (var i = 0; i < instanceCount; i++) {
          var object = new THREE.Mesh(geo, material);
          objectCount++;
          randomizeMatrix(matrix);
          object.applyMatrix(matrix);
          var pickingObject = object.clone();
          objectCount++;
          object.material = material.clone();
          object.material.uniforms.color.value.setHex(Math.random() * 0xffffff);
          materialList.push(object.material);
          pickingObject.material = pickingMaterial.clone();
          pickingObject.material.uniforms.pickingColor.value.setHex(i + 1);
          materialList.push(pickingObject.material);
          pickingData[i + 1] = object;
          scene.add(object);
          pickingScene.add(pickingObject);
        }
        material.dispose();
        pickingMaterial.dispose();
      }
      function makeSingleMaterial(geo) {
        var vert = document.getElementById("vertMaterial").textContent;
        var frag = document.getElementById("fragMaterial").textContent;
        var material = new THREE.RawShaderMaterial({
          vertexShader: vert,
          fragmentShader: frag,
          uniforms: {
            color: {
              value: new THREE.Color(),
            },
          },
        });
        materialList.push(material);
        var pickingMaterial = new THREE.RawShaderMaterial({
          vertexShader: "#define PICKING\n" + vert,
          fragmentShader: "#define PICKING\n" + frag,
          uniforms: {
            pickingColor: {
              value: new THREE.Color(),
            },
          },
        });
        materialList.push(pickingMaterial);
        if (useOverrideMaterial) {
          singleMaterial = material;
          singlePickingMaterial = pickingMaterial;
        }
        var matrix = new THREE.Matrix4();
        function onBeforeRender(
          renderer,
          scene,
          camera,
          geometry,
          material,
          group
        ) {
          var updateList = [];
          var u = material.uniforms;
          var d = this.userData;
          if (u.pickingColor) {
            u.pickingColor.value.setHex(d.pickingColor);
            updateList.push("pickingColor");
          }
          if (u.color) {
            u.color.value.setHex(d.color);
            updateList.push("color");
          }
          if (updateList.length) {
            var materialProperties = renderer.properties.get(material);
            if (materialProperties.program) {
              var gl = renderer.getContext();
              var p = materialProperties.program;
              gl.useProgram(p.program);
              var pu = p.getUniforms();
              updateList.forEach(function (name) {
                pu.setValue(gl, name, u[name].value);
              });
            }
          }
        }
        for (var i = 0; i < instanceCount; i++) {
          var object = new THREE.Mesh(geo, material);
          objectCount++;
          randomizeMatrix(matrix);
          object.applyMatrix(matrix);
          var pickingObject;
          if (!useOverrideMaterial) {
            pickingObject = object.clone();
            objectCount++;
          }
          object.material = material;
          object.userData["color"] = Math.random() * 0xffffff;
          if (useOverrideMaterial) {
            object.userData["pickingColor"] = i + 1;
            object.onBeforeRender = onBeforeRender;
          } else {
            pickingObject.material = pickingMaterial;
            pickingObject.userData["pickingColor"] = i + 1;
            pickingObject.onBeforeRender = onBeforeRender;
          }
          pickingData[i + 1] = object;
          scene.add(object);
          if (!useOverrideMaterial) pickingScene.add(pickingObject);
        }
      }
      function makeMerged(geo) {
        var vert = document.getElementById("vertMerged").textContent;
        var frag = document.getElementById("fragMerged").textContent;
        var material = new THREE.RawShaderMaterial({
          vertexShader: vert,
          fragmentShader: frag,
        });
        materialList.push(material);
        var pickingMaterial = new THREE.RawShaderMaterial({
          vertexShader: "#define PICKING\n" + vert,
          fragmentShader: "#define PICKING\n" + frag,
        });
        materialList.push(pickingMaterial);
        var bgeo = geo.clone();
        geometryList.push(bgeo);
        var mgeo = new THREE.BufferGeometry();
        geometryList.push(mgeo);
        var pos = bgeo.attributes.position;
        var posLen = bgeo.attributes.position.count * 3;
        var vertices = new THREE.BufferAttribute(
          new Float32Array(instanceCount * posLen),
          3
        );

        var matrix = new THREE.M

Don*_*rdy 7

似乎您的问题主要是,如何在three.js 中进行实例化?一旦你加载了一个模型,你用来创建它的格式并不重要。

既然如此,您可能只想查看three.js 实例化示例或使用其中一个帮助程序,例如three-instanced-mesh

从模型中获取几何体后,第二个链接显示了如何继续:

// Assumes your model only contains one mesh.
var geometry;
model.traverse(function (node) => {
  if (node.isMesh) {
    geometry = node.geometry;
  }
});

//material that the geometry will use 
var material = new THREE.MeshPhongMaterial();

//the instance group 
var cluster = new THREE.InstancedMesh( 
  geometry,
  material, 
  10000, //instance count 
  false, //is it dynamic 
  false  //does it have color 
  true,  //uniform scale
);

var _v3 = new THREE.Vector3();
var _q = new THREE.Quaternion();

for ( var i ; i < 10000 ; i ++ ) {

  cluster.setQuaternionAt( i , _q );
  cluster.setPositionAt( i , v3.set( Math.random() , Math.random(), Math.random() ) );
  cluster.setScaleAt( i , v3.set(1,1,1) );

}

scene.add( cluster );
Run Code Online (Sandbox Code Playgroud)