Cocos Creator 材质系统源码导读:Effect 到 Shader 的完整实现路径(含 3 个避坑点)
【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine
Cocos Creator 的材质系统围绕 Effect 与 Material 两个核心资源展开:Effect 存放着色器定义与默认参数,Material 持有运行时实例状态并在渲染阶段上传 uniform。本文沿 Material 的初始化路径与 CSM 阴影更新链走读源码,给出从编写 effect 到调参落地的完整流程,并整理出 3 个高频坑点,帮助你读懂引擎材质管线内部机制。
共享材质污染问题如何避免
场景里同时存在十个引用同一 effect 的模型,你只想给其中一盏灯换个金属度,结果十个节点全部变色。原因不是写错了代码,而是实例化时机不对:共享材质对象上的属性会被所有使用方读到。
另一个场景:3D 项目的实时阴影由 DirectionalLightComponent 的 shadowDistance、csmLevel、shadowBias 共同控制。参数散落在组件与渲染管线两处,调参时漏掉任何一环,阴影都会出现距离突变或锯齿。
最小实战:如何从内置 effect 出发写出第一个自定义材质
- 通读 editor/assets/effects/builtin-standard.effect 的前 30 行,理解
techniques / passes / properties三段的含义。 - 在 assets 下复制该文件改名,只保留需要的属性,删掉用不到的
defines。 - 用 TS 代码初始化材质并设置属性:
cocos/asset/assets/material.ts:
import { Material } from 'cc'; const mat = new Material(); mat.initialize({ effectName: 'my-pbr', technique: 0 }); mat.setProperty('roughness', 0.2); mat.setProperty('metallic', 0.8); mat.setProperty('mainColor', new Color(1, 1, 1, 1)); meshRenderer.material = mat;initialize接收的IMaterialInfo支持effectName/effectAsset/technique/defines/states五个字段,effectName 与 effectAsset 至少要指定一个。
源码走读:一个 .effect 文件如何变成可渲染的 Pass
这一节按执行链回答"一个 .effect 文件如何变成可渲染的 Pass",三个关键位置各自职责独立。
- EffectAsset:effect 的注册表。cocos/asset/assets/effect-asset.ts 中
EffectAsset内部维护静态注册表_effects与实例字段techniques(约 305 行)。effect 文件的 YAML 段由编辑器解析后填充这两个字段,EffectAsset.get(name)(约 261 行)就是 Material 按名字查表的入口。 - Material.initialize:从名字到 Pass 的装配过程。cocos/asset/assets/material.ts 的
initialize(约 207 行)按顺序做三件事:_fillInfo(约 389 行)根据info.effectName调用EffectAsset.get拿到 EffectAsset;_prepareInfo把defines、states补丁填进每个 pass;_createPasses(约 423 行)按 technique 的 passes 数量逐个生成 Pass。之后setProperty(约 303 行)遍历所有 Pass 逐个_uploadProperty;多 pass 材质可传passIdx指定目标,越界时开发模式打印警告 ID 16372。 - DirectionalLightComponent → CSMLayers:阴影参数如何生效。cocos/3d/lights/directional-light-component.ts 持有
_shadowDistance = 50、_shadowBias = 0.00001、_csmLevel = CSMLevel.LEVEL_4三个默认值;cocos/rendering/shadow/csm-layers.ts 的CSMLayers.update每帧检查csmLevel、shadowDistance、csmNeedUpdate是否变化,任一变化才调用_splitFrustumLevels重新切分视锥,否则直接复用旧结果。csmLevel的实际取值由管线能力决定:csmSupported为 false 时强制取 1。
渲染阶段,UBOGlobal/UBOCamera(定义于 cocos/rendering/define.ts)统一缓存相机矩阵与时间,材质属性与全局数据在这里合流。
📝 参数调优对照表
| 参数 | 默认值 | 调节建议与效果差异 |
|---|---|---|
| shadowDistance | 50(cocos/3d/lights/directional-light-component.ts) | 加大到 120~200 覆盖更大场景;阴影贴图覆盖面积变大,远处细节变粗 |
| shadowBias | 0.00001 | 加到 0.001~0.003 可消除阴影自照(acne);过大会产生 peter-panning(阴影脱离物体) |
| csmLevel | LEVEL_4 | 降到 LEVEL_2 可明显减少阴影贴图开销;近处阴影精度下降,适合中低端设备 |
| roughness | 0.5(builtin-standard 默认) | 0.8 以上高光变宽变柔,适合皮肤、布料;接近 0 时高光锐利 |
| metallic | 0.0(builtin-standard 默认) | 0.8 以上呈现金属反射;注意此时 albedo 会被压暗,非金属物体应保持 0 |
⚠️ 避坑清单:三个真实问题
1. 改共享材质导致全场景变色
- 现象:调整某个节点材质颜色后,所有使用同一 effect 的节点一起变色。
- 原因:
Material.initialize通过EffectAsset.get(name)取到的是全局注册表里的同一份资源,直接改共享实例等于改所有人。 - 解法:需要独立表现的节点单独
new Material()并initialize后再setProperty,让每个节点持有独立实例。
2. 多 pass 材质 setProperty 不生效
- 现象:设置了属性但某个 pass 没反应,控制台偶发警告 16372。
- 原因:多 pass 材质下
passIdx传错或越界,release 模式该 pass 会被静默跳过。 - 解法:对齐 effect 文件里
passes的数量;不确定时不传passIdx,引擎会写入所有匹配的 pass。
3. 阴影 acne(表面自照条纹)
- 现象:墙面、地面向光面出现网格状黑纹。
- 原因:
shadowBias默认 0.00001 过小,深度比较没有留出误差。 - 解法:先将 bias 提到 0.001;仍有问题再加大
shadowNormalBias,并检查shadowDistance是否过大导致贴图密度不足。
收尾
核心思路一句话:Effect 管定义、Material 管实例、渲染层管上传,改参数前先分清对象属于哪一层。延伸方向可以顺着UBOGlobal与UBOCamera继续读渲染管线全局缓冲的更新时机,这是接入自定义后处理 pass 的前置知识。
【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考