第15.3章:着色器资源管理

高效的着色器资源管理是大型项目成功的关键。本教程将深入讲解如何设计和实施着色器资源管理系统,包括资源加载、缓存、优化和动态管理等核心技术。

🎯 学习目标

  • 掌握着色器资源管理架构设计
  • 学会实现高效的资源加载和缓存系统
  • 了解动态着色器编译和热重载技�?- 建立完整的资源监控和优化体系

📦 资源管理架构

着色器资源管理�?

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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// src/shader-resource-manager.ts
interface ShaderResource {
id: string;
name: string;
path: string;
type: 'effect' | 'chunk' | 'include';
source: string;
compiledPrograms?: CompiledProgram[];
dependencies: string[];
lastModified: number;
size: number;
metadata: ShaderMetadata;
}

interface CompiledProgram {
name: string;
vertexShader: WebGLShader;
fragmentShader: WebGLShader;
program: WebGLProgram;
uniforms: Map<string, WebGLUniformLocation>;
attributes: Map<string, number>;
compilationTime: number;
platform: string;
}

interface ShaderMetadata {
version: string;
author: string;
description: string;
tags: string[];
complexity: 'low' | 'medium' | 'high';
performance: PerformanceProfile;
compatibility: CompatibilityInfo;
}

interface PerformanceProfile {
instructionCount: number;
textureReads: number;
mathComplexity: number;
registerPressure: number;
estimatedCost: number;
}

interface CompatibilityInfo {
webgl1: boolean;
webgl2: boolean;
mobile: boolean;
requiredExtensions: string[];
minPrecision: 'lowp' | 'mediump' | 'highp';
}

class ShaderResourceManager {
private gl: WebGLRenderingContext | WebGL2RenderingContext;
private resources: Map<string, ShaderResource> = new Map();
private compiledPrograms: Map<string, CompiledProgram> = new Map();
private dependencyGraph: Map<string, Set<string>> = new Map();
private loadingQueue: Set<string> = new Set();
private cache: ShaderCache;
private monitor: ResourceMonitor;
private hotReloader?: HotReloader;

constructor(gl: WebGLRenderingContext | WebGL2RenderingContext, options?: ManagerOptions) {
this.gl = gl;
this.cache = new ShaderCache(options?.cacheOptions);
this.monitor = new ResourceMonitor();

if (options?.enableHotReload) {
this.hotReloader = new HotReloader(this);
}

this.initializeBuiltinShaders();
}

// 资源加载
public async loadShader(path: string): Promise<ShaderResource> {
console.log(`📦 加载着色器: ${path}`);

// 检查是否已加载
const existingResource = this.getResource(path);
if (existingResource && !this.needsReload(existingResource)) {
return existingResource;
}

// 检查是否正在加�? if (this.loadingQueue.has(path)) {
return this.waitForLoad(path);
}

this.loadingQueue.add(path);

try {
// 从缓存加�? let resource = await this.cache.get(path);

if (!resource || this.needsReload(resource)) {
// 从文件系统加�? resource = await this.loadFromFile(path);

// 解析依赖
await this.resolveDependencies(resource);

// 编译着色器
await this.compileShader(resource);

// 保存到缓�? await this.cache.set(path, resource);
}

this.resources.set(path, resource);
this.monitor.recordLoad(resource);

console.log(`�?着色器加载完成: ${path}`);
return resource;
} finally {
this.loadingQueue.delete(path);
}
}

private async loadFromFile(path: string): Promise<ShaderResource> {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`加载着色器失败: ${path} (${response.status})`);
}

const source = await response.text();
const metadata = this.parseMetadata(source);
const dependencies = this.extractDependencies(source);

return {
id: this.generateResourceId(path),
name: this.extractName(path),
path: path,
type: this.inferType(path),
source: source,
dependencies: dependencies,
lastModified: Date.now(),
size: new Blob([source]).size,
metadata: metadata
};
}

private async resolveDependencies(resource: ShaderResource): Promise<void> {
const dependenciesToLoad = [];

for (const depPath of resource.dependencies) {
if (!this.resources.has(depPath)) {
dependenciesToLoad.push(this.loadShader(depPath));
}

// 构建依赖�? if (!this.dependencyGraph.has(resource.path)) {
this.dependencyGraph.set(resource.path, new Set());
}
this.dependencyGraph.get(resource.path)!.add(depPath);
}

