InstancedBufferGeometry & InstancedBufferAttribute
对于顶点信息相同的Geometry,如果想要渲染多个实例(例如:游戏中的“树”、“草”等)。最容易想到的就是创建多个Mesh,然后添加到Scene。但是这样写的运行效率并不高,更好的写法是通过InstancedBufferGeometry和InstancedBufferAttribute。
示例
通过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);
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。
BoxHelper失效
因为BoxHelper依赖于InstancedBufferGeometry.boundingBox,而computeBoundingBox依赖于position属性。
也就是说,对于InstanceGeometry,BoxHelper最终的渲染出来的顶点位置只和position有关,和instance无关,导致BoxHelper出错。
const boxHelper = new BoxHelper(mesh);
scene.add(boxHelper);