�?5.1章:VSCode着色器开发扩�?

VSCode是现代着色器开发的首选编辑器。本教程将深入讲解如何配置和使用VSCode扩展来提升着色器开发效率,包括语法高亮、智能提示、错误检查等功能�?

🎯 学习目标

  • 掌握VSCode着色器开发环境配�?- 学会使用专业的着色器开发扩�?- 了解自定义扩展开发方�?- 建立高效的着色器开发工作流

🛠�?核心扩展推荐

必装扩展列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"recommendations": [
"slevesque.shader",
"raczzalan.webgl-glsl-editor",
"dtoplak.vscode-glsllint",
"stef-levesque.hexdump",
"ms-vscode.live-server",
"bradlc.vscode-tailwindcss",
"formulahendry.auto-rename-tag",
"christian-kohler.path-intellisense",
"ms-vscode.vscode-json",
"redhat.vscode-yaml"
]
}

Shader扩展配置

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
// .vscode/settings.json
{
"shader.validate": true,
"shader.completion": true,
"shader.hover": true,
"shader.format": true,
"shader.linting": true,
"shader.includePaths": [
"./source/_posts/CocosShader/includes",
"./node_modules/@cocos/cocos-shader-includes"
],
"files.associations": {
"*.effect": "yaml",
"*.chunk": "glsl",
"*.vert": "glsl",
"*.frag": "glsl",
"*.vs": "glsl",
"*.fs": "glsl",
"*.glsl": "glsl",
"*.hlsl": "hlsl"
},
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.wordWrap": "on",
"editor.minimap.enabled": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"workbench.colorTheme": "Dark+ (default dark)",
"editor.semanticHighlighting.enabled": true
}

🎨 自定义着色器语法高亮