// 并行加载所有依�? await Promise.all(dependenciesToLoad);
}

private async compileShader(resource: ShaderResource): Promise<void> {
if (resource.type !== 'effect') {
return; // 只编译effect文件
}

const programs = this.parsePrograms(resource.source);
const compiledPrograms: CompiledProgram[] = [];

for (const programDef of programs) {
try {
const compiled = await this.compileProgram(programDef, resource);
compiledPrograms.push(compiled);

// 缓存编译后的程序
const programKey = `${resource.path}:${programDef.name}`;
this.compiledPrograms.set(programKey, compiled);
} catch (error) {
console.error(`编译程序失败 ${programDef.name}:`, error);
throw error;
}
}

resource.compiledPrograms = compiledPrograms;
}

private async compileProgram(programDef: ProgramDefinition, resource: ShaderResource): Promise<CompiledProgram> {
const startTime = performance.now();

// 解析顶点和片段着色器源码
const vertexSource = this.resolveShaderSource(programDef.vertexSource, resource);
const fragmentSource = this.resolveShaderSource(programDef.fragmentSource, resource);

// 编译着色器
const vertexShader = this.compileShaderSource(this.gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this.compileShaderSource(this.gl.FRAGMENT_SHADER, fragmentSource);

// 链接程序
const program = this.gl.createProgram()!;
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);

if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
const error = this.gl.getProgramInfoLog(program);
throw new Error(`程序链接失败: ${error}`);
}

// 收集uniform和attribute信息
const uniforms = this.collectUniforms(program);
const attributes = this.collectAttributes(program);

const compilationTime = performance.now() - startTime;

return {
name: programDef.name,
vertexShader,
fragmentShader,
program,
uniforms,
attributes,
compilationTime,
platform: this.detectPlatform()
};
}

private compileShaderSource(type: number, source: string): WebGLShader {
const shader = this.gl.createShader(type)!;
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);

if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
const error = this.gl.getShaderInfoLog(shader);
const typeName = type === this.gl.VERTEX_SHADER ? '顶点' : '片段';
throw new Error(`${typeName}着色器编译失败: ${error}\n\n源码:\n${source}`);
}

return shader;
}

private resolveShaderSource(source: string, resource: ShaderResource): string {
let resolved = source;

// 处理include指令
const includePattern = /#include\s+<([^>]+)>/g;
let match;

while ((match = includePattern.exec(source)) !== null) {
const includePath = match[1];
const includeResource = this.resources.get(includePath);

if (includeResource) {
resolved = resolved.replace(match[0], includeResource.source);
} else {
console.warn(`找不到include文件: ${includePath}`);
}
}

return resolved;
}

// 资源获取
public getResource(path: string): ShaderResource | undefined {
return this.resources.get(path);
}

public getProgram(resourcePath: string, programName: string): CompiledProgram | undefined {
const key = `${resourcePath}:${programName}`;
return this.compiledPrograms.get(key);
}

// 资源管理
public async reloadShader(path: string): Promise<void> {
console.log(`🔄 重新加载着色器: ${path}`);

// 清理旧资�? this.unloadShader(path);

// 重新加载
await this.loadShader(path);

// 重新加载依赖此着色器的资�? await this.reloadDependents(path);
}

public unloadShader(path: string): void {
const resource = this.resources.get(path);
if (!resource) return;

// 清理编译后的程序
if (resource.compiledPrograms) {
for (const program of resource.compiledPrograms) {
this.gl.deleteProgram(program.program);
this.gl.deleteShader(program.vertexShader);
this.gl.deleteShader(program.fragmentShader);

const key = `${path}:${program.name}`;
this.compiledPrograms.delete(key);
}
}

this.resources.delete(path);
this.monitor.recordUnload(resource);
}

private async reloadDependents(path: string): Promise<void> {
const dependents = this.findDependents(path);

for (const dependent of dependents) {
await this.reloadShader(dependent);
}
}

private findDependents(path: string): string[] {
const dependents: string[] = [];

for (const [resourcePath, dependencies] of this.dependencyGraph.entries()) {
if (dependencies.has(path)) {
dependents.push(resourcePath);
}
}

return dependents;
}

