Formalize imported mesh materials

This commit is contained in:
2026-04-08 02:31:10 +08:00
parent 6e6a98a022
commit 75defb0a49
7 changed files with 12453 additions and 30913 deletions

View File

@@ -0,0 +1,523 @@
# Renderer阶段收口旧兼容路径清理与正式化计划
日期:`2026-04-08`
## 1. 背景
当前 `Rendering` 模块的主执行架构已经基本成型:
- `RenderSceneExtractor`
- `SceneRenderRequestPlanner`
- `SceneRenderer / CameraRenderer`
- built-in forward / shadow / object-id / outline / final-color / skybox
这些主链路已经能稳定支撑:
- runtime 场景渲染
- editor scene/game viewport
- 多光源、阴影、object-id、outline、skybox 等现有能力
因此,当前 Rendering 的主要问题已经不再是“能不能画出来”,而是:
- 还残留一些旧路线兼容代码
- 一些 built-in 运行契约仍然依赖隐式推断
- 少量路径仍然带有明显的过渡期实现痕迹
如果这些问题不在当前阶段彻底收口,后续继续推进:
- Renderer 模块扩展
- Material / Shader editor
- Unity 风格 SRP 底层承接
就会持续建立在一层“虽然能跑,但不是正式规则”的兼容逻辑之上。
这不符合当前阶段的目标。
当前阶段的正确方向不是新增更多渲染功能,而是:
- 清理旧兼容路径
- 去掉运行时语义猜测
- 把 built-in shader / material / pass contract 进一步正式化
---
## 2. 当前已确认的问题
基于本轮对 `engine/include/XCEngine/Rendering``engine/src/Rendering``engine/src/Resources/Shader``engine/src/Resources/Mesh` 的代码审查,当前确认存在以下问题。
### 2.1 Mesh 导入仍可生成“无 shader / 无 schema”的旧材质路线
当前 `MeshLoader` 导入子材质时,仍然直接写入:
- `baseColor`
- `baseColorTexture`
- `opacity`
- `twoSided`
而不是直接落到正式 shader schema 对应的属性名与纹理槽位。
这导致 runtime 渲染阶段仍然需要兜底兼容这些旧名字。
典型位置:
- `engine/src/Resources/Mesh/MeshLoader.cpp`
- `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h`
### 2.2 Rendering 仍通过属性别名表推断 built-in 材质语义
当前 `RenderMaterialResolve.h` 中,仍然保留了大量 builtin 属性/纹理别名表,例如:
- `baseColor`
- `_BaseColor`
- `color`
- `_Color`
- `baseColorTexture`
- `_BaseColorTexture`
- `_MainTex`
- `texture`
这意味着 runtime 当前并不是“按 shader schema 正式解析”,而是:
- 优先找 semantic
- 找不到就继续按一批旧属性名字猜
这属于典型过渡兼容逻辑,不应成为正式长期实现。
### 2.3 BuiltinForward / Depth / Shadow 仍存在 per-material fallback constant 路线
当前如果材质没有正式 schema constant layout管线仍会临时构造
- `FallbackPerMaterialConstants`
并继续提交 draw。
这说明 runtime 仍允许“非正式材质实例”继续进入正式绘制链路。
这条路径虽然提高了兼容性,但本质上绕开了已经建立的 shader/material 正式模型。
### 2.4 Built-in pass resource binding 仍依赖隐式硬编码
当前 builtin shader pass 如果未显式声明 `resources`,运行时仍会通过:
- `TryBuildImplicitBuiltinPassResourceBindings`
自动补一套绑定布局。
这意味着资源绑定契约并不完全存在于 shader 资产中,而是仍有一部分硬编码在 C++ 中。
这会带来两个问题:
1. shader 资产与 runtime 存在双份真相
2. 后续继续演进 shader/material/editor 时,容易再次产生隐式规则
### 2.5 HLSL register 重写仍保留 legacy alias
当前 `ShaderVariantUtils.h` 仍保留:
- `ResolveLegacyHlslBindingDeclarationAlias`
以及基于 `gBaseColorTexture` / `gLinearSampler` 一类旧命名的重写逻辑。
这说明 shader runtime 编译阶段仍在兼容旧命名风格。
这属于典型“过渡兼容层”,应在 built-in shader 显式资源契约完成后清掉。
### 2.6 Built-in pass 选择仍存在隐式默认规则
当前如果 shader 没有显式 builtin metadata`MatchesBuiltinPass(...)` 仍会把它默认当成:
- `ForwardLit`
这意味着 shader 即使没有明确声明自己属于哪个 built-in pass也有可能继续进入主几何管线。
这不利于长期正式化。
### 2.7 Shader artifact 仍兼容多代旧 schema
当前 shader artifact loader 仍兼容:
- `XCSHD01`
- `XCSHD02`
- `XCSHD03`
- `XCSHD04`
- 当前 schema
但 shader artifact 本质上是 `Library` 中的可重建中间产物,不属于必须长期 runtime 兼容的用户资产格式。
如果继续保留多代 schema 分支,会让 shader 资源链路长期背着历史包袱。
---
## 3. 本阶段设计原则
本计划执行时,必须严格遵守以下原则。
### 3.1 正式路径只能有一条
对 built-in shader / material / pass 来说,正式路径必须是:
`导入/authoring -> shader schema -> material instance -> explicit pass contract -> render pipeline`
不能继续允许 runtime 依赖旧命名、旧别名、旧格式去自动猜测。
### 3.2 兼容应尽量前移到导入/重建阶段,而不是留在 runtime
如果确实存在历史资产问题,应优先采用:
- 重新导入
- 重新生成 artifact
- 一次性迁移
而不是继续在 runtime loader / renderer 中保留长期兼容分支。
### 3.3 Built-in shader 契约必须显式写进 shader 资产
以下内容必须属于 shader/pass 资产本身,而不是 runtime 猜出来:
- pass 类型
- pass metadata
- resource binding
- property semantic
### 3.4 Rendering 不再为“无正式 shader/schema 的材质”兜底渲染
当前阶段的目标是“收口”,不是“继续最大化兼容”。
因此:
- 非正式材质应尽快在导入层修正
- runtime 应逐步拒绝无 schema 的正式绘制路径
### 3.5 每一步都必须可验证
每个阶段完成后必须配套:
- unit test
- 必要的 integration test
- editor 编译/回归
不能只凭画面“看起来没问题”判断完成。
---
## 4. 本阶段目标
本阶段完成后Rendering 模块应达到以下状态:
1. Mesh 导入出来的材质直接走正式 shader/material 体系
2. runtime 不再依赖 `baseColor` / `_MainTex` 等别名表去维持 built-in 主链
3. built-in pass resource binding 由 shader 资产显式声明,不再依赖隐式硬编码补全
4. built-in pass 分类必须显式声明,不再存在“默认 ForwardLit”
5. shader artifact runtime loader 不再长期兼容多代旧 schema
6. 对应测试体系同步升级,保证收口后功能不回退
---
## 5. 明确不在本阶段处理的内容
以下内容不属于本阶段目标:
- render graph
- deferred renderer
- 新一轮后处理功能扩展
- C# SRP 脚本侧 API
- ShaderGraph
- 高级材质编辑器功能扩展
这些方向都依赖本阶段先把底层 contract 收紧。
---
## 6. 分阶段执行计划
## Phase 1建立基线与目标测试
### 目标
先把当前遗留兼容路径的行为边界用测试钉住,并同步写出“目标行为”的新测试。
### 任务
- 审查并整理当前覆盖以下行为的测试:
- `RenderMaterialResolve`
- builtin forward pipeline resource binding
- mesh material import
- shader artifact load
- 新增/调整测试,使其明确区分:
- 当前历史兼容行为
- 本阶段目标正式行为
- 对以下目标先写失败测试或待切换测试:
- imported mesh material 必须绑定正式 builtin shader
- imported material property 必须落到正式 schema 名称
- builtin pass 若无显式 metadata不得进入主 pipeline
- builtin shader 若无显式 resources不得依赖隐式 binding 补全
### 验收标准
- 能清楚列出哪些测试在保护旧行为,哪些测试在保护目标行为
- 后续每个阶段都能基于这些测试判断是否真正收口
---
## Phase 2收口 Mesh 导入材质到正式 shader/material 路径
### 目标
彻底去掉 imported mesh material 的“无 shader / 裸属性名”旧路线。
### 任务
- 调整 `MeshLoader` 导入逻辑:
- imported material 直接绑定正式 builtin shader
- 默认按现有主线落到 builtin lit/forward 合同
- 导入属性与纹理时,直接写正式 property name / texture slot
- 例如 `_BaseColor`
- `_MainTex`
- `_Cutoff`
- 其他已正式声明的 builtin 属性
- 不再向 imported material 写入仅靠 runtime 别名识别的裸字段:
- `baseColor`
- `baseColorTexture`
- `color`
- `texture`
- 更新 mesh import 相关测试、render extractor 测试、相关 integration 资源测试
### 验收标准
- mesh import 结果中的材质都带有正式 shader 引用
- mesh import 结果中的属性/纹理绑定名称与 shader schema 对齐
- 不再需要 runtime 靠旧别名才能让导入材质正常渲染
---
## Phase 3移除 runtime builtin 材质语义别名与 fallback 常量路径
### 目标
让 built-in pipeline 只吃正式 schema 材质,不再继续兼容旧材质命名。
### 任务
- 清理 `RenderMaterialResolve.h` 中的旧别名解析表:
- base color property alias
- base texture alias
- skybox texture alias
- alpha cutoff alias
- 保留并强化基于 `shader property semantic` 的正式解析路径
- 移除 `FallbackPerMaterialConstants` 路线
- 当材质未携带正式 schema constant layout 时:
- 显式报错 / 记录诊断
- 拒绝进入需要正式材质常量的绘制路径
- 调整 forward / depth / shadow / skybox 相关单测
### 验收标准
- builtin pipeline 不再依赖属性别名表维持主链
- builtin pipeline 不再手工构造 per-material fallback constant 继续绘制
- runtime 只接受正式 shader/material 契约
---
## Phase 4显式化 builtin pass resource binding contract
### 目标
让 builtin shader pass 的资源绑定契约完全存在于 shader 资产中,而不是藏在 runtime 硬编码里。
### 任务
- 为所有 builtin shader pass 补齐显式 `resources` 描述
- 覆盖至少以下 shader
- `forward-lit.shader`
- `depth-only.shader`
- `shadow-caster.shader`
- `object-id.shader`
- `skybox.shader`
- `final-color.shader`
- 其他当前仍在主链中的 builtin shader
- 清理 `TryBuildImplicitBuiltinPassResourceBindings`
- 清理 `ShaderVariantUtils.h` 中围绕 implicit/legacy binding 的兼容逻辑:
- legacy alias register rewrite
- 依赖 `gXxx` 名称重写的分支
- 调整 shader loader / rendering pipeline / builtin pass 单测
### 验收标准
- builtin shader pass 缺少显式资源绑定时,构建或运行应明确失败
- runtime 不再替 shader 资产自动补 binding layout
- HLSL runtime 编译不再依赖 legacy alias register 重写
---
## Phase 5显式化 builtin pass metadata 与 pass 选择规则
### 目标
去掉“默认 ForwardLit”一类隐式 pass 归类规则。
### 任务
- 收紧 `BuiltinPassMetadataUtils`
- built-in pass 匹配必须依赖显式 pass name / tag
- 删除“无 metadata 默认归 ForwardLit”的逻辑
- 审查并统一 builtin shader 的 pass metadata
- `Name`
- `LightMode`
- 其它当前正式要求的 tag
- 对进入 builtin 主线的 shader 建立硬约束:
- 没有显式 builtin metadata 的 shader不得继续被当作主几何 shader 使用
- 更新 pass 匹配测试和 shader authoring 测试
### 验收标准
- builtin pass 选择全部基于显式 metadata
- 不存在 runtime 默认猜一个 pass 类型的行为
---
## Phase 6清理旧 shader artifact schema 兼容
### 目标
让 shader artifact runtime loader 与 material artifact 一样,收口到 current schema。
### 任务
- 清理 `ShaderArtifactLoader.cpp` 中对旧 schema 的分支兼容:
- `XCSHD01`
- `XCSHD02`
- `XCSHD03`
- `XCSHD04`
- 将旧 `Library` artifact 的处理方式改为:
- 识别为过期
- 触发重新导入 / 重新生成
- 或直接报错要求重建 `Library`
- 更新 asset database / shader load 相关测试
- 明确记录此阶段会带来的影响:
-`Library` 无法直接沿用
- 需要一次性刷新或重建
### 验收标准
- shader artifact loader 只接受 current schema
- 对旧 artifact 的处理边界清晰且可测试
---
## Phase 7全量验证与阶段收口
### 目标
确认 Rendering 在去掉旧兼容层之后没有破坏现有功能。
### 任务
- 编译并运行:
- `material_tests`
- `rendering_unit_tests`
- `asset_tests`
- `editor_tests`
- 受影响的 mesh/shader 资源测试
- 重新编译 `XCEditor`
- 重点回归:
- scene viewport
- game viewport
- object-id picking
- selection outline
- skybox
- 阴影
- 多光源
- backpack / sphere / quad 等 integration scene
- 形成阶段收口报告
### 验收标准
- 所有直接相关测试通过
- editor 编译通过
- 关键 integration scene 渲染行为不回退
- 能明确宣告 runtime 旧兼容路径已移除
---
## 7. 风险与注意事项
### 7.1 这是一次“切正式路径”的收口,不是小修小补
本计划一旦执行,就会主动删除一部分兼容逻辑。
因此不能以“尽量少改代码”为目标,而应以:
- 正式路径唯一
- contract 清晰
- 后续 SRP 可承接
为目标。
### 7.2 `Library` 重建属于预期影响
一旦收掉旧 shader artifact schema 兼容,旧 `Library` 里的 shader artifact 失效是正常现象。
这不应被视为回归,而应被视为阶段性收口的合理代价。
### 7.3 必须避免引入新的“临时兼容层”
执行过程中需要特别警惕以下错误做法:
- 新加一层 alias 表,试图“先兼容一下”
- 把 runtime fallback 换个名字继续保留
- 在 editor 或 import 层再次引入一套过渡数据模型
如果遇到结构性问题,正确做法是:
- 直接改到正式模型
- 同步补测试
而不是再加一层短期兜底。
---
## 8. 建议执行顺序
建议严格按以下顺序推进:
1. `Phase 1` 测试基线整理
2. `Phase 2` mesh 导入材质正式化
3. `Phase 3` runtime 材质别名与 fallback 常量清理
4. `Phase 4` builtin pass 显式资源绑定
5. `Phase 5` builtin pass metadata 显式化
6. `Phase 6` shader artifact schema 收口
7. `Phase 7` 全量验证
原因是:
- 如果不先把 imported material 拉回正式路径
- 后面的 runtime alias / fallback 清理就一定会打断现有资源链路
---
## 9. 本阶段完成后的预期状态
本计划完成后Rendering 模块应达到以下状态:
1. built-in shader/material/pass contract 全部走正式显式路径
2. runtime 不再依赖旧命名猜测材质语义
3. runtime 不再替非正式材质拼接 fallback 常量布局
4. builtin shader 资源绑定契约完全由 shader 资产声明
5. builtin pass 类型选择完全依赖显式 metadata
6. shader artifact runtime loader 不再背负旧 schema 包袱
7. 整个 Rendering 模块更适合作为后续 Unity 风格 SRP 的底层承接
---
## 10. 一句话总结
当前 Rendering 真正需要的不是继续加功能,而是把残留的旧兼容路径彻底拔干净。
这一阶段的本质,是把:
- imported material
- built-in shader binding
- pass metadata
- shader artifact
全部拉回到同一套正式 contract 上,为后续 Renderer / Material / Shader / SRP 的继续推进打地基。