GLSL语法高亮配置

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
// .vscode/syntax/cocos-shader.tmLanguage.json
{
"name": "Cocos Shader",
"scopeName": "source.cocos-shader",
"fileTypes": ["effect", "chunk"],
"patterns": [
{
"name": "meta.cceffect.block",
"begin": "CCEffect\\s*%\\{",
"end": "\\}%",
"patterns": [
{
"include": "source.yaml"
}
]
},
{
"name": "meta.ccprogram.block",
"begin": "CCProgram\\s+(\\w+)\\s*%\\{",
"beginCaptures": {
"1": {
"name": "entity.name.function.ccprogram"
}
},
"end": "\\}%",
"patterns": [
{
"include": "#glsl-content"
}
]
},
{
"name": "meta.ccinclude.block",
"begin": "CCInclude\\s+(\\w+)\\s*%\\{",
"beginCaptures": {
"1": {
"name": "entity.name.function.ccinclude"
}
},
"end": "\\}%",
"patterns": [
{
"include": "#glsl-content"
}
]
}
],
"repository": {
"glsl-content": {
"patterns": [
{
"name": "keyword.control.glsl",
"match": "\\b(if|else|for|while|do|break|continue|return|discard)\\b"
},
{
"name": "storage.type.glsl",
"match": "\\b(void|bool|int|uint|float|double|vec2|vec3|vec4|bvec2|bvec3|bvec4|ivec2|ivec3|ivec4|uvec2|uvec3|uvec4|dvec2|dvec3|dvec4|mat2|mat3|mat4|mat2x2|mat2x3|mat2x4|mat3x2|mat3x3|mat3x4|mat4x2|mat4x3|mat4x4|dmat2|dmat3|dmat4|sampler2D|samplerCube|sampler2DArray|samplerCubeArray|isampler2D|usampler2D)\\b"
},
{
"name": "storage.modifier.glsl",
"match": "\\b(in|out|inout|uniform|varying|attribute|const|precision|highp|mediump|lowp|centroid|flat|smooth|noperspective|patch|sample|subroutine|coherent|volatile|restrict|readonly|writeonly|layout|location|binding|offset|align|set|push_constant)\\b"
},
{
"name": "support.function.glsl",
"match": "\\b(radians|degrees|sin|cos|tan|asin|acos|atan|sinh|cosh|tanh|asinh|acosh|atanh|pow|exp|log|exp2|log2|sqrt|inversesqrt|abs|sign|floor|trunc|round|roundEven|ceil|fract|mod|modf|min|max|clamp|mix|step|smoothstep|isnan|isinf|floatBitsToInt|floatBitsToUint|intBitsToFloat|uintBitsToFloat|length|distance|dot|cross|normalize|faceforward|reflect|refract|matrixCompMult|outerProduct|transpose|determinant|inverse|lessThan|lessThanEqual|greaterThan|greaterThanEqual|equal|notEqual|any|all|not|texture|textureProj|textureLod|textureOffset|texelFetch|texelFetchOffset|textureProjOffset|textureLodOffset|textureProjLod|textureProjLodOffset|textureGrad|textureGradOffset|textureProjGrad|textureProjGradOffset|textureSize|textureQueryLod|textureQueryLevels|textureSamples|textureGather|textureGatherOffset|textureGatherOffsets|dFdx|dFdy|fwidth|noise1|noise2|noise3|noise4|EmitVertex|EndPrimitive)\\b"
},
{
"name": "variable.language.glsl",
"match": "\\b(gl_VertexID|gl_InstanceID|gl_Position|gl_PointSize|gl_ClipDistance|gl_CullDistance|gl_PrimitiveIDIn|gl_InvocationID|gl_PrimitiveID|gl_Layer|gl_ViewportIndex|gl_TessLevelOuter|gl_TessLevelInner|gl_TessCoord|gl_PatchVerticesIn|gl_FragCoord|gl_FrontFacing|gl_ClipDistance|gl_CullDistance|gl_PointCoord|gl_PrimitiveID|gl_SampleID|gl_SamplePosition|gl_SampleMaskIn|gl_Layer|gl_ViewportIndex|gl_FragDepth|gl_SampleMask|gl_ClipVertex|gl_FrontColor|gl_BackColor|gl_FrontSecondaryColor|gl_BackSecondaryColor|gl_TexCoord|gl_FogFragCoord|gl_Color|gl_SecondaryColor)\\b"
},
{
"name": "constant.numeric.glsl",
"match": "\\b([0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?[fFhH]?)\\b"
},
{
"name": "string.quoted.double.glsl",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.glsl",
"match": "\\\\."
}
]
},
{
"name": "comment.line.double-slash.glsl",
"match": "//.*$"
},
{
"name": "comment.block.glsl",
"begin": "/\\*",
"end": "\\*/"
}
]
}
}
}

