硬件实例化
硬件实例化是一种让 GPU 用少量绘制调用渲染多个相同网格的技术。每个网格实例可具有少量不同状态,例如位置、旋转、缩放或颜色。适合树木、子弹、粒子等重复几何体。
PlayCanvas 最低要求 WebGL2,实例化是其核心功能,因此所有设备都支持实例化。
注意,GPU 会提交全部实例进行渲染,不执行相机视锥体剔除。
实时示例
- 基础实例化:演示默认 Mat4 实例化格式与 StandardMaterial。
- 自定义实例化:演示自定义顶点格式和着色器代码块覆盖。
- GLB 实例化:使用 GLB 文件中的 EXT_mesh_gpu_instancing 扩展。
- Gooch 实例化:完全自定义、支持实例化的 ShaderMaterial。
使用默认格式进行基础实例化
最简单的实例化方式是使用默认格式,为每个实例存储一个 Mat4 世界矩阵。
第 1 步:准备实例矩阵
将各实例的矩阵存入 Float32Array:
// store matrices for individual instances into array
const instanceCount = 1000;
const matrices = new Float32Array(instanceCount * 16);
const matrix = new pc.Mat4();
const pos = new pc.Vec3();
const rot = new pc.Quat();
const scl = new pc.Vec3();
for (let i = 0; i < instanceCount; i++) {
// set up position, rotation, scale for each instance
pos.set(Math.random() * 10, Math.random() * 10, Math.random() * 10);
rot.setFromEulerAngles(i * 30, i * 50, i * 70);
scl.set(0.1, 0.1, 0.1);
matrix.setTRS(pos, rot, scl);
// copy matrix elements into array of floats
for (let m = 0; m < 16; m++) {
matrices[i * 16 + m] = matrix.data[m];
}
}
第 2 步:创建顶点缓冲区并启用实例化
使用 pc.VertexFormat.getDefaultInstancingFormat 创建 VertexBuffer,并在 MeshInstance 上启用实例化:
const vbFormat = pc.VertexFormat.getDefaultInstancingFormat(app.graphicsDevice);
const vertexBuffer = new pc.VertexBuffer(app.graphicsDevice, vbFormat, instanceCount, {
data: matrices
});
// enable instancing on the mesh instance
const meshInst = entity.render.meshInstances[0];
meshInst.setInstancing(vertexBuffer);
动态更新
对于位置每帧变化的动态实例化,创建并更新动态顶点缓冲区:
// create dynamic vertex buffer (no initial data)
const vertexBuffer = new pc.VertexBuffer(app.graphicsDevice, vbFormat, instanceCount, {
usage: pc.BUFFER_DYNAMIC
});
// update per frame
vertexBuffer.setData(matrices);
自定义顶点格式
为提高实例化效率或提供自定义逐实例数据,可以定义自定义顶点格式,代替默认 Mat4 格式。
示例:位置与缩放格式
此格式为每个实例仅存储位置(3 个浮点数)和统一缩放(1 个浮点数),将单实例内存从 64 字节降至 16 字节:
const vbFormat = new pc.VertexFormat(app.graphicsDevice, [
{ semantic: pc.SEMANTIC_ATTR12, components: 3, type: pc.TYPE_FLOAT32 }, // position
{ semantic: pc.SEMANTIC_ATTR13, components: 1, type: pc.TYPE_FLOAT32 } // scale
]);
// store data for individual instances, 4 floats each
const instanceCount = 3000;
const data = new Float32Array(instanceCount * 4);
for (let i = 0; i < instanceCount; i++) {
const offset = i * 4;
data[offset + 0] = Math.random() * 10 - 5; // x
data[offset + 1] = Math.random() * 10 - 5; // y
data[offset + 2] = Math.random() * 10 - 5; // z
data[offset + 3] = 0.1 + Math.random() * 0.1; // scale
}
const vertexBuffer = new pc.VertexBuffer(app.graphicsDevice, vbFormat, instanceCount, {
data: data
});
使用自定义格式时,还必须提供自定义着色器或着色器代码块来解释数据,见下文。
StandardMaterial 着色器代码块
StandardMaterial 使用自定义实例化格式时,可覆盖实例化着色器代码块,定义如何根据自定义属性计算模型矩阵。
第 1 步:设置材质属性
告诉材质各语义映射到哪些属性名:
const material = new pc.StandardMaterial();
material.setAttribute('aInstPosition', pc.SEMANTIC_ATTR12);
material.setAttribute('aInstScale', pc.SEMANTIC_ATTR13);
第 2 步:覆盖实例化着色器代码块
同时提供 GLSL(WebGL)和 WGSL(WebGPU)自定义着色器代码:
material.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('transformInstancingVS', `
attribute vec3 aInstPosition;
attribute float aInstScale;
mat4 getModelMatrix() {
return mat4(
vec4(aInstScale, 0.0, 0.0, 0.0),
vec4(0.0, aInstScale, 0.0, 0.0),
vec4(0.0, 0.0, aInstScale, 0.0),
vec4(aInstPosition, 1.0)
);
}
`);
material.getShaderChunks(pc.SHADERLANGUAGE_WGSL).set('transformInstancingVS', `
attribute aInstPosition: vec3f;
attribute aInstScale: f32;
fn getModelMatrix() -> mat4x4f {
return mat4x4f(
vec4f(aInstScale, 0.0, 0.0, 0.0),
vec4f(0.0, aInstScale, 0.0, 0.0),
vec4f(0.0, 0.0, aInstScale, 0.0),
vec4f(aInstPosition, 1.0)
);
}
`);
material.update();
实例化代码块必须实现 getModelMatrix() 函数,返回各实例的世界矩阵。
支持实例化的自定义着色器
编写支持实例化的完全自定义着色器(使用 ShaderMaterial)时,通过 INSTANCING 预处理宏按条件包含实例化代码:
GLSL 示例
#include "transformCoreVS"
#if INSTANCING
attribute vec3 aInstPosition;
attribute float aInstScale;
mat4 getModelMatrix() {
return mat4(
vec4(aInstScale, 0.0, 0.0, 0.0),
vec4(0.0, aInstScale, 0.0, 0.0),
vec4(0.0, 0.0, aInstScale, 0.0),
vec4(aInstPosition, 1.0)
);
}
#endif
void main(void) {
mat4 modelMatrix = getModelMatrix();
vec3 localPos = getLocalPosition(vertex_position.xyz);
vec4 worldPos = modelMatrix * vec4(localPos, 1.0);
gl_Position = matrix_viewProjection * worldPos;
}
WGSL 示例
#include "transformCoreVS"
#if INSTANCING
attribute aInstPosition: vec3f;
attribute aInstScale: f32;
fn getModelMatrix() -> mat4x4f {
return mat4x4f(
vec4f(aInstScale, 0.0, 0.0, 0.0),
vec4f(0.0, aInstScale, 0.0, 0.0),
vec4f(0.0, 0.0, aInstScale, 0.0),
vec4f(aInstPosition, 1.0)
);
}
#endif
@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let modelMatrix: mat4x4f = getModelMatrix();
let localPos: vec3f = getLocalPosition(input.vertex_position.xyz);
let worldPos: vec4f = modelMatrix * vec4f(localPos, 1.0);
output.position = uniform.matrix_viewProjection * worldPos;
return output;
}
#if INSTANCING 条件让同一着色器同时适用于实例化和非实例化渲染。未启用实例化时,引擎提供默认的 getModelMatrix() 实现。
在自定义着色器中使用默认 Mat4 格式
使用默认实例化格式(每实例一个 Mat4)时,从四个 instance_line 属性读取矩阵:
attribute vec4 instance_line1;
attribute vec4 instance_line2;
attribute vec4 instance_line3;
attribute vec4 instance_line4;
mat4 getModelMatrix() {
return mat4(instance_line1, instance_line2, instance_line3, instance_line4);
}
GLB 实例化
GLB/glTF 文件可通过 EXT_mesh_gpu_instancing 扩展包含实例化数据。加载此类文件时,PlayCanvas 自动配置实例化:
const entity = assets.glb.resource.instantiateRenderEntity({
castShadows: true
});
app.root.addChild(entity);
这适用于在 Blender 等支持导出实例化几何体的工具中制作的场景。