第13.1章:自定义几何实例化技术

几何实例化是现代图形渲染中的重要优化技术,能够显著提升大量相似对象的渲染性能。本教程将深入讲解如何在Cocos Creator中实现自定义几何实例化。

🎯 学习目标

  • 理解几何实例化的原理和优势
  • 掌握Cocos Creator中的实例化实现方法
  • 学会设计高效的实例化数据结构
  • 了解实例化在不同场景中的应用

📋 前置知识

  • 熟悉Legacy Shader编程
  • 理解GPU渲染管线
  • 掌握顶点属性和Uniform变量

🔧 几何实例化基础

实例化原�?

几何实例化允许我们用一次draw call渲染多个相同几何体的不同实例,每个实例可以有不同的变换、颜色和其他属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 实例化渲染概念对比
class RenderingComparison {
// 传统渲染:每个对象一次draw call
traditional_rendering() {
for (let i = 0; i < 1000; i++) {
// 设置对象变换矩阵
this.setTransform(objects[i].transform);
// 绘制对象 - 1次draw call
this.drawMesh(mesh);
}
// 总计1000次draw call
}

// 实例化渲染:所有对象一次draw call
instanced_rendering() {
// 准备实例化数据
const instanceData = this.prepareInstanceData(objects);
// 绘制所有实例 - 1次draw call
this.drawInstanced(mesh, instanceData, 1000);
// 总计1次draw call
}
}

实例化数据结�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// 实例化着色器基础结构
CCEffect %{
techniques:
- name: instanced
passes:
- vert: instanced-vs:vert
frag: instanced-fs:frag
properties:
mainTexture: { value: white }
instanceCount: { value: 100 }
rasterizerState:
cullMode: back
depthStencilState:
depthTest: true
depthWrite: true
}%

CCProgram instanced-vs %{
precision highp float;

// 标准顶点属�? in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

// 实例化属性(每个实例一个值)
in vec4 a_instanceMatrix0; // 变换矩阵第一行
in vec4 a_instanceMatrix1; // 变换矩阵第二行
in vec4 a_instanceMatrix2; // 变换矩阵第三行 in vec4 a_instanceColor; // 实例颜色
in vec2 a_instanceUVOffset; // UV偏移
in float a_instanceScale; // 缩放因子

// 输出到片段着色器
out vec3 v_worldPos;
out vec3 v_normal;
out vec2 v_uv;
out vec4 v_instanceColor;

// 全局Uniform
uniform CCGlobal {
mat4 cc_matView;
mat4 cc_matProj;
mat4 cc_matViewProj;
vec4 cc_cameraPos;
vec4 cc_time;
};

void vert() {
// 重构实例变换矩阵
mat4 instanceMatrix = mat4(
a_instanceMatrix0.x, a_instanceMatrix1.x, a_instanceMatrix2.x, 0.0,
a_instanceMatrix0.y, a_instanceMatrix1.y, a_instanceMatrix2.y, 0.0,
a_instanceMatrix0.z, a_instanceMatrix1.z, a_instanceMatrix2.z, 0.0,
a_instanceMatrix0.w, a_instanceMatrix1.w, a_instanceMatrix2.w, 1.0
);

// 应用实例变换
vec4 localPos = vec4(a_position * a_instanceScale, 1.0);
vec4 worldPos = instanceMatrix * localPos;
v_worldPos = worldPos.xyz;

// 法线变换
mat3 normalMatrix = mat3(instanceMatrix);
v_normal = normalize(normalMatrix * a_normal);

// UV坐标
v_uv = a_texCoord + a_instanceUVOffset;

// 实例颜色
v_instanceColor = a_instanceColor;

// 投影变换
gl_Position = cc_matViewProj * worldPos;
}
}%

