Files
XCEngine/docs/api/XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md

188 lines
6.9 KiB
Markdown
Raw Normal View History

2026-03-26 21:32:43 +08:00
# MeshRendererComponent
**命名空间**: `XCEngine::Components`
**类型**: `class`
**头文件**: `XCEngine/Components/MeshRendererComponent.h`
**描述**: 保存材质槽、项目资产 `AssetRef`、运行时材质句柄以及少量渲染附加状态,把对象的“怎么画”这一侧数据收口到一个组件里。
2026-03-27 16:15:00 +08:00
## 角色概述
`MeshRendererComponent` 回答的问题是:
2026-03-27 16:15:00 +08:00
- 这个对象的 mesh 该用哪些材质、带哪些附加渲染状态去画?
2026-03-27 16:15:00 +08:00
当前它的职责可以分成四层:
2026-03-27 16:15:00 +08:00
- 保存运行时 `Material` 句柄数组
- 保存项目材质的稳定 `AssetRef`
- 仅对 `builtin://` 这类 virtual path 保留 `materialPaths`
- 保存 `castShadows / receiveShadows / renderLayer`
2026-03-27 16:15:00 +08:00
和 [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md) 一样,当前最需要纠正的文档心智是:
- 对项目资产材质,正式序列化协议已经是 `materialRefs`
- `materialPaths` 主要保留给 virtual path而不是给普通项目路径长期兜底
2026-03-27 16:15:00 +08:00
## 当前状态模型
2026-03-27 16:15:00 +08:00
当前实现维护 5 组与材质槽相关的状态:
2026-03-27 16:15:00 +08:00
| 状态 | 类型 | 作用 |
|------|------|------|
| `m_materials` | `std::vector<ResourceHandle<Material>>` | 当前已兑现的运行时材质句柄。 |
| `m_materialPaths` | `std::vector<std::string>` | 槽位路径缓存;项目资产通常会是 `Assets/...`virtual 资源可能是 `builtin://...`。 |
| `m_materialRefs` | `std::vector<Resources::AssetRef>` | 项目资产稳定身份。 |
| `m_pendingMaterialLoads` | `std::vector<std::shared_ptr<...>>` | deferred async material load 的挂起结果。 |
| `m_asyncMaterialLoadRequested` | `std::vector<bool>` | 防止重复发起异步加载。 |
2026-03-27 16:15:00 +08:00
所有数组都按槽位对齐;`slot 2` 的 path、ref、handle 和 pending state 都在描述同一材质槽。
2026-03-27 16:15:00 +08:00
## 绑定与恢复流程
2026-03-27 16:15:00 +08:00
### 1. 运行时按路径设置
2026-03-27 16:15:00 +08:00
[SetMaterialPath](SetMaterialPath.md) 当前仍然是同步接口:
2026-03-27 16:15:00 +08:00
1. 自动扩容到目标槽位
2. 清掉旧 pending async load 状态
3. 写入 `m_materialPaths[index]`
4. 若路径为空,则清掉 handle 和 `AssetRef`
5. 若路径非空,则同步 `Load<Material>(path)`
6. 再尝试按路径回填 `m_materialRefs[index]`
2026-03-27 16:15:00 +08:00
因此对项目路径 `Assets/runtime.material` 来说,运行时是允许直接按路径设置的;但场景文本正式协议已经不是“只存路径”。
2026-03-27 16:15:00 +08:00
### 2. 运行时按句柄设置
2026-03-27 16:15:00 +08:00
[SetMaterial](SetMaterial.md) / [SetMaterials](SetMaterials.md) 会从句柄反推出路径,再按路径尝试回填 `AssetRef`
2026-03-27 16:15:00 +08:00
这让“运行时直接塞句柄”和“后续还能稳定序列化回项目身份”保持一致。
2026-03-27 16:15:00 +08:00
### 3. 反序列化恢复
2026-03-27 16:15:00 +08:00
[Deserialize](Deserialize.md) 当前只识别:
2026-03-27 16:15:00 +08:00
- `materialPaths=<path0|path1|...>`
- `materialRefs=<guid,localId,resourceType|...>`
- `castShadows`
- `receiveShadows`
- `renderLayer`
恢复顺序是:
1. 某槽位若有有效 `materialRef`,优先按 `AssetRef` 恢复
2. 若处于 deferred scene load先尝试 `TryResolveAssetPath(materialRef, path)` 恢复 `Assets/...`
3. 若不在 deferred 模式,则直接按 `AssetRef` 加载材质
4. 若没有有效 `AssetRef`,只接受带 virtual scheme 的 `materialPaths`
5. 没有 `AssetRef` 的普通项目路径会被清空
也就是说,下面这种旧式数据:
```text
materialPaths=Assets/runtime.material;materialRefs=;
```
当前不会再被当成正式项目材质绑定恢复。
## 序列化语义
当前 [Serialize](Serialize.md) 会写出:
```text
materialPaths=<slot0|slot1|...>;
materialRefs=<slot0|slot1|...>;
castShadows=1;
receiveShadows=1;
renderLayer=0;
```
但其中最关键的规则是:
- 对有有效 `AssetRef` 的项目材质槽,`materialPaths` 会被清空
- 对没有 `AssetRef` 且是 virtual scheme 的槽位,`materialPaths` 才保留真实路径
因此:
- 项目材质的主协议是 `materialRefs`
- `builtin://materials/default-primitive` 这类虚拟路径才长期留在 `materialPaths`
## 与 deferred scene load 的关系
[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 当前都带副作用。
它们会内部触发:
1. `EnsureDeferredAsyncMaterialLoadStarted(index)`
2. `ResolvePendingMaterials()`
所以在 deferred 场景里,反序列化后常见状态是:
2026-03-27 16:15:00 +08:00
- `m_materialRefs[index]` 已恢复
- `m_materialPaths[index]` 可能已恢复
- `m_materials[index]` 仍然为空
2026-03-27 16:15:00 +08:00
直到后续第一次访问材质句柄时,异步兑现才真正开始或收口。
2026-03-27 16:15:00 +08:00
## 与渲染链路的关系
2026-03-27 16:15:00 +08:00
当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 挂进可见项,再由后续材质提取逻辑读取具体槽位材质。
2026-03-27 16:15:00 +08:00
因此当前对 renderer 的真实影响是:
2026-03-27 16:15:00 +08:00
- 组件是否存在并不等于材质已经可用
- deferred load 下,材质槽元数据可能先恢复,材质句柄后到
2026-03-27 16:15:00 +08:00
另外要继续明确一个实现边界:
2026-03-27 16:15:00 +08:00
- `castShadows``receiveShadows``renderLayer` 当前已可保存、序列化、脚本读写
- 但还不能简单等同于“这些标志已经完整进入当前 renderer 主路径”
2026-03-27 16:15:00 +08:00
## 测试与锚点
2026-03-27 16:15:00 +08:00
- `tests/Components/test_mesh_render_components.cpp`
- 覆盖 builtin virtual path 的序列化 / 反序列化
- 覆盖“没有 `AssetRef` 的普通项目路径不会作为正式协议保留”
- 覆盖项目材质按 `AssetRef` 序列化与恢复
- 覆盖 deferred async material load
- `tests/Scene/test_scene.cpp`
- 覆盖 builtin material path 的场景保存 / 加载
2026-03-27 16:15:00 +08:00
## 当前实现边界
2026-03-27 16:15:00 +08:00
- 当前只维护平铺材质槽数组,不处理更复杂的实例化或 streaming 策略
- 项目材质的正式序列化协议已经收口到 `materialRefs`
- 没有 `AssetRef` 的普通项目路径在反序列化时当前会被清掉;只有 virtual scheme 仍可单独依赖路径
- `GetMaterial()` / `GetMaterialHandle()` 是带副作用的读访问器;只读元数据时应优先使用 [GetMaterialPath](GetMaterialPath.md)、[GetMaterialPaths](GetMaterialPaths.md) 和 [GetMaterialAssetRefs](GetMaterialAssetRefs.md)
2026-03-27 16:15:00 +08:00
## 相关方法
- [GetMaterialCount](GetMaterialCount.md)
- [GetMaterial](GetMaterial.md)
- [GetMaterialHandle](GetMaterialHandle.md)
- [GetMaterialPath](GetMaterialPath.md)
- [GetMaterialPaths](GetMaterialPaths.md)
- [GetMaterialAssetRefs](GetMaterialAssetRefs.md)
2026-03-27 16:15:00 +08:00
- [SetMaterial](SetMaterial.md)
- [SetMaterialPath](SetMaterialPath.md)
- [SetMaterials](SetMaterials.md)
- [ClearMaterials](ClearMaterials.md)
- [GetCastShadows](GetCastShadows.md)
- [SetCastShadows](SetCastShadows.md)
- [GetReceiveShadows](GetReceiveShadows.md)
- [SetReceiveShadows](SetReceiveShadows.md)
- [GetRenderLayer](GetRenderLayer.md)
- [SetRenderLayer](SetRenderLayer.md)
- [Serialize](Serialize.md)
- [Deserialize](Deserialize.md)
2026-03-26 21:32:43 +08:00
## 相关文档
2026-03-27 16:15:00 +08:00
- [当前模块](../Components.md)
2026-03-26 21:32:43 +08:00
- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md)
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [BuiltinForwardPipeline](../../Rendering/Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)
2026-03-27 16:15:00 +08:00
- [API 总索引](../../../main.md)