// 资源监控
public getResourceStats(): ResourceStats {
const resources = Array.from(this.resources.values());
const compiledPrograms = Array.from(this.compiledPrograms.values());

return {
totalResources: resources.length,
totalSize: resources.reduce((sum, r) => sum + r.size, 0),
compiledPrograms: compiledPrograms.length,
averageCompilationTime: compiledPrograms.reduce((sum, p) => sum + p.compilationTime, 0) / compiledPrograms.length,
memoryUsage: this.estimateMemoryUsage(),
cacheHitRate: this.cache.getHitRate(),
loadTime: this.monitor.getAverageLoadTime()
};
}

private estimateMemoryUsage(): number {
// 估算着色器占用的内�? let totalMemory = 0;

for (const resource of this.resources.values()) {
totalMemory += resource.size; // 源码大小

if (resource.compiledPrograms) {
// 估算编译后的大小(通常是源码的2-4倍)
totalMemory += resource.size * 3;
}
}

return totalMemory;
}

// 清理资源
public cleanup(): void {
console.log('🧹 清理着色器资源...');

// 清理所有编译后的程�? for (const program of this.compiledPrograms.values()) {
this.gl.deleteProgram(program.program);
this.gl.deleteShader(program.vertexShader);
this.gl.deleteShader(program.fragmentShader);
}

this.resources.clear();
this.compiledPrograms.clear();
this.dependencyGraph.clear();

this.cache.clear();
this.hotReloader?.dispose();
}
}

着色器缓存系统

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
// src/shader-cache.ts
interface CacheEntry {
resource: ShaderResource;
timestamp: number;
accessCount: number;
lastAccessed: number;
size: number;
}

interface CacheOptions {
maxSize: number; // 最大缓存大小(字节�? maxAge: number; // 最大缓存时间(毫秒�? compressionEnabled: boolean;
persistToDisk: boolean;
maxEntries: number;
}

class ShaderCache {
private cache: Map<string, CacheEntry> = new Map();
private options: CacheOptions;
private currentSize: number = 0;
private hitCount: number = 0;
private missCount: number = 0;

constructor(options?: Partial<CacheOptions>) {
this.options = {
maxSize: 50 * 1024 * 1024, // 50MB默认
maxAge: 24 * 60 * 60 * 1000, // 24小时
compressionEnabled: true,
persistToDisk: true,
maxEntries: 1000,
...options
};

this.loadFromDisk();
this.startCleanupTimer();
}

public async get(key: string): Promise<ShaderResource | null> {
const entry = this.cache.get(key);

if (!entry) {
this.missCount++;
return null;
}

// 检查是否过�? if (this.isExpired(entry)) {
this.cache.delete(key);
this.currentSize -= entry.size;
this.missCount++;
return null;
}

// 更新访问信息
entry.accessCount++;
entry.lastAccessed = Date.now();

this.hitCount++;
return entry.resource;
}

public async set(key: string, resource: ShaderResource): Promise<void> {
// 计算资源大小
const size = this.calculateResourceSize(resource);

// 检查是否需要清理空�? if (this.currentSize + size > this.options.maxSize) {
await this.evictLRU(size);
}

// 删除旧条目(如果存在�? const existingEntry = this.cache.get(key);
if (existingEntry) {
this.currentSize -= existingEntry.size;
}

// 添加新条�? const entry: CacheEntry = {
resource: resource,
timestamp: Date.now(),
accessCount: 1,
lastAccessed: Date.now(),
size: size
};

this.cache.set(key, entry);
this.currentSize += size;

// 保存到磁�? if (this.options.persistToDisk) {
await this.saveToDisk(key, resource);
}
}

private calculateResourceSize(resource: ShaderResource): number {
let size = resource.size; // 源码大小

// 编译后的程序大小估算
if (resource.compiledPrograms) {
size += resource.compiledPrograms.length * 1024; // 每个程序�?KB
}

return size;
}

private async evictLRU(requiredSize: number): Promise<void> {
// 按最后访问时间排�? const entries = Array.from(this.cache.entries())
.sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed);

let freedSize = 0;
const toEvict: string[] = [];

for (const [key, entry] of entries) {
toEvict.push(key);
freedSize += entry.size;

if (freedSize >= requiredSize) {
break;
}
}

// 执行清理
for (const key of toEvict) {
const entry = this.cache.get(key);
if (entry) {
this.cache.delete(key);
this.currentSize -= entry.size;
}
}

console.log(`🗑�?缓存LRU清理: 释放${toEvict.length}个条目,${freedSize}字节`);
}