CCProgram instanced-fs %{
precision highp float;

in vec3 v_worldPos;
in vec3 v_normal;
in vec2 v_uv;
in vec4 v_instanceColor;

layout(location = 0) out vec4 fragColor;

uniform sampler2D mainTexture;

uniform CCForwardLight {
vec4 cc_mainLitDir;
vec4 cc_mainLitColor;
vec4 cc_ambientSky;
};

void frag() {
// 基础纹理
vec4 baseColor = texture(mainTexture, v_uv);

// 应用实例颜色
baseColor *= v_instanceColor;

// 简单光照
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(-cc_mainLitDir.xyz);
float NdotL = max(dot(normal, lightDir), 0.0);

vec3 lighting = cc_mainLitColor.rgb * NdotL + cc_ambientSky.rgb * 0.3;

fragColor = vec4(baseColor.rgb * lighting, baseColor.a);
}
}%

🎮 实例化系统实�?

TypeScript实例化管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// 几何实例化管理器
class GeometryInstancingManager {
private gl: WebGL2RenderingContext;
private instancedMeshes: Map<string, InstancedMesh> = new Map();
private maxInstancesPerBatch: number = 1000;

interface InstanceData {
transform: mat4;
color: vec4;
uvOffset: vec2;
scale: number;
visible: boolean;
userData?: any;
}

interface InstancedMesh {
mesh: Mesh;
shader: Shader;
instances: InstanceData[];
buffers: {
matrixBuffer: WebGLBuffer;
colorBuffer: WebGLBuffer;
uvBuffer: WebGLBuffer;
scaleBuffer: WebGLBuffer;
};
dirty: boolean;
}

public createInstancedMesh(meshId: string, mesh: Mesh, shader: Shader): void {
console.log(`🔧 创建实例化网格: ${meshId}`);

const instancedMesh: InstancedMesh = {
mesh: mesh,
shader: shader,
instances: [],
buffers: this.createInstanceBuffers(),
dirty: true
};

this.instancedMeshes.set(meshId, instancedMesh);
}

private createInstanceBuffers(): InstancedMesh['buffers'] {
return {
matrixBuffer: this.gl.createBuffer()!,
colorBuffer: this.gl.createBuffer()!,
uvBuffer: this.gl.createBuffer()!,
scaleBuffer: this.gl.createBuffer()!
};
}

public addInstance(meshId: string, instanceData: InstanceData): number {
const instancedMesh = this.instancedMeshes.get(meshId);
if (!instancedMesh) {
console.error(`实例化网格不存在: ${meshId}`);
return -1;
}

if (instancedMesh.instances.length >= this.maxInstancesPerBatch) {
console.warn(`达到最大实例数限制: ${this.maxInstancesPerBatch}`);
return -1;
}

const instanceId = instancedMesh.instances.length;
instancedMesh.instances.push(instanceData);
instancedMesh.dirty = true;

console.log(`�?添加实例 ${instanceId} �?${meshId}`);
return instanceId;
}

public updateInstance(meshId: string, instanceId: number, instanceData: Partial<InstanceData>): void {
const instancedMesh = this.instancedMeshes.get(meshId);
if (!instancedMesh || instanceId >= instancedMesh.instances.length) {
return;
}

Object.assign(instancedMesh.instances[instanceId], instanceData);
instancedMesh.dirty = true;
}

public removeInstance(meshId: string, instanceId: number): void {
const instancedMesh = this.instancedMeshes.get(meshId);
if (!instancedMesh || instanceId >= instancedMesh.instances.length) {
return;
}

instancedMesh.instances.splice(instanceId, 1);
instancedMesh.dirty = true;

console.log(`�?移除实例 ${instanceId} �?${meshId}`);
}

private updateInstanceBuffers(instancedMesh: InstancedMesh): void {
const instances = instancedMesh.instances.filter(inst => inst.visible);
const instanceCount = instances.length;

if (instanceCount === 0) return;

// 准备矩阵数据 (每个矩阵12个浮点数,忽略第4�?
const matrixData = new Float32Array(instanceCount * 12);
const colorData = new Float32Array(instanceCount * 4);
const uvData = new Float32Array(instanceCount * 2);
const scaleData = new Float32Array(instanceCount);

instances.forEach((instance, i) => {
// 矩阵数据 (转置为列主序)
const m = instance.transform;
const offset = i * 12;

matrixData[offset + 0] = m[0]; matrixData[offset + 1] = m[1]; matrixData[offset + 2] = m[2]; matrixData[offset + 3] = m[12];
matrixData[offset + 4] = m[4]; matrixData[offset + 5] = m[5]; matrixData[offset + 6] = m[6]; matrixData[offset + 7] = m[13];
matrixData[offset + 8] = m[8]; matrixData[offset + 9] = m[9]; matrixData[offset + 10] = m[10]; matrixData[offset + 11] = m[14];

// 颜色数据
const colorOffset = i * 4;
colorData[colorOffset + 0] = instance.color[0];
colorData[colorOffset + 1] = instance.color[1];
colorData[colorOffset + 2] = instance.color[2];
colorData[colorOffset + 3] = instance.color[3];

// UV偏移数据
const uvOffset = i * 2;
uvData[uvOffset + 0] = instance.uvOffset[0];
uvData[uvOffset + 1] = instance.uvOffset[1];

// 缩放数据
scaleData[i] = instance.scale;
});

// 更新GPU缓冲�? this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.matrixBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, matrixData, this.gl.DYNAMIC_DRAW);

this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.colorBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, colorData, this.gl.DYNAMIC_DRAW);

this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.uvBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, uvData, this.gl.DYNAMIC_DRAW);

