最小化Three.js绘制的数量

时间:2017-01-24 00:05:49

标签: performance three.js

我正在测试我的项目使用Three.js(http://agentscript.org),第一次测试看起来很慢:

  

https://asx-vqnhxlahpe.now.sh/test.html?three0

(注意:该网站可能需要一段时间,它会在一段时间不使用后“睡觉”)

这显然是由于我的使用方式太多,每个代理人一个。 renderer.info.render Object {calls: 10002, vertices: 60090, faces: 20000, points: 0}

我相信使用BufferGeometry可以解决这个问题,但我不知道如何使用它仍然能够访问每个代理,以便我的步/动画功能可以修改(移动,旋转)它们。

我该怎么做?

我意识到我可以简单地使用我自己的Shader,并计划这样做,但我更愿意先使用一个或两个中间解决方案。

3 个答案:

答案 0 :(得分:5)

您希望减少代理模拟中的绘制调用次数,并使相对容易的工作变得简单。您可以通过将代理集合表示为THREE.Points来完成此操作。

// geometry
var geometry = new THREE.BufferGeometry();

// attributes
var positions = new Float32Array( numAgents * 3 ); // 3 values per vertex
var rotations = new Float32Array( numAgents * 1 ); // 1 values per vertex

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

    positions[ i ] = 0;
    positions[ i + 1 ] = 0;
    positions[ i + 2 ] = 0;
    rotations[ i ] = 2 * Math.PI * Math.random();

}

geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'rotation', new THREE.BufferAttribute( rotations, 1 ) );

// material
var material = new THREE.PointsMaterial( {

    color: 0xff0000,
    size: 4,
    map: null

} );

// points
points = new THREE.Points( geometry, material );
scene.add( points );

这将在单个绘图调用中呈现,您可以更新CPU上的位置并将数据推送到每帧的GPU。您应该能够轻松地渲染100,000个代理。

小提琴:http://jsfiddle.net/730ffn4x/

如果您想将点表示为带方向的箭头,则需要使用箭头图像和自定义ShaderMaterial来提供纹理以旋转每个点。

如果每个代理必须表示为InstancedBufferGeometry,则

Mesh是合适的。 {MartinSchuhfuß的Here is a fine example与您想要的相似。

对于激烈的模拟,您将使用GPGPU。 See the three.js examples。在GPGPU中,模拟完全在GPU上运行,而CPU通常只做很少的工作。

three.js r.84

答案 1 :(得分:3)

在您的情况下,实例化是减少几何和绘制调用的最佳方法。 Three.js通过ANGLE_instanced_arrays扩展支持实例化。

看看这个 -

https://threejs.org/docs/#api/core/InstancedBufferGeometry

此处还有一个关于如何在Three.js -

中进行实例化的示例

https://threejs.org/examples/webgl_buffergeometry_instancing.html

答案 2 :(得分:2)

你是对的,使用BufferGeometries会对你的情况有所帮助。

这是我提出的:首先,我们将有一个代表箭头的小助手类:

// the base-shape of the arrow, two triangles
const arrowVertices = new Float32Array([
  -1,-1,0, 0,0,0, 0,2,0,
  0,2,0, 0,0,0, 1,-1, 0
]);

// some helper-variables
const v3 = new THREE.Vector3();
const m4 = new THREE.Matrix4();

class Arrow {
  constructor(buffer, offset) {
    this.numVertices = 6;
    this.buffer = buffer;
    this.offset = offset;
    this.heading = 0;
    this.position = new THREE.Vector3();
  }

  update() {
    m4.makeRotationZ(this.heading)
        .setPosition(this.position);

    for (let i = 0; i < this.numVertices; i++) {
      v3.fromArray(arrowVertices, i * 3);
      v3.applyMatrix4(m4a);
      v3.toArray(this.buffer, this.offset + i * 3);
    }
  }
}

然后,要使用它,请设置BufferGeometry并将箭头“附加”到它

// create the bufferGeometry (note: if you want proper shading, you will 
// also need an attribute 'normal' with face-vertex-normals)
const geometry = new THREE.BufferGeometry();
const positionBuffer = new Float32Array(NUM_INSTANCES * 6 * 3);

geometry.addAttribute('position', 
    new THREE.BufferAttribute(positionBuffer, 3));

const arrow = new Arrow(positionBuffer, 0);
const anotherArrow = new Arrow(positionBuffer, 6);

arrow.position.set(2, 1, 0);
arrow.heading = Math.PI/180 * 30;
arrow.update();

此技术的主要缺点是您需要手动处理箭头顶点的所有变换。对于只有6个顶点的简单箭头,这不应该是一个问题,它可能比每个箭头的网格更好。但是,我建议您再看看实例几何,它可以在顶点着色器中执行上面更新方法中发生的所有事情,从而以更少的开销执行更好。