private isExpired(entry: CacheEntry): boolean {
return Date.now() - entry.timestamp > this.options.maxAge;
}

private startCleanupTimer(): void {
setInterval(() => {
this.cleanup();
}, 60000); // 每分钟清理一�? }

private cleanup(): void {
const expiredKeys: string[] = [];

for (const [key, entry] of this.cache.entries()) {
if (this.isExpired(entry)) {
expiredKeys.push(key);
}
}

for (const key of expiredKeys) {
const entry = this.cache.get(key);
if (entry) {
this.cache.delete(key);
this.currentSize -= entry.size;
}
}

if (expiredKeys.length > 0) {
console.log(`🧹 缓存清理: 移除${expiredKeys.length}个过期条目`);
}
}

private async loadFromDisk(): Promise<void> {
if (!this.options.persistToDisk) return;

try {
// 简化的磁盘加载实现
// 实际项目中需要使用IndexedDB或其他持久化存储
} catch (error) {
console.warn('从磁盘加载缓存失�?', error);
}
}

private async saveToDisk(key: string, resource: ShaderResource): Promise<void> {
if (!this.options.persistToDisk) return;

try {
// 简化的磁盘保存实现
// 实际项目中需要使用IndexedDB或其他持久化存储
} catch (error) {
console.warn('保存缓存到磁盘失�?', error);
}
}

public getHitRate(): number {
const total = this.hitCount + this.missCount;
return total > 0 ? this.hitCount / total : 0;
}

public getStats(): CacheStats {
return {
size: this.currentSize,
entries: this.cache.size,
hitRate: this.getHitRate(),
hitCount: this.hitCount,
missCount: this.missCount,
maxSize: this.options.maxSize,
utilizationRate: this.currentSize / this.options.maxSize
};
}

public clear(): void {
this.cache.clear();
this.currentSize = 0;
this.hitCount = 0;
this.missCount = 0;
}
}

interface CacheStats {
size: number;
entries: number;
hitRate: number;
hitCount: number;
missCount: number;
maxSize: number;
utilizationRate: number;
}

热重载系�?

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
// src/hot-reloader.ts
class HotReloader {
private manager: ShaderResourceManager;
private watcher?: FileWatcher;
private reloadQueue: Set<string> = new Set();
private debounceTimer?: number;

constructor(manager: ShaderResourceManager) {
this.manager = manager;
this.initializeWatcher();
}

private initializeWatcher(): void {
// 简化的文件监视实现
// 实际项目中需要使用合适的文件监视API
this.watcher = new FileWatcher({
patterns: ['**/*.effect', '**/*.chunk'],
ignorePatterns: ['**/node_modules/**', '**/dist/**']
});

this.watcher.on('change', (filePath: string) => {
this.scheduleReload(filePath);
});

this.watcher.on('add', (filePath: string) => {
console.log(`📁 新增着色器文件: ${filePath}`);
});

this.watcher.on('unlink', (filePath: string) => {
console.log(`🗑�?删除着色器文件: ${filePath}`);
this.manager.unloadShader(filePath);
});
}

private scheduleReload(filePath: string): void {
this.reloadQueue.add(filePath);

// 防抖处理,避免频繁重�? if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}

this.debounceTimer = window.setTimeout(() => {
this.processReloadQueue();
}, 300);
}

private async processReloadQueue(): Promise<void> {
const filesToReload = Array.from(this.reloadQueue);
this.reloadQueue.clear();

console.log(`🔄 热重载着色器: ${filesToReload.join(', ')}`);

try {
// 并行重载所有文�? await Promise.all(
filesToReload.map(filePath => this.manager.reloadShader(filePath))
);

console.log('�?热重载完�?);

// 触发重载事件
this.onReloadComplete(filesToReload);
} catch (error) {
console.error('�?热重载失�?', error);
}
}

private onReloadComplete(reloadedFiles: string[]): void {
// 发送重载完成事�? window.dispatchEvent(new CustomEvent('shader-reload', {
detail: { files: reloadedFiles }
}));
}

public dispose(): void {
if (this.watcher) {
this.watcher.dispose();
}

if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
}

// 简化的文件监视器接�?interface FileWatcher {
on(event: 'change' | 'add' | 'unlink', callback: (filePath: string) => void): void;
dispose(): void;
}

