581 lines
24 KiB
C++
581 lines
24 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
|
#include <XCEngine/Core/Asset/IResource.h>
|
|
#include <XCEngine/Core/Math/Color.h>
|
|
#include <XCEngine/Core/Math/Quaternion.h>
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
#include <XCEngine/Rendering/RenderMaterialUtility.h>
|
|
#include <XCEngine/Rendering/RenderSceneExtractor.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/Scene/Scene.h>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Core;
|
|
using namespace XCEngine::Math;
|
|
using namespace XCEngine::Rendering;
|
|
using namespace XCEngine::Resources;
|
|
|
|
namespace {
|
|
|
|
Mesh* CreateTestMesh(const char* path) {
|
|
auto* mesh = new Mesh();
|
|
IResource::ConstructParams params = {};
|
|
params.name = "TestMesh";
|
|
params.path = path;
|
|
params.guid = ResourceGUID::Generate(path);
|
|
mesh->Initialize(params);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh* CreateSectionedTestMesh(const char* path, std::initializer_list<uint32_t> materialIds) {
|
|
Mesh* mesh = CreateTestMesh(path);
|
|
uint32_t startIndex = 0;
|
|
for (uint32_t materialId : materialIds) {
|
|
MeshSection section = {};
|
|
section.baseVertex = 0;
|
|
section.vertexCount = 3;
|
|
section.startIndex = startIndex;
|
|
section.indexCount = 3;
|
|
section.materialID = materialId;
|
|
mesh->AddSection(section);
|
|
startIndex += 3;
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char* shaderPass = nullptr, const char* lightMode = nullptr) {
|
|
auto* material = new Material();
|
|
IResource::ConstructParams params = {};
|
|
params.name = "TestMaterial";
|
|
params.path = path;
|
|
params.guid = ResourceGUID::Generate(path);
|
|
material->Initialize(params);
|
|
material->SetRenderQueue(renderQueue);
|
|
if (shaderPass != nullptr) {
|
|
material->SetShaderPass(shaderPass);
|
|
}
|
|
if (lightMode != nullptr) {
|
|
material->SetTag("LightMode", lightMode);
|
|
}
|
|
return material;
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, SelectsHighestDepthPrimaryCameraAndVisibleObjects) {
|
|
Scene scene("RenderScene");
|
|
|
|
GameObject* cameraObjectA = scene.CreateGameObject("CameraA");
|
|
auto* cameraA = cameraObjectA->AddComponent<CameraComponent>();
|
|
cameraA->SetPrimary(true);
|
|
cameraA->SetDepth(0.0f);
|
|
|
|
GameObject* cameraObjectB = scene.CreateGameObject("CameraB");
|
|
auto* cameraB = cameraObjectB->AddComponent<CameraComponent>();
|
|
cameraB->SetPrimary(true);
|
|
cameraB->SetDepth(5.0f);
|
|
cameraObjectB->GetTransform()->SetLocalPosition(Vector3(2.0f, 3.0f, 4.0f));
|
|
|
|
GameObject* visibleObject = scene.CreateGameObject("VisibleQuad");
|
|
visibleObject->GetTransform()->SetLocalPosition(Vector3(1.0f, 2.0f, 3.0f));
|
|
auto* meshFilter = visibleObject->AddComponent<MeshFilterComponent>();
|
|
visibleObject->AddComponent<MeshRendererComponent>();
|
|
Mesh* visibleMesh = CreateTestMesh("Meshes/visible.mesh");
|
|
meshFilter->SetMesh(visibleMesh);
|
|
|
|
GameObject* hiddenObject = scene.CreateGameObject("HiddenQuad");
|
|
hiddenObject->SetActive(false);
|
|
auto* hiddenMeshFilter = hiddenObject->AddComponent<MeshFilterComponent>();
|
|
hiddenObject->AddComponent<MeshRendererComponent>();
|
|
Mesh* hiddenMesh = CreateTestMesh("Meshes/hidden.mesh");
|
|
hiddenMeshFilter->SetMesh(hiddenMesh);
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 1280, 720);
|
|
|
|
ASSERT_TRUE(sceneData.HasCamera());
|
|
EXPECT_EQ(sceneData.camera, cameraB);
|
|
EXPECT_EQ(sceneData.cameraData.viewportWidth, 1280u);
|
|
EXPECT_EQ(sceneData.cameraData.viewportHeight, 720u);
|
|
EXPECT_EQ(sceneData.cameraData.worldPosition, Vector3(2.0f, 3.0f, 4.0f));
|
|
|
|
ASSERT_EQ(sceneData.visibleItems.size(), 1u);
|
|
EXPECT_EQ(sceneData.visibleItems[0].gameObject, visibleObject);
|
|
EXPECT_EQ(sceneData.visibleItems[0].mesh, visibleMesh);
|
|
EXPECT_EQ(sceneData.visibleItems[0].localToWorld.GetTranslation(), Vector3(1.0f, 2.0f, 3.0f));
|
|
EXPECT_FALSE(sceneData.visibleItems[0].hasSection);
|
|
EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast<int32_t>(MaterialRenderQueue::Geometry));
|
|
|
|
meshFilter->ClearMesh();
|
|
hiddenMeshFilter->ClearMesh();
|
|
delete visibleMesh;
|
|
delete hiddenMesh;
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, OverrideCameraTakesPriority) {
|
|
Scene scene("OverrideScene");
|
|
|
|
GameObject* primaryObject = scene.CreateGameObject("PrimaryCamera");
|
|
auto* primaryCamera = primaryObject->AddComponent<CameraComponent>();
|
|
primaryCamera->SetPrimary(true);
|
|
primaryCamera->SetDepth(10.0f);
|
|
|
|
GameObject* overrideObject = scene.CreateGameObject("OverrideCamera");
|
|
auto* overrideCamera = overrideObject->AddComponent<CameraComponent>();
|
|
overrideCamera->SetPrimary(false);
|
|
overrideCamera->SetDepth(-1.0f);
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, overrideCamera, 640, 480);
|
|
|
|
ASSERT_TRUE(sceneData.HasCamera());
|
|
EXPECT_EQ(sceneData.camera, overrideCamera);
|
|
EXPECT_NE(sceneData.camera, primaryCamera);
|
|
EXPECT_EQ(sceneData.cameraData.viewportWidth, 640u);
|
|
EXPECT_EQ(sceneData.cameraData.viewportHeight, 480u);
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) {
|
|
Scene scene("LightingScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
GameObject* fillLightObject = scene.CreateGameObject("FillLight");
|
|
auto* fillLight = fillLightObject->AddComponent<LightComponent>();
|
|
fillLight->SetLightType(LightType::Directional);
|
|
fillLight->SetColor(Color(0.2f, 0.4f, 0.8f, 1.0f));
|
|
fillLight->SetIntensity(0.5f);
|
|
|
|
GameObject* pointLightObject = scene.CreateGameObject("PointLight");
|
|
auto* pointLight = pointLightObject->AddComponent<LightComponent>();
|
|
pointLight->SetLightType(LightType::Point);
|
|
pointLight->SetIntensity(10.0f);
|
|
|
|
GameObject* mainLightObject = scene.CreateGameObject("MainLight");
|
|
auto* mainLight = mainLightObject->AddComponent<LightComponent>();
|
|
mainLight->SetLightType(LightType::Directional);
|
|
mainLight->SetColor(Color(1.0f, 0.8f, 0.6f, 1.0f));
|
|
mainLight->SetIntensity(2.5f);
|
|
mainLight->SetCastsShadows(true);
|
|
mainLightObject->GetTransform()->SetLocalRotation(
|
|
Quaternion::LookRotation(Vector3(-0.3f, -1.0f, -0.2f).Normalized()));
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
|
|
|
ASSERT_TRUE(sceneData.HasCamera());
|
|
ASSERT_TRUE(sceneData.lighting.HasMainDirectionalLight());
|
|
EXPECT_FLOAT_EQ(sceneData.lighting.mainDirectionalLight.intensity, 2.5f);
|
|
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.r, 1.0f);
|
|
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.g, 0.8f);
|
|
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.b, 0.6f);
|
|
EXPECT_TRUE(sceneData.lighting.mainDirectionalLight.castsShadows);
|
|
EXPECT_EQ(
|
|
sceneData.lighting.mainDirectionalLight.direction,
|
|
mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f);
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, FiltersVisibleItemsByCameraCullingMask) {
|
|
Scene scene("CullingMaskScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetCullingMask(1u << 5);
|
|
|
|
GameObject* visibleObject = scene.CreateGameObject("VisibleLayer5");
|
|
visibleObject->SetLayer(5);
|
|
auto* visibleMeshFilter = visibleObject->AddComponent<MeshFilterComponent>();
|
|
visibleObject->AddComponent<MeshRendererComponent>();
|
|
Mesh* visibleMesh = CreateTestMesh("Meshes/layer5.mesh");
|
|
visibleMeshFilter->SetMesh(visibleMesh);
|
|
|
|
GameObject* hiddenObject = scene.CreateGameObject("HiddenLayer0");
|
|
hiddenObject->SetLayer(0);
|
|
auto* hiddenMeshFilter = hiddenObject->AddComponent<MeshFilterComponent>();
|
|
hiddenObject->AddComponent<MeshRendererComponent>();
|
|
Mesh* hiddenMesh = CreateTestMesh("Meshes/layer0.mesh");
|
|
hiddenMeshFilter->SetMesh(hiddenMesh);
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
|
|
|
ASSERT_EQ(sceneData.visibleItems.size(), 1u);
|
|
EXPECT_EQ(sceneData.visibleItems[0].gameObject, visibleObject);
|
|
EXPECT_EQ(sceneData.visibleItems[0].mesh, visibleMesh);
|
|
|
|
visibleMeshFilter->ClearMesh();
|
|
hiddenMeshFilter->ClearMesh();
|
|
delete visibleMesh;
|
|
delete hiddenMesh;
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRenderQueue) {
|
|
Scene scene("SectionScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
GameObject* renderObject = scene.CreateGameObject("MultiSectionMesh");
|
|
renderObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 5.0f));
|
|
auto* meshFilter = renderObject->AddComponent<MeshFilterComponent>();
|
|
auto* meshRenderer = renderObject->AddComponent<MeshRendererComponent>();
|
|
|
|
Mesh* mesh = CreateSectionedTestMesh("Meshes/sectioned.mesh", { 1u, 0u });
|
|
Material* opaqueMaterial = CreateTestMaterial(
|
|
"Materials/opaque.mat",
|
|
static_cast<int32_t>(MaterialRenderQueue::Geometry),
|
|
"ForwardLit",
|
|
"ForwardBase");
|
|
Material* transparentMaterial = CreateTestMaterial(
|
|
"Materials/transparent.mat",
|
|
static_cast<int32_t>(MaterialRenderQueue::Transparent),
|
|
"ForwardLit",
|
|
"ForwardBase");
|
|
|
|
meshFilter->SetMesh(mesh);
|
|
meshRenderer->SetMaterial(0, opaqueMaterial);
|
|
meshRenderer->SetMaterial(1, transparentMaterial);
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
|
|
|
ASSERT_TRUE(sceneData.HasCamera());
|
|
ASSERT_EQ(sceneData.visibleItems.size(), 2u);
|
|
|
|
EXPECT_TRUE(sceneData.visibleItems[0].hasSection);
|
|
EXPECT_EQ(sceneData.visibleItems[0].sectionIndex, 1u);
|
|
EXPECT_EQ(sceneData.visibleItems[0].materialIndex, 0u);
|
|
EXPECT_EQ(sceneData.visibleItems[0].material, opaqueMaterial);
|
|
EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast<int32_t>(MaterialRenderQueue::Geometry));
|
|
|
|
EXPECT_TRUE(sceneData.visibleItems[1].hasSection);
|
|
EXPECT_EQ(sceneData.visibleItems[1].sectionIndex, 0u);
|
|
EXPECT_EQ(sceneData.visibleItems[1].materialIndex, 1u);
|
|
EXPECT_EQ(sceneData.visibleItems[1].material, transparentMaterial);
|
|
EXPECT_EQ(sceneData.visibleItems[1].renderQueue, static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
|
|
|
meshRenderer->ClearMaterials();
|
|
meshFilter->ClearMesh();
|
|
delete opaqueMaterial;
|
|
delete transparentMaterial;
|
|
delete mesh;
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront) {
|
|
Scene scene("QueueSortScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
auto createRenderable = [&](const char* name, float z, int32_t renderQueue) -> Mesh* {
|
|
GameObject* object = scene.CreateGameObject(name);
|
|
object->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, z));
|
|
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
|
|
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
|
|
Mesh* mesh = CreateTestMesh(name);
|
|
Material* material = CreateTestMaterial(name, renderQueue, "ForwardLit", "ForwardBase");
|
|
meshFilter->SetMesh(mesh);
|
|
meshRenderer->SetMaterial(0, material);
|
|
return mesh;
|
|
};
|
|
|
|
Mesh* opaqueFarMesh = createRenderable("OpaqueFar", 10.0f, static_cast<int32_t>(MaterialRenderQueue::Geometry));
|
|
Mesh* transparentNearMesh = createRenderable("TransparentNear", 2.0f, static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
|
Mesh* opaqueNearMesh = createRenderable("OpaqueNear", 2.0f, static_cast<int32_t>(MaterialRenderQueue::Geometry));
|
|
Mesh* transparentFarMesh = createRenderable("TransparentFar", 10.0f, static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
|
|
|
ASSERT_EQ(sceneData.visibleItems.size(), 4u);
|
|
EXPECT_STREQ(sceneData.visibleItems[0].gameObject->GetName().c_str(), "OpaqueNear");
|
|
EXPECT_STREQ(sceneData.visibleItems[1].gameObject->GetName().c_str(), "OpaqueFar");
|
|
EXPECT_STREQ(sceneData.visibleItems[2].gameObject->GetName().c_str(), "TransparentFar");
|
|
EXPECT_STREQ(sceneData.visibleItems[3].gameObject->GetName().c_str(), "TransparentNear");
|
|
|
|
auto cleanupObject = [](GameObject* object) {
|
|
auto* meshFilter = object->GetComponent<MeshFilterComponent>();
|
|
auto* meshRenderer = object->GetComponent<MeshRendererComponent>();
|
|
Material* material = meshRenderer != nullptr ? meshRenderer->GetMaterial(0) : nullptr;
|
|
if (meshRenderer != nullptr) {
|
|
meshRenderer->ClearMaterials();
|
|
}
|
|
if (meshFilter != nullptr) {
|
|
meshFilter->ClearMesh();
|
|
}
|
|
delete material;
|
|
};
|
|
|
|
cleanupObject(scene.Find("OpaqueFar"));
|
|
cleanupObject(scene.Find("TransparentNear"));
|
|
cleanupObject(scene.Find("OpaqueNear"));
|
|
cleanupObject(scene.Find("TransparentFar"));
|
|
|
|
delete opaqueFarMesh;
|
|
delete transparentNearMesh;
|
|
delete opaqueNearMesh;
|
|
delete transparentFarMesh;
|
|
}
|
|
|
|
TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasNoExplicitSlots) {
|
|
Scene scene("EmbeddedMaterialScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
GameObject* renderObject = scene.CreateGameObject("EmbeddedBackpack");
|
|
auto* meshFilter = renderObject->AddComponent<MeshFilterComponent>();
|
|
auto* meshRenderer = renderObject->AddComponent<MeshRendererComponent>();
|
|
|
|
Mesh* mesh = CreateSectionedTestMesh("Meshes/embedded.mesh", { 0u });
|
|
Material* embeddedMaterial = CreateTestMaterial(
|
|
"Materials/embedded.mat",
|
|
static_cast<int32_t>(MaterialRenderQueue::Transparent),
|
|
"ForwardLit",
|
|
"ForwardBase");
|
|
mesh->AddMaterial(embeddedMaterial);
|
|
meshFilter->SetMesh(mesh);
|
|
meshRenderer->ClearMaterials();
|
|
|
|
RenderSceneExtractor extractor;
|
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
|
|
|
ASSERT_EQ(sceneData.visibleItems.size(), 1u);
|
|
EXPECT_EQ(sceneData.visibleItems[0].material, embeddedMaterial);
|
|
EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
|
EXPECT_EQ(ResolveMaterial(sceneData.visibleItems[0]), embeddedMaterial);
|
|
|
|
meshFilter->ClearMesh();
|
|
delete mesh;
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardLitPassMetadata) {
|
|
Material forwardMaterial;
|
|
forwardMaterial.SetShaderPass("ForwardLit");
|
|
forwardMaterial.SetTag("LightMode", "ForwardBase");
|
|
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::ForwardLit));
|
|
|
|
Material noMetadataMaterial;
|
|
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::ForwardLit));
|
|
|
|
Material shadowMaterial;
|
|
shadowMaterial.SetShaderPass("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, MatchesBuiltinUnlitDepthAndObjectIdPassMetadata) {
|
|
Material unlitMaterial;
|
|
unlitMaterial.SetShaderPass("Unlit");
|
|
EXPECT_TRUE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::Unlit));
|
|
EXPECT_FALSE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::ForwardLit));
|
|
|
|
Material depthMaterial;
|
|
depthMaterial.SetTag("LightMode", "DepthOnly");
|
|
EXPECT_TRUE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::DepthOnly));
|
|
EXPECT_FALSE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::Unlit));
|
|
|
|
Material objectIdMaterial;
|
|
objectIdMaterial.SetShaderPass("ObjectId");
|
|
EXPECT_TRUE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ObjectId));
|
|
EXPECT_FALSE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ForwardLit));
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ShaderPassMetadataCanDriveBuiltinPassMatching) {
|
|
Material material;
|
|
auto* shader = new Shader();
|
|
|
|
ShaderPass forwardPass = {};
|
|
forwardPass.name = "ForwardLit";
|
|
shader->AddPass(forwardPass);
|
|
|
|
ResourceHandle<Shader> shaderHandle(shader);
|
|
material.SetShader(shaderHandle);
|
|
|
|
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
|
|
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ExplicitShaderPassMetadataDisablesImplicitForwardFallback) {
|
|
Material material;
|
|
auto* shader = new Shader();
|
|
|
|
ShaderPass unlitPass = {};
|
|
unlitPass.name = "Unlit";
|
|
shader->AddPass(unlitPass);
|
|
|
|
ResourceHandle<Shader> shaderHandle(shader);
|
|
material.SetShader(shaderHandle);
|
|
|
|
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
|
|
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanonicalNamesAndAliases) {
|
|
Material canonicalMaterial;
|
|
canonicalMaterial.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
|
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&canonicalMaterial), Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
|
|
|
Material aliasMaterial;
|
|
aliasMaterial.SetFloat4("_BaseColor", Vector4(0.7f, 0.6f, 0.5f, 0.4f));
|
|
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));
|
|
|
|
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(&aliasMaterial);
|
|
EXPECT_EQ(materialData.baseColorFactor, Vector4(0.7f, 0.6f, 0.5f, 0.4f));
|
|
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture);
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticMetadata) {
|
|
auto* shader = new Shader();
|
|
|
|
ShaderPropertyDesc colorProperty = {};
|
|
colorProperty.name = "TintColor";
|
|
colorProperty.displayName = "Tint";
|
|
colorProperty.type = ShaderPropertyType::Color;
|
|
colorProperty.semantic = "BaseColor";
|
|
shader->AddProperty(colorProperty);
|
|
|
|
ShaderPropertyDesc textureProperty = {};
|
|
textureProperty.name = "AlbedoMap";
|
|
textureProperty.displayName = "Albedo";
|
|
textureProperty.type = ShaderPropertyType::Texture2D;
|
|
textureProperty.semantic = "BaseColorTexture";
|
|
shader->AddProperty(textureProperty);
|
|
|
|
Material material;
|
|
material.SetShader(ResourceHandle<Shader>(shader));
|
|
material.SetFloat4("TintColor", Vector4(0.3f, 0.5f, 0.7f, 0.9f));
|
|
|
|
Texture* texture = new Texture();
|
|
IResource::ConstructParams textureParams = {};
|
|
textureParams.name = "SemanticTexture";
|
|
textureParams.path = "Textures/semantic_base_color.texture";
|
|
textureParams.guid = ResourceGUID::Generate(textureParams.path);
|
|
texture->Initialize(textureParams);
|
|
material.SetTexture("AlbedoMap", ResourceHandle<Texture>(texture));
|
|
|
|
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.3f, 0.5f, 0.7f, 0.9f));
|
|
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), texture);
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticDefaults) {
|
|
auto* shader = new Shader();
|
|
|
|
ShaderPropertyDesc colorProperty = {};
|
|
colorProperty.name = "TintColor";
|
|
colorProperty.displayName = "Tint";
|
|
colorProperty.type = ShaderPropertyType::Color;
|
|
colorProperty.defaultValue = "(0.11,0.22,0.33,0.44)";
|
|
colorProperty.semantic = "BaseColor";
|
|
shader->AddProperty(colorProperty);
|
|
|
|
Material material;
|
|
material.SetShader(ResourceHandle<Shader>(shader));
|
|
|
|
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.11f, 0.22f, 0.33f, 0.44f));
|
|
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr);
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) {
|
|
auto* shader = new Shader();
|
|
|
|
ShaderPropertyDesc colorProperty = {};
|
|
colorProperty.name = "_BaseColor";
|
|
colorProperty.type = ShaderPropertyType::Color;
|
|
colorProperty.defaultValue = "(0.25,0.5,0.75,1.0)";
|
|
colorProperty.semantic = "BaseColor";
|
|
shader->AddProperty(colorProperty);
|
|
|
|
Material material;
|
|
material.SetShader(ResourceHandle<Shader>(shader));
|
|
|
|
const MaterialConstantPayloadView payload = ResolveSchemaMaterialConstantPayload(&material);
|
|
ASSERT_TRUE(payload.IsValid());
|
|
ASSERT_EQ(payload.size, 16u);
|
|
ASSERT_TRUE(payload.layout.IsValid());
|
|
ASSERT_EQ(payload.layout.count, 1u);
|
|
EXPECT_EQ(payload.layout.size, 16u);
|
|
EXPECT_EQ(payload.layout.fields[0].name, "_BaseColor");
|
|
EXPECT_EQ(payload.layout.fields[0].offset, 0u);
|
|
EXPECT_EQ(payload.layout.fields[0].size, 16u);
|
|
EXPECT_EQ(payload.layout.fields[0].alignedSize, 16u);
|
|
|
|
const float* values = static_cast<const float*>(payload.data);
|
|
EXPECT_FLOAT_EQ(values[0], 0.25f);
|
|
EXPECT_FLOAT_EQ(values[1], 0.5f);
|
|
EXPECT_FLOAT_EQ(values[2], 0.75f);
|
|
EXPECT_FLOAT_EQ(values[3], 1.0f);
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
|
|
Material material;
|
|
material.SetFloat("opacity", 0.35f);
|
|
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 0.35f));
|
|
|
|
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));
|
|
}
|
|
|
|
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
|
|
Material material;
|
|
MaterialRenderState renderState;
|
|
renderState.cullMode = MaterialCullMode::Back;
|
|
renderState.blendEnable = true;
|
|
renderState.srcBlend = MaterialBlendFactor::SrcAlpha;
|
|
renderState.dstBlend = MaterialBlendFactor::InvSrcAlpha;
|
|
renderState.srcBlendAlpha = MaterialBlendFactor::One;
|
|
renderState.dstBlendAlpha = MaterialBlendFactor::InvSrcAlpha;
|
|
renderState.blendOp = MaterialBlendOp::Add;
|
|
renderState.blendOpAlpha = MaterialBlendOp::Subtract;
|
|
renderState.colorWriteMask = 0x7;
|
|
renderState.depthTestEnable = true;
|
|
renderState.depthWriteEnable = false;
|
|
renderState.depthFunc = MaterialComparisonFunc::LessEqual;
|
|
material.SetRenderState(renderState);
|
|
|
|
const XCEngine::RHI::RasterizerDesc rasterizerState = BuildRasterizerState(&material);
|
|
const XCEngine::RHI::BlendDesc blendState = BuildBlendState(&material);
|
|
const XCEngine::RHI::DepthStencilStateDesc depthStencilState = BuildDepthStencilState(&material);
|
|
|
|
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_TRUE(blendState.blendEnable);
|
|
EXPECT_EQ(blendState.srcBlend, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::SrcAlpha));
|
|
EXPECT_EQ(blendState.dstBlend, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::InvSrcAlpha));
|
|
EXPECT_EQ(blendState.srcBlendAlpha, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::One));
|
|
EXPECT_EQ(blendState.dstBlendAlpha, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::InvSrcAlpha));
|
|
EXPECT_EQ(blendState.blendOp, static_cast<uint32_t>(XCEngine::RHI::BlendOp::Add));
|
|
EXPECT_EQ(blendState.blendOpAlpha, static_cast<uint32_t>(XCEngine::RHI::BlendOp::Subtract));
|
|
EXPECT_EQ(blendState.colorWriteMask, 0x7);
|
|
|
|
EXPECT_TRUE(depthStencilState.depthTestEnable);
|
|
EXPECT_FALSE(depthStencilState.depthWriteEnable);
|
|
EXPECT_EQ(depthStencilState.depthFunc, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::LessEqual));
|
|
}
|
|
|
|
} // namespace
|