代码片段配置

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
// .vscode/snippets/cocos-shader.code-snippets
{
"CCEffect Template": {
"prefix": "cceffect",
"body": [
"CCEffect %{",
" techniques:",
" - name: ${1:technique-name}",
" passes:",
" - vert: ${2:vertex-program}:vert",
" frag: ${3:fragment-program}:frag",
" properties:",
" ${4:mainTexture}: { value: white }",
" ${5:mainColor}: { value: [1, 1, 1, 1], editor: { type: color } }",
" rasterizerState:",
" cullMode: ${6:back}",
" depthStencilState:",
" depthTest: ${7:true}",
" depthWrite: ${8:true}",
"}%",
"",
"CCProgram ${2:vertex-program} %{",
" precision highp float;",
" ",
" in vec3 a_position;",
" in vec3 a_normal;",
" in vec2 a_texCoord;",
" ",
" out vec3 v_worldPos;",
" out vec3 v_normal;",
" out vec2 v_uv;",
" ",
" uniform CCGlobal {",
" mat4 cc_matViewProj;",
" };",
" ",
" uniform CCLocal {",
" mat4 cc_matWorld;",
" mat4 cc_matWorldIT;",
" };",
" ",
" void vert() {",
" vec4 worldPos = cc_matWorld * vec4(a_position, 1.0);",
" v_worldPos = worldPos.xyz;",
" v_normal = normalize((cc_matWorldIT * vec4(a_normal, 0.0)).xyz);",
" v_uv = a_texCoord;",
" ",
" gl_Position = cc_matViewProj * worldPos;",
" }",
"}%",
"",
"CCProgram ${3:fragment-program} %{",
" precision highp float;",
" ",
" in vec3 v_worldPos;",
" in vec3 v_normal;",
" in vec2 v_uv;",
" ",
" layout(location = 0) out vec4 fragColor;",
" ",
" uniform sampler2D ${4:mainTexture};",
" uniform vec4 ${5:mainColor};",
" ",
" void frag() {",
" vec4 baseColor = texture(${4:mainTexture}, v_uv) * ${5:mainColor};",
" ",
" vec3 normal = normalize(v_normal);",
" vec3 lighting = vec3(1.0);",
" ",
" fragColor = vec4(baseColor.rgb * lighting, baseColor.a);",
" }",
"}%"
],
"description": "创建完整的Cocos Shader模板"
},
"Surface Shader Template": {
"prefix": "surface",
"body": [
"CCProgram surface-${1:shader-name} %{",
" precision highp float;",
" ",
" #include <surfaces/effect-macros>",
" #include <surfaces/default-functions>",
" ",
" #pragma define-meta SURFACES_EFFECT_NAME ${1:shader-name}",
" ",
" uniform sampler2D ${2:albedoTexture};",
" uniform vec4 ${3:mainColor};",
" ",
" #pragma surface-vertex",
" void surfaceVertex() {",
" SurfaceData.worldPos = (cc_matWorld * In.position).xyz;",
" SurfaceData.worldNormal = normalize((cc_matWorldIT * vec4(In.normal, 0.0)).xyz);",
" SurfaceData.uv = In.texCoord;",
" }",
" ",
" #pragma surface-fragment",
" void surfaceFragment() {",
" vec4 baseColor = texture(${2:albedoTexture}, SurfaceData.uv) * ${3:mainColor};",
" ",
" SurfaceData.albedo = baseColor.rgb;",
" SurfaceData.alpha = baseColor.a;",
" SurfaceData.metallic = 0.0;",
" SurfaceData.roughness = 1.0;",
" SurfaceData.normal = SurfaceData.worldNormal;",
" }",
"}%"
],
"description": "创建Surface Shader模板"
},
"Include Template": {
"prefix": "ccinclude",
"body": [
"CCInclude ${1:include-name} %{",
" precision highp float;",
" ",
" // ${2:Include description}",
" ",
" ${3:// Function definitions}",
" ",
"}%"
],
"description": "创建Include文件模板"
},
"Function Definition": {
"prefix": "func",
"body": [
"${1:return-type} ${2:function-name}(${3:parameters}) {",
" ${4:// Function body}",
" ${5:return result;}",
"}"
],
"description": "GLSL函数定义"
}
}

🔧 扩展开�?

Cocos Shader扩展开�?

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
// src/extension.ts
import * as vscode from 'vscode';
import { CocosShaderLanguageProvider } from './languageProvider';
import { CocosShaderDiagnosticsProvider } from './diagnosticsProvider';
import { CocosShaderPreviewProvider } from './previewProvider';

export function activate(context: vscode.ExtensionContext) {
console.log('🚀 Cocos Shader扩展已激�?);

// 注册语言服务提供�? const languageProvider = new CocosShaderLanguageProvider();
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
{ language: 'cocos-shader' },
languageProvider,
'.',
'_',
':'
)
);

// 注册诊断提供�? const diagnosticsProvider = new CocosShaderDiagnosticsProvider();
context.subscriptions.push(diagnosticsProvider);

// 注册预览提供�? const previewProvider = new CocosShaderPreviewProvider(context);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'cocosShaderPreview',
previewProvider
)
);

// 注册命令
registerCommands(context);

// 监听文件变化
setupFileWatchers(context);
}