class FileWatcher {
private callbacks: Map<string, ((filePath: string) => void)[]> = new Map();

constructor(options: { patterns: string[]; ignorePatterns: string[]; }) {
// 实际实现需要使用适当的文件监视API
}

public on(event: 'change' | 'add' | 'unlink', callback: (filePath: string) => void): void {
if (!this.callbacks.has(event)) {
this.callbacks.set(event, []);
}
this.callbacks.get(event)!.push(callback);
}

public dispose(): void {
this.callbacks.clear();
}
}

📊 资源监控仪表�?

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
// src/resource-monitor.ts
class ResourceMonitor {
private loadEvents: LoadEvent[] = [];
private performanceMetrics: PerformanceMetric[] = [];
private maxHistorySize: number = 1000;

interface LoadEvent {
resourcePath: string;
startTime: number;
endTime: number;
success: boolean;
error?: string;
size: number;
}

interface PerformanceMetric {
timestamp: number;
memoryUsage: number;
compilationTime: number;
cacheHitRate: number;
activeResources: number;
}

public recordLoad(resource: ShaderResource, startTime?: number): void {
const endTime = Date.now();

this.loadEvents.push({
resourcePath: resource.path,
startTime: startTime || endTime,
endTime: endTime,
success: true,
size: resource.size
});

this.trimHistory();
}

public recordLoadError(path: string, error: string, startTime: number): void {
this.loadEvents.push({
resourcePath: path,
startTime: startTime,
endTime: Date.now(),
success: false,
error: error,
size: 0
});

this.trimHistory();
}

public recordUnload(resource: ShaderResource): void {
console.log(`📤 卸载资源: ${resource.path}`);
}

public getAverageLoadTime(): number {
const successfulLoads = this.loadEvents.filter(e => e.success);
if (successfulLoads.length === 0) return 0;

const totalTime = successfulLoads.reduce((sum, e) => sum + (e.endTime - e.startTime), 0);
return totalTime / successfulLoads.length;
}

public getLoadSuccessRate(): number {
if (this.loadEvents.length === 0) return 1;

const successfulLoads = this.loadEvents.filter(e => e.success).length;
return successfulLoads / this.loadEvents.length;
}

public generateReport(): MonitorReport {
const now = Date.now();
const last24h = now - 24 * 60 * 60 * 1000;

const recent = this.loadEvents.filter(e => e.startTime > last24h);
const successful = recent.filter(e => e.success);
const failed = recent.filter(e => !e.success);

return {
period: '24小时',
totalLoads: recent.length,
successfulLoads: successful.length,
failedLoads: failed.length,
successRate: recent.length > 0 ? successful.length / recent.length : 1,
averageLoadTime: this.getAverageLoadTime(),
totalDataLoaded: successful.reduce((sum, e) => sum + e.size, 0),
errors: failed.map(e => ({ path: e.resourcePath, error: e.error || '未知错误' }))
};
}

private trimHistory(): void {
if (this.loadEvents.length > this.maxHistorySize) {
this.loadEvents = this.loadEvents.slice(-this.maxHistorySize);
}

if (this.performanceMetrics.length > this.maxHistorySize) {
this.performanceMetrics = this.performanceMetrics.slice(-this.maxHistorySize);
}
}
}

interface MonitorReport {
period: string;
totalLoads: number;
successfulLoads: number;
failedLoads: number;
successRate: number;
averageLoadTime: number;
totalDataLoaded: number;
errors: Array<{ path: string; error: string; }>;
}

interface ResourceStats {
totalResources: number;
totalSize: number;
compiledPrograms: number;
averageCompilationTime: number;
memoryUsage: number;
cacheHitRate: number;
loadTime: number;
}

📝 本章小结

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

  1. 资源管理架构: 建立完整的着色器资源管理系统
  2. 缓存系统: 实现高效的资源缓存和LRU清理机制
  3. *热重�?: 掌握开发时的热重载技�?4. 性能监控: 建立资源使用监控和优化体�?

🚀 完整系列总结

恭喜你完成了Cocos Creator Shader完整学习系列!从基础概念到高级优化,从简单特效到复杂渲染技术,你已经掌握了�?

  • 基础知识: Shader语法、Surface Shader架构
  • *实战技�?: 各种视觉特效的实�?- *优化技�?: 性能优化和跨平台兼容�?- *工具�?: 开发工具和工作流程

现在你已经具备了独立开发复杂着色器的能力!🎉�?