this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.scaleBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, scaleData, this.gl.DYNAMIC_DRAW);

instancedMesh.dirty = false;

console.log(`🔄 更新实例化缓冲区,实例数: ${instanceCount}`);
}

public renderInstanced(meshId: string): void {
const instancedMesh = this.instancedMeshes.get(meshId);
if (!instancedMesh) return;

const visibleInstances = instancedMesh.instances.filter(inst => inst.visible);
if (visibleInstances.length === 0) return;

// 更新缓冲区数�? if (instancedMesh.dirty) {
this.updateInstanceBuffers(instancedMesh);
}

// 绑定着色器
this.gl.useProgram(instancedMesh.shader.program);

// 绑定网格数据
this.bindMeshAttributes(instancedMesh.mesh);

// 绑定实例化属�? this.bindInstanceAttributes(instancedMesh);

// 执行实例化绘�? this.gl.drawElementsInstanced(
this.gl.TRIANGLES,
instancedMesh.mesh.indexCount,
this.gl.UNSIGNED_SHORT,
0,
visibleInstances.length
);

console.log(`🎨 渲染实例化网�?${meshId},实例数: ${visibleInstances.length}`);
}

private bindInstanceAttributes(instancedMesh: InstancedMesh): void {
const shader = instancedMesh.shader;

// 矩阵属�?(分为3个vec4)
for (let i = 0; i < 3; i++) {
const location = this.gl.getAttribLocation(shader.program, `a_instanceMatrix${i}`);
if (location !== -1) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.matrixBuffer);
this.gl.enableVertexAttribArray(location);
this.gl.vertexAttribPointer(location, 4, this.gl.FLOAT, false, 12 * 4, i * 4 * 4);
this.gl.vertexAttribDivisor(location, 1); // 每个实例一个�? }
}

// 颜色属�? const colorLocation = this.gl.getAttribLocation(shader.program, 'a_instanceColor');
if (colorLocation !== -1) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.colorBuffer);
this.gl.enableVertexAttribArray(colorLocation);
this.gl.vertexAttribPointer(colorLocation, 4, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribDivisor(colorLocation, 1);
}

