【问题标题】:How to use Three.js InstancedBufferGeometry & InstancedBufferAttribute?如何使用 Three.js InstancedBufferGeometry & InstancedBufferAttribute?
【发布时间】:2017-06-12 08:40:14
【问题描述】:

我相信这里描述的“实例化”提供了一种方法,可以为一个所有顶点/索引提供一个属性,例如,200 个顶点模型:

http://blog.tojicode.com/2013/07/webgl-instancing-with.html

换句话说,这提供了一种只有一个平移或方向属性数组的方法,该数组将应用于模型的所有 200 个顶点。因此,“实例化”10K 这些模型的场景只需要 10K 属性,而不是 2000K。

显然 Three 的 InstancedBufferGeometry 和 InstancedBufferAttribute 对象提供了这一点,但除了对象的稀疏描述之外,我还没有找到文档。我相信他们使用 ShaderMaterial,这很好,尽管除了在“vanilla” Three.js 中使用 GLSL 之外,还有其他方法。

有人能解释一下它们是如何工作的以及如何在 Three.js 中使用它们吗?

【问题讨论】:

  • 我读了这篇文章让我很困惑。什么是替代方案,即。没有将属性“应用于模型的所有 200 个顶点”?我认为顶点是属性。 “Instancing a scne of 10k ...”仍然只包含一个具有 200 个顶点的属性。渲染器将​​引用它 10k 次,不确定您的 2000k 数字来自哪里。
  • 废话,即使参加这样的论坛,3d 也没有回报。查看了 34 次,没有 cmets,没有接受 :(
  • 好吧,别生气。三 sez 使用 SO。问题是一个例子..一个不如例子中的先进。没有什么花哨。在问题的其他地方,我觉得 ShaderMaterial 需要一个简单的三角形示例。在那里挑战建立我自己的,我做到了:backspaces.net/temp/shader0.html。现在我希望在演示中实现 20 个左右的正方形。

标签: performance three.js webgl


【解决方案1】:

我自己在寻找这个答案时偶然发现了你的问题。这里只是两个使用实例化的例子(直接来自threejs.org/examples):

简要说明:

THREE.InstancedBufferGeometryTHREE.BufferGeometry 之间的主要区别在于前者可以使用特殊属性 (THREE.InstancedBufferAttributes),这些属性将用于每个实例

假设您正在创建一个框,并且您希望为其创建多个实例。顶点、法线和 UV 缓冲区都是标准的THREE.BufferAttribute 对象,因为它们描述了基本形状。但是为了将每个实例移动到自己的位置,您需要定义一个THREE.InstancedBufferAttribute 来保存位置(示例通常将此属性命名为“offset”)。

THREE.InstancedBufferAttributes 中的顶点引用数描述了您将拥有多少个实例。例如,将 9 个值放入 offset 表示将有 3 个实例(这包括原始形状)。您还可以通过设置THREE.InstancedBuferGeometry.maxInstancedCount 值来控制绘制其中的数量。

最后,您需要一个着色器来帮助控制实例化属性。

小例子:

var cubeGeo = new THREE.InstancedBufferGeometry().copy(new THREE.BoxBufferGeometry(10, 10, 10));
//cubeGeo.maxInstancedCount = 8;

cubeGeo.addAttribute("cubePos", new THREE.InstancedBufferAttribute(new Float32Array([
  25, 25, 25,
  25, 25, -25, -25, 25, 25, -25, 25, -25,
  25, -25, 25,
  25, -25, -25, -25, -25, 25, -25, -25, -25
]), 3, 1));

var vertexShader = [
  "precision highp float;",
  "",
  "uniform mat4 modelViewMatrix;",
  "uniform mat4 projectionMatrix;",
  "",
  "attribute vec3 position;",
  "attribute vec3 cubePos;",
  "",
  "void main() {",
  "",
  "	gl_Position = projectionMatrix * modelViewMatrix * vec4( cubePos + position, 1.0 );",
  "",
  "}"
].join("\n");
var fragmentShader = [
  "precision highp float;",
  "",
  "void main() {",
  "",
  "	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);",
  "",
  "}"
].join("\n");

var mat = new THREE.RawShaderMaterial({
  uniforms: {},
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  side: THREE.DoubleSide,
  transparent: false
});

var mesh = new THREE.Mesh(cubeGeo, mat);

scene.add(mesh);
html * {
  padding: 0;
  margin: 0;
  width: 100%;
  overflow: hidden;
}

#host {
  width: 100%;
  height: 100%;
}
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>