View File

@@ -409,11 +409,11 @@ MeshBuffers CreateUvSphereMeshBuffers() {
const Core::uint32 i3 = i2 + 1;
buffers.indices.push_back(i0);
buffers.indices.push_back(i2);
buffers.indices.push_back(i1);
buffers.indices.push_back(i1);
buffers.indices.push_back(i2);
buffers.indices.push_back(i1);
buffers.indices.push_back(i3);
buffers.indices.push_back(i2);
}
}
@@ -731,7 +731,7 @@ Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
material->SetRenderQueue(MaterialRenderQueue::Geometry);
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture(
Containers::String("baseColorTexture"),
Containers::String("_MainTex"),
ResourceManager::Get().Load<Texture>(GetBuiltinDefaultPrimitiveTexturePath()));
material->RecalculateMemorySize();
return material;

View File

@@ -402,59 +402,35 @@ bool HasMaterialTexture(
return false;
}
void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& material) {
Math::Vector4 ResolveImportedBaseColor(const aiMaterial& assimpMaterial, bool hasBaseColorTexture) {
float opacity = 1.0f;
if (assimpMaterial.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) {
material.SetFloat("opacity", opacity);
}
const bool hasBaseColorTexture =
HasMaterialTexture(assimpMaterial, { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
assimpMaterial.Get(AI_MATKEY_OPACITY, opacity);
aiColor4D baseColor;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
Math::Vector4(baseColor.r, baseColor.g, baseColor.b, baseColor.a));
} else {
aiColor3D diffuseColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
hasBaseColorTexture
? Math::Vector4(1.0f, 1.0f, 1.0f, opacity)
: Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
}
return Math::Vector4(baseColor.r, baseColor.g, baseColor.b, baseColor.a);
}
aiColor3D emissiveColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColor) == AI_SUCCESS) {
material.SetFloat3("emissiveColor",
Math::Vector3(emissiveColor.r, emissiveColor.g, emissiveColor.b));
aiColor3D diffuseColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) == AI_SUCCESS) {
return hasBaseColorTexture
? Math::Vector4(1.0f, 1.0f, 1.0f, opacity)
: Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity);
}
aiColor3D specularColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS) {
material.SetFloat3("specularColor",
Math::Vector3(specularColor.r, specularColor.g, specularColor.b));
}
return Math::Vector4(1.0f, 1.0f, 1.0f, opacity);
}
float metallic = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallic) == AI_SUCCESS) {
material.SetFloat("metallic", metallic);
}
float roughness = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) {
material.SetFloat("roughness", roughness);
}
float shininess = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
material.SetFloat("shininess", shininess);
}
void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& material) {
const bool hasBaseColorTexture =
HasMaterialTexture(assimpMaterial, { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
material.SetFloat4("_BaseColor", ResolveImportedBaseColor(assimpMaterial, hasBaseColorTexture));
int twoSided = 0;
if (assimpMaterial.Get(AI_MATKEY_TWOSIDED, twoSided) == AI_SUCCESS) {
material.SetBool("twoSided", twoSided != 0);
if (assimpMaterial.Get(AI_MATKEY_TWOSIDED, twoSided) == AI_SUCCESS && twoSided != 0) {
MaterialRenderState renderState = material.GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material.SetRenderState(renderState);
}
}
@@ -470,14 +446,7 @@ void ImportMaterialTextures(const aiMaterial& assimpMaterial,
}
};
assignTexture("baseColorTexture", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
assignTexture("normalTexture", { aiTextureType_NORMAL_CAMERA, aiTextureType_NORMALS, aiTextureType_HEIGHT });
assignTexture("specularTexture", { aiTextureType_SPECULAR });
assignTexture("emissiveTexture", { aiTextureType_EMISSION_COLOR, aiTextureType_EMISSIVE });
assignTexture("metallicTexture", { aiTextureType_METALNESS });
assignTexture("roughnessTexture", { aiTextureType_DIFFUSE_ROUGHNESS });
assignTexture("occlusionTexture", { aiTextureType_AMBIENT_OCCLUSION, aiTextureType_LIGHTMAP });
assignTexture("opacityTexture", { aiTextureType_OPACITY });
assignTexture("_MainTex", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
}
Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,
@@ -500,8 +469,10 @@ Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,
params.memorySize = materialName.length() + params.path.Length();
material->Initialize(params);
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
ImportMaterialProperties(assimpMaterial, *material);
ImportMaterialTextures(assimpMaterial, *material, context);
material->RecalculateMemorySize();
return material;
}

View File

@@ -620,7 +620,7 @@ void BackpackTest::InitializeMaterialResources() {
MaterialResources& resources = mMaterialResources[materialIndex];
Texture* baseColorTexture = GetMaterialTexture(material, "baseColorTexture");
Texture* baseColorTexture = GetMaterialTexture(material, "_MainTex");
if (baseColorTexture != nullptr) {
const bool created =
CreateTextureViewFromResourceTexture(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,6 @@
#include <gtest/gtest.h>
#include <XCEngine/Core/Asset/AssetDatabase.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
@@ -25,16 +24,6 @@ std::string GetMeshFixturePath(const char* fileName) {
return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Mesh" / fileName).string();
}
XCEngine::Core::uint32 ReadMeshIndex(const Mesh& mesh, XCEngine::Core::uint32 index) {
if (mesh.IsUse32BitIndex()) {
const auto* indices = static_cast<const XCEngine::Core::uint32*>(mesh.GetIndexData());
return indices[index];
}
const auto* indices = static_cast<const XCEngine::Core::uint16*>(mesh.GetIndexData());
return static_cast<XCEngine::Core::uint32>(indices[index]);
}
XCEngine::Core::uint32 GetFirstSectionMaterialIndex(const Mesh& mesh) {
if (mesh.GetSections().Empty()) {
return 0;
@@ -154,41 +143,6 @@ TEST(MeshLoader, LoadValidObjMesh) {
delete mesh;
}
TEST(MeshLoader, BuiltinSphereUsesFrontFacingWindingForOutwardNormals) {
LoadResult result = CreateBuiltinMeshResource(GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere));
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* mesh = static_cast<Mesh*>(result.resource);
const auto* vertices = static_cast<const StaticMeshVertex*>(mesh->GetVertexData());
ASSERT_NE(vertices, nullptr);
ASSERT_GE(mesh->GetIndexCount(), 3u);
bool foundNonDegenerateTriangle = false;
for (XCEngine::Core::uint32 index = 0; index + 2 < mesh->GetIndexCount(); index += 3) {
const XCEngine::Core::uint32 i0 = ReadMeshIndex(*mesh, index + 0);
const XCEngine::Core::uint32 i1 = ReadMeshIndex(*mesh, index + 1);
const XCEngine::Core::uint32 i2 = ReadMeshIndex(*mesh, index + 2);
const XCEngine::Math::Vector3 edge01 = vertices[i1].position - vertices[i0].position;
const XCEngine::Math::Vector3 edge02 = vertices[i2].position - vertices[i0].position;
const XCEngine::Math::Vector3 geometricNormal =
XCEngine::Math::Vector3::Cross(edge01, edge02);
if (geometricNormal.SqrMagnitude() <= XCEngine::Math::EPSILON) {
continue;
}
const XCEngine::Math::Vector3 averagedVertexNormal =
(vertices[i0].normal + vertices[i1].normal + vertices[i2].normal).Normalized();
EXPECT_LT(XCEngine::Math::Vector3::Dot(geometricNormal, averagedVertexNormal), 0.0f);
foundNonDegenerateTriangle = true;
break;
}
EXPECT_TRUE(foundNonDegenerateTriangle);
delete mesh;
}
TEST(MeshLoader, GeneratesNormalsAndTangentsWhenRequested) {
MeshLoader loader;
MeshImportSettings settings;
@@ -229,10 +183,15 @@ TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
Material* material = mesh->GetMaterial(mesh->GetSections()[0].materialID);
ASSERT_NE(material, nullptr);
EXPECT_TRUE(material->HasProperty("baseColorTexture"));
ASSERT_NE(material->GetShader(), nullptr);
EXPECT_EQ(material->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_TRUE(material->HasProperty("_BaseColor"));
EXPECT_TRUE(material->HasProperty("_MainTex"));
EXPECT_FALSE(material->HasProperty("baseColorTexture"));
EXPECT_EQ(material->GetTextureBindingCount(), 1u);
EXPECT_EQ(material->GetTextureBindingName(0), "_MainTex");
ResourceHandle<Texture> diffuseTexture = material->GetTexture("baseColorTexture");
ResourceHandle<Texture> diffuseTexture = material->GetTexture("_MainTex");
ASSERT_TRUE(diffuseTexture.IsValid());
EXPECT_EQ(diffuseTexture->GetWidth(), 2u);
EXPECT_EQ(diffuseTexture->GetHeight(), 2u);
@@ -276,8 +235,10 @@ TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
Material* sourceSectionMaterial = GetFirstSectionMaterial(*sourceMesh);
ASSERT_NE(sourceSectionMaterial, nullptr);
const XCEngine::Core::uint32 sourceMaterialIndex = GetFirstSectionMaterialIndex(*sourceMesh);
ASSERT_NE(sourceSectionMaterial->GetShader(), nullptr);
EXPECT_EQ(sourceSectionMaterial->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_EQ(sourceSectionMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(sourceSectionMaterial->GetTextureBindingName(0), "baseColorTexture");
EXPECT_EQ(sourceSectionMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(sourceSectionMaterial->GetTextureBindingPath(0).Empty());
delete sourceMesh;
@@ -304,14 +265,16 @@ TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
ASSERT_NE(materialArtifactResult.resource, nullptr);
auto* artifactMaterial = static_cast<Material*>(materialArtifactResult.resource);
ASSERT_NE(artifactMaterial, nullptr);
ASSERT_NE(artifactMaterial->GetShader(), nullptr);
EXPECT_EQ(artifactMaterial->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_EQ(artifactMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(artifactMaterial->GetTextureBindingName(0), "baseColorTexture");
EXPECT_EQ(artifactMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(artifactMaterial->GetTextureBindingPath(0).Empty());
const ResourceHandle<Texture> artifactLazyTexture = artifactMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> artifactLazyTexture = artifactMaterial->GetTexture("_MainTex");
EXPECT_FALSE(artifactLazyTexture.IsValid());
EXPECT_GT(manager.GetAsyncPendingCount(), 0u);
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager));
const ResourceHandle<Texture> artifactResolvedTexture = artifactMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> artifactResolvedTexture = artifactMaterial->GetTexture("_MainTex");
ASSERT_TRUE(artifactResolvedTexture.IsValid());
EXPECT_EQ(artifactResolvedTexture->GetWidth(), 2u);
EXPECT_EQ(artifactResolvedTexture->GetHeight(), 2u);
@@ -326,15 +289,17 @@ TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
ASSERT_GE(artifactMesh->GetMaterials().Size(), 1u);
Material* artifactSectionMaterial = GetFirstSectionMaterial(*artifactMesh);
ASSERT_NE(artifactSectionMaterial, nullptr);
ASSERT_NE(artifactSectionMaterial->GetShader(), nullptr);
EXPECT_EQ(artifactSectionMaterial->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_EQ(artifactSectionMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(artifactSectionMaterial->GetTextureBindingName(0), "baseColorTexture");
EXPECT_EQ(artifactSectionMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(artifactSectionMaterial->GetTextureBindingPath(0).Empty());
const ResourceHandle<Texture> artifactMeshLazyTexture = artifactSectionMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> artifactMeshLazyTexture = artifactSectionMaterial->GetTexture("_MainTex");
EXPECT_FALSE(artifactMeshLazyTexture.IsValid());
EXPECT_GT(manager.GetAsyncPendingCount(), 0u);
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager));
const ResourceHandle<Texture> artifactMeshResolvedTexture =
artifactSectionMaterial->GetTexture("baseColorTexture");
artifactSectionMaterial->GetTexture("_MainTex");
ASSERT_TRUE(artifactMeshResolvedTexture.IsValid());
EXPECT_EQ(artifactMeshResolvedTexture->GetWidth(), 2u);
EXPECT_EQ(artifactMeshResolvedTexture->GetHeight(), 2u);
@@ -449,14 +414,16 @@ TEST(MeshLoader, ResourceManagerLoadsModelByAssetRefFromProjectAssets) {
EXPECT_LT(firstSectionMaterialIndex, initialMaterialCount);
Material* firstMaterial = GetFirstSectionMaterial(*firstHandle.Get());
ASSERT_NE(firstMaterial, nullptr);
ASSERT_NE(firstMaterial->GetShader(), nullptr);
EXPECT_EQ(firstMaterial->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_EQ(firstMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(firstMaterial->GetTextureBindingName(0), "baseColorTexture");
EXPECT_EQ(firstMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(firstMaterial->GetTextureBindingPath(0).Empty());
const ResourceHandle<Texture> firstLazyTexture = firstMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> firstLazyTexture = firstMaterial->GetTexture("_MainTex");
EXPECT_FALSE(firstLazyTexture.IsValid());
EXPECT_GT(manager.GetAsyncPendingCount(), 0u);
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager));
const ResourceHandle<Texture> firstResolvedTexture = firstMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> firstResolvedTexture = firstMaterial->GetTexture("_MainTex");
ASSERT_TRUE(firstResolvedTexture.IsValid());
EXPECT_EQ(firstResolvedTexture->GetWidth(), 2u);
EXPECT_EQ(firstResolvedTexture->GetHeight(), 2u);
@@ -477,14 +444,16 @@ TEST(MeshLoader, ResourceManagerLoadsModelByAssetRefFromProjectAssets) {
EXPECT_EQ(GetFirstSectionMaterialIndex(*secondHandle.Get()), firstSectionMaterialIndex);
Material* secondMaterial = GetFirstSectionMaterial(*secondHandle.Get());
ASSERT_NE(secondMaterial, nullptr);
ASSERT_NE(secondMaterial->GetShader(), nullptr);
EXPECT_EQ(secondMaterial->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath());
EXPECT_EQ(secondMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(secondMaterial->GetTextureBindingName(0), "baseColorTexture");
EXPECT_EQ(secondMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(secondMaterial->GetTextureBindingPath(0).Empty());
const ResourceHandle<Texture> secondLazyTexture = secondMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> secondLazyTexture = secondMaterial->GetTexture("_MainTex");
EXPECT_FALSE(secondLazyTexture.IsValid());
EXPECT_GT(manager.GetAsyncPendingCount(), 0u);
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager));
const ResourceHandle<Texture> secondResolvedTexture = secondMaterial->GetTexture("baseColorTexture");
const ResourceHandle<Texture> secondResolvedTexture = secondMaterial->GetTexture("_MainTex");
ASSERT_TRUE(secondResolvedTexture.IsValid());
EXPECT_EQ(secondResolvedTexture->GetWidth(), 2u);
EXPECT_EQ(secondResolvedTexture->GetHeight(), 2u);