// UV偏移属�? const uvLocation = this.gl.getAttribLocation(shader.program, 'a_instanceUVOffset');
if (uvLocation !== -1) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.uvBuffer);
this.gl.enableVertexAttribArray(uvLocation);
this.gl.vertexAttribPointer(uvLocation, 2, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribDivisor(uvLocation, 1);
}

// 缩放属�? const scaleLocation = this.gl.getAttribLocation(shader.program, 'a_instanceScale');
if (scaleLocation !== -1) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, instancedMesh.buffers.scaleBuffer);
this.gl.enableVertexAttribArray(scaleLocation);
this.gl.vertexAttribPointer(scaleLocation, 1, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribDivisor(scaleLocation, 1);
}
}

public getInstanceCount(meshId: string): number {
const instancedMesh = this.instancedMeshes.get(meshId);
return instancedMesh ? instancedMesh.instances.filter(inst => inst.visible).length : 0;
}

public getTotalDrawCalls(): number {
return Array.from(this.instancedMeshes.values())
.filter(mesh => mesh.instances.some(inst => inst.visible)).length;
}
}

🌿 草地实例化示�?

大规模草地渲�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 草地实例化着色器
CCProgram grass-instanced %{
precision highp float;

// 顶点属�? in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

// 实例化属�? in vec4 a_instanceMatrix0;
in vec4 a_instanceMatrix1;
in vec4 a_instanceMatrix2;
in vec4 a_grassParams; // x: height, y: bend, z: wind_phase, w: variation
in vec3 a_grassColor; // 草地颜色变化

out vec3 v_worldPos;
out vec3 v_normal;
out vec2 v_uv;
out vec3 v_grassColor;
out float v_windEffect;

uniform CCGlobal {
mat4 cc_matViewProj;
vec4 cc_time;
vec4 cc_cameraPos;
};

uniform vec4 windParams; // x: strength, y: frequency, z: direction_x, w: direction_z

void vert() {
// 重构实例变换矩阵
mat4 instanceMatrix = mat4(
a_instanceMatrix0.x, a_instanceMatrix1.x, a_instanceMatrix2.x, 0.0,
a_instanceMatrix0.y, a_instanceMatrix1.y, a_instanceMatrix2.y, 0.0,
a_instanceMatrix0.z, a_instanceMatrix1.z, a_instanceMatrix2.z, 0.0,
a_instanceMatrix0.w, a_instanceMatrix1.w, a_instanceMatrix2.w, 1.0
);

vec3 worldPos = (instanceMatrix * vec4(a_position, 1.0)).xyz;

// 风力效果
float windStrength = windParams.x;
float windFrequency = windParams.y;
vec2 windDirection = windParams.zw;

// 计算风力位移
float windPhase = a_grassParams.z + cc_time.x * windFrequency;
float heightFactor = a_position.y / a_grassParams.x; // 归一化高�?
vec2 windOffset = windDirection * windStrength * sin(windPhase) * heightFactor * heightFactor;
worldPos.xz += windOffset * a_grassParams.y; // bend factor

v_worldPos = worldPos;
v_windEffect = length(windOffset);

// 法线变换
v_normal = normalize((instanceMatrix * vec4(a_normal, 0.0)).xyz);

// UV和颜�? v_uv = a_texCoord;
v_grassColor = a_grassColor;

gl_Position = cc_matViewProj * vec4(worldPos, 1.0);
}
}%