<script>
  var WIDTH = window.innerWidth,
    HEIGHT = window.innerHeight,
    FOV = 35,
    NEAR = 1,
    FAR = 1000;

  var renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  document.getElementById('host').appendChild(renderer.domElement);

  var stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0';
  document.body.appendChild(stats.domElement);


  var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 250;

  var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
  trackballControl.rotateSpeed = 5.0; // need to speed it up a little

  var scene = new THREE.Scene();

  var light = new THREE.PointLight(0xffffff, 1, Infinity);
  camera.add(light);

  scene.add(light);

  function render() {
    if (typeof updateVertices !== "undefined") {
      updateVertices();
    }
    renderer.render(scene, camera);
    stats.update();
  }

  function animate() {
    requestAnimationFrame(animate);
    trackballControl.update();
    render();
  }

  animate();
</script>

【讨论】:

  • 大声笑,我确实解释了它们是如何工作的(绘制调用开销和东西),但我没有解释如何使用它们。一旦我的 PR 被接受,您应该可以将其与默认材质一起使用,而无需额外的着色器/缓冲工作。
  • @palihead 我对使用默认材料非常感兴趣。你有这方面的样本吗?
【解决方案2】:

这个问题有点令人困惑,但我会尽力而为。

我认为您对某些事情感到困惑,我在三个.js 用户中看到了很多。首先,我认为属性术语是错误的。您不是在创建成千上万个属性,而是像网格这样的实体,可能有一个属性 - position,或多个 uvnormalaMyAttribute 等。

事实上,webgl 可以处理的属性的最大数量是不同的,但大概是 16 个,而不是数千个。

一个属性可以包含定义 200 个“顶点”的数据,但这也是相对的,它们可以有 2 个组件,也可以有 4 个组件。

当您从“200 个顶点模型”中制作多个“对象”时,您并没有将几何体相乘。属性计数没有增加,我实际上不确定制服会发生什么,但就 gpu 而言,它仍然拥有 200 个顶点。 Javascript 仍然包含一些属性和统一位置,以及一些Geometry 类的一个实例。

你做的乘法是“节点/对象”,在 javascript 中你将有多个说 Mesh 对象。这将包含各种其他类型、矩阵、向量、四元数等。

当您调用渲染函数时,渲染器将为这些节点中的每一个发出绘制调用。每次这样做时,它都需要设置 webgl 的状态来处理特定的绘图调用。如果这是散布在场景中的同一个对象,则唯一不同的是位置/旋转/比例的统一。如果他们有材料,那么它可能是一种颜色的统一,或不同的纹理。无论哪种方式,这都会产生开销,并减慢速度。

假设您的模型是一棵树。用它创建一个森林并渲染a forest,而不是many trees,将消除这种开销,仍然进行相同数量的着色器处理。每棵树的每个顶点都需要在其上运行着色器。

这当然会增加你的属性所持有的数据。现在,不是在一些方便的对象空间(最好称之为“树空间”)中保存 200 个顶点,而是需要在“森林空间”中保存 200 x N 个顶点。 IE。 tree 0 的顶点存在于森林的某处,tree N 的相同顶点存在于森林的其他地方。如果您要为每棵树创建一个新几何体,将变换烘焙到其顶点,然后与另一棵树合并等等,就会发生这种情况。

实例化可以让您更聪明地处理这种情况。您可以保存原始树的 200 个顶点,以及描述它们将在何处绘制 N 次的属性,而不是保存所有这些共享公共属性(它是同一棵树)的单个顶点。因此,与其合并几何体并单独对它们进行转换,不如构造一个仅包含转换的属性。

您很可能无法将其与原版材质一起使用,因为它们不知道如何处理您的自定义属性。然而,对于three.js 处理着色器的方式,注入一些逻辑和扩展现有材质并不难。

【讨论】:

    猜你喜欢
    • 2018-10-09
    • 1970-01-01
    • 2019-05-22
    • 2018-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    相关资源
    最近更新 更多