class CocosShaderLanguageProvider implements vscode.CompletionItemProvider {
private keywords: vscode.CompletionItem[] = [];
private functions: vscode.CompletionItem[] = [];
private types: vscode.CompletionItem[] = [];

constructor() {
this.initializeCompletions();
}

private initializeCompletions(): void {
// GLSL关键字补�? const glslKeywords = [
'precision', 'highp', 'mediump', 'lowp',
'in', 'out', 'inout', 'uniform', 'varying', 'attribute',
'layout', 'location', 'binding',
'if', 'else', 'for', 'while', 'do', 'break', 'continue', 'return', 'discard'
];

this.keywords = glslKeywords.map(keyword => {
const item = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword);
item.detail = `GLSL关键字`;
return item;
});

// GLSL内置函数补全
const glslFunctions = [
{ name: 'texture', detail: 'texture(sampler, coord)', doc: '纹理采样函数' },
{ name: 'normalize', detail: 'normalize(vec)', doc: '向量归一�? },
{ name: 'dot', detail: 'dot(vec1, vec2)', doc: '向量点积' },
{ name: 'cross', detail: 'cross(vec1, vec2)', doc: '向量叉积' },
{ name: 'mix', detail: 'mix(x, y, a)', doc: '线性插�? },
{ name: 'step', detail: 'step(edge, x)', doc: '阶跃函数' },
{ name: 'smoothstep', detail: 'smoothstep(edge0, edge1, x)', doc: '平滑阶跃函数' },
{ name: 'clamp', detail: 'clamp(x, minVal, maxVal)', doc: '值限制函�? },
{ name: 'pow', detail: 'pow(x, y)', doc: '幂函�? },
{ name: 'sin', detail: 'sin(angle)', doc: '正弦函数' },
{ name: 'cos', detail: 'cos(angle)', doc: '余弦函数' }
];

this.functions = glslFunctions.map(func => {
const item = new vscode.CompletionItem(func.name, vscode.CompletionItemKind.Function);
item.detail = func.detail;
item.documentation = new vscode.MarkdownString(func.doc);
item.insertText = new vscode.SnippetString(`${func.name}($1)`);
return item;
});

// GLSL类型补全
const glslTypes = [
'void', 'bool', 'int', 'uint', 'float',
'vec2', 'vec3', 'vec4', 'bvec2', 'bvec3', 'bvec4',
'ivec2', 'ivec3', 'ivec4', 'uvec2', 'uvec3', 'uvec4',
'mat2', 'mat3', 'mat4',
'sampler2D', 'samplerCube', 'sampler2DArray'
];

this.types = glslTypes.map(type => {
const item = new vscode.CompletionItem(type, vscode.CompletionItemKind.Class);
item.detail = `GLSL数据类型`;
return item;
});
}

public provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const line = document.lineAt(position);
const linePrefix = line.text.substr(0, position.character);

// 检查是否在CCEffect块中
if (this.isInCCEffectBlock(document, position)) {
return this.provideCCEffectCompletions(linePrefix);
}

// 检查是否在CCProgram块中
if (this.isInCCProgramBlock(document, position)) {
return this.provideGLSLCompletions(linePrefix);
}

return [];
}

private isInCCEffectBlock(document: vscode.TextDocument, position: vscode.Position): boolean {
// 检查是否在CCEffect %{ ... }%块中
const text = document.getText(new vscode.Range(0, 0, position.line, position.character));
const ccEffectStart = text.lastIndexOf('CCEffect %{');
const ccEffectEnd = text.lastIndexOf('}%');

return ccEffectStart > ccEffectEnd;
}

private isInCCProgramBlock(document: vscode.TextDocument, position: vscode.Position): boolean {
// 检查是否在CCProgram %{ ... }%块中
const text = document.getText(new vscode.Range(0, 0, position.line, position.character));
const ccProgramStart = text.lastIndexOf('CCProgram');
const ccProgramEnd = text.lastIndexOf('}%');

return ccProgramStart > ccProgramEnd;
}

private provideCCEffectCompletions(linePrefix: string): vscode.CompletionItem[] {
// CCEffect YAML配置补全
const ccEffectItems = [
this.createYamlCompletionItem('techniques', 'techniques:\n- name: ${1:technique-name}'),
this.createYamlCompletionItem('passes', 'passes:\n- vert: ${1:vs}:vert\n frag: ${2:fs}:frag'),
this.createYamlCompletionItem('properties', 'properties:\n ${1:property}: { value: ${2:value} }'),
this.createYamlCompletionItem('rasterizerState', 'rasterizerState:\n cullMode: ${1:back}'),
this.createYamlCompletionItem('depthStencilState', 'depthStencilState:\n depthTest: ${1:true}\n depthWrite: ${2:true}'),
this.createYamlCompletionItem('blendState', 'blendState:\n targets:\n - blend: ${1:true}')
];

return ccEffectItems;
}

private provideGLSLCompletions(linePrefix: string): vscode.CompletionItem[] {
return [...this.keywords, ...this.functions, ...this.types];
}

private createYamlCompletionItem(label: string, snippet: string): vscode.CompletionItem {
const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Property);
item.insertText = new vscode.SnippetString(snippet);
item.detail = 'CCEffect配置';
return item;
}
}