CCProgram grass-fs %{
precision highp float;

in vec3 v_worldPos;
in vec3 v_normal;
in vec2 v_uv;
in vec3 v_grassColor;
in float v_windEffect;

layout(location = 0) out vec4 fragColor;

uniform sampler2D grassTexture;
uniform vec4 grassTint;

uniform CCForwardLight {
vec4 cc_mainLitDir;
vec4 cc_mainLitColor;
vec4 cc_ambientSky;
};

void frag() {
vec4 baseColor = texture(grassTexture, v_uv);

// Alpha测试 (草叶边缘)
if (baseColor.a < 0.5) discard;

// 应用草地颜色变化
baseColor.rgb *= v_grassColor * grassTint.rgb;

// 风力效果增强绿色
baseColor.rgb = mix(baseColor.rgb, baseColor.rgb * vec3(0.8, 1.2, 0.9), v_windEffect * 0.3);

// 简单光�? vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(-cc_mainLitDir.xyz);
float NdotL = max(dot(normal, lightDir), 0.0);

// 子表面散射近�? float backLight = max(0.0, dot(-normal, lightDir)) * 0.5;
float lighting = NdotL + backLight;

vec3 finalColor = baseColor.rgb * (cc_mainLitColor.rgb * lighting + cc_ambientSky.rgb * 0.4);

fragColor = vec4(finalColor, 1.0);
}
}%

TypeScript草地系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 草地实例化系�?class GrassInstancingSystem {
private instancingManager: GeometryInstancingManager;
private grassPatches: Map<string, GrassPatch> = new Map();

interface GrassPatch {
center: vec3;
radius: number;
density: number;
instances: GrassInstance[];
lodLevel: number;
}

interface GrassInstance {
position: vec3;
height: number;
bend: number;
windPhase: number;
variation: number;
color: vec3;
}

public createGrassField(fieldId: string, center: vec3, size: vec2, density: number): void {
console.log(`🌿 创建草地区域: ${fieldId}, 尺寸: ${size}, 密度: ${density}`);

const grassCount = Math.floor(size[0] * size[1] * density);
const instances: GrassInstance[] = [];

for (let i = 0; i < grassCount; i++) {
const x = center[0] + (Math.random() - 0.5) * size[0];
const z = center[2] + (Math.random() - 0.5) * size[1];
const y = this.getTerrainHeight(x, z);

instances.push({
position: [x, y, z],
height: 0.5 + Math.random() * 0.5,
bend: 0.3 + Math.random() * 0.4,
windPhase: Math.random() * Math.PI * 2,
variation: Math.random(),
color: this.generateGrassColor()
});
}

// 添加到实例化管理�? instances.forEach(grass => {
const instanceData = this.createGrassInstanceData(grass);
this.instancingManager.addInstance('grass', instanceData);
});

console.log(`�?草地创建完成,实例数: ${instances.length}`);
}

private generateGrassColor(): vec3 {
// 生成自然的草地颜色变�? const baseGreen = [0.2, 0.6, 0.1];
const variation = 0.3;

return [
baseGreen[0] + (Math.random() - 0.5) * variation,
baseGreen[1] + (Math.random() - 0.5) * variation,
baseGreen[2] + (Math.random() - 0.5) * variation
];
}

private createGrassInstanceData(grass: GrassInstance): InstanceData {
// 创建变换矩阵
const scale = grass.height;
const rotation = Math.random() * Math.PI * 2;
const transform = mat4.create();

mat4.translate(transform, transform, grass.position);
mat4.rotateY(transform, transform, rotation);
mat4.scale(transform, transform, [scale, scale, scale]);

return {
transform: transform,
color: [grass.color[0], grass.color[1], grass.color[2], 1.0],
uvOffset: [0, 0],
scale: 1.0,
visible: true,
userData: {
height: grass.height,
bend: grass.bend,
windPhase: grass.windPhase,
variation: grass.variation
}
};
}

public updateWind(windStrength: number, windDirection: vec2, windFrequency: number): void {
// 更新风力参数到着色器
const windParams = [windStrength, windFrequency, windDirection[0], windDirection[1]];
// 这里应该更新到着色器uniform
console.log(`💨 更新风力参数: 强度=${windStrength}, 方向=${windDirection}, 频率=${windFrequency}`);
}

