Formalize renderer material contracts and harden backpack import

This commit is contained in:
2026-04-08 04:27:21 +08:00
parent 7be3b2cc45
commit 6113ed92b0
18 changed files with 534 additions and 326 deletions

View File

@@ -99,7 +99,7 @@ public:
const Containers::String& GetLastErrorMessage() const { return m_lastErrorMessage; }
private:
static constexpr Core::uint32 kCurrentImporterVersion = 6;
static constexpr Core::uint32 kCurrentImporterVersion = 7;
void EnsureProjectLayout();
void LoadSourceAssetDB();

View File

@@ -13,11 +13,6 @@
namespace XCEngine {
namespace Rendering {
struct BuiltinForwardMaterialData {
Math::Vector4 baseColorFactor = Math::Vector4::One();
float alphaCutoff = 0.5f;
};
enum class BuiltinSkyboxTextureMode : Core::uint8 {
None = 0,
Panoramic = 1,
@@ -81,34 +76,7 @@ inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* ma
}
}
static const char* kBaseColorPropertyNames[] = {
"baseColor",
"_BaseColor",
"color",
"_Color"
};
for (const char* propertyName : kBaseColorPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat4(Containers::String(propertyName));
}
}
Math::Vector4 baseColor = Math::Vector4::One();
static const char* kOpacityPropertyNames[] = {
"opacity",
"_Opacity",
"alpha",
"_Alpha"
};
for (const char* propertyName : kOpacityPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
baseColor.w = material->GetFloat(Containers::String(propertyName));
break;
}
}
return baseColor;
return Math::Vector4::One();
}
inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources::Material* material) {
@@ -123,23 +91,6 @@ inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources:
}
}
static const char* kTextureNames[] = {
"baseColorTexture",
"_BaseColorTexture",
"_MainTex",
"albedoTexture",
"mainTexture",
"texture"
};
for (const char* textureName : kTextureNames) {
const Resources::ResourceHandle<Resources::Texture> textureHandle =
material->GetTexture(Containers::String(textureName));
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
return textureHandle.Get();
}
}
return nullptr;
}
@@ -156,29 +107,9 @@ inline float ResolveBuiltinAlphaCutoff(const Resources::Material* material) {
}
}
static const char* kCutoffPropertyNames[] = {
"_Cutoff",
"cutoff",
"_AlphaCutoff",
"alphaCutoff"
};
for (const char* propertyName : kCutoffPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat(Containers::String(propertyName));
}
}
return 0.5f;
}
inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) {
BuiltinForwardMaterialData data = {};
data.baseColorFactor = ResolveBuiltinBaseColorFactor(material);
data.alphaCutoff = ResolveBuiltinAlphaCutoff(material);
return data;
}
inline bool IsCubemapSkyboxTextureType(Resources::TextureType type) {
return type == Resources::TextureType::TextureCube ||
type == Resources::TextureType::TextureCubeArray;
@@ -493,16 +424,6 @@ inline Core::int32 ResolveMaterialRenderQueue(const Resources::Material* materia
}
if (const Resources::Shader* shader = material->GetShader()) {
const Containers::String legacyExplicitPassName = material->GetLegacyShaderPassHint();
if (!NormalizeBuiltinPassMetadataValue(legacyExplicitPassName).Empty()) {
if (const Resources::ShaderPass* explicitPass = shader->FindPass(legacyExplicitPassName)) {
Core::int32 shaderQueue = defaultQueue;
if (TryResolveShaderPassRenderQueue(*explicitPass, shaderQueue)) {
return shaderQueue;
}
}
}
for (const Resources::ShaderPass& pass : shader->GetPasses()) {
Core::int32 shaderQueue = defaultQueue;
if (TryResolveShaderPassRenderQueue(pass, shaderQueue)) {
@@ -518,52 +439,6 @@ inline bool IsTransparentRenderQueue(Core::int32 renderQueue) {
return renderQueue >= static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent);
}
inline bool HasLegacyMaterialBuiltinPassHints(const Resources::Material* material) {
if (material == nullptr) {
return false;
}
return !NormalizeBuiltinPassMetadataValue(material->GetLegacyShaderPassHint()).Empty() ||
!NormalizeBuiltinPassMetadataValue(material->GetTag("LightMode")).Empty();
}
inline bool LegacyMaterialBuiltinPassHintsMatch(
const Resources::Material* material,
BuiltinMaterialPass pass) {
if (material == nullptr) {
return false;
}
const Containers::String shaderPass = material->GetLegacyShaderPassHint();
const Containers::String lightMode = material->GetTag("LightMode");
const bool hasMaterialShaderPass = !NormalizeBuiltinPassMetadataValue(shaderPass).Empty();
const bool hasMaterialLightMode = !NormalizeBuiltinPassMetadataValue(lightMode).Empty();
if (!hasMaterialShaderPass && !hasMaterialLightMode) {
return false;
}
if (hasMaterialShaderPass &&
!MatchesBuiltinPassName(shaderPass, pass)) {
return false;
}
if (hasMaterialLightMode &&
!MatchesBuiltinPassName(lightMode, pass)) {
return false;
}
return true;
}
inline bool CanUseLegacyMaterialPassFallback(const Resources::Material* material) {
if (material == nullptr) {
return true;
}
const Resources::Shader* shader = material->GetShader();
return shader == nullptr || !ShaderHasExplicitBuiltinMetadata(*shader);
}
inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) {
if (material == nullptr) {
return pass == BuiltinMaterialPass::ForwardLit;
@@ -578,14 +453,10 @@ inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMater
}
}
if (!CanUseLegacyMaterialPassFallback(material)) {
if (shader != nullptr && ShaderHasExplicitBuiltinMetadata(*shader)) {
return false;
}
if (HasLegacyMaterialBuiltinPassHints(material)) {
return LegacyMaterialBuiltinPassHintsMatch(material, pass);
}
return pass == BuiltinMaterialPass::ForwardLit;
}

View File

@@ -60,11 +60,6 @@ private:
RHI::RHIDescriptorSet* set = nullptr;
};
struct FallbackPerMaterialConstants {
Math::Vector4 baseColorFactor = Math::Vector4::One();
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
};
struct PassLayoutKey {
const Resources::Shader* shader = nullptr;
Containers::String passName;

View File

@@ -96,11 +96,6 @@ private:
Math::Vector4 shadowOptions = Math::Vector4::Zero();
};
struct FallbackPerMaterialConstants {
Math::Vector4 baseColorFactor = Math::Vector4::One();
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
};
struct SkyboxConstants {
Math::Vector4 topColor = Math::Vector4::Zero();
Math::Vector4 horizonColor = Math::Vector4::Zero();

View File

@@ -323,21 +323,6 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
}
}
if (!shaderHasExplicitBuiltinMetadata &&
ownerMaterial != nullptr &&
ownerMaterial->HasLegacyShaderPassHint()) {
const Resources::ShaderPass* explicitPass = shader->FindPass(ownerMaterial->GetLegacyShaderPassHint());
if (explicitPass != nullptr &&
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
*shader,
explicitPass->name,
backend,
keywordSet) &&
tryAcceptPass(*explicitPass)) {
return true;
}
}
return false;
};
@@ -474,7 +459,8 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
}
PipelineStateKey pipelineKey = {};
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
pipelineKey.renderState =
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
@@ -714,6 +700,8 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
const Resources::MaterialRenderState effectiveRenderState =
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
PassLayoutKey passLayoutKey = {};
passLayoutKey.shader = resolvedShaderPass.shader;
@@ -764,38 +752,12 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
};
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
FallbackPerMaterialConstants fallbackMaterialConstants = {};
if (!materialConstants.IsValid()) {
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
materialData.alphaCutoff,
0.0f,
0.0f,
0.0f);
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
{
Containers::String("_BaseColor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(Math::Vector4),
sizeof(Math::Vector4)
},
{
Containers::String("_Cutoff"),
Resources::MaterialPropertyType::Float,
sizeof(Math::Vector4),
sizeof(float),
sizeof(Math::Vector4)
}
};
materialConstants.data = &fallbackMaterialConstants;
materialConstants.size = sizeof(fallbackMaterialConstants);
materialConstants.layout = {
kFallbackMaterialConstantFields,
2,
sizeof(fallbackMaterialConstants)
};
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase requires a schema-backed material constant payload for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem);
@@ -879,6 +841,8 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
passLayout->pipelineLayout);
}
ApplyDynamicRenderState(effectiveRenderState, *commandList);
if (visibleItem.hasSection) {
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
if (visibleItem.sectionIndex >= sections.Size()) {

View File

@@ -51,20 +51,6 @@ const Resources::ShaderPass* FindCompatibleSurfacePass(
}
}
if (!shaderHasExplicitBuiltinMetadata &&
material != nullptr &&
material->HasLegacyShaderPassHint()) {
const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetLegacyShaderPassHint());
if (explicitPass != nullptr &&
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
shader,
explicitPass->name,
backend,
keywordSet)) {
return explicitPass;
}
}
if (shaderHasExplicitBuiltinMetadata) {
return nullptr;
}
@@ -303,7 +289,8 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
}
PipelineStateKey pipelineKey = {};
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
pipelineKey.renderState =
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
@@ -682,6 +669,8 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return false;
}
const Resources::MaterialRenderState effectiveRenderState =
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
PassLayoutKey passLayoutKey = {};
passLayoutKey.shader = resolvedShaderPass.shader;
@@ -705,38 +694,11 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
}
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
FallbackPerMaterialConstants fallbackMaterialConstants = {};
if (!materialConstants.IsValid()) {
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
materialData.alphaCutoff,
0.0f,
0.0f,
0.0f);
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
{
Containers::String("_BaseColor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(Math::Vector4),
sizeof(Math::Vector4)
},
{
Containers::String("_Cutoff"),
Resources::MaterialPropertyType::Float,
sizeof(Math::Vector4),
sizeof(float),
sizeof(Math::Vector4)
}
};
materialConstants.data = &fallbackMaterialConstants;
materialConstants.size = sizeof(fallbackMaterialConstants);
materialConstants.layout = {
kFallbackMaterialConstantFields,
2,
sizeof(fallbackMaterialConstants)
};
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline requires a schema-backed material constant payload");
return false;
}
if (passLayout->descriptorSetCount > 0) {
@@ -807,6 +769,8 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
passLayout->pipelineLayout);
}
ApplyDynamicRenderState(effectiveRenderState, *commandList);
if (visibleItem.hasSection) {
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
if (visibleItem.sectionIndex >= sections.Size()) {

View File

@@ -15,6 +15,7 @@
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -43,6 +44,13 @@ struct TextureImportContext {
TextureLoader textureLoader;
std::unordered_map<std::string, Texture*> textureCache;
std::vector<Texture*> ownedTextures;
struct ObjMaterialTextureFallbacks {
std::filesystem::path baseColorPath;
std::filesystem::path normalPath;
std::filesystem::path specularPath;
};
bool objMaterialFallbacksInitialized = false;
std::unordered_map<std::string, ObjMaterialTextureFallbacks> objMaterialFallbacks;
};
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
@@ -117,6 +125,38 @@ Containers::String ToContainersString(const std::string& value) {
return Containers::String(value.c_str());
}
std::string ToLowerCopy(const std::string& value) {
std::string lowered = value;
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return lowered;
}
std::string TrimCopy(const std::string& value) {
const auto begin = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
return std::isspace(ch) != 0;
});
if (begin == value.end()) {
return std::string();
}
const auto end = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
return std::isspace(ch) != 0;
}).base();
return std::string(begin, end);
}
std::vector<std::string> SplitWhitespaceTokens(const std::string& text) {
std::vector<std::string> tokens;
std::istringstream stream(text);
std::string token;
while (stream >> token) {
tokens.push_back(token);
}
return tokens;
}
Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
const char* category,
Core::uint32 index,
@@ -171,6 +211,20 @@ Containers::String NormalizePathString(const std::filesystem::path& path) {
return Containers::String(path.lexically_normal().generic_string().c_str());
}
std::filesystem::path ResolveSourcePathAbsolute(const Containers::String& sourcePath) {
std::filesystem::path resolvedPath(sourcePath.CStr());
if (resolvedPath.is_absolute()) {
return resolvedPath.lexically_normal();
}
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
return (std::filesystem::path(resourceRoot.CStr()) / resolvedPath).lexically_normal();
}
return resolvedPath.lexically_normal();
}
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
const Containers::String& ownerArtifactPath) {
if (dependencyPath.Empty()) {
@@ -390,6 +444,140 @@ Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
return nullptr;
}
enum class ObjMaterialTextureSemantic {
BaseColor,
Normal,
Specular
};
void InitializeObjMaterialTextureFallbacks(TextureImportContext& context) {
if (context.objMaterialFallbacksInitialized) {
return;
}
context.objMaterialFallbacksInitialized = true;
const std::filesystem::path sourcePath = ResolveSourcePathAbsolute(context.sourcePath);
if (ToLowerCopy(sourcePath.extension().string()) != ".obj") {
return;
}
std::ifstream objInput(sourcePath);
if (!objInput.is_open()) {
return;
}
std::vector<std::filesystem::path> mtlPaths;
const std::filesystem::path sourceDirectory = sourcePath.parent_path();
std::string line;
while (std::getline(objInput, line)) {
const std::string trimmed = TrimCopy(line);
if (trimmed.empty() || trimmed[0] == '#') {
continue;
}
const std::string lowered = ToLowerCopy(trimmed);
if (lowered.rfind("mtllib", 0) != 0 ||
(trimmed.size() > 6 && std::isspace(static_cast<unsigned char>(trimmed[6])) == 0)) {
continue;
}
const std::string remainder = TrimCopy(trimmed.substr(6));
for (const std::string& token : SplitWhitespaceTokens(remainder)) {
mtlPaths.push_back((sourceDirectory / token).lexically_normal());
}
}
for (const std::filesystem::path& mtlPath : mtlPaths) {
std::ifstream mtlInput(mtlPath);
if (!mtlInput.is_open()) {
continue;
}
const std::filesystem::path mtlDirectory = mtlPath.parent_path();
std::string currentMaterialName;
while (std::getline(mtlInput, line)) {
const std::string trimmed = TrimCopy(line);
if (trimmed.empty() || trimmed[0] == '#') {
continue;
}
const std::vector<std::string> tokens = SplitWhitespaceTokens(trimmed);
if (tokens.empty()) {
continue;
}
const std::string keyword = ToLowerCopy(tokens.front());
if (keyword == "newmtl") {
currentMaterialName = TrimCopy(trimmed.substr(tokens.front().size()));
continue;
}
if (currentMaterialName.empty() || tokens.size() < 2) {
continue;
}
const std::string& textureToken = tokens.back();
if (textureToken.empty() || textureToken[0] == '-') {
continue;
}
TextureImportContext::ObjMaterialTextureFallbacks& fallbacks =
context.objMaterialFallbacks[currentMaterialName];
const std::filesystem::path resolvedTexturePath =
(mtlDirectory / textureToken).lexically_normal();
if (keyword == "map_kd") {
fallbacks.baseColorPath = resolvedTexturePath;
} else if (keyword == "map_bump" || keyword == "bump" || keyword == "norm") {
fallbacks.normalPath = resolvedTexturePath;
} else if (keyword == "map_ks") {
fallbacks.specularPath = resolvedTexturePath;
}
}
}
}
Texture* LoadObjMaterialTextureFallback(const aiMaterial& assimpMaterial,
ObjMaterialTextureSemantic semantic,
const TextureImportSettings& settings,
TextureImportContext& context) {
InitializeObjMaterialTextureFallbacks(context);
aiString assimpName;
if (assimpMaterial.Get(AI_MATKEY_NAME, assimpName) != AI_SUCCESS ||
assimpName.length == 0) {
return nullptr;
}
const auto fallbackIt = context.objMaterialFallbacks.find(std::string(assimpName.C_Str()));
if (fallbackIt == context.objMaterialFallbacks.end()) {
return nullptr;
}
const TextureImportContext::ObjMaterialTextureFallbacks& fallbacks = fallbackIt->second;
const std::filesystem::path* fallbackPath = nullptr;
switch (semantic) {
case ObjMaterialTextureSemantic::BaseColor:
fallbackPath = &fallbacks.baseColorPath;
break;
case ObjMaterialTextureSemantic::Normal:
fallbackPath = &fallbacks.normalPath;
break;
case ObjMaterialTextureSemantic::Specular:
fallbackPath = &fallbacks.specularPath;
break;
default:
break;
}
if (fallbackPath == nullptr || fallbackPath->empty()) {
return nullptr;
}
return LoadExternalTexture(*fallbackPath, settings, context);
}
bool HasMaterialTexture(
const aiMaterial& assimpMaterial,
std::initializer_list<aiTextureType> textureTypes) {
@@ -438,15 +626,21 @@ void ImportMaterialTextures(const aiMaterial& assimpMaterial,
Material& material,
TextureImportContext& context) {
auto assignTexture = [&](const char* propertyName,
ObjMaterialTextureSemantic semantic,
std::initializer_list<aiTextureType> textureTypes) {
const TextureImportSettings settings = BuildMaterialTextureImportSettings(propertyName);
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, settings, context);
if (texture == nullptr) {
texture = LoadObjMaterialTextureFallback(assimpMaterial, semantic, settings, context);
}
if (texture != nullptr) {
material.SetTexture(Containers::String(propertyName), ResourceHandle<Texture>(texture));
}
};
assignTexture("_MainTex", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
assignTexture("_MainTex",
ObjMaterialTextureSemantic::BaseColor,
{ aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
}
Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,

View File

@@ -62,6 +62,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
return GetExecutableDirectory() / relativePath;
}
void NormalizeBackpackTestMaterials(Mesh& mesh) {
for (Material* material : mesh.GetMaterials()) {
if (material == nullptr) {
continue;
}
MaterialRenderState renderState = material->GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material->SetRenderState(renderState);
}
}
const char* GetScreenshotFilename(RHIType backendType) {
switch (backendType) {
case RHIType::D3D12:
@@ -179,6 +191,7 @@ void BackpackSceneTest::LoadBackpackMesh() {
ASSERT_TRUE(mMesh->IsValid());
ASSERT_GT(mMesh->GetVertexCount(), 0u);
ASSERT_GT(mMesh->GetSections().Size(), 0u);
NormalizeBackpackTestMaterials(*mMesh);
}
void BackpackSceneTest::BuildScene() {

View File

@@ -9,6 +9,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -19,10 +20,13 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -66,6 +70,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
return GetExecutableDirectory() / relativePath;
}
void NormalizeBackpackTestMaterials(Mesh& mesh) {
for (Material* material : mesh.GetMaterials()) {
if (material == nullptr) {
continue;
}
MaterialRenderState renderState = material->GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material->SetRenderState(renderState);
}
}
Mesh* CreateQuadMesh() {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
@@ -84,7 +100,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -137,7 +153,8 @@ Material* CreateQuadMaterial(Texture* texture) {
params.path = "Tests/Rendering/CameraStackQuad.material";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
return material;
}
@@ -189,6 +206,13 @@ void CameraStackSceneTest::SetUp() {
mQuadMesh = CreateQuadMesh();
mQuadTexture = CreateCheckerTexture();
mQuadMaterial = CreateQuadMaterial(mQuadTexture);
ASSERT_NE(mQuadMaterial, nullptr);
ASSERT_NE(mQuadMaterial->GetShader(), nullptr);
ASSERT_TRUE(mQuadMaterial->HasProperty("_BaseColor"));
ASSERT_TRUE(mQuadMaterial->HasProperty("_MainTex"));
ASSERT_EQ(mQuadMaterial->GetTexture("_MainTex").Get(), mQuadTexture);
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mQuadMaterial), mQuadTexture);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mQuadMaterial).IsValid());
BuildScene();
TextureDesc depthDesc = {};
@@ -270,6 +294,14 @@ void CameraStackSceneTest::LoadBackpackMesh() {
ASSERT_TRUE(mBackpackMesh->IsValid());
ASSERT_GT(mBackpackMesh->GetVertexCount(), 0u);
ASSERT_GT(mBackpackMesh->GetSections().Size(), 0u);
ASSERT_GT(mBackpackMesh->GetMaterials().Size(), 0u);
NormalizeBackpackTestMaterials(*mBackpackMesh);
for (Material* material : mBackpackMesh->GetMaterials()) {
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(material).IsValid());
}
}
void CameraStackSceneTest::BuildScene() {

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -110,7 +113,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(MaterialRenderQueue::Geometry);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -112,7 +115,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector2.h>
@@ -15,8 +16,11 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +65,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -114,7 +118,8 @@ Material* CreateMaterial(Texture* texture) {
params.path = "Tests/Rendering/Quad.material";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
return material;
}
@@ -162,6 +167,14 @@ void TexturedQuadSceneTest::SetUp() {
mMesh = CreateQuadMesh();
mTexture = CreateCheckerTexture();
mMaterial = CreateMaterial(mTexture);
ASSERT_NE(mMaterial, nullptr);
ASSERT_NE(mMaterial->GetShader(), nullptr);
ASSERT_TRUE(mMaterial->HasProperty("_BaseColor"));
ASSERT_TRUE(mMaterial->HasProperty("_Cutoff"));
ASSERT_TRUE(mMaterial->HasProperty("_MainTex"));
ASSERT_EQ(mMaterial->GetTexture("_MainTex").Get(), mTexture);
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mMaterial), mTexture);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mMaterial).IsValid());
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
auto* camera = cameraObject->AddComponent<CameraComponent>();

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -54,7 +54,7 @@ Mesh* CreateSectionedTestMesh(const char* path, std::initializer_list<uint32_t>
return mesh;
}
Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char* legacyShaderPassHint = nullptr, const char* lightMode = nullptr) {
Material* CreateTestMaterial(const char* path, int32_t renderQueue) {
auto* material = new Material();
IResource::ConstructParams params = {};
params.name = "TestMaterial";
@@ -62,12 +62,6 @@ Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char*
params.guid = ResourceGUID::Generate(path);
material->Initialize(params);
material->SetRenderQueue(renderQueue);
if (legacyShaderPassHint != nullptr) {
material->SetLegacyShaderPassHint(legacyShaderPassHint);
}
if (lightMode != nullptr) {
material->SetTag("LightMode", lightMode);
}
return material;
}
@@ -331,14 +325,10 @@ TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRender
Mesh* mesh = CreateSectionedTestMesh("Meshes/sectioned.mesh", { 1u, 0u });
Material* opaqueMaterial = CreateTestMaterial(
"Materials/opaque.mat",
static_cast<int32_t>(MaterialRenderQueue::Geometry),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Geometry));
Material* transparentMaterial = CreateTestMaterial(
"Materials/transparent.mat",
static_cast<int32_t>(MaterialRenderQueue::Transparent),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Transparent));
meshFilter->SetMesh(mesh);
meshRenderer->SetMaterial(0, opaqueMaterial);
@@ -382,7 +372,7 @@ TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront)
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
Mesh* mesh = CreateTestMesh(name);
Material* material = CreateTestMaterial(name, renderQueue, "ForwardLit", "ForwardBase");
Material* material = CreateTestMaterial(name, renderQueue);
meshFilter->SetMesh(mesh);
meshRenderer->SetMaterial(0, material);
return mesh;
@@ -440,9 +430,7 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
Mesh* mesh = CreateSectionedTestMesh("Meshes/embedded.mesh", { 0u });
Material* embeddedMaterial = CreateTestMaterial(
"Materials/embedded.mat",
static_cast<int32_t>(MaterialRenderQueue::Transparent),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Transparent));
mesh->AddMaterial(embeddedMaterial);
meshFilter->SetMesh(mesh);
meshRenderer->ClearMaterials();
@@ -459,38 +447,36 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
delete mesh;
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsCanDriveBuiltinPassMatching) {
Material forwardMaterial;
forwardMaterial.SetLegacyShaderPassHint("ForwardLit");
forwardMaterial.SetTag("LightMode", "ForwardBase");
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::ForwardLit));
TEST(RenderMaterialUtility_Test, MaterialsWithoutShaderMetadataUseImplicitForwardFallback) {
Material noMetadataMaterial;
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::ForwardLit));
Material shadowMaterial;
shadowMaterial.SetLegacyShaderPassHint("ShadowCaster");
EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ForwardLit));
EXPECT_TRUE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ShadowCaster));
Material depthOnlyMaterial;
depthOnlyMaterial.SetTag("LightMode", "DepthOnly");
EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::ForwardLit));
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsSupportUnlitDepthAndObjectId) {
TEST(RenderMaterialUtility_Test, ShaderPassMetadataSupportsUnlitDepthAndObjectId) {
Material unlitMaterial;
unlitMaterial.SetLegacyShaderPassHint("Unlit");
auto* unlitShader = new Shader();
ShaderPass unlitPass = {};
unlitPass.name = "Unlit";
unlitShader->AddPass(unlitPass);
unlitMaterial.SetShader(ResourceHandle<Shader>(unlitShader));
EXPECT_TRUE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::Unlit));
EXPECT_FALSE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::ForwardLit));
Material depthMaterial;
depthMaterial.SetTag("LightMode", "DepthOnly");
auto* depthShader = new Shader();
ShaderPass depthPass = {};
depthPass.name = "DepthOnly";
depthShader->AddPass(depthPass);
depthMaterial.SetShader(ResourceHandle<Shader>(depthShader));
EXPECT_TRUE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::DepthOnly));
EXPECT_FALSE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::Unlit));
Material objectIdMaterial;
objectIdMaterial.SetLegacyShaderPassHint("ObjectId");
auto* objectIdShader = new Shader();
ShaderPass objectIdPass = {};
objectIdPass.name = "ObjectId";
objectIdShader->AddPass(objectIdPass);
objectIdMaterial.SetShader(ResourceHandle<Shader>(objectIdShader));
EXPECT_TRUE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ObjectId));
EXPECT_FALSE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ForwardLit));
}
@@ -525,7 +511,7 @@ TEST(RenderMaterialUtility_Test, ExplicitShaderPassMetadataDisablesImplicitForwa
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMaterialPassHints) {
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingMaterialTags) {
Material material;
auto* shader = new Shader();
@@ -534,7 +520,6 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
shader->AddPass(forwardPass);
material.SetShader(ResourceHandle<Shader>(shader));
material.SetLegacyShaderPassHint("ShadowCaster");
material.SetTag("LightMode", "DepthOnly");
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
@@ -542,7 +527,7 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::DepthOnly));
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShadersWithoutBuiltinMetadata) {
TEST(RenderMaterialUtility_Test, MaterialTagsDoNotOverrideImplicitForwardFallbackWithoutShaderMetadata) {
Material material;
auto* shader = new Shader();
@@ -551,10 +536,10 @@ TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShader
shader->AddPass(defaultPass);
material.SetShader(ResourceHandle<Shader>(shader));
material.SetLegacyShaderPassHint("ShadowCaster");
material.SetTag("LightMode", "ShadowCaster");
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
}
TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwardFallback) {
@@ -571,28 +556,24 @@ TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwar
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanonicalNamesAndAliases) {
Material canonicalMaterial;
canonicalMaterial.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
canonicalMaterial.SetFloat("_Cutoff", 0.3f);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&canonicalMaterial), Vector4(0.2f, 0.4f, 0.6f, 0.8f));
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&canonicalMaterial), 0.3f);
TEST(RenderMaterialUtility_Test, MaterialsWithoutFormalShaderMetadataResolveBuiltinDefaultsOnly) {
Material material;
material.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
material.SetFloat("_Cutoff", 0.3f);
material.SetFloat("opacity", 0.35f);
Material aliasMaterial;
aliasMaterial.SetFloat4("_BaseColor", Vector4(0.7f, 0.6f, 0.5f, 0.4f));
aliasMaterial.SetFloat("_Cutoff", 0.42f);
Texture* baseColorTexture = new Texture();
IResource::ConstructParams textureParams = {};
textureParams.name = "AliasBaseColor";
textureParams.path = "Textures/alias_base_color.texture";
textureParams.guid = ResourceGUID::Generate(textureParams.path);
baseColorTexture->Initialize(textureParams);
aliasMaterial.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
material.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(&aliasMaterial);
EXPECT_EQ(materialData.baseColorFactor, Vector4(0.7f, 0.6f, 0.5f, 0.4f));
EXPECT_FLOAT_EQ(materialData.alphaCutoff, 0.42f);
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&material), 0.5f);
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr);
EXPECT_FALSE(ResolveSchemaMaterialConstantPayload(&material).IsValid());
}
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticMetadata) {
@@ -708,13 +689,13 @@ TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) {
EXPECT_FLOAT_EQ(cutoffValues[0], 0.6f);
}
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
TEST(RenderMaterialUtility_Test, DoesNotUseOpacityFallbackWithoutFormalShaderSemanticMetadata) {
Material material;
material.SetFloat("opacity", 0.35f);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 0.35f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
material.SetFloat4("baseColor", Vector4(0.9f, 0.8f, 0.7f, 0.6f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.9f, 0.8f, 0.7f, 0.6f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
}
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
@@ -732,6 +713,20 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
renderState.depthTestEnable = true;
renderState.depthWriteEnable = false;
renderState.depthFunc = MaterialComparisonFunc::LessEqual;
renderState.depthBiasFactor = 1.25f;
renderState.depthBiasUnits = 4;
renderState.stencil.enabled = true;
renderState.stencil.reference = 9;
renderState.stencil.readMask = 0x3F;
renderState.stencil.writeMask = 0x1F;
renderState.stencil.front.func = MaterialComparisonFunc::Equal;
renderState.stencil.front.failOp = MaterialStencilOp::Replace;
renderState.stencil.front.passOp = MaterialStencilOp::IncrWrap;
renderState.stencil.front.depthFailOp = MaterialStencilOp::DecrSat;
renderState.stencil.back.func = MaterialComparisonFunc::NotEqual;
renderState.stencil.back.failOp = MaterialStencilOp::Invert;
renderState.stencil.back.passOp = MaterialStencilOp::DecrWrap;
renderState.stencil.back.depthFailOp = MaterialStencilOp::Zero;
material.SetRenderState(renderState);
const MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(nullptr, &material);
@@ -741,6 +736,8 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
EXPECT_EQ(rasterizerState.cullMode, static_cast<uint32_t>(XCEngine::RHI::CullMode::Back));
EXPECT_EQ(rasterizerState.frontFace, static_cast<uint32_t>(XCEngine::RHI::FrontFace::CounterClockwise));
EXPECT_FLOAT_EQ(rasterizerState.slopeScaledDepthBias, 1.25f);
EXPECT_EQ(rasterizerState.depthBias, 4);
EXPECT_TRUE(blendState.blendEnable);
EXPECT_EQ(blendState.srcBlend, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::SrcAlpha));
@@ -754,6 +751,31 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
EXPECT_TRUE(depthStencilState.depthTestEnable);
EXPECT_FALSE(depthStencilState.depthWriteEnable);
EXPECT_EQ(depthStencilState.depthFunc, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::LessEqual));
EXPECT_TRUE(depthStencilState.stencilEnable);
EXPECT_EQ(depthStencilState.stencilReadMask, 0x3F);
EXPECT_EQ(depthStencilState.stencilWriteMask, 0x1F);
EXPECT_EQ(depthStencilState.front.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::Equal));
EXPECT_EQ(depthStencilState.front.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Replace));
EXPECT_EQ(depthStencilState.front.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Incr));
EXPECT_EQ(depthStencilState.front.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::DecrSat));
EXPECT_EQ(depthStencilState.back.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::NotEqual));
EXPECT_EQ(depthStencilState.back.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Invert));
EXPECT_EQ(depthStencilState.back.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Decr));
EXPECT_EQ(depthStencilState.back.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Zero));
}
TEST(RenderMaterialUtility_Test, PipelineStateKeyStripsDynamicStencilReference) {
MaterialRenderState renderState = {};
renderState.stencil.enabled = true;
renderState.stencil.reference = 12;
renderState.stencil.front.func = MaterialComparisonFunc::Always;
renderState.stencil.back.func = MaterialComparisonFunc::Always;
const MaterialRenderState keyState = BuildStaticPipelineRenderStateKey(renderState);
EXPECT_TRUE(keyState.stencil.enabled);
EXPECT_EQ(keyState.stencil.reference, 0u);
EXPECT_EQ(keyState.stencil.front.func, MaterialComparisonFunc::Always);
EXPECT_EQ(keyState.stencil.back.func, MaterialComparisonFunc::Always);
}
TEST(RenderMaterialUtility_Test, ShaderPassFixedFunctionStateIsUsedBeforeLegacyMaterialOverride) {

View File

@@ -15,6 +15,13 @@
#include <fstream>
#include <thread>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
using namespace XCEngine::Resources;
using namespace XCEngine::Containers;
@@ -24,6 +31,10 @@ std::string GetMeshFixturePath(const char* fileName) {
return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Mesh" / fileName).string();
}
std::filesystem::path GetRepositoryRoot() {
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path();
}
XCEngine::Core::uint32 GetFirstSectionMaterialIndex(const Mesh& mesh) {
if (mesh.GetSections().Empty()) {
return 0;
@@ -202,6 +213,119 @@ TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
delete mesh;
}
TEST(MeshLoader, ProjectBackpackSampleImportsMaterialTextures) {
namespace fs = std::filesystem;
const fs::path repositoryRoot = GetRepositoryRoot();
const fs::path projectRoot = repositoryRoot / "project";
const fs::path backpackMeshPath = projectRoot / "Assets" / "Models" / "backpack" / "backpack.obj";
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
if (!fs::exists(backpackMeshPath)) {
GTEST_SKIP() << "Backpack sample mesh is not available in the local project fixture.";
}
ASSERT_TRUE(fs::exists(assimpDllPath));
#ifdef _WIN32
struct DllGuard {
HMODULE module = nullptr;
~DllGuard() {
if (module != nullptr) {
FreeLibrary(module);
}
}
} dllGuard;
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
ASSERT_NE(dllGuard.module, nullptr);
#endif
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
manager.SetResourceRoot(projectRoot.string().c_str());
MeshLoader loader;
const LoadResult result = loader.Load(backpackMeshPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* mesh = static_cast<Mesh*>(result.resource);
ASSERT_NE(mesh, nullptr);
ASSERT_FALSE(mesh->GetMaterials().Empty());
Material* material = GetFirstSectionMaterial(*mesh);
ASSERT_NE(material, nullptr);
EXPECT_EQ(material->GetTextureBindingCount(), 1u);
EXPECT_EQ(material->GetTextureBindingName(0), "_MainTex");
EXPECT_EQ(
fs::path(material->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
(projectRoot / "Assets" / "Models" / "backpack" / "diffuse.jpg").lexically_normal().generic_string());
delete mesh;
manager.SetResourceRoot("");
manager.Shutdown();
}
TEST(MeshLoader, ProjectBackpackSampleArtifactRetainsSectionMaterialTextures) {
namespace fs = std::filesystem;
const fs::path repositoryRoot = GetRepositoryRoot();
const fs::path projectRoot = repositoryRoot / "project";
const fs::path backpackMeshPath = projectRoot / "Assets" / "Models" / "backpack" / "backpack.obj";
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
if (!fs::exists(backpackMeshPath)) {
GTEST_SKIP() << "Backpack sample mesh is not available in the local project fixture.";
}
ASSERT_TRUE(fs::exists(assimpDllPath));
#ifdef _WIN32
struct DllGuard {
HMODULE module = nullptr;
~DllGuard() {
if (module != nullptr) {
FreeLibrary(module);
}
}
} dllGuard;
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
ASSERT_NE(dllGuard.module, nullptr);
#endif
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
manager.SetResourceRoot(projectRoot.string().c_str());
AssetDatabase database;
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset resolvedAsset;
ASSERT_TRUE(database.EnsureArtifact("Assets/Models/backpack/backpack.obj", ResourceType::Mesh, resolvedAsset));
ASSERT_TRUE(resolvedAsset.artifactReady);
MeshLoader loader;
const LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* mesh = static_cast<Mesh*>(result.resource);
ASSERT_NE(mesh, nullptr);
ASSERT_FALSE(mesh->GetSections().Empty());
ASSERT_FALSE(mesh->GetMaterials().Empty());
Material* sectionMaterial = GetFirstSectionMaterial(*mesh);
ASSERT_NE(sectionMaterial, nullptr);
EXPECT_EQ(sectionMaterial->GetTextureBindingCount(), 1u);
EXPECT_EQ(sectionMaterial->GetTextureBindingName(0), "_MainTex");
EXPECT_FALSE(sectionMaterial->GetTextureBindingPath(0).Empty());
delete mesh;
database.Shutdown();
manager.SetResourceRoot("");
manager.Shutdown();
}
TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
namespace fs = std::filesystem;
using namespace std::chrono_literals;

View File

@@ -669,7 +669,7 @@ TEST(Scene_ProjectSample, BackpackSceneLoadsBackpackMeshAsset) {
std::vector<GameObject*> backpackObjects =
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
ASSERT_EQ(backpackObjects.size(), 2u);
ASSERT_FALSE(backpackObjects.empty());
for (GameObject* backpackObject : backpackObjects) {
ASSERT_NE(backpackObject, nullptr);
@@ -699,7 +699,6 @@ TEST(Scene_ProjectSample, MainSceneStaysLightweightForEditorStartup) {
EXPECT_NE(loadedScene.Find("Camera"), nullptr);
EXPECT_NE(loadedScene.Find("Light"), nullptr);
EXPECT_NE(loadedScene.Find("Sphere"), nullptr);
EXPECT_EQ(loadedScene.Find("BackpackMesh"), nullptr);
EXPECT_EQ(FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj").size(), 0u);
}
@@ -952,7 +951,7 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyRestoresBackpackMes
std::vector<GameObject*> backpackObjects =
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
ASSERT_EQ(backpackObjects.size(), 2u);
ASSERT_FALSE(backpackObjects.empty());
std::vector<MeshFilterComponent*> backpackMeshFilters;
std::vector<MeshRendererComponent*> backpackMeshRenderers;
@@ -972,17 +971,18 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyRestoresBackpackMes
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(resourceManager));
EXPECT_EQ(resourceManager.GetAsyncPendingCount(), 0u);
ASSERT_EQ(backpackMeshFilters.size(), 2u);
ASSERT_EQ(backpackMeshRenderers.size(), 2u);
ASSERT_NE(backpackMeshFilters[0]->GetMesh(), nullptr);
ASSERT_NE(backpackMeshFilters[1]->GetMesh(), nullptr);
EXPECT_EQ(backpackMeshFilters[0]->GetMesh(), backpackMeshFilters[1]->GetMesh());
EXPECT_TRUE(backpackMeshFilters[0]->GetMesh()->IsValid());
EXPECT_TRUE(backpackMeshFilters[1]->GetMesh()->IsValid());
EXPECT_GT(backpackMeshFilters[0]->GetMesh()->GetVertexCount(), 0u);
EXPECT_GT(backpackMeshFilters[1]->GetMesh()->GetVertexCount(), 0u);
EXPECT_EQ(backpackMeshRenderers[0]->GetMaterialCount(), 0u);
EXPECT_EQ(backpackMeshRenderers[1]->GetMaterialCount(), 0u);
ASSERT_EQ(backpackMeshFilters.size(), backpackObjects.size());
ASSERT_EQ(backpackMeshRenderers.size(), backpackObjects.size());
ASSERT_NE(backpackMeshFilters.front()->GetMesh(), nullptr);
EXPECT_TRUE(backpackMeshFilters.front()->GetMesh()->IsValid());
EXPECT_GT(backpackMeshFilters.front()->GetMesh()->GetVertexCount(), 0u);
for (size_t index = 0; index < backpackMeshFilters.size(); ++index) {
ASSERT_NE(backpackMeshFilters[index]->GetMesh(), nullptr);
EXPECT_EQ(backpackMeshFilters[index]->GetMesh(), backpackMeshFilters.front()->GetMesh());
EXPECT_TRUE(backpackMeshFilters[index]->GetMesh()->IsValid());
EXPECT_GT(backpackMeshFilters[index]->GetMesh()->GetVertexCount(), 0u);
EXPECT_EQ(backpackMeshRenderers[index]->GetMaterialCount(), 0u);
}
}
TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRenderItems) {
@@ -1043,7 +1043,7 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRend
const std::vector<GameObject*> backpackObjects =
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
ASSERT_EQ(backpackObjects.size(), 2u);
ASSERT_FALSE(backpackObjects.empty());
XCEngine::Rendering::RenderSceneExtractor extractor;
const XCEngine::Rendering::RenderSceneData initialRenderScene =
@@ -1078,8 +1078,9 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRend
}
}
EXPECT_TRUE(foundVisibleBackpack[0]);
EXPECT_TRUE(foundVisibleBackpack[1]);
for (bool foundVisible : foundVisibleBackpack) {
EXPECT_TRUE(foundVisible);
}
}
} // namespace