class CocosShaderDiagnosticsProvider {
private diagnosticCollection: vscode.DiagnosticCollection;

constructor() {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('cocos-shader');

// 监听文档变化
vscode.workspace.onDidSaveTextDocument(this.validateDocument, this);
vscode.workspace.onDidOpenTextDocument(this.validateDocument, this);
vscode.workspace.onDidChangeTextDocument(event => {
this.validateDocument(event.document);
});
}

private validateDocument(document: vscode.TextDocument): void {
if (document.languageId !== 'cocos-shader') {
return;
}

const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();

// 检查常见错�? this.checkSyntaxErrors(document, text, diagnostics);
this.checkBestPractices(document, text, diagnostics);

this.diagnosticCollection.set(document.uri, diagnostics);
}

private checkSyntaxErrors(document: vscode.TextDocument, text: string, diagnostics: vscode.Diagnostic[]): void {
// 检查CCEffect块语�? const ccEffectMatches = text.matchAll(/CCEffect\s*%\{[\s\S]*?\}%/g);
for (const match of ccEffectMatches) {
// 验证YAML语法
const yamlContent = match[0].slice(match[0].indexOf('%{') + 2, -2);
try {
// 这里可以使用yaml解析库验证语�? } catch (error) {
const range = this.getMatchRange(document, match);
diagnostics.push(new vscode.Diagnostic(
range,
`CCEffect YAML语法错误: ${error.message}`,
vscode.DiagnosticSeverity.Error
));
}
}

// 检查CCProgram块语�? const ccProgramMatches = text.matchAll(/CCProgram\s+(\w+)\s*%\{[\s\S]*?\}%/g);
for (const match of ccProgramMatches) {
this.validateGLSLSyntax(document, match, diagnostics);
}
}

private checkBestPractices(document: vscode.TextDocument, text: string, diagnostics: vscode.Diagnostic[]): void {
// 检查性能最佳实�? const lines = text.split('\n');

lines.forEach((line, index) => {
const trimmedLine = line.trim();

// 检查精度声�? if (trimmedLine.includes('float') && !text.includes('precision')) {
const range = new vscode.Range(index, 0, index, line.length);
diagnostics.push(new vscode.Diagnostic(
range,
'建议在着色器开头声明精度:precision mediump float;',
vscode.DiagnosticSeverity.Warning
));
}

// 检查discard使用
if (trimmedLine.includes('discard')) {
const range = new vscode.Range(index, line.indexOf('discard'), index, line.indexOf('discard') + 7);
diagnostics.push(new vscode.Diagnostic(
range,
'过度使用discard可能影响性能,特别是在移动设备上',
vscode.DiagnosticSeverity.Information
));
}

// 检查复杂数学函�? const expensiveFunctions = ['pow', 'sin', 'cos', 'tan', 'exp', 'log'];
expensiveFunctions.forEach(func => {
if (trimmedLine.includes(func + '(')) {
const range = new vscode.Range(index, line.indexOf(func), index, line.indexOf(func) + func.length);
diagnostics.push(new vscode.Diagnostic(
range,
`${func}是昂贵的数学函数,考虑使用查找表或近似算法`,
vscode.DiagnosticSeverity.Information
));
}
});
});
}

