docs: sync mesh component assetref docs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# MeshFilterComponent::ClearMesh
|
||||
|
||||
清空当前 mesh。
|
||||
清空当前 mesh 绑定状态。
|
||||
|
||||
```cpp
|
||||
void ClearMesh();
|
||||
@@ -8,10 +8,18 @@ void ClearMesh();
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前实现会重置内部 handle,并清空缓存路径。
|
||||
当前实现会同时重置:
|
||||
|
||||
- `m_pendingMeshLoad`
|
||||
- `m_asyncMeshLoadRequested`
|
||||
- `m_mesh`
|
||||
- `m_meshPath`
|
||||
- `m_meshRef`
|
||||
|
||||
因此它不仅移除当前 mesh,也会清掉 deferred scene load 的待完成状态和资产引用元数据。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [SetMesh](SetMesh.md)
|
||||
- [GetMeshPath](GetMeshPath.md)
|
||||
- [SetMeshPath](SetMeshPath.md)
|
||||
|
||||
@@ -1,26 +1,89 @@
|
||||
# MeshFilterComponent::Deserialize
|
||||
|
||||
反序列化 mesh 信息。
|
||||
反序列化 mesh 绑定信息。
|
||||
|
||||
```cpp
|
||||
void Deserialize(std::istream& is) override;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
## 当前实现行为
|
||||
|
||||
当前实现会:
|
||||
当前实现会先清空:
|
||||
|
||||
1. 先清空现有 handle 和路径。
|
||||
2. 按 `;` 分割 token。
|
||||
3. 识别 `mesh=<path>`。
|
||||
4. 若路径非空,则调用 `ResourceManager::Get().Load<Resources::Mesh>()` 尝试重新加载。
|
||||
- `m_pendingMeshLoad`
|
||||
- `m_asyncMeshLoadRequested`
|
||||
- `m_mesh`
|
||||
- `m_meshPath`
|
||||
- `m_meshRef`
|
||||
|
||||
## 当前实现说明
|
||||
然后只解析两项键值:
|
||||
|
||||
- 如果资源加载失败,`m_mesh` 可能仍然为空,但 `m_meshPath` 会保留反序列化得到的路径。
|
||||
- 当前序列化格式很轻量,适合人类可读文本,但没有更强的容错与版本化机制。
|
||||
- `meshRef=<guid,localId,resourceType>`
|
||||
- `meshPath=<path>`
|
||||
|
||||
## 恢复顺序
|
||||
|
||||
### 1. `meshRef` 有效
|
||||
|
||||
这是当前项目资产恢复的主路径。
|
||||
|
||||
当前会先保存 `m_meshRef = pendingMeshRef`,然后分两种情况:
|
||||
|
||||
- 若 `ResourceManager::IsDeferredSceneLoadEnabled()` 为真
|
||||
- 先尝试 `TryResolveAssetPath(meshRef, resolvedPath)`
|
||||
- 成功时只恢复 `m_meshPath = resolvedPath`
|
||||
- 不立即同步加载 mesh
|
||||
- 若不在 deferred 模式
|
||||
- 直接 `Load<Mesh>(pendingMeshRef)`
|
||||
- 加载成功则从资源句柄反写 `m_meshPath`
|
||||
- 失败时才回退到文本里读到的 `pendingMeshPath`
|
||||
|
||||
### 2. `meshRef` 无效,但 `meshPath` 是 virtual scheme
|
||||
|
||||
当前只接受这类路径回退:
|
||||
|
||||
- 若处于 deferred scene load,直接保留 `m_meshPath`
|
||||
- 否则调用 [SetMeshPath](SetMeshPath.md) 走同步加载
|
||||
|
||||
### 3. `meshRef` 无效,且 `meshPath` 只是普通项目路径
|
||||
|
||||
当前会直接忽略。
|
||||
|
||||
也就是说,像下面这种旧数据:
|
||||
|
||||
```text
|
||||
meshPath=Assets/runtime.obj;meshRef=;
|
||||
```
|
||||
|
||||
当前不会再被当成正式项目资产绑定恢复。
|
||||
|
||||
## 已删除的旧心智
|
||||
|
||||
这页最需要明确的一点是:
|
||||
|
||||
- 当前不再兼容 `mesh=<path>`
|
||||
- 当前也不再把“没有 `AssetRef` 的普通项目路径”当成长期场景协议
|
||||
|
||||
只有这两类数据还能稳定恢复:
|
||||
|
||||
- 有效 `meshRef`
|
||||
- 带 virtual scheme 的 `meshPath`
|
||||
|
||||
## 与 deferred scene load 的关系
|
||||
|
||||
在 deferred 模式下,反序列化阶段更像是恢复绑定元数据,而不是立刻拿到 mesh 句柄。
|
||||
|
||||
真正的兑现一般留到后续:
|
||||
|
||||
- [GetMesh](GetMesh.md)
|
||||
- [GetMeshHandle](GetMeshHandle.md)
|
||||
|
||||
第一次访问这些接口时,内部才会启动或收口异步加载。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [Serialize](Serialize.md)
|
||||
- [GetMesh](GetMesh.md)
|
||||
- [GetMeshHandle](GetMeshHandle.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
# MeshFilterComponent::GetMesh
|
||||
|
||||
获取当前 mesh 指针。
|
||||
获取当前可用的 mesh 指针。
|
||||
|
||||
```cpp
|
||||
Resources::Mesh* GetMesh() const;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前实现不是单纯返回 `m_mesh.Get()`,还会在内部额外执行两步:
|
||||
|
||||
1. `EnsureDeferredAsyncMeshLoadStarted()`:在 deferred scene load 模式下,如果当前只有路径还没有实际 mesh,会按 `m_meshPath` 发起一次异步加载。
|
||||
2. `ResolvePendingMesh()`:如果异步回调已经完成,则把结果写回 `m_mesh`、`m_meshPath` 和 `m_meshRef`。
|
||||
|
||||
因此,这个访问器既是读取接口,也是 deferred mesh 解析流程的推进点。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 当前 mesh 指针;如果没有设置则返回 `nullptr`。
|
||||
- 如果当前已经有可用 mesh,返回对应指针。
|
||||
- 如果没有 mesh,或异步加载尚未完成,返回 `nullptr`。
|
||||
- 异步加载失败时也返回 `nullptr`,同时保留已有路径信息。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 在延迟场景加载模式下,第一次调用可能只是发起异步加载,本次仍返回空指针。
|
||||
- 渲染提取逻辑会直接依赖这个返回值;返回 `nullptr` 时,对象不会进入可见渲染列表。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMeshHandle](GetMeshHandle.md)
|
||||
- [SetMesh](SetMesh.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# MeshFilterComponent::GetMeshAssetRef
|
||||
|
||||
获取当前 mesh 的资产引用。
|
||||
|
||||
```cpp
|
||||
const Resources::AssetRef& GetMeshAssetRef() const;
|
||||
```
|
||||
|
||||
## 返回值
|
||||
|
||||
- 返回内部保存的 `AssetRef` 常量引用。
|
||||
|
||||
## 行为说明
|
||||
|
||||
`m_meshRef` 会在以下路径里更新:
|
||||
|
||||
- [SetMeshPath](SetMeshPath.md) 成功从路径反查资产引用时
|
||||
- [SetMesh](SetMesh.md) 成功从 mesh 路径反查资产引用时
|
||||
- [Deserialize](Deserialize.md) 直接读到 `meshRef=` 字段时
|
||||
- 异步 mesh 兑现后,再次按最终路径回填资产引用时
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 这是只读元数据访问器,不会触发加载。
|
||||
- 当路径无法映射到项目资产,或反序列化数据里没有有效 `meshRef` 时,返回值可能是无效引用。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMeshPath](GetMeshPath.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
@@ -6,11 +6,25 @@
|
||||
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
|
||||
当前实现和 [GetMesh](GetMesh.md) 一样,会先尝试推进 deferred scene load:
|
||||
|
||||
- 如果还没请求过异步加载,会按 `m_meshPath` 发起一次 `LoadAsync()`。
|
||||
- 如果异步结果已经回到 `m_pendingMeshLoad`,会先把结果兑现到 `m_mesh` 再返回句柄引用。
|
||||
|
||||
## 返回值
|
||||
|
||||
- 当前保存的 mesh `ResourceHandle` 常量引用。
|
||||
- 返回内部 `m_mesh` 的常量引用。
|
||||
- 在 mesh 尚未加载完成时,返回的句柄可能为空。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 这是“可能带副作用”的访问器,而不是纯 getter。
|
||||
- 如果调用方只想读取序列化主数据而不触发加载,应优先看 [GetMeshPath](GetMeshPath.md) 或 [GetMeshAssetRef](GetMeshAssetRef.md)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMesh](GetMesh.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
|
||||
@@ -8,14 +8,22 @@ const std::string& GetMeshPath() const;
|
||||
|
||||
## 返回值
|
||||
|
||||
- 当前缓存的 mesh 路径。
|
||||
- 返回组件当前保存的路径字符串。
|
||||
|
||||
## 行为特点
|
||||
|
||||
- 这是当前场景文本序列化的主数据之一。
|
||||
- 当 mesh 通过句柄设置或异步加载成功后,路径可能被更新为资源对象实际返回的 `GetPath()`。
|
||||
- 即使 mesh 当前还没加载成功,路径也可能已经存在。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 当前路径只是组件内部缓存值,不保证文件一定存在。
|
||||
- 它不会触发异步加载,也不会等待待完成的 mesh 解析。
|
||||
- 路径存在不代表 `GetMesh()` 一定非空。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
- [Serialize](Serialize.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
|
||||
@@ -6,97 +6,150 @@
|
||||
|
||||
**头文件**: `XCEngine/Components/MeshFilterComponent.h`
|
||||
|
||||
**描述**: 保存网格资源引用及其路径,让场景对象能够声明“我使用哪一个 Mesh”。
|
||||
**描述**: 保存单个 mesh 绑定状态,把运行时 `Mesh` 句柄、项目资产 `AssetRef` 和少量 virtual path 协议收口到同一个组件里。
|
||||
|
||||
## 角色概述
|
||||
|
||||
`MeshFilterComponent` 负责“几何数据来自哪里”,而不是“怎么绘制”。在当前对象模型里:
|
||||
`MeshFilterComponent` 回答的是一个很具体的问题:
|
||||
|
||||
- `MeshFilterComponent` 提供网格
|
||||
- [MeshRendererComponent](../MeshRendererComponent/MeshRendererComponent.md) 提供材质和渲染附加配置
|
||||
- 两者一起被 [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md) 消费
|
||||
- 这个对象当前引用哪一个 mesh?
|
||||
|
||||
这和 Unity 里 `MeshFilter` / `MeshRenderer` 分工的思路是一致的,优点是几何与渲染配置解耦,便于编辑器或脚本单独替换。
|
||||
它不处理材质,也不直接发起绘制。当前真正的职责有三层:
|
||||
|
||||
## 当前实现行为
|
||||
- 保存运行时 `Mesh` 句柄,供渲染提取和运行时逻辑直接使用
|
||||
- 保存项目资产 `AssetRef`,让场景在资源移动或重命名后仍能稳定恢复
|
||||
- 仅在没有可用 `AssetRef` 时,为 `builtin://` 这类 virtual path 保留文本路径
|
||||
|
||||
### 1. 同时保存资源句柄和资源路径
|
||||
这意味着当前文档里最需要纠正的一点是:
|
||||
|
||||
内部状态有两份:
|
||||
- 项目资产的正式序列化协议已经不再是普通路径字符串
|
||||
- 对项目 mesh,当前主身份是 `meshRef`
|
||||
- `meshPath` 主要保留给 `builtin://` 或其它 virtual scheme
|
||||
|
||||
- `m_mesh`:`ResourceHandle<Mesh>`
|
||||
- `m_meshPath`:`std::string`
|
||||
## 当前状态模型
|
||||
|
||||
这样设计的直接收益是:
|
||||
当前实现维护 5 份与 mesh 绑定相关的状态:
|
||||
|
||||
- 运行时可以快速拿到已加载资源
|
||||
- 序列化时又能稳定写出路径
|
||||
| 状态 | 类型 | 作用 |
|
||||
|------|------|------|
|
||||
| `m_mesh` | `ResourceHandle<Resources::Mesh>` | 当前已兑现的运行时 mesh 句柄。 |
|
||||
| `m_meshPath` | `std::string` | 当前内存里的路径缓存;对项目资产通常会是 `Assets/...`,对 virtual 资源则可能是 `builtin://...`。 |
|
||||
| `m_meshRef` | `Resources::AssetRef` | 项目资产的稳定身份。 |
|
||||
| `m_pendingMeshLoad` | `std::shared_ptr<...>` | deferred async load 的挂起结果。 |
|
||||
| `m_asyncMeshLoadRequested` | `bool` | 防止重复发起异步 mesh 加载。 |
|
||||
|
||||
### 2. `SetMeshPath()` 会尝试加载,但即使加载失败也保留路径
|
||||
这几份状态不是完全等价的,尤其要区分:
|
||||
|
||||
按当前实现:
|
||||
- `m_meshRef` 是项目资产正式协议
|
||||
- `m_meshPath` 是运行时缓存与 virtual path 回退字段
|
||||
|
||||
- 传入空路径时会清空 handle 和 path
|
||||
- 传入非空路径时会调用 `ResourceManager::Get().Load<Mesh>(...)`
|
||||
- 无论资源是否成功加载,`m_meshPath` 都会先被更新
|
||||
## 绑定与恢复流程
|
||||
|
||||
这是一条很重要的行为约定。它意味着:
|
||||
### 1. 运行时按路径设置
|
||||
|
||||
- 文档页或编辑器可以先持有“用户想要的路径”
|
||||
- 即使资源此刻没加载出来,序列化信息也不会丢
|
||||
[SetMeshPath](SetMeshPath.md) 当前仍然是“立即尝试同步加载”的接口:
|
||||
|
||||
这一点已经被 `tests/Components/test_mesh_render_components.cpp` 覆盖。
|
||||
1. 清掉旧的 pending async load 状态
|
||||
2. 直接写入 `m_meshPath`
|
||||
3. 若路径为空,则清空句柄与 `AssetRef`
|
||||
4. 否则同步 `Load<Mesh>(path)`
|
||||
5. 再尝试按路径回填 `m_meshRef`
|
||||
|
||||
### 3. 直接设置 handle 时会反向推导路径
|
||||
因此对项目路径 `Assets/...` 来说,运行时是允许直接设置路径的;但这不等于序列化后仍靠路径当正式协议。
|
||||
|
||||
`SetMesh(const ResourceHandle<Mesh>&)` 会:
|
||||
### 2. 运行时按句柄设置
|
||||
|
||||
- 保存 handle
|
||||
- 如果 handle 非空,则从 `mesh->GetPath()` 反推出 `m_meshPath`
|
||||
- 如果 handle 为空,则清空路径
|
||||
[SetMesh](SetMesh.md) 会保存句柄,并从 `mesh->GetPath()` 反推路径,再按路径尝试回填 `AssetRef`。
|
||||
|
||||
这保证了路径和运行时资源引用不会长期脱节。
|
||||
这保证了“直接塞一个已加载 mesh”与“后续还能序列化出稳定项目身份”这两件事可以同时成立。
|
||||
|
||||
### 3. 反序列化恢复
|
||||
|
||||
[Deserialize](Deserialize.md) 当前只识别:
|
||||
|
||||
- `meshRef=<guid,localId,resourceType>`
|
||||
- `meshPath=<virtual path>`
|
||||
|
||||
恢复顺序是:
|
||||
|
||||
1. 如果 `meshRef` 有效,优先按 `AssetRef` 恢复
|
||||
2. 若当前处于 deferred scene load,先尝试 `TryResolveAssetPath(meshRef, path)` 恢复 `Assets/...` 路径,但不立即同步加载
|
||||
3. 若不在 deferred 模式,则直接按 `AssetRef` 加载 mesh
|
||||
4. 若没有有效 `meshRef`,只接受带 virtual scheme 的 `meshPath`
|
||||
5. 没有 `AssetRef` 的普通项目路径会被忽略
|
||||
|
||||
这里是当前实现最容易被旧文档写错的地方:
|
||||
|
||||
- `mesh=...` 已经不是当前协议
|
||||
- `meshPath=Assets/...` 且没有 `meshRef`,当前不会被当成正式项目资产绑定恢复
|
||||
|
||||
## 序列化语义
|
||||
|
||||
当前序列化只写一个字段:
|
||||
当前 [Serialize](Serialize.md) 的真实输出规则是:
|
||||
|
||||
- `mesh=<path>`
|
||||
- 总是写 `meshRef=...;`
|
||||
- 只有在没有有效 `AssetRef` 且 `m_meshPath` 是 virtual scheme 时,才额外写 `meshPath=...;`
|
||||
|
||||
反序列化时会先清空旧状态,再通过 `SetMeshPath()` 恢复路径并尝试加载资源。
|
||||
因此:
|
||||
|
||||
这意味着:
|
||||
- 项目资产 mesh 常见输出是只有 `meshRef`
|
||||
- `builtin://meshes/cube` 这类路径才会稳定出现在 `meshPath`
|
||||
|
||||
- 场景文件的真实持久化载体是路径
|
||||
- 运行时 handle 是可重建缓存,而不是主数据
|
||||
这和商业引擎里“项目资产走稳定内部引用,builtin/虚拟资源走协议路径”的思路是一致的。
|
||||
|
||||
## 与渲染提取的关系
|
||||
## 与 deferred scene load 的关系
|
||||
|
||||
`RenderSceneExtractor` 只有在以下条件都满足时才会把对象当成可见渲染项:
|
||||
[GetMesh](GetMesh.md) 和 [GetMeshHandle](GetMeshHandle.md) 都不是纯 getter。
|
||||
|
||||
- `GameObject` 处于激活层级
|
||||
- 同时存在 `MeshFilterComponent` 和 `MeshRendererComponent`
|
||||
- 两个组件都已启用
|
||||
- `MeshFilterComponent::GetMesh()` 返回非空且 `mesh->IsValid()`
|
||||
当前它们会内部触发:
|
||||
|
||||
因此,只有 `MeshFilterComponent` 还不够,它必须和 `MeshRendererComponent` 配对使用。
|
||||
1. `EnsureDeferredAsyncMeshLoadStarted()`
|
||||
2. `ResolvePendingMesh()`
|
||||
|
||||
## 线程语义
|
||||
因此在 deferred scene load 模式下,反序列化阶段往往只恢复:
|
||||
|
||||
- 当前实现没有内部加锁。
|
||||
- 资源加载调用走 `ResourceManager`,但本组件文档不提供额外线程安全保证。
|
||||
- `m_meshRef`
|
||||
- `m_meshPath`
|
||||
|
||||
## 当前实现限制
|
||||
真正 mesh 句柄的兑现,通常发生在后续访问器首次被调用时。
|
||||
|
||||
- 它只负责引用网格,不处理子网格裁剪、LOD 或网格 streaming 策略。
|
||||
- 场景保存后真正持久化的是路径,不是已经加载好的资源实例。
|
||||
- 当前目录下只有 [SetMesh](SetMesh.md) 方法页,`SetMeshPath()` 也是 public API,应优先参考本页的行为说明。
|
||||
## 与渲染链路的关系
|
||||
|
||||
`RenderSceneUtility::AppendRenderItemsForGameObject()` 只有在以下条件同时满足时,才会把对象加入可见渲染项:
|
||||
|
||||
- 对象激活
|
||||
- `MeshFilterComponent` 与 `MeshRendererComponent` 都存在且启用
|
||||
- `meshFilter->GetMesh()` 返回非空并且 mesh 有效
|
||||
|
||||
所以当前 `MeshFilterComponent` 的真实影响是:
|
||||
|
||||
- 它决定对象什么时候拥有几何体
|
||||
- 但 deferred load 下,“路径和 `AssetRef` 已恢复”不等于“这一帧已经可绘制”
|
||||
|
||||
## 测试与锚点
|
||||
|
||||
- `tests/Components/test_mesh_render_components.cpp`
|
||||
- 覆盖 builtin virtual path 的序列化 / 反序列化
|
||||
- 覆盖“没有 `AssetRef` 的普通路径会被忽略”
|
||||
- 覆盖项目 mesh 按 `AssetRef` 序列化与恢复
|
||||
- 覆盖 deferred async load
|
||||
- `tests/Scene/test_scene.cpp`
|
||||
- 覆盖 builtin mesh path 的场景保存 / 加载
|
||||
- 覆盖样例场景里项目 mesh `Assets/Models/backpack/backpack.obj` 的恢复和延迟加载
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 当前只绑定单个 mesh,不处理 LOD、mesh streaming 或多几何切换
|
||||
- 项目 mesh 的正式序列化协议已经收口到 `AssetRef`,不再鼓励把普通项目路径当长期场景格式依赖
|
||||
- 没有 `AssetRef` 的普通项目路径在反序列化时当前会被忽略;只有 `builtin://` / 其它 virtual scheme 还能单独靠路径存活
|
||||
- `GetMesh()` / `GetMeshHandle()` 是带副作用的访问器;只读元数据时应优先用 [GetMeshPath](GetMeshPath.md) 和 [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
|
||||
## 相关方法
|
||||
|
||||
- [GetMesh](GetMesh.md)
|
||||
- [GetMeshHandle](GetMeshHandle.md)
|
||||
- [GetMeshPath](GetMeshPath.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
- [SetMesh](SetMesh.md)
|
||||
- [SetMeshPath](SetMeshPath.md)
|
||||
- [ClearMesh](ClearMesh.md)
|
||||
|
||||
@@ -1,22 +1,67 @@
|
||||
# MeshFilterComponent::Serialize
|
||||
|
||||
序列化当前 mesh 信息。
|
||||
序列化当前 mesh 绑定信息。
|
||||
|
||||
```cpp
|
||||
void Serialize(std::ostream& os) const override;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
## 当前实现行为
|
||||
|
||||
当前实现会输出:
|
||||
当前实现会先准备一份待写出的 `meshRef`:
|
||||
|
||||
- 优先使用当前内存中的 `m_meshRef`
|
||||
- 如果 `m_meshRef` 还无效,但 `m_meshPath` 是普通项目路径而不是 virtual scheme,则会再尝试一次 `TryGetAssetRef(...)`
|
||||
|
||||
随后输出规则只有两条:
|
||||
|
||||
1. 总是写出 `meshRef=<...>;`
|
||||
2. 只有当 `meshRef` 仍无效,且 `m_meshPath` 是 virtual scheme 时,才额外写出 `meshPath=<...>;`
|
||||
|
||||
## 当前序列化结果
|
||||
|
||||
### 项目资产 mesh
|
||||
|
||||
例如当前组件绑定的是 `Assets/runtime.obj` 且能解析出有效 `AssetRef`,输出通常会像:
|
||||
|
||||
```text
|
||||
mesh=<path>;
|
||||
meshRef=<guid,localId,resourceType>;
|
||||
```
|
||||
|
||||
这里只序列化路径,不会序列化 mesh 二进制内容本身。
|
||||
注意:
|
||||
|
||||
- 这时不会再写 `meshPath=Assets/runtime.obj;`
|
||||
|
||||
### builtin / virtual path mesh
|
||||
|
||||
例如当前组件绑定的是 `builtin://meshes/cube`,而它没有项目资产 `AssetRef`,输出会像:
|
||||
|
||||
```text
|
||||
meshRef=;
|
||||
meshPath=builtin://meshes/cube;
|
||||
```
|
||||
|
||||
## 关键语义
|
||||
|
||||
- 当前已经不再输出旧的 `mesh=<path>` 键
|
||||
- 项目资产的正式场景身份是 `meshRef`,不是普通路径字符串
|
||||
- `meshPath` 现在主要是 virtual path 回退字段
|
||||
|
||||
## 设计说明
|
||||
|
||||
这套设计的价值在于:
|
||||
|
||||
- 项目资源移动、改名或重新导入后,场景仍可按稳定身份恢复
|
||||
- builtin / 虚拟资源又不必强行塞进项目资产数据库
|
||||
|
||||
因此它不是“路径 + `AssetRef` 两份主数据并列”,而是:
|
||||
|
||||
- 项目资产优先 `AssetRef`
|
||||
- virtual resource 回退 `meshPath`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
- [GetMeshPath](GetMeshPath.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MeshFilterComponent::SetMesh
|
||||
|
||||
设置当前 mesh。
|
||||
直接设置当前 mesh 资源。
|
||||
|
||||
```cpp
|
||||
void SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh);
|
||||
@@ -9,18 +9,26 @@ void SetMesh(Resources::Mesh* mesh);
|
||||
|
||||
## 行为说明
|
||||
|
||||
无论使用哪种重载,当前实现都会:
|
||||
两个重载最终都会走 `ResourceHandle<Mesh>` 版本。当前实现会:
|
||||
|
||||
- 更新内部 `ResourceHandle`
|
||||
- 如果 mesh 非空,则把其 `GetPath()` 转成 `std::string` 写入 `m_meshPath`
|
||||
- 否则清空路径
|
||||
1. 清掉待完成的异步加载状态。
|
||||
2. 保存传入的 mesh 句柄。
|
||||
3. 如果句柄非空,则从 `mesh->GetPath()` 反推出 `m_meshPath`。
|
||||
4. 再尝试按该路径反查 `m_meshRef`。
|
||||
5. 如果句柄为空或路径为空,则清空 `m_meshRef`。
|
||||
|
||||
## 参数
|
||||
|
||||
- `mesh` - 要设置的 mesh 句柄或裸指针。
|
||||
- `mesh` - 要绑定的 mesh 句柄或裸指针。
|
||||
|
||||
## 设计含义
|
||||
|
||||
这让运行时代码可以直接复用已加载 mesh,同时仍保留后续序列化和场景保存所需的路径 / 资产引用信息。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [GetMesh](GetMesh.md)
|
||||
- [GetMeshPath](GetMeshPath.md)
|
||||
- [GetMeshAssetRef](GetMeshAssetRef.md)
|
||||
- [ClearMesh](ClearMesh.md)
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
# SetMeshPath
|
||||
# MeshFilterComponent::SetMeshPath
|
||||
|
||||
**所属类型**: [MeshFilterComponent](MeshFilterComponent.md)
|
||||
|
||||
## 签名
|
||||
按资源路径声明当前组件应引用哪个 mesh。
|
||||
|
||||
```cpp
|
||||
void SetMeshPath(const std::string& meshPath);
|
||||
```
|
||||
|
||||
## 作用
|
||||
## 行为说明
|
||||
|
||||
按资源路径声明当前组件应引用哪个 `Mesh`,并尝试通过 `ResourceManager` 立即加载该资源。
|
||||
当前实现会按以下顺序执行:
|
||||
|
||||
## 当前实现行为
|
||||
1. 清掉 `m_pendingMeshLoad`,并把 `m_asyncMeshLoadRequested` 复位为 `false`。
|
||||
2. 把 `m_meshPath` 直接更新为传入值。
|
||||
3. 如果路径为空,则清空 `m_mesh` 和 `m_meshRef` 后返回。
|
||||
4. 如果路径非空,则立即调用 `ResourceManager::Load<Mesh>(meshPath)` 尝试同步加载 mesh。
|
||||
5. 无论 mesh 是否加载成功,都会再尝试按路径反查 `AssetRef`。
|
||||
|
||||
- 先把 `m_meshPath` 更新为传入值。
|
||||
- 如果路径为空,清空当前 `m_mesh` handle 并返回。
|
||||
- 如果路径非空,调用 `ResourceManager::Get().Load<Resources::Mesh>(meshPath.c_str())`。
|
||||
- 即使加载失败,路径也会被保留下来。
|
||||
## 参数
|
||||
|
||||
## 使用建议
|
||||
- `meshPath` - 要绑定的 mesh 资源路径。
|
||||
|
||||
这是编辑器或序列化恢复路径更应该使用的接口,因为它保留了“目标资源路径”这一主数据。
|
||||
## 关键语义
|
||||
|
||||
- 这个 setter 会把传入路径直接写进运行时缓存,即使加载失败也会保留路径文本。
|
||||
- 这个 API 是“立即同步尝试加载”的语义;deferred scene load 只影响 [Deserialize](Deserialize.md) 后的恢复路径,不会改变这里的行为。
|
||||
- 如果资产数据库能识别这条路径,`m_meshRef` 可能有效,即使 `m_mesh` 仍为空。
|
||||
- 对项目资产来说,当前场景正式协议仍优先写 `AssetRef`;这里保留的路径更像运行时输入和缓存,而不是长期持久化格式本体。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [MeshFilterComponent](MeshFilterComponent.md)
|
||||
- [返回类型总览](MeshFilterComponent.md)
|
||||
- [SetMesh](SetMesh.md)
|
||||
- [Serialize](Serialize.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
|
||||
@@ -6,44 +6,76 @@
|
||||
void Deserialize(std::istream& is) override;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
## 当前实现行为
|
||||
|
||||
当前实现会:
|
||||
当前实现会先:
|
||||
|
||||
1. 先清空材质并重置默认标志。
|
||||
2. 解析 `materialPaths`、`materialRefs`、`castShadows`、`receiveShadows` 和 `renderLayer`。
|
||||
3. 兼容读取历史 `materials=` 键,并把它当作 `materialPaths=` 处理。
|
||||
4. 让 `m_materials`、`m_materialPaths`、`m_materialRefs`、pending 数组和 async 标记数组在槽位数量上重新对齐。
|
||||
1. 调用 `ClearMaterials()`
|
||||
2. 把默认值重置为:
|
||||
- `castShadows = true`
|
||||
- `receiveShadows = true`
|
||||
- `renderLayer = 0`
|
||||
|
||||
随后每个槽位按下面的优先级恢复:
|
||||
然后只解析:
|
||||
|
||||
1. 如果 `materialRef` 有效,先尝试按 `AssetRef` 恢复。
|
||||
2. 如果当前处于 deferred scene load,优先尝试把 `AssetRef` 重新解析回路径,但不立即同步加载材质。
|
||||
3. 如果没有有效 `AssetRef`,或者按 `AssetRef` 恢复失败,再退回到路径恢复。
|
||||
4. 只有在非 deferred 路径下,才会直接调用 [SetMaterialPath](SetMaterialPath.md) 做同步加载。
|
||||
- `materialPaths=<path0|path1|...>`
|
||||
- `materialRefs=<guid,localId,resourceType|...>`
|
||||
- `castShadows`
|
||||
- `receiveShadows`
|
||||
- `renderLayer`
|
||||
|
||||
这意味着“反序列化完成”并不等于“所有材质句柄都已经可用”。
|
||||
## 恢复顺序
|
||||
|
||||
## 当前实现说明
|
||||
### 1. 某槽位 `materialRef` 有效
|
||||
|
||||
- 默认值是:`castShadows = true`、`receiveShadows = true`、`renderLayer = 0`。
|
||||
- 如果某个槽位最终只恢复出了路径或 `AssetRef`,对应 `m_materials[index]` 仍可能为空。
|
||||
- 在 deferred scene load 模式下,真正的兑现通常要等首次 [GetMaterial](GetMaterial.md) 或 [GetMaterialHandle](GetMaterialHandle.md) 调用时才会触发。
|
||||
这是当前项目材质恢复的主路径。
|
||||
|
||||
## 项目资产恢复语义
|
||||
当前会先保存 `m_materialRefs[i] = pendingMaterialRefs[i]`,然后分两种情况:
|
||||
|
||||
当前实现特别强调项目资产的稳定恢复:
|
||||
- 若 `ResourceManager::IsDeferredSceneLoadEnabled()` 为真
|
||||
- 先尝试 `TryResolveAssetPath(materialRef, resolvedPath)`
|
||||
- 成功时只恢复 `m_materialPaths[i] = resolvedPath`
|
||||
- 不立即同步加载材质
|
||||
- 若不在 deferred 模式,或 deferred 下未成功解析路径
|
||||
- 直接 `Load<Material>(pendingMaterialRef)`
|
||||
- 成功时从句柄反写 `m_materialPaths[i]`
|
||||
|
||||
- 当场景文本里有有效 `materialRef=` 时,组件会尽量依赖它,而不是单纯依赖字符串路径。
|
||||
- 如果 `AssetRef` 能解析到当前项目中的材质,路径会被重新规范化回 `Assets/...`。
|
||||
- 如果解析失败,仍会回退到文本路径。
|
||||
### 2. 某槽位 `materialRef` 无效
|
||||
|
||||
这和商业引擎常见的“文本可读路径 + 稳定内部引用”双轨设计一致,优点是既便于调试,也能提高资源重命名后的恢复成功率。
|
||||
这时只剩路径回退分支:
|
||||
|
||||
## 兼容性说明
|
||||
- 若 `m_materialPaths[i]` 不是 virtual scheme,当前会直接清空该路径
|
||||
- 若是 virtual scheme 且处于 deferred 模式,则只保留路径
|
||||
- 若是 virtual scheme 且不在 deferred 模式,则调用 [SetMaterialPath](SetMaterialPath.md) 走同步加载
|
||||
|
||||
- 仍兼容历史 `materials=` 文本。
|
||||
- 新版 [Serialize](Serialize.md) 不再输出 `materials=`。
|
||||
## 已删除的旧心智
|
||||
|
||||
这页当前必须明确:
|
||||
|
||||
- 不再读取历史 `materials=` 键
|
||||
- 不再把“没有 `AssetRef` 的普通项目路径”当作正式项目材质恢复协议
|
||||
|
||||
因此像下面这种旧式数据:
|
||||
|
||||
```text
|
||||
materialPaths=Assets/runtime.material;materialRefs=;
|
||||
```
|
||||
|
||||
当前恢复后,这个普通路径会被清掉,而不是继续被保留。
|
||||
|
||||
## 与 deferred scene load 的关系
|
||||
|
||||
在 deferred 模式下,反序列化阶段通常只恢复:
|
||||
|
||||
- `m_materialRefs`
|
||||
- `m_materialPaths`
|
||||
|
||||
真正材质句柄的兑现,往往留到后续:
|
||||
|
||||
- [GetMaterial](GetMaterial.md)
|
||||
- [GetMaterialHandle](GetMaterialHandle.md)
|
||||
|
||||
这些访问器第一次被调用时,内部才会启动或收口异步材质加载。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -6,164 +6,156 @@
|
||||
|
||||
**头文件**: `XCEngine/Components/MeshRendererComponent.h`
|
||||
|
||||
**描述**: 保存材质槽、资产引用、阴影开关和渲染层等绘制配置,把场景对象与具体 `Material` 资源绑定起来。
|
||||
**描述**: 保存材质槽、项目资产 `AssetRef`、运行时材质句柄以及少量渲染附加状态,把对象的“怎么画”这一侧数据收口到一个组件里。
|
||||
|
||||
## 角色概述
|
||||
|
||||
`MeshRendererComponent` 负责回答“这个对象上的 mesh 应该用什么材质、以什么附加状态被渲染”。它不提供几何来源,也不直接发起 draw call;当前链路里它主要扮演四个角色:
|
||||
`MeshRendererComponent` 回答的问题是:
|
||||
|
||||
- 为运行时保存可直接使用的 `Material` 句柄。
|
||||
- 为场景文本保存稳定的材质路径数组。
|
||||
- 为项目资产保存 `AssetRef`,让场景在资源移动或重命名后仍有机会恢复材质绑定。
|
||||
- 为脚本层、编辑器和渲染提取层提供统一的材质槽视图。
|
||||
- 这个对象的 mesh 该用哪些材质、带哪些附加渲染状态去画?
|
||||
|
||||
和它配套工作的主要对象是:
|
||||
当前它的职责可以分成四层:
|
||||
|
||||
- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md):负责 mesh 资源本身。
|
||||
- `ResourceManager`:负责按路径或 `AssetRef` 加载 `Material`,也负责延迟异步加载。
|
||||
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md):只有 mesh 和材质都可用时,才会把对象整理成可见渲染项。
|
||||
- 保存运行时 `Material` 句柄数组
|
||||
- 保存项目材质的稳定 `AssetRef`
|
||||
- 仅对 `builtin://` 这类 virtual path 保留 `materialPaths`
|
||||
- 保存 `castShadows / receiveShadows / renderLayer`
|
||||
|
||||
这种拆分很接近商业引擎里常见的 `MeshFilter + MeshRenderer` 思路。好处是“几何来源”和“绘制配置”可以分别序列化、分别编辑,也更适合后续做资源热更新、材质复用和编辑器 Inspector。
|
||||
和 [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md) 一样,当前最需要纠正的文档心智是:
|
||||
|
||||
- 对项目资产材质,正式序列化协议已经是 `materialRefs`
|
||||
- `materialPaths` 主要保留给 virtual path,而不是给普通项目路径长期兜底
|
||||
|
||||
## 当前状态模型
|
||||
|
||||
当前实现维护了五份与材质槽相关的状态:
|
||||
当前实现维护 5 组与材质槽相关的状态:
|
||||
|
||||
| 状态 | 类型 | 作用 |
|
||||
|------|------|------|
|
||||
| `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>` | 防止同一槽位重复发起异步加载。 |
|
||||
| `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>` | 防止重复发起异步加载。 |
|
||||
|
||||
这几组数组会尽量保持同一槽位语义对齐。换句话说,`slot 3` 的 handle、path、`AssetRef`、pending state 都是在描述“第 3 个材质槽”。
|
||||
所有数组都按槽位对齐;`slot 2` 的 path、ref、handle 和 pending state 都在描述同一材质槽。
|
||||
|
||||
## 绑定与解析流程
|
||||
## 绑定与恢复流程
|
||||
|
||||
### 1. 路径驱动的绑定
|
||||
### 1. 运行时按路径设置
|
||||
|
||||
[SetMaterialPath](SetMaterialPath.md) 会:
|
||||
[SetMaterialPath](SetMaterialPath.md) 当前仍然是同步接口:
|
||||
|
||||
- 先确保槽位存在。
|
||||
- 清掉该槽位旧的异步挂起状态。
|
||||
- 写入新的 `m_materialPaths[index]`。
|
||||
- 立即调用 `ResourceManager::Load<Material>(path)` 做一次同步加载尝试。
|
||||
- 再调用 `TryGetAssetRef(path, ResourceType::Material, ...)` 尝试回填 `AssetRef`。
|
||||
1. 自动扩容到目标槽位
|
||||
2. 清掉旧 pending async load 状态
|
||||
3. 写入 `m_materialPaths[index]`
|
||||
4. 若路径为空,则清掉 handle 和 `AssetRef`
|
||||
5. 若路径非空,则同步 `Load<Material>(path)`
|
||||
6. 再尝试按路径回填 `m_materialRefs[index]`
|
||||
|
||||
即使同步加载失败,路径仍会保留;如果这条路径能映射到项目资产,`m_materialRefs[index]` 也可能仍然有效。
|
||||
因此对项目路径 `Assets/runtime.material` 来说,运行时是允许直接按路径设置的;但场景文本正式协议已经不是“只存路径”。
|
||||
|
||||
要注意一个很容易误解的点:按当前源码,`SetMaterialPath()` 本身仍是“立即同步加载”的接口,它不会因为 deferred scene load 模式而自动改成纯记录路径。
|
||||
### 2. 运行时按句柄设置
|
||||
|
||||
### 2. 句柄驱动的绑定
|
||||
[SetMaterial](SetMaterial.md) / [SetMaterials](SetMaterials.md) 会从句柄反推出路径,再按路径尝试回填 `AssetRef`。
|
||||
|
||||
[SetMaterial](SetMaterial.md) 会:
|
||||
这让“运行时直接塞句柄”和“后续还能稳定序列化回项目身份”保持一致。
|
||||
|
||||
- 保存句柄。
|
||||
- 通过 `material->GetPath()` 反推路径。
|
||||
- 再按路径尝试回填 `AssetRef`。
|
||||
### 3. 反序列化恢复
|
||||
|
||||
这让“运行时直接塞一个已经加载好的材质”与“场景序列化仍能恢复路径/资产引用”两件事可以同时成立。
|
||||
|
||||
### 3. 反序列化后的恢复策略
|
||||
|
||||
[Deserialize](Deserialize.md) 现在会同时处理:
|
||||
[Deserialize](Deserialize.md) 当前只识别:
|
||||
|
||||
- `materialPaths=<path0|path1|...>`
|
||||
- `materialRefs=<guid,localId,resourceType|...>`
|
||||
- 历史兼容键 `materials=<path0|path1|...>`
|
||||
- `castShadows`
|
||||
- `receiveShadows`
|
||||
- `renderLayer`
|
||||
|
||||
恢复优先级大致是:
|
||||
恢复顺序是:
|
||||
|
||||
1. 如果 `materialRef` 有效,优先尝试按 `AssetRef` 恢复。
|
||||
2. 如果当前是 deferred scene load,尽量先把 `AssetRef` 解析回路径,但不立即同步兑现材质。
|
||||
3. 如果没有可用 `AssetRef`,再按路径恢复。
|
||||
1. 某槽位若有有效 `materialRef`,优先按 `AssetRef` 恢复
|
||||
2. 若处于 deferred scene load,先尝试 `TryResolveAssetPath(materialRef, path)` 恢复 `Assets/...`
|
||||
3. 若不在 deferred 模式,则直接按 `AssetRef` 加载材质
|
||||
4. 若没有有效 `AssetRef`,只接受带 virtual scheme 的 `materialPaths`
|
||||
5. 没有 `AssetRef` 的普通项目路径会被清空
|
||||
|
||||
### 4. 首次访问时的异步兑现
|
||||
也就是说,下面这种旧式数据:
|
||||
|
||||
[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 虽然是 `const`,但当前实现会通过 `const_cast` 内部触发:
|
||||
```text
|
||||
materialPaths=Assets/runtime.material;materialRefs=;
|
||||
```
|
||||
|
||||
1. `EnsureDeferredAsyncMaterialLoadStarted(index)`
|
||||
2. `ResolvePendingMaterials()`
|
||||
|
||||
也就是说,这两个访问器不只是“读缓存”,还会推动路径恢复后的材质兑现流程向前走。
|
||||
|
||||
从行为上看,它们的主用途是支持 deferred scene load,但按当前源码,只要某个槽位“有路径、还没有 material、也还没发起过请求”,首次访问就可能触发一次异步加载尝试。
|
||||
当前不会再被当成正式项目材质绑定恢复。
|
||||
|
||||
## 序列化语义
|
||||
|
||||
当前序列化会输出五段键值:
|
||||
当前 [Serialize](Serialize.md) 会写出:
|
||||
|
||||
```text
|
||||
materialPaths=<fallbackPath0|fallbackPath1|...>;
|
||||
materialRefs=<guid,localId,resourceType|...>;
|
||||
materialPaths=<slot0|slot1|...>;
|
||||
materialRefs=<slot0|slot1|...>;
|
||||
castShadows=1;
|
||||
receiveShadows=1;
|
||||
renderLayer=0;
|
||||
```
|
||||
|
||||
其中最关键的设计点是:
|
||||
但其中最关键的规则是:
|
||||
|
||||
- `materialRefs` 是主身份信息,面向项目资产恢复。
|
||||
- `materialPaths` 更像回退字段,只在该槽位没有有效 `AssetRef` 时才真正写出路径。
|
||||
- 对有有效 `AssetRef` 的项目材质槽,`materialPaths` 会被清空
|
||||
- 对没有 `AssetRef` 且是 virtual scheme 的槽位,`materialPaths` 才保留真实路径
|
||||
|
||||
例如一个项目资产材质槽,当前文本很可能是:
|
||||
因此:
|
||||
|
||||
```text
|
||||
materialPaths=;
|
||||
materialRefs=<有效 guid,localId,type>;
|
||||
```
|
||||
- 项目材质的主协议是 `materialRefs`
|
||||
- `builtin://materials/default-primitive` 这类虚拟路径才长期留在 `materialPaths`
|
||||
|
||||
这不是数据丢失,而是说明当前序列化更信任 `AssetRef` 作为稳定身份。
|
||||
## 与 deferred scene load 的关系
|
||||
|
||||
[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 当前都带副作用。
|
||||
|
||||
它们会内部触发:
|
||||
|
||||
1. `EnsureDeferredAsyncMaterialLoadStarted(index)`
|
||||
2. `ResolvePendingMaterials()`
|
||||
|
||||
所以在 deferred 场景里,反序列化后常见状态是:
|
||||
|
||||
- `m_materialRefs[index]` 已恢复
|
||||
- `m_materialPaths[index]` 可能已恢复
|
||||
- `m_materials[index]` 仍然为空
|
||||
|
||||
直到后续第一次访问材质句柄时,异步兑现才真正开始或收口。
|
||||
|
||||
## 与渲染链路的关系
|
||||
|
||||
当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 直接挂进可见渲染对象,再由后续材质提取逻辑读取材质槽。
|
||||
当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 挂进可见项,再由后续材质提取逻辑读取具体槽位材质。
|
||||
|
||||
这意味着:
|
||||
因此当前对 renderer 的真实影响是:
|
||||
|
||||
- `MeshRendererComponent` 是否“有槽位”不重要。
|
||||
- 重要的是在真正提取材质时,对应槽位能否拿到有效 `Material`。
|
||||
- 在 deferred / async 场景里,对象可能已经有 `MeshFilterComponent + MeshRendererComponent`,但某个时刻材质仍为空,直到异步结果被 `ResolvePendingMaterials()` 收口。
|
||||
- 组件是否存在并不等于材质已经可用
|
||||
- deferred load 下,材质槽元数据可能先恢复,材质句柄后到
|
||||
|
||||
## 阴影与渲染层的现实状态
|
||||
另外要继续明确一个实现边界:
|
||||
|
||||
当前组件公开了:
|
||||
- `castShadows`、`receiveShadows` 和 `renderLayer` 当前已可保存、序列化、脚本读写
|
||||
- 但还不能简单等同于“这些标志已经完整进入当前 renderer 主路径”
|
||||
|
||||
- `GetCastShadows()` / `SetCastShadows()`
|
||||
- `GetReceiveShadows()` / `SetReceiveShadows()`
|
||||
- `GetRenderLayer()` / `SetRenderLayer()`
|
||||
## 测试与锚点
|
||||
|
||||
但按当前源码检索,这几个字段目前主要被:
|
||||
- `tests/Components/test_mesh_render_components.cpp`
|
||||
- 覆盖 builtin virtual path 的序列化 / 反序列化
|
||||
- 覆盖“没有 `AssetRef` 的普通项目路径不会作为正式协议保留”
|
||||
- 覆盖项目材质按 `AssetRef` 序列化与恢复
|
||||
- 覆盖 deferred async material load
|
||||
- `tests/Scene/test_scene.cpp`
|
||||
- 覆盖 builtin material path 的场景保存 / 加载
|
||||
|
||||
- 组件测试、场景序列化测试
|
||||
- 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 主路径。
|
||||
- 当前只维护平铺材质槽数组,不处理更复杂的实例化或 streaming 策略
|
||||
- 项目材质的正式序列化协议已经收口到 `materialRefs`
|
||||
- 没有 `AssetRef` 的普通项目路径在反序列化时当前会被清掉;只有 virtual scheme 仍可单独依赖路径
|
||||
- `GetMaterial()` / `GetMaterialHandle()` 是带副作用的读访问器;只读元数据时应优先使用 [GetMaterialPath](GetMaterialPath.md)、[GetMaterialPaths](GetMaterialPaths.md) 和 [GetMaterialAssetRefs](GetMaterialAssetRefs.md)
|
||||
|
||||
## 相关方法
|
||||
|
||||
|
||||
@@ -6,33 +6,71 @@
|
||||
void Serialize(std::ostream& os) const override;
|
||||
```
|
||||
|
||||
## 行为说明
|
||||
## 当前实现行为
|
||||
|
||||
当前实现不再输出旧的 `materials=` 键,而是并行写出材质路径 fallback 和稳定资产引用:
|
||||
当前实现会先准备两份待写出的槽位数组:
|
||||
|
||||
- `serializedRefs`
|
||||
- `serializedPaths`
|
||||
|
||||
对每个槽位,处理规则是:
|
||||
|
||||
1. 若当前 `AssetRef` 无效、`materialPath` 非空、且路径不是 virtual scheme,则再尝试一次 `TryGetAssetRef(...)`
|
||||
2. 若该槽位最终有有效 `AssetRef`,或路径本来就不是 virtual scheme,则把 `serializedPaths[i]` 清空
|
||||
|
||||
然后按槽位顺序输出:
|
||||
|
||||
```text
|
||||
materialPaths=|builtin://materials/default;
|
||||
materialRefs=<guid,localId,resourceType>|;
|
||||
materialPaths=<slot0|slot1|...>;
|
||||
materialRefs=<slot0|slot1|...>;
|
||||
castShadows=1;
|
||||
receiveShadows=0;
|
||||
renderLayer=3;
|
||||
receiveShadows=1;
|
||||
renderLayer=0;
|
||||
```
|
||||
|
||||
其中:
|
||||
## 当前序列化结果
|
||||
|
||||
- `materialPaths` 和 `materialRefs` 都按材质槽顺序用 `|` 分隔。
|
||||
- 对于能稳定映射到项目资产的槽位,当前会优先写 `materialRefs`,并把同槽位 `materialPaths` 留空。
|
||||
- 对于没有有效 `AssetRef` 的槽位,例如 `builtin://` 这类虚拟路径,才会把路径写进 `materialPaths`。
|
||||
- 会保留尾部空槽位,避免编辑器材质槽数量在反序列化后缩水。
|
||||
### 项目材质槽
|
||||
|
||||
## 当前实现含义
|
||||
如果某槽位绑定的是项目材质 `Assets/runtime.material`,且能解析出有效 `AssetRef`,常见输出会像:
|
||||
|
||||
- 场景文本现在同时保存“人类可读路径 fallback”和“项目资产稳定身份”。
|
||||
- 项目资产改名或移动后,更应依赖 `materialRefs` 恢复绑定,而不是依赖旧路径字符串。
|
||||
- 旧版依赖 `materials=` 的读取逻辑仍由 [Deserialize](Deserialize.md) 保留兼容。
|
||||
```text
|
||||
materialPaths=;
|
||||
materialRefs=<guid,localId,resourceType>;
|
||||
```
|
||||
|
||||
也就是说:
|
||||
|
||||
- 不会再把普通项目路径当正式场景数据保留下来
|
||||
|
||||
### builtin / virtual path 槽
|
||||
|
||||
如果某槽位绑定的是 `builtin://materials/default-primitive`,而它没有项目资产 `AssetRef`,则常见输出会像:
|
||||
|
||||
```text
|
||||
materialPaths=builtin://materials/default-primitive;
|
||||
materialRefs=;
|
||||
```
|
||||
|
||||
## 关键语义
|
||||
|
||||
- 当前不再输出旧的 `materials=` 键
|
||||
- 项目资产材质的正式场景身份是 `materialRefs`
|
||||
- `materialPaths` 现在主要承担 virtual path 回退职责
|
||||
- 会保留尾部空槽位,避免反序列化后材质槽数量缩水
|
||||
|
||||
## 设计说明
|
||||
|
||||
这种设计和 `MeshFilterComponent` 的协议方向一致:
|
||||
|
||||
- 项目资源走稳定 `AssetRef`
|
||||
- builtin / 虚拟资源走显式协议路径
|
||||
|
||||
这样既能提高资源改名、移动后的恢复成功率,也不会强迫 virtual 资源进入项目资产数据库。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [返回类型总览](MeshRendererComponent.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
- [GetMaterialAssetRefs](GetMaterialAssetRefs.md)
|
||||
- [GetMaterialPaths](GetMaterialPaths.md)
|
||||
- [Deserialize](Deserialize.md)
|
||||
|
||||
@@ -21,14 +21,15 @@ void SetMaterialPath(size_t index, const std::string& materialPath);
|
||||
- 这是一个同步设置接口,不是“只记录路径、等以后再加载”的接口。
|
||||
- 虚拟路径或内置路径也允许写入,但通常拿不到有效 `AssetRef`。
|
||||
- 如果后续重新设置同一槽位,旧的异步状态会被显式丢弃。
|
||||
- 对项目材质来说,这个 API 写入的是运行时路径缓存;真正序列化时只要能得到有效 `AssetRef`,普通项目路径就不会作为正式场景数据长期保留。
|
||||
|
||||
## 使用建议
|
||||
|
||||
这比直接设置 handle 更适合:
|
||||
|
||||
- 编辑器材质面板
|
||||
- 场景文本反序列化后的路径恢复
|
||||
- 希望保留稳定文本路径的工具链
|
||||
- 运行时或工具侧按路径即时指定材质
|
||||
- virtual path 资源输入
|
||||
|
||||
如果调用方已经手里拿到了一个确定可用的材质句柄,并且希望以运行时对象为主写入,则可以考虑 [SetMaterial](SetMaterial.md)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user