public updateGrassLOD(cameraPos: vec3): void {
// 基于距离的LOD系统
this.grassPatches.forEach((patch, patchId) => {
const distance = vec3.distance(cameraPos, patch.center);

let newLodLevel = 0;
if (distance > 100) newLodLevel = 3;
else if (distance > 50) newLodLevel = 2;
else if (distance > 20) newLodLevel = 1;

if (newLodLevel !== patch.lodLevel) {
this.applyGrassLOD(patchId, newLodLevel);
patch.lodLevel = newLodLevel;
}
});
}

private applyGrassLOD(patchId: string, lodLevel: number): void {
const patch = this.grassPatches.get(patchId);
if (!patch) return;

// 根据LOD级别调整可见实例�? const visibilityRatio = [1.0, 0.5, 0.25, 0.1][lodLevel];

patch.instances.forEach((grass, index) => {
const visible = Math.random() < visibilityRatio;
this.instancingManager.updateInstance('grass', index, { visible });
});

console.log(`🎯 草地LOD更新: ${patchId}, 级别: ${lodLevel}, 可见�? ${(visibilityRatio * 100).toFixed(0)}%`);
}
}

🏗�?建筑实例化系�?

城市建筑群渲�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 城市建筑实例化系�?class CityBuildingInstancing {
private instancingManager: GeometryInstancingManager;
private buildingTypes: Map<string, BuildingType> = new Map();

interface BuildingType {
meshId: string;
baseHeight: number;
heightVariation: number;
colorPalette: vec3[];
density: number;
}

interface CityBlock {
bounds: BoundingBox;
buildingDensity: number;
buildingTypes: string[];
instances: BuildingInstance[];
}

interface BuildingInstance {
position: vec3;
rotation: number;
height: number;
width: number;
depth: number;
color: vec3;
type: string;
windowPattern: number;
}

constructor() {
this.initBuildingTypes();
}

private initBuildingTypes(): void {
// 住宅�? this.buildingTypes.set('residential', {
meshId: 'building_residential',
baseHeight: 15.0,
heightVariation: 20.0,
colorPalette: [
[0.9, 0.85, 0.8], // 米色
[0.8, 0.8, 0.9], // 淡蓝
[0.9, 0.9, 0.8] // 淡黄
],
density: 0.6
});

// 商业建筑
this.buildingTypes.set('commercial', {
meshId: 'building_commercial',
baseHeight: 25.0,
heightVariation: 40.0,
colorPalette: [
[0.7, 0.8, 0.9], // 现代�? [0.8, 0.8, 0.8], // 灰色
[0.6, 0.7, 0.8] // 深蓝
],
density: 0.3
});

// 摩天大楼
this.buildingTypes.set('skyscraper', {
meshId: 'building_skyscraper',
baseHeight: 80.0,
heightVariation: 120.0,
colorPalette: [
[0.5, 0.6, 0.7], // 钢铁�? [0.4, 0.5, 0.6], // 深灰
[0.6, 0.6, 0.5] // 金属�? ],
density: 0.1
});
}

public generateCityBlock(blockId: string, bounds: BoundingBox, zoning: string[]): void {
console.log(`🏙�?生成城市街区: ${blockId}`);

const cityBlock: CityBlock = {
bounds: bounds,
buildingDensity: 0.4,
buildingTypes: zoning,
instances: []
};

// 计算街区内的建筑数量
const area = (bounds.max[0] - bounds.min[0]) * (bounds.max[2] - bounds.min[2]);
const buildingCount = Math.floor(area * cityBlock.buildingDensity / 100); // �?00平方米一个建�?
for (let i = 0; i < buildingCount; i++) {
const building = this.generateRandomBuilding(bounds, zoning);
cityBlock.instances.push(building);

// 添加到实例化系统
const instanceData = this.createBuildingInstanceData(building);
this.instancingManager.addInstance(building.type, instanceData);
}

console.log(`�?街区生成完成,建筑数: ${buildingCount}`);
}

