InstancedBufferGeometry & InstancedBufferAttribute

694 阅读3分钟

InstancedBufferGeometry & InstancedBufferAttribute

对于顶点信息相同的Geometry,如果想要渲染多个实例(例如:游戏中的“树”、“草”等)。最容易想到的就是创建多个Mesh,然后添加到Scene。但是这样写的运行效率并不高,更好的写法是通过InstancedBufferGeometryInstancedBufferAttribute

示例

通过BufferAttribute定义顶点属性

我们现在举个最简单的例子,画10个一摸一样的三角形。

首先,我们要定义三角形的顶点属性,为了简单,我们这里只定义顶点位置:

const positions = [
  -2, 2, 0,
  -2, -2, 0,
  2, 2, 0
];
const instancedGeoemetry = new InstancedBufferGeometry();
instancedGeoemetry.setAttribute('position', new Float32BufferAttribute(positions, 3));

通过InstancedBufferAttribute定义差异化信息

现在,需要通过InstancedBufferAttribute定义差异化信息,也就是说,对于每个三角形来说,它们的顶点位置都是position,但是,我们需要给不同的三角形应用不同的信息,比如说,对顶点在X方向上进行不同的偏移,这样,就可以用同样的顶点,渲染出多个三角形。

const COUNT = 10;
const offsetX = new Array(COUNT).fill(0);
offsetX.forEach((v, i, a) => a[i] = i * 10);
instancedGeoemetry.setAttribute('offsetX', new InstancedBufferAttribute(new Uint16Array(offsetX), 1));

对于每个Instance,在这个例子中就是三角形,我们应用不同的offsetX。上面的例子就是说,对于第i个三角形,它的三个顶点都在X上偏移i * 10

在顶点着色器中使用InstancedBufferAttribute

对于我们上面定义的offset,需要在自定义的顶点着色器中使用:

const material = new RawShaderMaterial({
  vertexShader: `
    precision highp float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

    attribute vec3 position;
    attribute float offsetX;

    void main(){
      vec3 _position = position;
      _position.x += offsetX; // 使用offsetX
      
	  gl_Position = projectionMatrix * modelViewMatrix * vec4(_position, 1.0);
    }`
});

创建Mesh

const mesh = new Mesh(instancedGeoemetry, material);

example-1.PNG

InstancedBufferGeometry.instanceCount

InstancedBufferGeometry派生自BufferGeometry,并添加了一个新的instanceCount属性。用上面的例子来说,就是渲染instanceCount个三角形。默认是Infinity

在我们上面的例子中,通过InstancedBufferAttribute指定了10个差异化的offsetX,也就是说,通过InstancedBufferAttribute的长度,可以隐式的确定有多少个instance,而instanceCount是画多少个instance

InstancedBufferAttribute.meshPerAttribute

这个值用来指定,这个差异化数据会用在多少个连续的mesh(或instance)上。默认值为1。就拿我们上面的例子来说,每个offset只会应用在一个instance上。

下面,再举一个例子,给每个三角形添加不一样的颜色,但是,每两个连续的三角形设置一样的颜色。

先添加一个颜色属性:

const colors = [
  1, 0, 0,
	0, 1, 0,
	0, 0, 1,
	1, 1, 0,
	0, 1, 1
];
instancedGeoemetry.setAttribute('color', new InstancedBufferAttribute(new Float32Array(colors), 3, false, 2));

const material = new RawShaderMaterial({
  vertexShader: `
    precision highp float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

    attribute vec3 position;
    attribute float offsetX;
    attribute vec3 color;

	varying vec4 vColor;

    void main(){
      vec3 _position = position;
      _position.x += offsetX;
      
	  gl_Position = projectionMatrix * modelViewMatrix * vec4(_position, 1.0);
	  vColor = vec4(color, 1.0);
    }`,
    fragmentShader: `
      precision highp float;
      varying vec4 vColor;

      void main(){
        gl_FragColor = vColor;
      }`
});

注意上面的,在调用构造器时,第四个参数指定为2,也就是meshPerAttribute指定为2。

example-2.PNG

BoxHelper失效

因为BoxHelper依赖于InstancedBufferGeometry.boundingBox,而computeBoundingBox依赖于position属性。

也就是说,对于InstanceGeometryBoxHelper最终的渲染出来的顶点位置只和position有关,和instance无关,导致BoxHelper出错。

const boxHelper = new BoxHelper(mesh);
scene.add(boxHelper);

example-3.PNG