t1k:cocos:base:effect-authoring
| Field | Value |
|---|---|
| Module | base |
| Version | 1.16.0 |
| Effort | medium |
| Tools | — |
Keywords: cceffect, ccprogram, chunk, cocos, cocos-creator, effect, glsl, include, macro, material, mtl, shader, uniform
How to invoke
Section titled “How to invoke”/t1k:cocos:base:effect-authoringt1k-cocos-base-effect-authoring
Section titled “t1k-cocos-base-effect-authoring”Hand-authoring .effect files for Cocos Creator 3.8.7. Covers the whole chain from GLSL to a material visible on a node: pick the archetype, get the chunk includes right, pack properties, author the .mtl, wire it up, verify it compiled.
The expensive part of Cocos shader work is not writing GLSL — it is the include graph and the fact that a failed import reports EFX2406: compilation failed: EXPAND THIS MESSAGE FOR MORE INFO and nothing else. This skill exists to make the first attempt compile, and to make the second attempt diagnosable.
Version scope: claims below are verified against 3.8.7 only. § 3 includes the procedure to re-verify on another version — run it rather than assuming.
When to invoke
Section titled “When to invoke”- Writing a new
.effectfrom scratch, or porting one from another engine (pair witht1k-cocos-base-unity-shader-port). - An
.effectfails to import and the editor error is unreadable. - A material exists but does not show up on the node, or MCP refuses to assign it.
- Reviewing an existing
.effectfor the anti-patterns in § 7.
1. Decision tree — pick the archetype
Section titled “1. Decision tree — pick the archetype”| Question | Yes → | No → |
|---|---|---|
Rendered under a Canvas (Sprite / Label / UI)? | B. UI sprite → references/archetype-ui-sprite.md | next |
| Time-scrolled, additive, no depth (background flow, glow, energy)? | C. Scroll-additive VFX → references/archetype-scroll-vfx.md | next |
| Needs world position or world normal (fresnel, rim, world-space FX)? | A. 3D unlit mesh → references/archetype-3d-unlit-mesh.md | A, minus the world-matrix includes |
Archetype D — imported from DCC (a 400+ line multi-technique surface shader, e.g. imported-specular-glossiness-fade.effect): do not hand-author or hand-edit these. Change the material’s property values; if the shading model itself is wrong, re-export from the DCC tool.
2. Anatomy
Section titled “2. Anatomy”CCEffect %{ ...YAML... }% ← techniques / passes / render state / propertiesCCProgram <name> %{ ...GLSL... }% ← one block per shader stage, referenced as `<name>:<fn>`A pass wires them: vert: my-vs:vert means “program my-vs, entry function vert”.
The single most important behavioral fact: the editor compiles every macro variant, not just the one your material uses. A #if branch that never runs at runtime still has to compile. This is why a missing include breaks the import even for a mesh that is never instanced — see § 3.
3. Include map
Section titled “3. Include map”Each row is verified against engine chunk source at
<CocosInstall>/resources/resources/3d/engine/editor/assets/chunks.
| You need | Include | Evidence | Trap |
|---|---|---|---|
| World matrix (pos/normal) | legacy/decode-base and legacy/local-batch | decode-base.chunk:13-16 declares the attributes; :32-38 declares a_matWorld0/1/2 | see “world-matrix trap” below |
| Skinned mesh | legacy/input-standard (instead of decode-base alone) | input-standard.chunk:12-21 — CCVertInput calls CCSkin(position, normal, tangent) | An unskinned normal freezes a fresnel rim in bind pose while the mesh animates under it |
| Sprite texture (2D/UI) | #pragma builtin(local) + layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture; | all three builtin 2D effects use binding = 12 | do not also include decode-base — it declares a_position, and your own declaration then collides |
| Alpha test | builtin/internal/alpha-test, then call ALPHA_TEST(color) | alpha-test.chunk:9-19, two overloads (vec4, float) | the uniform block only exists under USE_ALPHA_TEST; calling it unguarded is fine, the macro handles it |
| Time | builtin/uniforms/cc-global → cc_time.x | — | |
| Local world matrix without instancing | builtin/uniforms/cc-local → cc_matWorld | — | guard behind #if USE_LOCAL for 2D |
The world-matrix trap — why two includes, not one
Section titled “The world-matrix trap — why two includes, not one”legacy/local-batch.chunk is two lines:
#include <builtin/uniforms/cc-local-batched>#include <builtin/functionalities/world-transform>It declares no attributes. CCGetWorldMatrix / CCGetWorldMatrixFull actually live in world-transform.chunk, and their #if USE_INSTANCING branch reads a_matWorld0/1/2 — which only decode-base declares, and only inside its own USE_INSTANCING guard.
Because the editor compiles every variant, the USE_INSTANCING=1 variant is always built. So including world-transform (or local-batch) without decode-base fails every time, even for a mesh that is never instanced:
Error EFX2406: compilation failed: ↓↓↓↓↓ EXPAND THIS MESSAGE FOR MORE INFO ↓↓↓↓↓ERROR: 0:21: 'a_matWorld0' : undeclared identifierTwo dead ends worth naming:
builtin/uniforms/cc-local-batchdoes not exist. The chunk iscc-local-batched(past tense). Wrong name →Error EFX2001: can not resolve 'builtin/uniforms/cc-local-batch'.input-standardreachesdecode-baseindirectly — viadecode-standard(decode-standard.chunk:2). So skinned meshes get the attributes for free and must NOT includedecode-baseagain.
Re-verifying on a version other than 3.8.7
Section titled “Re-verifying on a version other than 3.8.7”CHUNKS="<CocosInstall>/resources/resources/3d/engine/editor/assets/chunks"ls "$CHUNKS/builtin/uniforms/" | grep local # confirm exact chunk namescat "$CHUNKS/legacy/local-batch.chunk" # see what it actually pulls inThen copy the include block from that version’s own assets/effects/builtin-unlit.effect. Grep before you guess — a wrong chunk name costs a full import cycle.
4. Properties and macros
Section titled “4. Properties and macros”Full reference: references/properties-and-macros.md. The conventions that matter:
- Pack scalars into a
vec4, don’t give each float its own uniform:emissionPower: { value: 1.0, target: params.x, editor: { displayName: Emission Power } }fresnelPower: { value: 0.0, target: params.y, editor: { slide: true, range: [0.0, 10.0], step: 0.01 } }uniform Constants { vec4 mainColor; vec4 params; }; - Name the uniform block
Constants. editor: { parent: USE_XXX }hides a property until its macro is on;visible: falsehides the raw packed vector.- Declare an optional feature as a macro and gate both the sampler and its use:
#if USE_EMISSIVE_MAPuniform sampler2D emissiveTexture;#endif
5. Verify loop — three steps
Section titled “5. Verify loop — three steps”A .effect that “looks fine” and a .effect that imported are different things. Always run this.
Step 1 — force the import. A running editor does not pick up newly written asset files on its own:
curl -s -X POST http://127.0.0.1:3000/mcp \ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"manage_asset","arguments":{"action":"refresh","url":"db://assets/<your-folder>"}}}'No MCP? Use the editor’s Assets → Refresh. Never hand-write the .effect.meta — let the editor generate it.
Step 2 — check the artifact. A successful import writes library/<first 2 chars of uuid>/<uuid>.json:
UUID=$(grep -o '"uuid": "[^"]*"' path/to/my.effect.meta | head -1 | cut -d'"' -f4)ls "library/${UUID:0:2}/$UUID.json" && echo COMPILED || echo FAILEDNo artifact = compile failed. There is no other reliable signal.
Step 3 — read the real error. The editor UI truncates to EXPAND THIS MESSAGE FOR MORE INFO; the full GLSL error is in the log (which contains binary bytes, hence grep -a):
grep -a "my.effect" temp/logs/project.log | tail -20Decode what you find with references/troubleshooting.md.
6. Material and wiring
Section titled “6. Material and wiring”Full detail: references/material-and-wiring.md.
.mtl is plain JSON referencing the effect by uuid. Colors are integers 0–255, not floats 0–1:
{ "__type__": "cc.Material", "_effectAsset": { "__uuid__": "<effect uuid>", "__expectedType__": "cc.EffectAsset" }, "_techIdx": 0, "_defines": [ { "USE_EMISSIVE_MAP": true } ], "_props": [ { "mainColor": { "__type__": "cc.Color", "r": 255, "g": 242, "b": 242, "a": 255 } } ]}MCP cannot assign a MeshRenderer material array. set_property on sharedMaterials / _materials silently no-ops (changeVerified: false), passing an array value hangs the editor for ~2 minutes then times out, and the indexed path sharedMaterials.0 is rejected outright. Mesh assignment (property: "mesh") works normally.
Workaround: hand-author a small .prefab with _materials baked in, refresh, then manage_node create assetUuid:<prefab>. Recipe in references/material-and-wiring.md.
7. Anti-patterns
Section titled “7. Anti-patterns”| Don’t | Why |
|---|---|
| Guess a chunk name | cc-local-batch vs cc-local-batched costs a full import cycle. Grep the chunks folder. |
| Give every float its own uniform | Wastes uniform slots and makes the material editor a wall of fields. Pack into vec4. |
Hand-write .effect.meta | The editor owns the uuid. Write the .effect, refresh, let the meta appear. |
Declare in vec3 a_position; after including decode-base | ERROR: 0:154: 'a_position' : redefinition |
| Edit one effect of a shared-math pair | Effects that port the same source shader (e.g. a body and a prop variant) drift apart silently. Note the pairing in a header comment and change both. |
| Treat “it compiled” as “it works” | The binding number on a builtin-tagged sampler is not compile-validated — see references/archetype-ui-sprite.md. |
References
Section titled “References”| File | Contents |
|---|---|
references/archetype-3d-unlit-mesh.md | Verified skeleton, fresnel/rim math, skinning, instancing |
references/archetype-ui-sprite.md | Verified skeleton, cc_spriteTexture binding caveat, 2D macros |
references/archetype-scroll-vfx.md | Verified skeleton, additive state, UV scroll + noise distortion |
references/properties-and-macros.md | Full editor: field table, packing rules, #pragma define-meta |
references/material-and-wiring.md | .mtl schema, MCP limits, bake-prefab workaround |
references/troubleshooting.md | Error code → cause → fix, with verbatim messages |
Related
Section titled “Related”t1k-cocos-base-unity-shader-port— porting from a Unity.shader. It produces a port spec (archetype, property table, render state, translated fragment math) and hands off here for the actual file. Start there when the source is Unity; start here when authoring fresh.t1k-cocos-playable-unity-particle— UnityParticleSystemprefab conversion. Its materials still need an.effect; this skill covers that half.