private generateRandomBuilding(bounds: BoundingBox, allowedTypes: string[]): BuildingInstance {
// 随机选择建筑类型
const typeIndex = Math.floor(Math.random() * allowedTypes.length);
const buildingTypeName = allowedTypes[typeIndex];
const buildingType = this.buildingTypes.get(buildingTypeName)!;

// 随机位置 (避开道路)
const margin = 5.0; // 道路边距
const x = bounds.min[0] + margin + Math.random() * (bounds.max[0] - bounds.min[0] - 2 * margin);
const z = bounds.min[2] + margin + Math.random() * (bounds.max[2] - bounds.min[2] - 2 * margin);
const y = this.getTerrainHeight(x, z);

// 随机尺寸
const height = buildingType.baseHeight + Math.random() * buildingType.heightVariation;
const width = 8 + Math.random() * 12;
const depth = 8 + Math.random() * 12;

// 随机颜色
const colorIndex = Math.floor(Math.random() * buildingType.colorPalette.length);
const baseColor = buildingType.colorPalette[colorIndex];
const colorVariation = 0.1;
const color: vec3 = [
baseColor[0] + (Math.random() - 0.5) * colorVariation,
baseColor[1] + (Math.random() - 0.5) * colorVariation,
baseColor[2] + (Math.random() - 0.5) * colorVariation
];

return {
position: [x, y, z],
rotation: Math.floor(Math.random() * 4) * Math.PI / 2, // 90度对�? height: height,
width: width,
depth: depth,
color: color,
type: buildingTypeName,
windowPattern: Math.floor(Math.random() * 4)
};
}

private createBuildingInstanceData(building: BuildingInstance): InstanceData {
const transform = mat4.create();

// 位置、旋转、缩�? mat4.translate(transform, transform, building.position);
mat4.rotateY(transform, transform, building.rotation);
mat4.scale(transform, transform, [building.width, building.height, building.depth]);

return {
transform: transform,
color: [building.color[0], building.color[1], building.color[2], 1.0],
uvOffset: [
(building.windowPattern % 2) * 0.5, // 窗户纹理变化
Math.floor(building.windowPattern / 2) * 0.5
],
scale: 1.0,
visible: true,
userData: {
buildingType: building.type,
height: building.height,
windowPattern: building.windowPattern
}
};
}

public updateCityLOD(cameraPos: vec3, viewDistance: number): void {
// 城市LOD系统
const cityBlocks = this.getAllCityBlocks();

cityBlocks.forEach(block => {
const blockCenter = this.getBlockCenter(block.bounds);
const distance = vec3.distance(cameraPos, blockCenter);

if (distance > viewDistance * 2) {
// 超远距离:完全不显示
this.setCityBlockVisibility(block, false);
} else if (distance > viewDistance) {
// 远距离:显示简化版�? this.setCityBlockLOD(block, 'low');
} else if (distance > viewDistance * 0.5) {
// 中距离:中等质量
this.setCityBlockLOD(block, 'medium');
} else {
// 近距离:高质�? this.setCityBlockLOD(block, 'high');
}
});
}
}

📊 性能分析工具

