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

196 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MeshRendererComponent
**命名空间**: `XCEngine::Components`
**类型**: `class`
**头文件**: `XCEngine/Components/MeshRendererComponent.h`
**描述**: 保存材质槽、资产引用、阴影开关和渲染层等绘制配置,把场景对象与具体 `Material` 资源绑定起来。
## 角色概述
`MeshRendererComponent` 负责回答“这个对象上的 mesh 应该用什么材质、以什么附加状态被渲染”。它不提供几何来源,也不直接发起 draw call当前链路里它主要扮演四个角色
- 为运行时保存可直接使用的 `Material` 句柄。
- 为场景文本保存稳定的材质路径数组。
- 为项目资产保存 `AssetRef`,让场景在资源移动或重命名后仍有机会恢复材质绑定。
- 为脚本层、编辑器和渲染提取层提供统一的材质槽视图。
和它配套工作的主要对象是:
- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md):负责 mesh 资源本身。
- `ResourceManager`:负责按路径或 `AssetRef` 加载 `Material`,也负责延迟异步加载。
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md):只有 mesh 和材质都可用时,才会把对象整理成可见渲染项。
这种拆分很接近商业引擎里常见的 `MeshFilter + MeshRenderer` 思路。好处是“几何来源”和“绘制配置”可以分别序列化、分别编辑,也更适合后续做资源热更新、材质复用和编辑器 Inspector。
## 当前状态模型
当前实现维护了五份与材质槽相关的状态:
| 状态 | 类型 | 作用 |
|------|------|------|
| `m_materials` | `std::vector<ResourceHandle<Material>>` | 当前已兑现的运行时材质句柄。 |
| `m_materialPaths` | `std::vector<std::string>` | 可序列化、可调试的路径数组。 |
| `m_materialRefs` | `std::vector<Resources::AssetRef>` | 项目资产数据库可解析的稳定引用。 |
| `m_pendingMaterialLoads` | `std::vector<std::shared_ptr<...>>` | 异步加载尚未收口时的挂起结果。 |
| `m_asyncMaterialLoadRequested` | `std::vector<bool>` | 防止同一槽位重复发起异步加载。 |
这几组数组会尽量保持同一槽位语义对齐。换句话说,`slot 3` 的 handle、path、`AssetRef`、pending state 都是在描述“第 3 个材质槽”。
## 绑定与解析流程
### 1. 路径驱动的绑定
[SetMaterialPath](SetMaterialPath.md) 会:
- 先确保槽位存在。
- 清掉该槽位旧的异步挂起状态。
- 写入新的 `m_materialPaths[index]`
- 立即调用 `ResourceManager::Load<Material>(path)` 做一次同步加载尝试。
- 再调用 `TryGetAssetRef(path, ResourceType::Material, ...)` 尝试回填 `AssetRef`
即使同步加载失败,路径仍会保留;如果这条路径能映射到项目资产,`m_materialRefs[index]` 也可能仍然有效。
要注意一个很容易误解的点:按当前源码,`SetMaterialPath()` 本身仍是“立即同步加载”的接口,它不会因为 deferred scene load 模式而自动改成纯记录路径。
### 2. 句柄驱动的绑定
[SetMaterial](SetMaterial.md) 会:
- 保存句柄。
- 通过 `material->GetPath()` 反推路径。
- 再按路径尝试回填 `AssetRef`
这让“运行时直接塞一个已经加载好的材质”与“场景序列化仍能恢复路径/资产引用”两件事可以同时成立。
### 3. 反序列化后的恢复策略
[Deserialize](Deserialize.md) 现在会同时处理:
- `materialPaths=<path0|path1|...>`
- `materialRefs=<guid,localId,resourceType|...>`
- 历史兼容键 `materials=<path0|path1|...>`
恢复优先级大致是:
1. 如果 `materialRef` 有效,优先尝试按 `AssetRef` 恢复。
2. 如果当前是 deferred scene load尽量先把 `AssetRef` 解析回路径,但不立即同步兑现材质。
3. 如果没有可用 `AssetRef`,再按路径恢复。
### 4. 首次访问时的异步兑现
[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 虽然是 `const`,但当前实现会通过 `const_cast` 内部触发:
1. `EnsureDeferredAsyncMaterialLoadStarted(index)`
2. `ResolvePendingMaterials()`
也就是说,这两个访问器不只是“读缓存”,还会推动路径恢复后的材质兑现流程向前走。
从行为上看,它们的主用途是支持 deferred scene load但按当前源码只要某个槽位“有路径、还没有 material、也还没发起过请求”首次访问就可能触发一次异步加载尝试。
## 序列化语义
当前序列化会输出五段键值:
```text
materialPaths=<fallbackPath0|fallbackPath1|...>;
materialRefs=<guid,localId,resourceType|...>;
castShadows=1;
receiveShadows=1;
renderLayer=0;
```
其中最关键的设计点是:
- `materialRefs` 是主身份信息,面向项目资产恢复。
- `materialPaths` 更像回退字段,只在该槽位没有有效 `AssetRef` 时才真正写出路径。
例如一个项目资产材质槽,当前文本很可能是:
```text
materialPaths=;
materialRefs=<有效 guid,localId,type>;
```
这不是数据丢失,而是说明当前序列化更信任 `AssetRef` 作为稳定身份。
## 与渲染链路的关系
当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 直接挂进可见渲染对象,再由后续材质提取逻辑读取材质槽。
这意味着:
- `MeshRendererComponent` 是否“有槽位”不重要。
- 重要的是在真正提取材质时,对应槽位能否拿到有效 `Material`
- 在 deferred / async 场景里,对象可能已经有 `MeshFilterComponent + MeshRendererComponent`,但某个时刻材质仍为空,直到异步结果被 `ResolvePendingMaterials()` 收口。
## 阴影与渲染层的现实状态
当前组件公开了:
- `GetCastShadows()` / `SetCastShadows()`
- `GetReceiveShadows()` / `SetReceiveShadows()`
- `GetRenderLayer()` / `SetRenderLayer()`
但按当前源码检索,这几个字段目前主要被:
- 组件测试、场景序列化测试
- Mono scripting internal call
消费;还没有看到它们被当前已取证的渲染提取路径真正读取。也就是说:
- 它们当前可以保存。
- 可以被序列化。
- 也可以被脚本层读写。
- 但不能简单等同于“阴影层级和 render layer 已完整进入 renderer 主路径”。
## 测试与真实使用点
- `tests/Components/test_mesh_render_components.cpp` 覆盖了槽位扩容、双轨序列化、历史键兼容、项目材质 `AssetRef` 恢复,以及 deferred async material load。
- `tests/Scene/test_scene.cpp` 覆盖了场景序列化后对 `castShadows` / `receiveShadows` / `renderLayer` 的恢复。
- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` 暴露了阴影和 render layer 的脚本读写入口。
## 线程与访问语义
- 当前实现没有内部加锁。
- 路径写入、序列化和槽位编辑默认按主线程使用。
- `GetMaterial()` / `GetMaterialHandle()` 是带副作用的读访问器;如果调用方只想查看元数据而不触发兑现,应优先使用 [GetMaterialPath](GetMaterialPath.md)、[GetMaterialPaths](GetMaterialPaths.md) 或 [GetMaterialAssetRefs](GetMaterialAssetRefs.md)。
## 当前实现限制
- 当前只维护“平铺材质槽数组”,不处理 submesh 级独立绑定策略、材质实例化策略或 streaming 策略。
- `GetMaterial()` / `GetMaterialHandle()` 可能在首次访问时触发异步加载,因此它们不是纯只读 getter。
- `materialPaths` 在序列化文本里只作为 fallback 字段输出,不应把“文本里 path 为空”误解成“组件内存里没有路径缓存”。
- `castShadows``receiveShadows``renderLayer` 还没有完整接入当前已取证的 renderer 主路径。
## 相关方法
- [GetMaterialCount](GetMaterialCount.md)
- [GetMaterial](GetMaterial.md)
- [GetMaterialHandle](GetMaterialHandle.md)
- [GetMaterialPath](GetMaterialPath.md)
- [GetMaterialPaths](GetMaterialPaths.md)
- [GetMaterialAssetRefs](GetMaterialAssetRefs.md)
- [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)
## 相关文档
- [当前模块](../Components.md)
- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md)
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [BuiltinForwardPipeline](../../Rendering/Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)
- [API 总索引](../../../main.md)