Cesium 实例化潜入潜出
1、WebGL Instance 的原理
狭义的的WebGL 中说使用 Instance, 一般指使用 glDrawArraysInstanced
用于实例化渲染的函数。它允许在一次绘制调用中渲染多个相同的几何体实例,而无需为每个实例发起单独的绘制调用。
Three.js 就是使用这种方式, 狭义的Cesium 实例化并不是这样使用, 后面细说。
使用 glDrawArraysInstanced 的基本步骤:
- 创建并绑定缓冲区
// 创建几何体数据缓冲区(顶点位置)
const geometryBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, geometryBuffer);
const geometryData = new Float32Array([
// 顶点数据 (例如:位置数据)
0.0, 0.5, 0.0, // 顶点1
-0.5, -0.5, 0.0, // 顶点2
0.5, -0.5, 0.0, // 顶点3
]);
gl.bufferData(gl.ARRAY_BUFFER, geometryData, gl.STATIC_DRAW);
// 创建实例数据缓冲区(变换矩阵数据)
const instanceBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
const instanceData = new Float32Array([
// 变换数据 (每个实例的变换矩阵)
1.0, 0.0, 0.0, 0.0, // 实例1的变换矩阵
0.0, 1.0, 0.0, 1.0, // 实例2的变换矩阵
-1.0, 0.0, 0.0, 0.0, // 实例3的变换矩阵
]);
gl.bufferData(gl.ARRAY_BUFFER, instanceData, gl.STATIC_DRAW);
- 绑定缓冲区
// 绑定几何体缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, geometryBuffer);
const positionLocation = gl.getAttribLocation(program, "a_position");
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
// 绑定实例缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
const instanceLocation = gl.getAttribLocation(program, "a_instanceTransform");
gl.vertexAttribPointer(instanceLocation, 4, gl.FLOAT, false, 4 * 4, 0);
gl.vertexAttribDivisor(instanceLocation, 1); // 每个实例使用不同的值
gl.enableVertexAttribArray(instanceLocation);
- 着色器
// 顶点着色器
attribute vec3 a_position;
// 每个实例的变换数据
attribute vec4 a_instanceTransform;
uniform mat4 u_projectionMatrix;
uniform mat4 u_viewMatrix;
void main(void) {
mat4 modelMatrix = mat4(
a_instanceTransform.x, 0.0, 0.0, 0.0,
0.0, a_instanceTransform.y, 0.0, 0.0,
0.0, 0.0, a_instanceTransform.z, 0.0,
0.0, 0.0, 0.0, 1.0
);
gl_Position = u_projectionMatrix * u_viewMatrix * modelMatrix * vec4(a_position, 1.0);
}
// 片元着色器
void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 渲染红色
}
- 绘制
// 假设我们已经设置好了所有缓冲区、着色器等
const instanceCount = 3; // 实例数量
const vertexCount = 3; // 顶点数量(单个几何体的顶点数)
gl.drawArraysInstanced(gl.TRIANGLES, 0, vertexCount, instanceCount);
// 解绑相关资源
gl.bindBuffer(gl.ARRAY_BUFFER, null);
2、Cesium 实例化的原理
Cesium 的狭义实例化并不是使用 glDrawArraysInstanced 实现的, 比如下面的代码, 像这种"普通的几何体"。
但并不是没有使用 glDrawArraysInstanced 实现实例化, 比如广告牌, “特殊的几何体”。
// 创建5个立方体
let dimensions = new Cesium.Cartesian3(1.0, 1.0, 1.0);
let boxGeometry = Cesium.BoxGeometry.fromDimensions({
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
dimensions: dimensions,
});
const instances = [];
for (let i = 0; i < 5; ++i) {
const offsetX = Math.random() * 0.05;
const offsetY = Math.random() * 0.05;
const boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(
Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(info.lon + offsetX, info.lat + offsetY),
),
500.0,
new Cesium.Matrix4(),
);
instances.push(
new Cesium.GeometryInstance({
geometry: boxGeometry,
modelMatrix: boxModelMatrix,
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.fromRandom({ alpha: 1.0 }),
),
},
}),
);
}
window.primitive = viewer.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PerInstanceColorAppearance({
translucent: false,
closed: true,
}),
}),
);
创建流程概览:
Primitive.prototype.update() {
loadAsynchronous()
}
function loadAsynchronous() {
// 根据参数创建对应的几何体, 对于上面的例子, 这里会重复执行5次, 即使参数相同
promises.push(createGeometryTaskProcessors[i].scheduleTask);
// 合并几何体
combineGeometryTaskProcessor.scheduleTask();
|
| -- PrimitivePipeline.combineGeometry()
|
| -- geometryPipeline() {
// 关键: 将所有顶点应用实例的模型矩阵
transformToWorldCoordinates();
}
}
还有很多其他关键操作, 比如顶点索引的合并, 实例对应的自定义属性, 包围盒等。这里只关心顶点位置数据。
3、总结
Three.js 方式:
Cesium 方式:
cesium在提交给GPU之前已经将顶点应用了实例对应的模型矩阵。