跳到主要内容

硬件实例化

硬件实例化是一种让 GPU 用少量绘制调用渲染多个相同网格的技术。每个网格实例可具有少量不同状态,例如位置、旋转、缩放或颜色。适合树木、子弹、粒子等重复几何体。

PlayCanvas 最低要求 WebGL2,实例化是其核心功能,因此所有设备都支持实例化。

注意,GPU 会提交全部实例进行渲染,不执行相机视锥体剔除。

实时示例​

  • 基础实例化:演示默认 Mat4 实例化格式与 StandardMaterial。
基础实例化
  • 自定义实例化:演示自定义顶点格式和着色器代码块覆盖。
自定义实例化
  • GLB 实例化:使用 GLB 文件中的 EXT_mesh_gpu_instancing 扩展。
GLB 实例化
  • Gooch 实例化:完全自定义、支持实例化的 ShaderMaterial。
Gooch 实例化

使用默认格式进行基础实例化​

最简单的实例化方式是使用默认格式,为每个实例存储一个 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 等支持导出实例化几何体的工具中制作的场景。