private validateGLSLSyntax(document: vscode.TextDocument, match: RegExpMatchArray, diagnostics: vscode.Diagnostic[]): void {
// 简化的GLSL语法验证
const glslContent = match[0];

// 检查括号匹�? let braceCount = 0;
let parenCount = 0;

for (let i = 0; i < glslContent.length; i++) {
switch (glslContent[i]) {
case '{': braceCount++; break;
case '}': braceCount--; break;
case '(': parenCount++; break;
case ')': parenCount--; break;
}
}

if (braceCount !== 0 || parenCount !== 0) {
const range = this.getMatchRange(document, match);
diagnostics.push(new vscode.Diagnostic(
range,
'括号不匹�?,
vscode.DiagnosticSeverity.Error
));
}
}

private getMatchRange(document: vscode.TextDocument, match: RegExpMatchArray): vscode.Range {
const text = document.getText();
const startIndex = match.index || 0;
const endIndex = startIndex + match[0].length;

const startPos = document.positionAt(startIndex);
const endPos = document.positionAt(endIndex);

return new vscode.Range(startPos, endPos);
}

public dispose(): void {
this.diagnosticCollection.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
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
// src/previewProvider.ts
class CocosShaderPreviewProvider implements vscode.WebviewViewProvider {
private webviewView?: vscode.WebviewView;
private currentDocument?: vscode.TextDocument;

constructor(private context: vscode.ExtensionContext) {}

public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
token: vscode.CancellationToken
): void {
this.webviewView = webviewView;

webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this.context.extensionUri]
};

webviewView.webview.html = this.getWebviewContent();

// 监听文档变化
vscode.window.onDidChangeActiveTextEditor(this.updatePreview, this);
vscode.workspace.onDidSaveTextDocument(this.updatePreview, this);
}

private updatePreview(document?: vscode.TextDocument): void {
if (!this.webviewView) return;

const activeDocument = document || vscode.window.activeTextEditor?.document;
if (!activeDocument || activeDocument.languageId !== 'cocos-shader') {
return;
}

this.currentDocument = activeDocument;
const shaderContent = this.parseShaderContent(activeDocument.getText());

this.webviewView.webview.postMessage({
command: 'updateShader',
content: shaderContent
});
}

private parseShaderContent(text: string): ShaderContent {
const content: ShaderContent = {
effect: null,
programs: []
};

// 解析CCEffect
const effectMatch = text.match(/CCEffect\s*%\{([\s\S]*?)\}%/);
if (effectMatch) {
try {
// 这里需要YAML解析
content.effect = effectMatch[1];
} catch (error) {
console.error('解析CCEffect失败:', error);
}
}

// 解析CCProgram
const programMatches = text.matchAll(/CCProgram\s+(\w+)\s*%\{([\s\S]*?)\}%/g);
for (const match of programMatches) {
content.programs.push({
name: match[1],
source: match[2]
});
}

return content;
}

private getWebviewContent(): string {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cocos Shader Preview</title>
<style>
body {
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
margin: 0;
padding: 10px;
}

.preview-container {
width: 100%;
height: 300px;
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
overflow: hidden;
position: relative;
}

#preview-canvas {
width: 100%;
height: 100%;
display: block;
}

.info-panel {
margin-top: 10px;
padding: 10px;
background-color: var(--vscode-editor-inactiveSelectionBackground);
border-radius: 4px;
font-size: 12px;
}

.error-message {
color: var(--vscode-errorForeground);
background-color: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
padding: 8px;
border-radius: 4px;
margin-bottom: 10px;
}

.property-list {
margin-top: 10px;
}

.property-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
padding: 2px 0;
}

.property-name {
font-weight: bold;
}

.property-value {
color: var(--vscode-descriptionForeground);
}
</style>
</head>
<body>
<div class="preview-container">
<canvas id="preview-canvas"></canvas>
</div>