实例化性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 实例化性能分析�?class InstancingPerformanceProfiler {
private frameStats: FrameStats[] = [];
private maxFrameHistory = 100;

interface FrameStats {
frameTime: number;
drawCalls: number;
instancedDrawCalls: number;
totalInstances: number;
triangles: number;
gpuMemoryUsage: number;
timestamp: number;
}

interface InstancingReport {
averageFrameTime: number;
drawCallReduction: number;
memoryEfficiency: number;
performanceGain: number;
recommendations: string[];
}

public startFrame(): void {
this.currentFrameStartTime = performance.now();
}

public endFrame(drawCalls: number, instancedDrawCalls: number, totalInstances: number): void {
const frameTime = performance.now() - this.currentFrameStartTime;

const stats: FrameStats = {
frameTime: frameTime,
drawCalls: drawCalls,
instancedDrawCalls: instancedDrawCalls,
totalInstances: totalInstances,
triangles: this.calculateTriangleCount(),
gpuMemoryUsage: this.estimateGPUMemoryUsage(),
timestamp: Date.now()
};

this.frameStats.push(stats);

// 保持历史记录在限制内
if (this.frameStats.length > this.maxFrameHistory) {
this.frameStats.shift();
}
}

public generateReport(): InstancingReport {
if (this.frameStats.length === 0) {
return this.getEmptyReport();
}

const recentStats = this.frameStats.slice(-30); // 最�?0�? const averageFrameTime = recentStats.reduce((sum, stat) => sum + stat.frameTime, 0) / recentStats.length;

// 计算draw call减少比例
const traditionalDrawCalls = recentStats.reduce((sum, stat) => sum + stat.totalInstances, 0) / recentStats.length;
const actualDrawCalls = recentStats.reduce((sum, stat) => sum + stat.drawCalls, 0) / recentStats.length;
const drawCallReduction = (traditionalDrawCalls - actualDrawCalls) / traditionalDrawCalls;

// 内存效率
const memoryEfficiency = this.calculateMemoryEfficiency(recentStats);

// 性能提升
const performanceGain = this.estimatePerformanceGain(drawCallReduction, averageFrameTime);

// 生成建议
const recommendations = this.generateRecommendations(recentStats);

return {
averageFrameTime: averageFrameTime,
drawCallReduction: drawCallReduction,
memoryEfficiency: memoryEfficiency,
performanceGain: performanceGain,
recommendations: recommendations
};
}

private generateRecommendations(stats: FrameStats[]): string[] {
const recommendations: string[] = [];

const avgFrameTime = stats.reduce((sum, stat) => sum + stat.frameTime, 0) / stats.length;
const avgInstances = stats.reduce((sum, stat) => sum + stat.totalInstances, 0) / stats.length;
const avgDrawCalls = stats.reduce((sum, stat) => sum + stat.drawCalls, 0) / stats.length;

if (avgFrameTime > 16.67) {
recommendations.push('帧时间过长,考虑减少实例数量或优化着色器');
}

if (avgInstances / avgDrawCalls < 10) {
recommendations.push('实例化效率较低,考虑合并更多对象到同一实例化批�?);
}

if (avgInstances > 10000) {
recommendations.push('实例数量很大,考虑实现LOD系统');
}

const memoryUsage = stats[stats.length - 1]?.gpuMemoryUsage || 0;
if (memoryUsage > 500 * 1024 * 1024) { // 500MB
recommendations.push('GPU内存使用量较高,考虑优化实例数据结构');
}

return recommendations;
}

public logPerformanceReport(): void {
const report = this.generateReport();

console.log('📊 实例化性能报告:');
console.log(` 平均帧时�? ${report.averageFrameTime.toFixed(2)}ms`);
console.log(` Draw Call减少: ${(report.drawCallReduction * 100).toFixed(1)}%`);
console.log(` 内存效率: ${(report.memoryEfficiency * 100).toFixed(1)}%`);
console.log(` 性能提升: ${(report.performanceGain * 100).toFixed(1)}%`);

if (report.recommendations.length > 0) {
console.log('💡 优化建议:');
report.recommendations.forEach((rec, index) => {
console.log(` ${index + 1}. ${rec}`);
});
}
}
}

📝 本章小结

通过本教程,你应该掌握了�?

  1. *实例化原�?: 理解几何实例化的工作机制和优�?2. 系统实现: 学会构建完整的实例化管理系统
  2. 实际应用: 掌握草地、建筑等常见场景的实例化技�?4. 性能优化: 了解LOD系统和性能监控方法
  3. *最佳实�?: 学会在实际项目中合理使用实例化技�?

🚀 下一步学�?

继续学习UBO内存布局优化技术!🎮�?