<div class="info-panel">
<h3>着色器信息</h3>
<div id="shader-info">
<p>请选择一个Cocos Shader文件来预�?/p>
</div>
</div>

<script>
const vscode = acquireVsCodeApi();
const canvas = document.getElementById('preview-canvas');
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');

let currentShader = null;

// 监听消息
window.addEventListener('message', event => {
const message = event.data;

switch (message.command) {
case 'updateShader':
updateShaderPreview(message.content);
break;
}
});

function updateShaderPreview(content) {
if (!gl) {
showError('WebGL不支�?);
return;
}

try {
// 编译着色器
const shader = compileShader(content);
if (shader) {
currentShader = shader;
renderPreview();
updateInfo(content);
}
} catch (error) {
showError('着色器编译失败: ' + error.message);
}
}

function compileShader(content) {
// 简化的着色器编译
// 实际实现需要更复杂的GLSL转换逻辑
const vertexProgram = content.programs.find(p => p.name.includes('vs') || p.name.includes('vert'));
const fragmentProgram = content.programs.find(p => p.name.includes('fs') || p.name.includes('frag'));

if (!vertexProgram || !fragmentProgram) {
throw new Error('找不到顶点着色器或片段着色器');
}

// 转换Cocos Shader到标准GLSL
const vertexShader = convertToGLSL(vertexProgram.source, 'vertex');
const fragmentShader = convertToGLSL(fragmentProgram.source, 'fragment');

return createShaderProgram(vertexShader, fragmentShader);
}

function convertToGLSL(source, type) {
// 简化的转换逻辑
let glslSource = source;

// 移除Cocos特定的语�? glslSource = glslSource.replace(/CCGlobal\s*\{[\s\S]*?\}/g, '');
glslSource = glslSource.replace(/CCLocal\s*\{[\s\S]*?\}/g, '');

// 添加WebGL兼容的头�? if (type === 'vertex') {
glslSource = 'attribute vec3 a_position;\\n' + glslSource;
} else {
glslSource = 'precision mediump float;\\n' + glslSource;
}

return glslSource;
}

function createShaderProgram(vertexSource, fragmentSource) {
const vertexShader = compileShaderSource(gl.VERTEX_SHADER, vertexSource);
const fragmentShader = compileShaderSource(gl.FRAGMENT_SHADER, fragmentSource);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error('着色器程序链接失败: ' + gl.getProgramInfoLog(program));
}

return program;
}

function compileShaderSource(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error('着色器编译失败: ' + gl.getShaderInfoLog(shader));
}

return shader;
}

function renderPreview() {
if (!currentShader) return;

// 简单的预览渲染
gl.useProgram(currentShader);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.2, 0.2, 0.2, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

// 这里可以渲染一个简单的平面或立方体来展示着色器效果
}

function updateInfo(content) {
const infoDiv = document.getElementById('shader-info');

let html = '<div class="property-list">';
html += \`<div class="property-item"><span class="property-name">程序数量:</span><span class="property-value">\${content.programs.length}</span></div>\`;

content.programs.forEach(program => {
html += \`<div class="property-item"><span class="property-name">\${program.name}:</span><span class="property-value">已编�?/span></div>\`;
});

html += '</div>';
infoDiv.innerHTML = html;
}

function showError(message) {
const infoDiv = document.getElementById('shader-info');
infoDiv.innerHTML = \`<div class="error-message">\${message}</div>\`;
}

// 初始化画布尺�? function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}

window.addEventListener('resize', resizeCanvas);
resizeCanvas();
</script>
</body>
</html>
`;
}
}

interface ShaderContent {
effect: string | null;
programs: Array<{
name: string;
source: string;
}>;
}

📝 本章小结

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

  1. VSCode配置: 配置专业的着色器开发环�?2. 扩展使用: 使用现有扩展提升开发效�?3. *自定义扩�?: 开发专门的Cocos Shader扩展
  2. 实时预览: 实现着色器的实时预览功�?

🚀 下一步学�?

继续学习着色器开发工作流!🔄✨