#include "MetaCoreRender/MetaCoreFilamentSceneBridge.h" #include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreComponents.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRender/MetaCoreImGuiHelper.h" #include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "stb/stb_image.h" #include #include "MetaCoreFoundation/MetaCoreAssetRegistry.h" #include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCoreHash.h" #include "MetaCoreRendering/MetaCoreRendering.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MetaCoreScene/MetaCoreTransformUtils.h" #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 引入平台和 OpenGL 头文件以创建纹理 #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #endif #include #if !defined(_WIN32) #define GLFW_EXPOSE_NATIVE_X11 #include #include #endif namespace MetaCore { namespace { constexpr std::uint8_t kSceneContentLayerMask = 0x01U; constexpr std::uint8_t kEnvironmentSkyboxLayerMask = 0x02U; constexpr std::uint8_t kEditorGridLayerMask = 0x04U; constexpr std::size_t kGltfFileReadChunkBytes = 4U * 1024U * 1024U; void* MetaCoreGetFilamentNativeWindowHandle(MetaCoreWindow& window) { #if defined(_WIN32) return window.GetNativeWindowHandle(); #else auto* glfwWindow = static_cast(window.GetNativeWindowHandle()); if (glfwWindow == nullptr) { return nullptr; } const Window x11Window = glfwGetX11Window(glfwWindow); return reinterpret_cast(static_cast(x11Window)); #endif } filament::Engine::Backend MetaCoreResolveFilamentBackend() { const char* backend = std::getenv("METACORE_FILAMENT_BACKEND"); if (backend == nullptr) { return filament::Engine::Backend::DEFAULT; } const std::string_view backendName(backend); if (backendName == "opengl") { return filament::Engine::Backend::OPENGL; } if (backendName == "vulkan") { return filament::Engine::Backend::VULKAN; } return filament::Engine::Backend::DEFAULT; } template [[nodiscard]] std::string MetaCoreDebugPointer(T* pointer) { std::ostringstream stream; stream << static_cast(pointer); return stream.str(); } void MetaCoreFilamentDebugLog(const std::string& message) { std::cerr << "[MetaCoreDebug][Filament] " << message << std::endl; } [[nodiscard]] bool MetaCoreMaterialDebugLogEnabled() { const char* value = std::getenv("METACORE_DEBUG_MATERIAL"); if (value == nullptr) { return false; } return std::strcmp(value, "1") == 0 || std::strcmp(value, "true") == 0 || std::strcmp(value, "TRUE") == 0; } void MetaCoreFilamentMaterialDebugLog(const std::string& message) { if (MetaCoreMaterialDebugLogEnabled()) { MetaCoreFilamentDebugLog(message); } } [[nodiscard]] std::string MetaCoreDebugVec3String(const glm::vec3& value) { std::ostringstream stream; stream << "(" << value.x << "," << value.y << "," << value.z << ")"; return stream.str(); } [[nodiscard]] std::string MetaCoreDebugGuidListString(const std::vector& guids) { if (guids.empty()) { return "[]"; } std::ostringstream stream; stream << "["; for (std::size_t index = 0; index < guids.size(); ++index) { if (index != 0) { stream << ","; } stream << (guids[index].IsValid() ? guids[index].ToString() : std::string("")); } stream << "]"; return stream.str(); } [[nodiscard]] bool MetaCorePreserveGltfBlendAlphaMode() { const char* preserveBlend = std::getenv("METACORE_GLTF_PRESERVE_BLEND_ALPHA"); if (preserveBlend == nullptr) { return false; } return std::strcmp(preserveBlend, "1") == 0 || std::strcmp(preserveBlend, "true") == 0 || std::strcmp(preserveBlend, "TRUE") == 0; } class MetaCoreGltfMaterialProvider final : public filament::gltfio::MaterialProvider { public: explicit MetaCoreGltfMaterialProvider(filament::gltfio::MaterialProvider* innerProvider) : InnerProvider_(innerProvider) {} ~MetaCoreGltfMaterialProvider() override { delete InnerProvider_; InnerProvider_ = nullptr; } filament::MaterialInstance* createMaterialInstance( filament::gltfio::MaterialKey* config, filament::gltfio::UvMap* uvmap, const char* label = "material", const char* extras = nullptr ) override { NormalizeAlphaMode(config, label); return InnerProvider_ != nullptr ? InnerProvider_->createMaterialInstance(config, uvmap, label, extras) : nullptr; } filament::Material* getMaterial( filament::gltfio::MaterialKey* config, filament::gltfio::UvMap* uvmap, const char* label = "material" ) override { NormalizeAlphaMode(config, label); return InnerProvider_ != nullptr ? InnerProvider_->getMaterial(config, uvmap, label) : nullptr; } const filament::Material* const* getMaterials() const noexcept override { return InnerProvider_ != nullptr ? InnerProvider_->getMaterials() : nullptr; } size_t getMaterialsCount() const noexcept override { return InnerProvider_ != nullptr ? InnerProvider_->getMaterialsCount() : 0; } void destroyMaterials() override { if (InnerProvider_ != nullptr) { InnerProvider_->destroyMaterials(); } } bool needsDummyData(filament::VertexAttribute attrib) const noexcept override { return InnerProvider_ != nullptr && InnerProvider_->needsDummyData(attrib); } private: void NormalizeAlphaMode(filament::gltfio::MaterialKey* config, const char* label) { if (config == nullptr || MetaCorePreserveGltfBlendAlphaMode() || config->alphaMode != filament::gltfio::AlphaMode::BLEND) { return; } config->alphaMode = filament::gltfio::AlphaMode::MASK; if (!LoggedBlendAsMask_) { MetaCoreFilamentDebugLog( "GLTF alphaMode=BLEND is rendered as MASK by default for stable foliage depth. " "Set METACORE_GLTF_PRESERVE_BLEND_ALPHA=1 to keep GLTF blend materials." ); LoggedBlendAsMask_ = true; } if (label != nullptr && label[0] != '\0') { MetaCoreFilamentDebugLog(std::string("GLTF material alphaMode adjusted to MASK label=\"") + label + "\""); } } filament::gltfio::MaterialProvider* InnerProvider_ = nullptr; bool LoggedBlendAsMask_ = false; }; } // namespace class MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridgeImpl { using EntityChildrenMap = std::unordered_map, utils::Entity::Hasher>; struct PendingResourceLoad { std::string GltfPath{}; std::unique_ptr Loader{}; }; public: MetaCoreFilamentSceneBridgeImpl() = default; ~MetaCoreFilamentSceneBridgeImpl() { Shutdown(); } // 提供公有接口用于构建渲染同步快照 MetaCoreSceneRenderSyncSnapshot BuildSnapshot(const MetaCoreScene& scene) const { return RenderSync_.BuildSnapshot(scene); } bool Initialize(MetaCoreWindow& window, bool offscreen = true) { std::cout << "FilamentSceneBridge: Initializing..." << std::endl; Window_ = &window; // 创建 Filament Engine (不共享上下文,避免闪退) Engine_ = filament::Engine::create(MetaCoreResolveFilamentBackend()); if (!Engine_) { std::cerr << "Failed to create Filament Engine with shared context!" << std::endl; return false; } // 创建绑定真实窗口的交换链 SwapChain_ = Engine_->createSwapChain(MetaCoreGetFilamentNativeWindowHandle(window)); if (!SwapChain_) { std::cerr << "Failed to create Filament SwapChain with window handle!" << std::endl; return false; } // 创建渲染器 Renderer_ = Engine_->createRenderer(); // 创建场景 Scene_ = Engine_->createScene(); EnvironmentScene_ = Engine_->createScene(); LoadDefaultEnvironmentLighting(); // 创建默认灯光 Light_ = utils::EntityManager::get().create(); filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL) .color(filament::Color::toLinear({ 1.0f, 1.0f, 1.0f })) .intensity(100000.0f) .direction({ 0.5f, 0.5f, -1.0f }) // Z-up 下 Z 向下 .castShadows(true) .build(*Engine_, Light_); Scene_->addEntity(Light_); EntitiesInScene_.insert(Light_); // 创建相机 utils::Entity cameraEntity = utils::EntityManager::get().create(); Camera_ = Engine_->createCamera(cameraEntity); // 创建视图 View_ = Engine_->createView(); View_->setScene(Scene_); View_->setCamera(Camera_); ConfigureSceneViewRenderState(); EnvironmentView_ = Engine_->createView(); EnvironmentView_->setScene(EnvironmentScene_); EnvironmentView_->setCamera(Camera_); ConfigureEnvironmentViewRenderState(); GridView_ = Engine_->createView(); GridView_->setScene(Scene_); GridView_->setCamera(Camera_); ConfigureGridViewRenderState(); if (offscreen) { // 创建 UI 视图 UIView_ = Engine_->createView(); // 创建 ImGuiHelper ImGuiHelper_ = new MetaCoreImGuiHelper(Engine_, UIView_, "", ImGui::GetCurrentContext()); // 初始化离屏渲染纹理 auto [width, height] = window.GetFramebufferSize(); // 1. 创建 Filament 颜色纹理 FilamentTexture_ = filament::Texture::Builder() .width(static_cast(width)) .height(static_cast(height)) .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) .format(filament::Texture::InternalFormat::RGBA8) .build(*Engine_); // 2. 创建深度纹理 DepthTexture_ = filament::Texture::Builder() .width(static_cast(width)) .height(static_cast(height)) .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) .format(filament::Texture::InternalFormat::DEPTH24) .build(*Engine_); // 3. 创建 RenderTarget 并绑定 RenderTarget_ = filament::RenderTarget::Builder() .texture(filament::RenderTarget::AttachmentPoint::COLOR, FilamentTexture_) .texture(filament::RenderTarget::AttachmentPoint::DEPTH, DepthTexture_) .build(*Engine_); ++RenderTargetGeneration_; DebugRenderTargetState("Initialize offscreen render target"); // 4. 设置到 View View_->setRenderTarget(RenderTarget_); View_->setViewport({0, 0, static_cast(width), static_cast(height)}); EnvironmentView_->setRenderTarget(RenderTarget_); EnvironmentView_->setViewport({0, 0, static_cast(width), static_cast(height)}); GridView_->setRenderTarget(RenderTarget_); GridView_->setViewport({0, 0, static_cast(width), static_cast(height)}); ConfigureSceneViewRenderState(); ConfigureEnvironmentViewRenderState(); ConfigureGridViewRenderState(); } else { auto [width, height] = window.GetFramebufferSize(); View_->setViewport({0, 0, static_cast(width), static_cast(height)}); EnvironmentView_->setViewport({0, 0, static_cast(width), static_cast(height)}); GridView_->setViewport({0, 0, static_cast(width), static_cast(height)}); ConfigureSceneViewRenderState(); ConfigureEnvironmentViewRenderState(); ConfigureGridViewRenderState(); } // 确保开启后处理(为了HDR) View_->setPostProcessingEnabled(true); EnvironmentView_->setPostProcessingEnabled(false); GridView_->setPostProcessingEnabled(false); // 初始化 gltfio MaterialProvider_ = new MetaCoreGltfMaterialProvider( filament::gltfio::createJitShaderProvider(Engine_) ); StbTextureProvider_ = filament::gltfio::createStbProvider(Engine_); NameManager_ = new utils::NameComponentManager(utils::EntityManager::get()); AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ }); RuntimeAssetSubscriptionId_ = MetaCoreRuntimeAssetRegistry::Get().Subscribe( [this](const MetaCoreAssetChangeEvent& event) { if (event.AssetGuid.IsValid()) { InvalidatedModelGuids_.insert(event.AssetGuid); } if (event.SourceAssetGuid.IsValid()) { InvalidatedModelGuids_.insert(event.SourceAssetGuid); } } ); std::cout << "FilamentSceneBridge: Initialized with Offscreen=" << (offscreen ? "true" : "false") << std::endl; return true; } void SetEditorGridVisible(bool visible) { if (Engine_ == nullptr || Scene_ == nullptr) { return; } if (visible) { if (!EnsureEditorGridRenderable()) { return; } if (!EditorGridEntityInScene_) { Scene_->addEntity(EditorGridEntity_); EntitiesInScene_.insert(EditorGridEntity_); EditorGridEntityInScene_ = true; } if (!EditorGridAxisEntityInScene_) { Scene_->addEntity(EditorGridAxisEntity_); EntitiesInScene_.insert(EditorGridAxisEntity_); EditorGridAxisEntityInScene_ = true; } return; } if (EditorGridEntityInScene_) { Scene_->remove(EditorGridEntity_); EntitiesInScene_.erase(EditorGridEntity_); EditorGridEntityInScene_ = false; } if (EditorGridAxisEntityInScene_) { Scene_->remove(EditorGridAxisEntity_); EntitiesInScene_.erase(EditorGridAxisEntity_); EditorGridAxisEntityInScene_ = false; } } void SetRenderDebugView(MetaCoreRenderDebugView debugView) { ActiveDebugView_ = debugView; } [[nodiscard]] MetaCoreRenderDebugView GetRenderDebugView() const { return ActiveDebugView_; } void SetRuntimeOverlayRenderCallback(MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback callback) { RuntimeOverlayRenderCallback_ = std::move(callback); } [[nodiscard]] bool VerifyEditorGridVisibleForTesting() const { return EditorGridEntityInScene_ && EditorGridAxisEntityInScene_; } [[nodiscard]] bool VerifyEnvironmentLoadedForTesting() const { return EnvironmentLoaded_ && EnvironmentIblTexture_ != nullptr && EnvironmentSkyboxTexture_ != nullptr && EnvironmentIndirectLight_ != nullptr && EnvironmentSkybox_ != nullptr; } [[nodiscard]] bool VerifyEnvironmentBackgroundVisibleForTesting() const { return EnvironmentBackgroundVisible_ && EnvironmentScene_ != nullptr && EnvironmentScene_->getSkybox() == EnvironmentSkybox_; } [[nodiscard]] float GetEnvironmentIndirectLightIntensityForTesting() const { return EnvironmentIndirectLight_ != nullptr ? EnvironmentIndirectLight_->getIntensity() : 0.0F; } void Shutdown() { if (RuntimeAssetSubscriptionId_ != 0) { MetaCoreRuntimeAssetRegistry::Get().Unsubscribe(RuntimeAssetSubscriptionId_); RuntimeAssetSubscriptionId_ = 0; } if (Engine_) { std::cout << "FilamentSceneBridge: Shutting down..." << std::endl; DebugRenderTargetState("Shutdown begin"); RestoreDebugMaterials(); DestroyProductionRenderables(); DestroyEditorGridRenderable(); for (auto& [hostRootId, pendingLoad] : PendingResourceLoads_) { if (pendingLoad.Loader != nullptr) { pendingLoad.Loader->asyncCancelLoad(); } } PendingResourceLoads_.clear(); // 1. 销毁所有加载的资产,切断其内部 Renderable 对克隆材质实例的占用引用 std::cout << "[DEBUG] LoadedAssets_ size: " << LoadedAssets_.size() << std::endl; for (auto& [id, asset] : LoadedAssets_) { std::cout << "[DEBUG] Destroying asset for ID: " << id << ", asset pointer: " << asset << std::endl; MetaCoreFilamentDebugLog( "Shutdown destroyAsset begin hostRootId=" + std::to_string(id) + " asset=" + MetaCoreDebugPointer(asset) ); if (asset) { Engine_->flushAndWait(); AssetLoader_->destroyAsset(asset); } MetaCoreFilamentDebugLog( "Shutdown destroyAsset end hostRootId=" + std::to_string(id) + " asset=" + MetaCoreDebugPointer(asset) ); std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl; } LoadedAssets_.clear(); HostRootSourceModelGuids_.clear(); InvalidatedModelGuids_.clear(); // 2. 销毁所有 pivotEntity 中间辅助坐标变换实体 for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { utils::Entity entity = assetEntity.second; if (!entity) continue; bool isAssetEntity = false; if (assetEntity.first) { const utils::Entity* entities = assetEntity.first->getEntities(); size_t entityCount = assetEntity.first->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { if (entities[i] == entity) { isAssetEntity = true; break; } } } if (!isAssetEntity) { if (Scene_) { Scene_->remove(entity); EntitiesInScene_.erase(entity); } Engine_->destroy(entity); utils::EntityManager::get().destroy(entity); } } ObjectToFilamentEntity_.clear(); ObjectWorldMatrices_.clear(); // 3. 销毁所有克隆的材质实例,由于引用的实体都已被销毁,此时可安全释放 for (auto& [id, mats] : AssetDuplicatedMaterials_) { MetaCoreFilamentDebugLog( "Shutdown destroy duplicated materials hostRootId=" + std::to_string(id) + " count=" + std::to_string(mats.size()) ); Engine_->flushAndWait(); for (auto* mat : mats) { if (mat) { Engine_->destroy(mat); } } } AssetDuplicatedMaterials_.clear(); DestroyMaterialTextureCache(); DestroyEnvironmentLighting(); DestroySolidColorSkybox(); if(RenderDebugMaterial_!=nullptr){Engine_->destroy(RenderDebugMaterial_);RenderDebugMaterial_=nullptr;} for (auto& [id, entity] : SceneLightEntities_) { if (Scene_) { Scene_->remove(entity); EntitiesInScene_.erase(entity); } Engine_->destroy(entity); utils::EntityManager::get().destroy(entity); } SceneLightEntities_.clear(); EntitiesInScene_.clear(); // 销毁 gltfio 资源 std::cout << "[DEBUG] Destroying gltfio resources..." << std::endl; if (AssetLoader_) { filament::gltfio::AssetLoader::destroy(&AssetLoader_); AssetLoader_ = nullptr; } if (MaterialProvider_) { MaterialProvider_->destroyMaterials(); delete MaterialProvider_; MaterialProvider_ = nullptr; } if (StbTextureProvider_) { delete StbTextureProvider_; StbTextureProvider_ = nullptr; } DestroyDeferredRenderTargetResources(true); // 销毁离屏渲染资源 if (RenderTarget_) { DebugRenderTargetState("Shutdown destroy current render target begin"); Engine_->flushAndWait(); Engine_->destroy(RenderTarget_); RenderTarget_ = nullptr; } if (FilamentTexture_) { MetaCoreFilamentDebugLog( "Shutdown destroy current colorTexture=" + MetaCoreDebugPointer(FilamentTexture_) ); Engine_->destroy(FilamentTexture_); FilamentTexture_ = nullptr; } if (DepthTexture_) { MetaCoreFilamentDebugLog( "Shutdown destroy current depthTexture=" + MetaCoreDebugPointer(DepthTexture_) ); Engine_->destroy(DepthTexture_); DepthTexture_ = nullptr; } DebugRenderTargetState("Shutdown after current render target destroy"); if (GLTextureId_) { glDeleteTextures(1, &GLTextureId_); GLTextureId_ = 0; } // 销毁 View Engine_->flushAndWait(); PickCallbacks_.clear(); if (ColorGrading_) { Engine_->destroy(ColorGrading_); ColorGrading_ = nullptr; } Engine_->destroy(View_); View_ = nullptr; if (EnvironmentView_) { Engine_->destroy(EnvironmentView_); EnvironmentView_ = nullptr; } if (GridView_) { Engine_->destroy(GridView_); GridView_ = nullptr; } if (UIView_) { Engine_->destroy(UIView_); UIView_ = nullptr; } if (ImGuiHelper_) { delete ImGuiHelper_; ImGuiHelper_ = nullptr; } // 销毁相机组件和实体 if (Camera_) { utils::Entity cameraEntity = Camera_->getEntity(); Engine_->destroyCameraComponent(cameraEntity); utils::EntityManager::get().destroy(cameraEntity); } // 销毁其他资源 if (EnvironmentScene_) { Engine_->destroy(EnvironmentScene_); EnvironmentScene_ = nullptr; } Engine_->destroy(Scene_); Engine_->destroy(Renderer_); Engine_->destroy(SwapChain_); delete NameManager_; NameManager_ = nullptr; // 最后销毁 Engine filament::Engine::destroy(&Engine_); Engine_ = nullptr; } Window_ = nullptr; } void SetProjectRootPath(const std::filesystem::path& projectRootPath) { if (ProjectRootPath_ != projectRootPath) { DestroyMaterialTextureCache(); DestroyEnvironmentLighting(); } ProjectRootPath_ = projectRootPath; ReloadRenderSettings(); ReloadRenderProfiles(); RenderConfigurationFingerprint_ = ComputeRenderConfigurationFingerprint(); LastRenderConfigurationPoll_ = std::chrono::steady_clock::now(); ConfigurePostProcessing(); SynchronizeEnvironment(ActiveEnvironmentSettings_); } void ReloadRenderSettings() { RenderSettings_ = MetaCoreBuildDefaultRenderSettings(); if (ProjectRootPath_.empty()) return; MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRenderingGeneratedTypes(registry); const auto loaded = MetaCoreReadRenderSettings(ProjectRootPath_ / "Runtime" / "Rendering.mcruntime", registry); if (loaded) RenderSettings_ = *loaded; } [[nodiscard]] std::uint64_t ComputeRenderConfigurationFingerprint() const { if (ProjectRootPath_.empty()) return 0U; std::vector files; const auto settingsPath = ProjectRootPath_ / "Runtime" / "Rendering.mcruntime"; std::error_code error; if (std::filesystem::is_regular_file(settingsPath, error)) files.push_back(settingsPath); for (const auto& relativeRoot : {std::filesystem::path("Assets"), std::filesystem::path("Runtime")}) { const auto root = ProjectRootPath_ / relativeRoot; if (!std::filesystem::is_directory(root, error)) continue; for (std::filesystem::recursive_directory_iterator iterator(root, std::filesystem::directory_options::skip_permission_denied, error), end; !error && iterator != end; iterator.increment(error)) { if (!iterator->is_regular_file(error)) continue; const auto extension = iterator->path().extension(); if (extension == ".mcenvironment" || extension == ".mcpostprocess") files.push_back(iterator->path()); } error.clear(); } std::sort(files.begin(), files.end()); std::uint64_t fingerprint = 1469598103934665603ULL; const auto combine = [&fingerprint](std::uint64_t value) { fingerprint ^= value; fingerprint *= 1099511628211ULL; }; for (const auto& path : files) { combine(static_cast(std::hash{}(path.generic_string()))); combine(static_cast(std::filesystem::file_size(path, error))); error.clear(); combine(static_cast(std::filesystem::last_write_time(path, error).time_since_epoch().count())); error.clear(); } return fingerprint; } [[nodiscard]] bool PollRenderConfigurationChanges() { const auto now = std::chrono::steady_clock::now(); if (now - LastRenderConfigurationPoll_ < std::chrono::milliseconds(250)) return false; LastRenderConfigurationPoll_ = now; const std::uint64_t fingerprint = ComputeRenderConfigurationFingerprint(); if (fingerprint == RenderConfigurationFingerprint_) return false; RenderConfigurationFingerprint_ = fingerprint; ReloadRenderSettings(); ReloadRenderProfiles(); DestroyEnvironmentLighting(); MetaCoreFilamentDebugLog("Rendering settings/Profile files changed; reloaded and reapplied"); return true; } void ReloadRenderProfiles() { EnvironmentProfiles_.clear(); PostProcessProfiles_.clear(); MissingEnvironmentProfileDiagnostics_.clear(); MissingPostProcessProfileDiagnostics_.clear(); ActiveScenePostProcessGuid_ = {}; ActiveCameraPostProcessGuid_ = {}; if (ProjectRootPath_.empty()) return; MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRenderingGeneratedTypes(registry); for (const auto& relativeRoot : {std::filesystem::path("Assets"), std::filesystem::path("Runtime")}) { const auto root = ProjectRootPath_ / relativeRoot; std::error_code error; if (!std::filesystem::exists(root, error)) continue; std::filesystem::recursive_directory_iterator iterator(root, std::filesystem::directory_options::skip_permission_denied, error); const std::filesystem::recursive_directory_iterator end; while (!error && iterator != end) { if (iterator->is_regular_file(error)) { const auto extension = iterator->path().extension(); if (extension == ".mcenvironment") { if (auto profile = MetaCoreReadEnvironmentProfile(iterator->path(), registry)) EnvironmentProfiles_.insert_or_assign(profile->AssetGuid, *profile); } else if (extension == ".mcpostprocess") { if (auto profile = MetaCoreReadPostProcessProfile(iterator->path(), registry)) PostProcessProfiles_.insert_or_assign(profile->AssetGuid, *profile); } } iterator.increment(error); } } } void ConfigurePostProcessing(const MetaCorePostProcessProfileDocument* requestedProfile = nullptr) { if (!View_) return; MetaCorePostProcessProfileDocument fallback; fallback.AntiAliasing = RenderSettings_.AntiAliasing; fallback.MsaaSamples = RenderSettings_.MsaaSamples; const auto& profile = requestedProfile ? *requestedProfile : fallback; ActivePostProcessProfile_ = profile; const filament::ShadowType shadowType = RenderSettings_.ShadowFilter == MetaCoreShadowFilter::Hard ? filament::ShadowType::PCF : (RenderSettings_.ShadowFilter == MetaCoreShadowFilter::PcfLow ? filament::ShadowType::DPCF : filament::ShadowType::PCSS); View_->setShadowType(shadowType); const bool fxaa = profile.AntiAliasing == MetaCoreAntiAliasingMode::Fxaa; View_->setAntiAliasing(fxaa ? filament::AntiAliasing::FXAA : filament::AntiAliasing::NONE); filament::TemporalAntiAliasingOptions taa; taa.enabled = profile.AntiAliasing == MetaCoreAntiAliasingMode::Taa; View_->setTemporalAntiAliasingOptions(taa); filament::MultiSampleAntiAliasingOptions msaa; const std::uint32_t effectiveMsaa = profile.AntiAliasing == MetaCoreAntiAliasingMode::Taa ? 1U : profile.MsaaSamples; if (effectiveMsaa != profile.MsaaSamples) std::cerr << "[MetaCoreRender] TAA+MSAA is unsupported; requested MSAA=" << profile.MsaaSamples << "; fallback=MSAA 1\n"; msaa.enabled = effectiveMsaa > 1U; msaa.sampleCount = static_cast(effectiveMsaa); View_->setMultiSampleAntiAliasingOptions(msaa); filament::BloomOptions bloom; bloom.enabled = profile.BloomEnabled; bloom.strength = std::clamp(profile.BloomStrength, 0.0F, 1.0F); bloom.threshold = profile.BloomThreshold > 0.0F; View_->setBloomOptions(bloom); if (ColorGrading_ && Engine_) Engine_->destroy(ColorGrading_); filament::ColorGrading::ToneMapping toneMapping = filament::ColorGrading::ToneMapping::ACES; if (profile.ToneMapping == MetaCoreToneMappingMode::Filmic) toneMapping = filament::ColorGrading::ToneMapping::FILMIC; else if (profile.ToneMapping == MetaCoreToneMappingMode::Linear) toneMapping = filament::ColorGrading::ToneMapping::LINEAR; ColorGrading_ = filament::ColorGrading::Builder() .toneMapping(toneMapping) .saturation(std::clamp(profile.Saturation, 0.0F, 2.0F)) .contrast(std::clamp(profile.Contrast, 0.0F, 2.0F)) .build(*Engine_); View_->setColorGrading(ColorGrading_); if (Camera_) { constexpr float aperture = 16.0F; const float ev100 = profile.ExposureMode == MetaCoreExposureMode::Manual ? profile.ManualEv100 : 0.5F * (profile.AutoExposureMinEv100 + profile.AutoExposureMaxEv100); const float shutter = (aperture * aperture) / std::exp2(ev100); Camera_->setExposure(aperture, shutter, 100.0F); AutoExposureEv100_ = ev100; } } void UpdateAutoExposure() { if (!Camera_ || ActivePostProcessProfile_.ExposureMode != MetaCoreExposureMode::Automatic) return; float illuminance = std::max(0.001F, ActiveEnvironmentSettings_.IndirectLightIntensity * 0.001F); for (const auto& light : LastSyncSnapshot_.Lights) { if (!light.Enabled) continue; if (light.Type == MetaCoreLightType::Directional) illuminance += std::max(0.0F, light.Intensity); else { const glm::vec3 offset=glm::vec3(light.WorldMatrix[3])-CurrentSceneView_.CameraPosition;const float distanceSquared=std::max(0.01F,glm::dot(offset,offset));illuminance+=std::max(0.0F,light.Intensity)/distanceSquared; } } const float target = std::clamp(std::log2(illuminance * 8.0F), ActivePostProcessProfile_.AutoExposureMinEv100, ActivePostProcessProfile_.AutoExposureMaxEv100); const auto now=std::chrono::steady_clock::now();const float delta=LastAutoExposureUpdate_.time_since_epoch().count()==0?1.0F/60.0F:std::clamp(std::chrono::duration(now-LastAutoExposureUpdate_).count(),0.0F,0.25F);LastAutoExposureUpdate_=now; const float speed=target>AutoExposureEv100_?ActivePostProcessProfile_.AutoExposureSpeedUp:ActivePostProcessProfile_.AutoExposureSpeedDown;AutoExposureEv100_+= (target-AutoExposureEv100_)*(1.0F-std::exp(-std::max(0.01F,speed)*delta)); constexpr float aperture=16.0F;Camera_->setExposure(aperture,(aperture*aperture)/std::exp2(AutoExposureEv100_),100.0F); } void DestroyMaterialTextureCache() { if (Engine_) { Engine_->flushAndWait(); for (auto& [_, texture] : MaterialTextureCache_) { if (texture != nullptr) { Engine_->destroy(texture); } } } MaterialTextureCache_.clear(); } [[nodiscard]] static std::optional> ReadBinaryFile(const std::filesystem::path& path) { std::ifstream input(path, std::ios::binary | std::ios::ate); if (!input.is_open()) { return std::nullopt; } const std::streamsize fileSize = input.tellg(); if (fileSize <= 0) { return std::nullopt; } std::vector bytes(static_cast(fileSize)); input.seekg(0, std::ios::beg); if (!input.read(reinterpret_cast(bytes.data()), fileSize)) { return std::nullopt; } return bytes; } [[nodiscard]] std::filesystem::path ResolveDefaultEnvironmentPath(std::string_view filename) const { const std::array candidates{ std::filesystem::path("third_party") / "filament_installed" / "bin" / "assets" / "ibl" / "lightroom_14b" / filename, std::filesystem::path("third_party") / "filament_installed.before-linux-sdk" / "bin" / "assets" / "ibl" / "lightroom_14b" / filename, std::filesystem::path("build_filament") / "samples" / "assets" / "ibl" / "lightroom_14b" / filename, std::filesystem::path("build_filament") / "libs" / "ktxreader" / filename }; std::vector searchRoots; std::filesystem::path current = std::filesystem::current_path(); for (int depth = 0; depth < 5 && !current.empty(); ++depth) { searchRoots.push_back(current); const std::filesystem::path parent = current.parent_path(); if (parent == current) { break; } current = parent; } if (!ProjectRootPath_.empty()) { searchRoots.push_back(ProjectRootPath_); searchRoots.push_back(ProjectRootPath_.parent_path()); } for (const std::filesystem::path& root : searchRoots) { for (const std::filesystem::path& relativePath : candidates) { const std::filesystem::path absolutePath = (root / relativePath).lexically_normal(); if (std::filesystem::exists(absolutePath)) { return absolutePath; } } } return {}; } [[nodiscard]] filament::Texture* LoadKtx1Texture( const std::filesystem::path& path, bool srgb, filament::math::float3* outSphericalHarmonics = nullptr, std::vector* retainedBytes = nullptr ) { if (Engine_ == nullptr || path.empty()) { return nullptr; } auto bytes = ReadBinaryFile(path); if (!bytes.has_value()) { return nullptr; } const std::uint8_t* data = bytes->data(); std::uint32_t size = static_cast(bytes->size()); if (retainedBytes != nullptr) { *retainedBytes = std::move(*bytes); data = retainedBytes->data(); size = static_cast(retainedBytes->size()); } auto bundle = std::make_unique( data, size ); if (outSphericalHarmonics != nullptr) { if (!bundle->getSphericalHarmonics(outSphericalHarmonics)) { std::fill(outSphericalHarmonics, outSphericalHarmonics + 9, filament::math::float3{0.0f, 0.0f, 0.0f}); } } filament::Texture* texture = ktxreader::Ktx1Reader::createTexture(Engine_, bundle.release(), srgb); if (texture != nullptr) { // Ktx1Bundle references the supplied bytes while Filament queues backend uploads. // Keep environment KTX payloads alive alongside their textures, and also flush // here for temporary callers. Engine_->flushAndWait(); } if (texture == nullptr && retainedBytes != nullptr) { retainedBytes->clear(); } return texture; } [[nodiscard]] static bool IsSafeProjectRelativeKtxPath(const std::filesystem::path& path) { if (path.empty() || path.is_absolute() || path.has_root_name() || path.has_root_directory()) { return false; } for (const std::filesystem::path& component : path) { if (component == "..") { return false; } } std::string extension = path.extension().string(); std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) { return static_cast(std::tolower(value)); }); return extension == ".ktx"; } [[nodiscard]] bool ResolveEnvironmentPaths( const MetaCoreSceneEnvironmentSettings& settings, std::filesystem::path& outIblPath, std::filesystem::path& outSkyboxPath ) const { outIblPath.clear(); outSkyboxPath.clear(); if (settings.Source == MetaCoreSceneEnvironmentSource::ProjectKtxPair) { if (ProjectRootPath_.empty() || !IsSafeProjectRelativeKtxPath(settings.IblKtxPath) || !IsSafeProjectRelativeKtxPath(settings.SkyboxKtxPath)) { return false; } outIblPath = (ProjectRootPath_ / settings.IblKtxPath).lexically_normal(); outSkyboxPath = (ProjectRootPath_ / settings.SkyboxKtxPath).lexically_normal(); std::error_code errorCode; return std::filesystem::is_regular_file(outIblPath, errorCode) && std::filesystem::is_regular_file(outSkyboxPath, errorCode); } outIblPath = ResolveDefaultEnvironmentPath("lightroom_14b_ibl.ktx"); outSkyboxPath = ResolveDefaultEnvironmentPath("lightroom_14b_skybox.ktx"); return !outIblPath.empty() && !outSkyboxPath.empty(); } [[nodiscard]] static bool EnvironmentSettingsEqual( const MetaCoreSceneEnvironmentSettings& lhs, const MetaCoreSceneEnvironmentSettings& rhs ) { return lhs.Source == rhs.Source && lhs.IblKtxPath == rhs.IblKtxPath && lhs.SkyboxKtxPath == rhs.SkyboxKtxPath && lhs.SkyboxVisible == rhs.SkyboxVisible && std::abs(lhs.RotationDegrees - rhs.RotationDegrees) <= 0.0001F && std::abs(lhs.IndirectLightIntensity - rhs.IndirectLightIntensity) <= 0.0001F && std::abs(lhs.SkyboxIntensity - rhs.SkyboxIntensity) <= 0.0001F; } [[nodiscard]] static float NormalizeEnvironmentRotation(float degrees) { if (!std::isfinite(degrees)) { return 0.0F; } float normalized = std::fmod(degrees, 360.0F); if (normalized < 0.0F) { normalized += 360.0F; } return normalized; } void UpdateEnvironmentBackgroundVisibility() { const bool shouldShow = EnvironmentSkybox_ != nullptr && Scene_ != nullptr && ActiveEnvironmentSettings_.SkyboxVisible && CurrentSceneView_.ClearFlags == MetaCoreCameraClearFlags::Skybox; if (EnvironmentBackgroundVisible_ != shouldShow) { MetaCoreFilamentDebugLog( std::string("Scene environment background ") + (shouldShow ? "enabled" : "disabled") + " clearFlags=" + std::to_string(static_cast(CurrentSceneView_.ClearFlags)) + " skyboxVisible=" + (ActiveEnvironmentSettings_.SkyboxVisible ? "true" : "false") ); } if (Scene_ != nullptr) { if (shouldShow && Scene_->getSkybox() != EnvironmentSkybox_) { Scene_->setSkybox(EnvironmentSkybox_); } else if (!shouldShow && Scene_->getSkybox() == EnvironmentSkybox_) { Scene_->setSkybox(nullptr); } } EnvironmentBackgroundVisible_ = shouldShow; } bool RebuildEnvironmentSkybox() { if (Engine_ == nullptr || EnvironmentSkyboxTexture_ == nullptr) { return false; } if (Scene_ != nullptr && Scene_->getSkybox() == EnvironmentSkybox_) { Scene_->setSkybox(nullptr); } if (EnvironmentSkybox_ != nullptr) { Engine_->destroy(EnvironmentSkybox_); EnvironmentSkybox_ = nullptr; } EnvironmentSkybox_ = filament::Skybox::Builder() .environment(EnvironmentSkyboxTexture_) .intensity(std::max(0.0F, ActiveEnvironmentSettings_.SkyboxIntensity)) .build(*Engine_); if (EnvironmentSkybox_ == nullptr) { return false; } EnvironmentSkybox_->setLayerMask(0xFFU, kSceneContentLayerMask); UpdateEnvironmentBackgroundVisibility(); return true; } bool LoadEnvironmentResources( const std::filesystem::path& iblPath, const std::filesystem::path& skyboxPath ) { EnvironmentIblKtxBytes_.clear(); EnvironmentSkyboxKtxBytes_.clear(); filament::math::float3 sphericalHarmonics[9]{}; EnvironmentIblTexture_ = LoadKtx1Texture(iblPath, false, sphericalHarmonics, &EnvironmentIblKtxBytes_); EnvironmentSkyboxTexture_ = LoadKtx1Texture(skyboxPath, false, nullptr, &EnvironmentSkyboxKtxBytes_); if (EnvironmentIblTexture_ == nullptr || EnvironmentSkyboxTexture_ == nullptr) { return false; } EnvironmentIndirectLight_ = filament::IndirectLight::Builder() .reflections(EnvironmentIblTexture_) .irradiance(3, sphericalHarmonics) .intensity(ActiveEnvironmentSettings_.IndirectLightIntensity) .build(*Engine_); if (EnvironmentIndirectLight_ == nullptr) { return false; } Scene_->setIndirectLight(EnvironmentIndirectLight_); EnvironmentLoaded_ = true; return RebuildEnvironmentSkybox(); } void SynchronizeEnvironment(const MetaCoreSceneEnvironmentSettings& requestedSettings) { if (Engine_ == nullptr || Scene_ == nullptr) { return; } std::filesystem::path iblPath; std::filesystem::path skyboxPath; const bool requestedPathsValid = ResolveEnvironmentPaths(requestedSettings, iblPath, skyboxPath); if (!requestedPathsValid && requestedSettings.Source == MetaCoreSceneEnvironmentSource::ProjectKtxPair) { const std::string failureSignature = requestedSettings.IblKtxPath.generic_string() + "|" + requestedSettings.SkyboxKtxPath.generic_string(); if (LastEnvironmentValidationFailure_ != failureSignature) { MetaCoreFilamentDebugLog( "Scene environment KTX pair is unavailable or unsafe; using the built-in environment. ibl=\"" + requestedSettings.IblKtxPath.generic_string() + "\" skybox=\"" + requestedSettings.SkyboxKtxPath.generic_string() + "\"" ); LastEnvironmentValidationFailure_ = failureSignature; } MetaCoreSceneEnvironmentSettings fallbackSettings; if (!ResolveEnvironmentPaths(fallbackSettings, iblPath, skyboxPath)) { DestroyEnvironmentLighting(); ActiveEnvironmentSettings_ = requestedSettings; return; } } else if (!requestedPathsValid) { DestroyEnvironmentLighting(); ActiveEnvironmentSettings_ = requestedSettings; return; } else { LastEnvironmentValidationFailure_.clear(); } const bool resourcesChanged = !EnvironmentLoaded_ || ActiveEnvironmentSettings_.Source != requestedSettings.Source || ActiveEnvironmentSettings_.IblKtxPath != requestedSettings.IblKtxPath || ActiveEnvironmentSettings_.SkyboxKtxPath != requestedSettings.SkyboxKtxPath; const bool skyboxIntensityChanged = !EnvironmentSettingsEqual(ActiveEnvironmentSettings_, requestedSettings) && std::abs(ActiveEnvironmentSettings_.SkyboxIntensity - requestedSettings.SkyboxIntensity) > 0.0001F; ActiveEnvironmentSettings_ = requestedSettings; ActiveEnvironmentSettings_.RotationDegrees = NormalizeEnvironmentRotation(ActiveEnvironmentSettings_.RotationDegrees); ActiveEnvironmentSettings_.IndirectLightIntensity = std::max(0.0F, ActiveEnvironmentSettings_.IndirectLightIntensity); ActiveEnvironmentSettings_.SkyboxIntensity = std::max(0.0F, ActiveEnvironmentSettings_.SkyboxIntensity); if (resourcesChanged) { DestroyEnvironmentLighting(); if (!LoadEnvironmentResources(iblPath, skyboxPath)) { DestroyEnvironmentLighting(); if (requestedSettings.Source != MetaCoreSceneEnvironmentSource::ProjectKtxPair) { MetaCoreFilamentDebugLog("Built-in scene environment KTX load failed; falling back to solid clear color"); return; } MetaCoreFilamentDebugLog("Scene environment KTX load failed; falling back to the built-in environment"); MetaCoreSceneEnvironmentSettings fallbackSettings; if (!ResolveEnvironmentPaths(fallbackSettings, iblPath, skyboxPath) || !LoadEnvironmentResources(iblPath, skyboxPath)) { MetaCoreFilamentDebugLog("Built-in scene environment fallback load failed; falling back to solid clear color"); DestroyEnvironmentLighting(); return; } } LoadedEnvironmentIblPath_ = iblPath; LoadedEnvironmentSkyboxPath_ = skyboxPath; MetaCoreFilamentDebugLog( "Scene environment loaded ibl=\"" + iblPath.generic_string() + "\" skybox=\"" + skyboxPath.generic_string() + "\"" ); } else { if (EnvironmentIndirectLight_ != nullptr) { EnvironmentIndirectLight_->setIntensity(ActiveEnvironmentSettings_.IndirectLightIntensity); } if (skyboxIntensityChanged && !RebuildEnvironmentSkybox()) { MetaCoreFilamentDebugLog("Scene environment skybox intensity update failed"); } } if (EnvironmentIndirectLight_ != nullptr) { const glm::mat3 rotation = glm::mat3(glm::rotate( glm::mat4(1.0F), glm::radians(ActiveEnvironmentSettings_.RotationDegrees), glm::vec3(0.0F, 0.0F, 1.0F) )); EnvironmentIndirectLight_->setRotation( *reinterpret_cast(glm::value_ptr(rotation)) ); } UpdateEnvironmentBackgroundVisibility(); } void LoadDefaultEnvironmentLighting() { SynchronizeEnvironment(MetaCoreSceneEnvironmentSettings{}); } void DestroyEnvironmentLighting() { if (Scene_ != nullptr) { if (Scene_->getIndirectLight() == EnvironmentIndirectLight_) { Scene_->setIndirectLight(nullptr); } } if (Scene_ != nullptr && Scene_->getSkybox() == EnvironmentSkybox_) { Scene_->setSkybox(nullptr); } if (EnvironmentScene_ != nullptr && EnvironmentScene_->getSkybox() == EnvironmentSkybox_) { EnvironmentScene_->setSkybox(nullptr); } if (Engine_ != nullptr) { Engine_->flushAndWait(); if (EnvironmentSkybox_ != nullptr) { Engine_->destroy(EnvironmentSkybox_); EnvironmentSkybox_ = nullptr; } if (EnvironmentIndirectLight_ != nullptr) { Engine_->destroy(EnvironmentIndirectLight_); EnvironmentIndirectLight_ = nullptr; } if (EnvironmentSkyboxTexture_ != nullptr) { Engine_->destroy(EnvironmentSkyboxTexture_); EnvironmentSkyboxTexture_ = nullptr; } if (EnvironmentIblTexture_ != nullptr) { Engine_->destroy(EnvironmentIblTexture_); EnvironmentIblTexture_ = nullptr; } } EnvironmentIblKtxBytes_.clear(); EnvironmentSkyboxKtxBytes_.clear(); EnvironmentLoaded_ = false; EnvironmentBackgroundVisible_ = false; LoadedEnvironmentIblPath_.clear(); LoadedEnvironmentSkyboxPath_.clear(); } [[nodiscard]] filament::Skybox* GetOrCreateSolidColorSkybox(const MetaCoreSceneView& sceneView) { if (Engine_ == nullptr) { return nullptr; } const filament::math::float4 color{ sceneView.BackgroundColor.x, sceneView.BackgroundColor.y, sceneView.BackgroundColor.z, sceneView.BackgroundAlpha }; constexpr float epsilon = 0.0001f; const bool sameColor = std::abs(SolidColorSkyboxColor_.x - color.x) <= epsilon && std::abs(SolidColorSkyboxColor_.y - color.y) <= epsilon && std::abs(SolidColorSkyboxColor_.z - color.z) <= epsilon && std::abs(SolidColorSkyboxColor_.w - color.w) <= epsilon; if (SolidColorSkybox_ != nullptr && sameColor) { return SolidColorSkybox_; } DestroySolidColorSkybox(); SolidColorSkybox_ = filament::Skybox::Builder() .color(color) .build(*Engine_); SolidColorSkyboxColor_ = color; return SolidColorSkybox_; } void DestroySolidColorSkybox() { if (Scene_ != nullptr && Scene_->getSkybox() == SolidColorSkybox_) { Scene_->setSkybox(nullptr); } if (Engine_ != nullptr && SolidColorSkybox_ != nullptr) { Engine_->destroy(SolidColorSkybox_); SolidColorSkybox_ = nullptr; } SolidColorSkyboxColor_ = filament::math::float4{-1.0f, -1.0f, -1.0f, -1.0f}; } [[nodiscard]] std::filesystem::path ResolveMaterialTexturePath( const std::string& texturePath, const std::string& sourceModelPath ) const { if (texturePath.empty()) { return {}; } const std::filesystem::path rawPath(texturePath); if (rawPath.is_absolute()) { return std::filesystem::exists(rawPath) ? rawPath : std::filesystem::path{}; } const std::filesystem::path projectRelativePath = ProjectRootPath_ / rawPath; if (std::filesystem::exists(projectRelativePath)) { return projectRelativePath; } if (!sourceModelPath.empty()) { const std::filesystem::path modelRelativePath(sourceModelPath); const std::filesystem::path modelDirectory = modelRelativePath.parent_path(); if (!modelDirectory.empty()) { const std::filesystem::path modelRelativeTexturePath = ProjectRootPath_ / modelDirectory / rawPath; if (std::filesystem::exists(modelRelativeTexturePath)) { return modelRelativeTexturePath; } } } const std::filesystem::path assetsRelativePath = ProjectRootPath_ / "Assets" / rawPath; if (std::filesystem::exists(assetsRelativePath)) { return assetsRelativePath; } return {}; } [[nodiscard]] filament::Texture* LoadMaterialTexture( const std::string& texturePath, const std::string& sourceModelPath, bool srgb ) { if (Engine_ == nullptr || texturePath.empty()) { return nullptr; } const std::filesystem::path absolutePath = ResolveMaterialTexturePath(texturePath, sourceModelPath); if (absolutePath.empty()) { return nullptr; } const std::string cacheKey = absolutePath.lexically_normal().generic_string() + (srgb ? "|srgb" : "|linear"); if (const auto cacheIterator = MaterialTextureCache_.find(cacheKey); cacheIterator != MaterialTextureCache_.end()) { return cacheIterator->second; } int width = 0; int height = 0; int channels = 0; unsigned char* pixels = stbi_load(absolutePath.string().c_str(), &width, &height, &channels, 4); if (pixels == nullptr || width <= 0 || height <= 0) { if (pixels != nullptr) { stbi_image_free(pixels); } MetaCoreFilamentDebugLog("Material texture load failed path=\"" + absolutePath.generic_string() + "\""); return nullptr; } const std::size_t byteSize = static_cast(width) * static_cast(height) * 4U; filament::Texture::PixelBufferDescriptor pixelBuffer( pixels, byteSize, filament::Texture::Format::RGBA, filament::Texture::Type::UBYTE, [](void* buffer, size_t, void*) { stbi_image_free(buffer); } ); filament::Texture* texture = filament::Texture::Builder() .width(static_cast(width)) .height(static_cast(height)) .levels(1) .sampler(filament::Texture::Sampler::SAMPLER_2D) .format(srgb ? filament::Texture::InternalFormat::SRGB8_A8 : filament::Texture::InternalFormat::RGBA8) .build(*Engine_); if (texture == nullptr) { return nullptr; } texture->setImage(*Engine_, 0, std::move(pixelBuffer)); MaterialTextureCache_[cacheKey] = texture; return texture; } void ParseModelHierarchyToEcs(MetaCoreScene& scene, MetaCoreGameObject& parentObject, filament::gltfio::FilamentAsset* asset) { if (!asset) return; auto& tm = Engine_->getTransformManager(); // 绑定顶级父 GameObject 标识到 Filament Asset 根 Entity,彻底打通顶级 Gizmo 的同步! ObjectToFilamentEntity_[parentObject.GetId()] = { asset, asset->getRoot() }; // 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题 std::unordered_map, utils::Entity::Hasher> parentToChildren = BuildFullParentToChildrenMap(asset); // 2. 建立 ECS 的父子关系映射 std::unordered_map> ecsParentToChildren; std::vector subtreeIds = scene.GetSubtreeObjectIds(parentObject.GetId()); for (MetaCoreId subId : subtreeIds) { auto subObj = scene.FindGameObject(subId); if (subObj) { ecsParentToChildren[subObj.GetParentId()].push_back(subObj); } } // 3. 递归 DFS 精确结构匹配并初始化 Transform 属性,穿透所有过渡节点 std::function matchRecursive = [&](MetaCoreGameObject currentObj, utils::Entity currentEntity) { ObjectToFilamentEntity_[currentObj.GetId()] = { asset, currentEntity }; if (currentEntity != asset->getRoot()) { auto instance = tm.getInstance(currentEntity); if (instance) { filament::math::mat4f localTransform = tm.getTransform(instance); glm::mat4 filamentMatrix; std::memcpy(glm::value_ptr(filamentMatrix), &localTransform[0][0], sizeof(float) * 16); glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); glm::mat4 matrix = R_minus90X * filamentMatrix * R_plus90X; // 拆解矩阵 glm::vec3 scale; glm::quat rotation; glm::vec3 translation; glm::vec3 skew; glm::vec4 perspective; glm::decompose(matrix, scale, rotation, translation, skew, perspective); auto& transform = currentObj.GetComponent(); transform.Position = translation; transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation)); transform.Scale = scale; } } // 穿透过渡节点获取当前实体的有效 Filament 子实体列表 std::vector fChildren; GetEffectiveFilamentChildren(currentEntity, asset, parentToChildren, fChildren); auto& eChildren = ecsParentToChildren[currentObj.GetId()]; for (utils::Entity fChild : fChildren) { const char* nodeName = asset->getName(fChild); std::string name = nodeName ? nodeName : ""; MetaCoreGameObject matchedEObj; for (auto it = eChildren.begin(); it != eChildren.end(); ++it) { if (it->GetName() == name) { matchedEObj = *it; eChildren.erase(it); break; } } // 模糊/降级匹配 if (!matchedEObj && name.empty() && !eChildren.empty()) { matchedEObj = eChildren.front(); eChildren.erase(eChildren.begin()); } if (matchedEObj) { matchRecursive(matchedEObj, fChild); } } }; // 从根开始执行递归层级树匹配 matchRecursive(parentObject, asset->getRoot()); } static std::string NormalizePath(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); return path; } [[nodiscard]] std::optional> ReadGltfFileResponsive( const std::filesystem::path& absolutePath ) { std::ifstream file(absolutePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { return std::nullopt; } const std::streamoff fileSize = file.tellg(); if (fileSize < 0 || static_cast(fileSize) > std::numeric_limits::max()) { return std::nullopt; } file.seekg(0, std::ios::beg); std::vector buffer(static_cast(fileSize)); std::size_t offset = 0; while (offset < buffer.size()) { const std::size_t bytesToRead = std::min(kGltfFileReadChunkBytes, buffer.size() - offset); file.read(buffer.data() + offset, static_cast(bytesToRead)); const std::streamsize bytesRead = file.gcount(); if (bytesRead <= 0) { return std::nullopt; } offset += static_cast(bytesRead); if (Window_ != nullptr) { Window_->PollEvents(); } } return buffer; } void CancelPendingResourceLoad(MetaCoreId hostRootId) { const auto iterator = PendingResourceLoads_.find(hostRootId); if (iterator == PendingResourceLoads_.end()) { return; } if (iterator->second.Loader != nullptr) { iterator->second.Loader->asyncCancelLoad(); } PendingResourceLoads_.erase(iterator); } void UpdatePendingResourceLoads(const std::unordered_set& activeHostRootIds) { for (auto iterator = PendingResourceLoads_.begin(); iterator != PendingResourceLoads_.end();) { const MetaCoreId hostRootId = iterator->first; PendingResourceLoad& pendingLoad = iterator->second; if (!activeHostRootIds.contains(hostRootId) || !LoadedAssets_.contains(hostRootId)) { if (pendingLoad.Loader != nullptr) { pendingLoad.Loader->asyncCancelLoad(); } iterator = PendingResourceLoads_.erase(iterator); continue; } if (pendingLoad.Loader == nullptr) { iterator = PendingResourceLoads_.erase(iterator); continue; } pendingLoad.Loader->asyncUpdateLoad(); if (pendingLoad.Loader->asyncGetLoadProgress() >= 1.0F) { MetaCoreFilamentDebugLog( "Load async resources complete hostRootId=" + std::to_string(hostRootId) ); iterator = PendingResourceLoads_.erase(iterator); continue; } ++iterator; } } bool BeginAsyncResourceLoad( MetaCoreId hostRootId, filament::gltfio::FilamentAsset* asset, const std::filesystem::path& absolutePath ) { auto [iterator, inserted] = PendingResourceLoads_.try_emplace(hostRootId); PendingResourceLoad& pendingLoad = iterator->second; pendingLoad.GltfPath = absolutePath.string(); filament::gltfio::ResourceConfiguration resourceConfig{}; resourceConfig.engine = Engine_; resourceConfig.gltfPath = pendingLoad.GltfPath.c_str(); resourceConfig.normalizeSkinningWeights = true; pendingLoad.Loader = std::make_unique(resourceConfig); if (StbTextureProvider_) { pendingLoad.Loader->addTextureProvider("image/png", StbTextureProvider_); pendingLoad.Loader->addTextureProvider("image/jpeg", StbTextureProvider_); } if (!pendingLoad.Loader->asyncBeginLoad(asset)) { pendingLoad.Loader.reset(); PendingResourceLoads_.erase(iterator); return false; } MetaCoreFilamentDebugLog( "Load async resources begin hostRootId=" + std::to_string(hostRootId) ); return true; } void SyncScene(const MetaCoreSceneRenderSyncSnapshot& inputSnapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { if (!AssetLoader_) return; const bool renderConfigurationChanged = PollRenderConfigurationChanges(); RestoreDebugMaterials(); const auto syncStart = std::chrono::steady_clock::now(); (void)compatibilityMeshOnly; if (useScenePrimaryCamera) { MetaCoreSceneView primarySceneView; if (MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(inputSnapshot, primarySceneView)) ApplySceneView(primarySceneView); } MetaCoreSceneRenderSyncSnapshot resolvedSnapshot = inputSnapshot; if (RenderSettings_.EnableLod) { for (const auto& lodGroup : resolvedSnapshot.LodGroups) { auto renderable = std::find_if(resolvedSnapshot.Renderables.begin(), resolvedSnapshot.Renderables.end(), [&](const auto& value) { return value.ObjectId == lodGroup.ObjectId; }); if (renderable == resolvedSnapshot.Renderables.end() || !lodGroup.Component.Enabled || lodGroup.Component.Levels.empty()) continue; const glm::vec3 position = glm::vec3(lodGroup.WorldMatrix[3]); const float radius = std::max({glm::length(glm::vec3(lodGroup.WorldMatrix[0])), glm::length(glm::vec3(lodGroup.WorldMatrix[1])), glm::length(glm::vec3(lodGroup.WorldMatrix[2])), 0.001F}); float screenHeight = 0.0F; if (CurrentSceneView_.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) screenHeight = radius / std::max(0.001F, CurrentSceneView_.OrthographicSize); else { const float distance = std::max(0.001F, glm::distance(position, CurrentSceneView_.CameraPosition)); const float halfFov = glm::radians(CurrentSceneView_.VerticalFieldOfViewDegrees * 0.5F); screenHeight = radius / (distance * std::max(0.001F, std::tan(halfFov))); } const auto previous = LodSelections_.contains(lodGroup.ObjectId) ? LodSelections_.at(lodGroup.ObjectId) : -1; const std::int32_t selected = MetaCoreSelectLod(lodGroup.Component, screenHeight, previous); LodSelections_.insert_or_assign(lodGroup.ObjectId, selected); if (selected < 0) { renderable->Visible = false; continue; } const auto& level = lodGroup.Component.Levels[static_cast(selected)]; if (level.MeshAssetGuid.IsValid()) { renderable->MeshSource = MetaCoreMeshSourceKind::Asset; renderable->MeshAssetGuid = level.MeshAssetGuid; renderable->SourceModelAssetGuid = {}; renderable->SourceModelPath.clear(); } if (!level.MaterialAssetGuids.empty()) renderable->MaterialAssetGuids = level.MaterialAssetGuids; } } const auto& snapshot = resolvedSnapshot; RenderingStatistics_ = {}; const auto& viewport = View_->getViewport(); const float aspect = viewport.height > 0U ? static_cast(viewport.width) / static_cast(viewport.height) : 1.0F; const glm::mat4 viewMatrix = glm::lookAt(CurrentSceneView_.CameraPosition, CurrentSceneView_.CameraTarget, CurrentSceneView_.CameraUp); const glm::mat4 projectionMatrix = CurrentSceneView_.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic ? glm::ortho(-CurrentSceneView_.OrthographicSize * aspect, CurrentSceneView_.OrthographicSize * aspect, -CurrentSceneView_.OrthographicSize, CurrentSceneView_.OrthographicSize, CurrentSceneView_.NearClip, CurrentSceneView_.FarClip) : glm::perspective(glm::radians(CurrentSceneView_.VerticalFieldOfViewDegrees), aspect, CurrentSceneView_.NearClip, CurrentSceneView_.FarClip); const glm::mat4 viewProjection = projectionMatrix * viewMatrix; const MetaCoreViewFrustum frustum = MetaCoreExtractFrustum(viewProjection); std::vector visibilityItems; visibilityItems.reserve(resolvedSnapshot.Renderables.size()); for (const auto& renderable : resolvedSnapshot.Renderables) { if (!renderable.Visible) { const bool lodCulled = LodSelections_.contains(renderable.ObjectId) && LodSelections_.at(renderable.ObjectId) < 0; MetaCoreAccumulateVisibilityResult(RenderingStatistics_, lodCulled ? MetaCoreVisibilityResult::LodCulled : MetaCoreVisibilityResult::DistanceCulled); continue; } visibilityItems.push_back({renderable.ObjectId, renderable.WorldBounds, renderable.RenderingLayerMask, renderable.MaxDrawDistance, !renderable.StaticForCulling}); } VisibilityBvh_.Build(std::move(visibilityItems)); const auto bvhVisible = VisibilityBvh_.Query(frustum, CurrentSceneView_.CameraPosition, CurrentSceneView_.CullingMask, &RenderingStatistics_); const std::unordered_set bvhVisibleSet(bvhVisible.begin(), bvhVisible.end()); const glm::vec3 cameraDirection = glm::normalize(CurrentSceneView_.CameraTarget - CurrentSceneView_.CameraPosition); const bool cameraCut = !HasPreviousVisibilityCamera_ || glm::distance(PreviousVisibilityCameraPosition_, CurrentSceneView_.CameraPosition) > 2.0F || glm::dot(PreviousVisibilityCameraDirection_, cameraDirection) < 0.8F; OcclusionHistory_.BeginFrame(); const auto projectBounds = [&](const MetaCoreRenderBounds& bounds, glm::vec4& screenBounds, float& nearestDepth, float& farthestDepth) { const std::array corners{{ {bounds.Minimum.x,bounds.Minimum.y,bounds.Minimum.z},{bounds.Maximum.x,bounds.Minimum.y,bounds.Minimum.z}, {bounds.Minimum.x,bounds.Maximum.y,bounds.Minimum.z},{bounds.Maximum.x,bounds.Maximum.y,bounds.Minimum.z}, {bounds.Minimum.x,bounds.Minimum.y,bounds.Maximum.z},{bounds.Maximum.x,bounds.Minimum.y,bounds.Maximum.z}, {bounds.Minimum.x,bounds.Maximum.y,bounds.Maximum.z},{bounds.Maximum.x,bounds.Maximum.y,bounds.Maximum.z}}}; glm::vec2 minimum(1.0F), maximum(0.0F);bool valid=false; for(const glm::vec3& corner:corners){const glm::vec4 clip=viewProjection*glm::vec4(corner,1.0F);if(clip.w<=0.0001F)continue;const glm::vec2 uv=glm::vec2(clip)/clip.w*0.5F+0.5F;minimum=glm::min(minimum,uv);maximum=glm::max(maximum,uv);valid=true;} screenBounds={minimum.x,minimum.y,maximum.x,maximum.y};const float radius=glm::length(bounds.Extents());const float distance=glm::distance(CurrentSceneView_.CameraPosition,bounds.Center());nearestDepth=std::max(CurrentSceneView_.NearClip,distance-radius);farthestDepth=std::min(CurrentSceneView_.FarClip,distance+radius);return valid; }; std::unordered_set currentVisible; for (auto& renderable : resolvedSnapshot.Renderables) { if (!renderable.Visible || !bvhVisibleSet.contains(renderable.ObjectId)) { renderable.Visible=false; continue; } bool queryVisible=true;glm::vec4 screenBounds{};float nearestDepth=0.0F,farthestDepth=0.0F; if(RenderSettings_.EnableOcclusion&&projectBounds(renderable.WorldBounds,screenBounds,nearestDepth,farthestDepth))queryVisible=PreviousHzb_.IsVisible(screenBounds,nearestDepth); const bool newlyVisible=!LastFrameVisibleObjects_.contains(renderable.ObjectId); const bool finalVisible=!RenderSettings_.EnableOcclusion||OcclusionHistory_.IsVisible(renderable.ObjectId,queryVisible,cameraCut,!renderable.StaticForCulling,newlyVisible,RenderSettings_.OcclusionGraceFrames); renderable.Visible=finalVisible; if(finalVisible){currentVisible.insert(renderable.ObjectId);MetaCoreAccumulateVisibilityResult(RenderingStatistics_,MetaCoreVisibilityResult::Visible,renderable.TriangleCount);RenderingStatistics_.EstimatedVideoMemoryBytes+=renderable.EstimatedVideoMemoryBytes;} else MetaCoreAccumulateVisibilityResult(RenderingStatistics_,MetaCoreVisibilityResult::OcclusionCulled); } constexpr std::uint32_t hzbWidth=64U,hzbHeight=36U; std::vector softwareDepth(static_cast(hzbWidth)*hzbHeight,CurrentSceneView_.FarClip); for(const auto& renderable:resolvedSnapshot.Renderables){if(!renderable.Visible||!renderable.StaticForCulling)continue;glm::vec4 bounds{};float nearestDepth=0.0F,farthestDepth=0.0F;if(!projectBounds(renderable.WorldBounds,bounds,nearestDepth,farthestDepth))continue;const std::uint32_t minX=std::min(hzbWidth-1U,static_cast(std::clamp(bounds.x,0.0F,1.0F)*hzbWidth)),maxX=std::min(hzbWidth-1U,static_cast(std::clamp(bounds.z,0.0F,1.0F)*hzbWidth));const std::uint32_t minY=std::min(hzbHeight-1U,static_cast(std::clamp(bounds.y,0.0F,1.0F)*hzbHeight)),maxY=std::min(hzbHeight-1U,static_cast(std::clamp(bounds.w,0.0F,1.0F)*hzbHeight));for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)softwareDepth[static_cast(y)*hzbWidth+x]=std::min(softwareDepth[static_cast(y)*hzbWidth+x],farthestDepth);} (void)PreviousHzb_.Build(hzbWidth,hzbHeight,softwareDepth);LastFrameVisibleObjects_=std::move(currentVisible);PreviousVisibilityCameraPosition_=CurrentSceneView_.CameraPosition;PreviousVisibilityCameraDirection_=cameraDirection;HasPreviousVisibilityCamera_=true; std::vector instanceCandidates; instanceCandidates.reserve(snapshot.Renderables.size()); for (const auto& renderable : snapshot.Renderables) { std::uint64_t stateHash = static_cast(renderable.MeshSource) | (static_cast(renderable.BuiltinMesh) << 8U) | (static_cast(renderable.AlphaMode) << 16U) | (static_cast(renderable.DoubleSided) << 24U); std::uint64_t parameterHash = std::hash{}(renderable.Metallic) ^ (std::hash{}(renderable.Roughness) << 1U) ^ (std::hash{}(renderable.BaseColor.x) << 2U) ^ (std::hash{}(renderable.BaseColor.y) << 3U) ^ (std::hash{}(renderable.BaseColor.z) << 4U); instanceCandidates.push_back({renderable.ObjectId, renderable.MeshAssetGuid, renderable.MaterialAssetGuids, parameterHash, stateHash, renderable.Visible}); } const auto instanceBatches = MetaCoreBuildInstanceBatches(instanceCandidates, RenderSettings_.EnableInstancing); RenderingStatistics_.InstanceBatchCount = static_cast(instanceBatches.size()); RenderingStatistics_.DrawCallCount = static_cast(instanceBatches.size()); LastSyncSnapshot_ = snapshot; MetaCoreSceneEnvironmentSettings resolvedEnvironment = LastSyncSnapshot_.Environment; if (resolvedEnvironment.EnvironmentProfileGuid.IsValid()) { const auto profile = EnvironmentProfiles_.find(resolvedEnvironment.EnvironmentProfileGuid); if (profile != EnvironmentProfiles_.end()) { resolvedEnvironment.Source = MetaCoreSceneEnvironmentSource::ProjectKtxPair; resolvedEnvironment.IblKtxPath = profile->second.IblKtxPath; resolvedEnvironment.SkyboxKtxPath = profile->second.SkyboxKtxPath; resolvedEnvironment.SkyboxVisible = profile->second.SkyboxVisible; resolvedEnvironment.RotationDegrees = profile->second.RotationDegrees; resolvedEnvironment.IndirectLightIntensity = profile->second.IndirectLightIntensity; resolvedEnvironment.SkyboxIntensity = profile->second.SkyboxIntensity; } else if (MissingEnvironmentProfileDiagnostics_.insert(resolvedEnvironment.EnvironmentProfileGuid).second) { std::cerr << "[MetaCoreRender] Environment Profile missing; guid=" << resolvedEnvironment.EnvironmentProfileGuid.ToString() << "; fallback=scene environment\n"; } } SynchronizeEnvironment(resolvedEnvironment); MetaCoreAssetGuid cameraPostProcessGuid{}; if (useScenePrimaryCamera && LastSyncSnapshot_.PrimaryCamera) cameraPostProcessGuid = LastSyncSnapshot_.PrimaryCamera->PostProcessProfileGuid; const MetaCoreAssetGuid scenePostProcessGuid = LastSyncSnapshot_.Environment.PostProcessProfileGuid; if (renderConfigurationChanged || scenePostProcessGuid != ActiveScenePostProcessGuid_ || cameraPostProcessGuid != ActiveCameraPostProcessGuid_) { const MetaCorePostProcessProfileDocument* sceneProfile = nullptr; const MetaCorePostProcessProfileDocument* cameraProfile = nullptr; if (const auto found = PostProcessProfiles_.find(scenePostProcessGuid); found != PostProcessProfiles_.end()) sceneProfile = &found->second; else if (scenePostProcessGuid.IsValid() && MissingPostProcessProfileDiagnostics_.insert(scenePostProcessGuid).second) std::cerr << "[MetaCoreRender] Scene Post-process Profile missing; guid=" << scenePostProcessGuid.ToString() << "; fallback=engine defaults\n"; if (const auto found = PostProcessProfiles_.find(cameraPostProcessGuid); found != PostProcessProfiles_.end()) cameraProfile = &found->second; else if (cameraPostProcessGuid.IsValid() && MissingPostProcessProfileDiagnostics_.insert(cameraPostProcessGuid).second) std::cerr << "[MetaCoreRender] Camera Post-process Profile missing; guid=" << cameraPostProcessGuid.ToString() << "; fallback=scene profile\n"; ConfigurePostProcessing(cameraProfile ? cameraProfile : sceneProfile); ActiveScenePostProcessGuid_ = scenePostProcessGuid; ActiveCameraPostProcessGuid_ = cameraPostProcessGuid; } ObjectWorldMatrices_ = LastSyncSnapshot_.WorldMatrices; const bool materialDebugLogging = MetaCoreMaterialDebugLogEnabled(); // 构建物体ID到网格快照的快速映射,方便后续查询可见性及材质属性 std::unordered_map renderableMap; for (const auto& r : snapshot.Renderables) { renderableMap[r.ObjectId] = &r; } // 定义用于检测场景中某个对象及其所有祖先节点当前在快照里是否应为可见状态的辅助方法 std::unordered_map visibilityCache; std::function isObjectVisible = [&](MetaCoreId objectId) -> bool { if (objectId == 0) { return true; } auto cacheIt = visibilityCache.find(objectId); if (cacheIt != visibilityCache.end()) { return cacheIt->second; } if (!snapshot.WorldMatrices.contains(objectId)) { visibilityCache[objectId] = false; return false; } bool selfVisible = true; MetaCoreId parentId = 0; auto rIt = renderableMap.find(objectId); if (rIt != renderableMap.end()) { selfVisible = rIt->second->Visible; parentId = rIt->second->ParentId; } else if (scene != nullptr) { auto obj = scene->FindGameObject(objectId); if (obj) { parentId = obj.GetParentId(); } } if (!selfVisible) { visibilityCache[objectId] = false; return false; } bool parentVisible = isObjectVisible(parentId); visibilityCache[objectId] = parentVisible; return parentVisible; }; SyncSceneLights(LastSyncSnapshot_); SynchronizeProductionComponents(LastSyncSnapshot_); // 1. 收集当前 snapshot 中所有存在的模型 Root ID std::unordered_set activeHostRootIds; for (const auto& renderable : snapshot.Renderables) { if (renderable.MeshSource == MetaCoreMeshSourceKind::Asset || (renderable.MeshSource == MetaCoreMeshSourceKind::Builtin && (renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Cube || renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Plane))) { if (renderable.HostRootId != 0) { activeHostRootIds.insert(renderable.HostRootId); } } } if (materialDebugLogging && (!HasLoggedSnapshotDebug_ || LastDebugSnapshotRevision_ != snapshot.SceneRevision || LastDebugRenderableCount_ != snapshot.Renderables.size())) { std::ostringstream summary; summary << "Sync snapshot frame=" << snapshot.FrameId << " revision=" << snapshot.SceneRevision << " renderables=" << snapshot.Renderables.size() << " activeHosts=" << activeHostRootIds.size() << " loadedAssets=" << LoadedAssets_.size() << " mappings=" << ObjectToFilamentEntity_.size(); MetaCoreFilamentMaterialDebugLog(summary.str()); for (const auto& renderable : snapshot.Renderables) { std::ostringstream renderableStream; renderableStream << "Sync renderable objectId=" << renderable.ObjectId << " name=\"" << renderable.Name << "\"" << " hostRootId=" << renderable.HostRootId << " host=\"" << renderable.HostRootName << "\"" << " modelNode=" << renderable.ModelNodeIndex << " materialGuids=" << MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) << " baseColor=" << MetaCoreDebugVec3String(renderable.BaseColor) << " metallic=" << renderable.Metallic << " roughness=" << renderable.Roughness << " source=\"" << renderable.SourceModelPath << "\""; MetaCoreFilamentMaterialDebugLog(renderableStream.str()); } HasLoggedSnapshotDebug_ = true; LastDebugSnapshotRevision_ = snapshot.SceneRevision; LastDebugRenderableCount_ = snapshot.Renderables.size(); } UpdatePendingResourceLoads(activeHostRootIds); // 2. 检查所有已加载的资产,若其 Root ID 不在 activeHostRootIds 中,则说明已被删除,执行清理 for (auto it = LoadedAssets_.begin(); it != LoadedAssets_.end(); ) { MetaCoreId hostRootId = it->first; filament::gltfio::FilamentAsset* asset = it->second; const auto sourceGuid = HostRootSourceModelGuids_.find(hostRootId); const bool invalidated = sourceGuid != HostRootSourceModelGuids_.end() && InvalidatedModelGuids_.contains(sourceGuid->second); if (!activeHostRootIds.contains(hostRootId) || invalidated) { std::cout << "FilamentSceneBridge: Unloading deleted asset: " << hostRootId << std::endl; const size_t assetEntityCount = asset != nullptr ? asset->getEntityCount() : 0; const auto materialIt = AssetDuplicatedMaterials_.find(hostRootId); const size_t duplicatedMaterialCount = materialIt != AssetDuplicatedMaterials_.end() ? materialIt->second.size() : 0; MetaCoreFilamentDebugLog( "Unload begin hostRootId=" + std::to_string(hostRootId) + " asset=" + MetaCoreDebugPointer(asset) + " active=" + (activeHostRootIds.contains(hostRootId) ? std::string("true") : std::string("false")) + " invalidated=" + (invalidated ? std::string("true") : std::string("false")) + " entities=" + std::to_string(assetEntityCount) + " duplicatedMaterials=" + std::to_string(duplicatedMaterialCount) ); // 从场景中移除该资产关联的所有实体 if (Scene_ && asset) { const utils::Entity* entities = asset->getEntities(); size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { Scene_->remove(entities[i]); EntitiesInScene_.erase(entities[i]); } } // 查找并清理对应的 pivotEntity auto pivotIt = ObjectToFilamentEntity_.find(hostRootId); if (pivotIt != ObjectToFilamentEntity_.end()) { utils::Entity pivotEntity = pivotIt->second.second; bool isPivotNode = (asset == nullptr || pivotEntity != asset->getRoot()); if (Scene_ && pivotEntity) { Scene_->remove(pivotEntity); EntitiesInScene_.erase(pivotEntity); } if (isPivotNode && pivotEntity) { utils::EntityManager::get().destroy(pivotEntity); } } // 等待后台渲染线程消费完上一帧命令,再销毁 GLTF 资产持有的纹理/材质。 // 脚本播放会每帧推进 Transform,暂停/热更新等操作可能和渲染同步落在相邻帧, // 不等待会让 OpenGL descriptor 绑定已释放的 FTexture。 CancelPendingResourceLoad(hostRootId); if (Engine_) { MetaCoreFilamentDebugLog( "Unload flush before destroyAsset hostRootId=" + std::to_string(hostRootId) ); Engine_->flushAndWait(); } // 销毁资产本身 if (asset) { MetaCoreFilamentDebugLog( "Unload destroyAsset begin hostRootId=" + std::to_string(hostRootId) + " asset=" + MetaCoreDebugPointer(asset) ); AssetLoader_->destroyAsset(asset); MetaCoreFilamentDebugLog( "Unload destroyAsset end hostRootId=" + std::to_string(hostRootId) + " asset=" + MetaCoreDebugPointer(asset) ); } // 从 ObjectToFilamentEntity_ 和 ObjectWorldMatrices_ 中清除所有指向该 asset 的映射 if (asset != nullptr) { for (auto entIt = ObjectToFilamentEntity_.begin(); entIt != ObjectToFilamentEntity_.end(); ) { if (entIt->second.first == asset) { ObjectWorldMatrices_.erase(entIt->first); entIt = ObjectToFilamentEntity_.erase(entIt); } else { ++entIt; } } } // 确保顶级父 ID 的变换缓存也被清除 ObjectWorldMatrices_.erase(hostRootId); // 销毁克隆的材质 auto matIt = AssetDuplicatedMaterials_.find(hostRootId); if (matIt != AssetDuplicatedMaterials_.end()) { MetaCoreFilamentDebugLog( "Unload destroy duplicated materials begin hostRootId=" + std::to_string(hostRootId) + " count=" + std::to_string(matIt->second.size()) ); if (Engine_) { Engine_->flushAndWait(); } for (auto* mat : matIt->second) { if (mat) { Engine_->destroy(mat); } } MetaCoreFilamentDebugLog( "Unload destroy duplicated materials end hostRootId=" + std::to_string(hostRootId) ); AssetDuplicatedMaterials_.erase(matIt); } // 从 LoadedAssets_ 中移除 HostRootSourceModelGuids_.erase(hostRootId); it = LoadedAssets_.erase(it); MetaCoreFilamentDebugLog( "Unload end hostRootId=" + std::to_string(hostRootId) + " loadedAssets=" + std::to_string(LoadedAssets_.size()) + " objectMappings=" + std::to_string(ObjectToFilamentEntity_.size()) ); } else { ++it; } } InvalidatedModelGuids_.clear(); for (const auto& renderable : snapshot.Renderables) { if (renderable.MeshSource != MetaCoreMeshSourceKind::Asset && renderable.MeshSource != MetaCoreMeshSourceKind::Builtin) { continue; } std::string modelPath; if (renderable.SourceModelAssetGuid.IsValid()) { modelPath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(renderable.SourceModelAssetGuid).generic_string(); } if (modelPath.empty() && !renderable.SourceModelPath.empty()) { modelPath = renderable.SourceModelPath; } if (modelPath.empty() && renderable.MeshSource == MetaCoreMeshSourceKind::Builtin) { if (renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Cube) { modelPath = "Assets/Models/Cube.glb"; } else if (renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Plane) { modelPath = "Assets/Models/Plane.glb"; } } if (modelPath.empty()) { continue; } std::filesystem::path absolutePath = ProjectRootPath_ / modelPath; MetaCoreId hostRootId = renderable.HostRootId; std::string hostRootName = renderable.HostRootName; // 如果该模型的顶级根宿主已经被加载过,则不需要重复加载,直接更新变换 if (LoadedAssets_.contains(hostRootId)) { continue; } // 加载新模型,以顶级根宿主 hostRootId 进行注册存储 std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRootName << ")" << std::endl; MetaCoreFilamentDebugLog( "Load begin hostRootId=" + std::to_string(hostRootId) + " host=\"" + hostRootName + "\"" + " modelPath=\"" + modelPath + "\"" + " loadedAssets=" + std::to_string(LoadedAssets_.size()) ); auto buffer = ReadGltfFileResponsive(absolutePath); if (!buffer.has_value()) { std::cerr << "Failed to open file: " << absolutePath << std::endl; continue; } filament::gltfio::FilamentAsset* asset = AssetLoader_->createAsset( reinterpret_cast(buffer->data()), static_cast(buffer->size()) ); if (!asset) { std::cerr << "Failed to create asset!" << std::endl; continue; } MetaCoreFilamentDebugLog( "Load createAsset hostRootId=" + std::to_string(hostRootId) + " asset=" + MetaCoreDebugPointer(asset) + " entities=" + std::to_string(asset->getEntityCount()) ); // Use gltfio async loading so textures and buffers do not stall the first sync frame. if (!BeginAsyncResourceLoad(hostRootId, asset, absolutePath)) { std::cerr << "Warning: GLTF async resources could not start for: " << absolutePath << std::endl; } // 添加到场景 Scene_->addEntities(asset->getEntities(), asset->getEntityCount()); { const utils::Entity* entities = asset->getEntities(); size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { EntitiesInScene_.insert(entities[i]); } } LoadedAssets_[hostRootId] = asset; if (renderable.SourceModelAssetGuid.IsValid()) { HostRootSourceModelGuids_[hostRootId] = renderable.SourceModelAssetGuid; } // 复制材质实例防止共享干扰,放在加载好资源后 { auto& rm = Engine_->getRenderableManager(); std::vector& dupList = AssetDuplicatedMaterials_[hostRootId]; const utils::Entity* assetEntities = asset->getEntities(); size_t assetEntityCount = asset->getEntityCount(); for (size_t i = 0; i < assetEntityCount; ++i) { utils::Entity ent = assetEntities[i]; if (!ent) continue; auto rmInst = rm.getInstance(ent); if (rmInst) { size_t primCount = rm.getPrimitiveCount(rmInst); for (size_t primIndex = 0; primIndex < primCount; ++primIndex) { filament::MaterialInstance* originalMat = rm.getMaterialInstanceAt(rmInst, primIndex); if (originalMat) { filament::MaterialInstance* dupMat = filament::MaterialInstance::duplicate(originalMat); if (dupMat) { rm.setMaterialInstanceAt(rmInst, primIndex, dupMat); dupList.push_back(dupMat); } } } } } MetaCoreFilamentDebugLog( "Load duplicated materials hostRootId=" + std::to_string(hostRootId) + " count=" + std::to_string(dupList.size()) ); } if (scene != nullptr) { // 有 ECS 实例模式下同步层级结构并补挂 Tag auto hostRootObj = scene->FindGameObject(hostRootId); if (hostRootObj) { MetaCoreGameObject nonConstHostRoot = hostRootObj; // 现场补挂 Tag,确保后续帧和拖拽时 100% 命中 Tag 快速通道,绝不走 Fallback if (!nonConstHostRoot.HasComponent()) { nonConstHostRoot.AddComponent(MetaCoreModelRootTag{ modelPath }); } // 如果名字是默认的 "Cube",自动改为模型文件名 if (nonConstHostRoot.GetName() == "Cube") { std::string filename = std::filesystem::path(modelPath).filename().string(); nonConstHostRoot.GetName() = filename; } ParseModelHierarchyToEcs(*scene, nonConstHostRoot, asset); } } else { // 如果 scene 为 nullptr,代表纯快照渲染模式。我们需要直接在 ObjectToFilamentEntity_ 中建立子实体的映射。 // 顶级父 ID 对应 asset->getRoot() ObjectToFilamentEntity_[hostRootId] = { asset, asset->getRoot() }; // 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题 std::unordered_map, utils::Entity::Hasher> parentToChildren = BuildFullParentToChildrenMap(asset); // 2. 建立快照中的父子关系映射 std::unordered_map> snapParentToChildren; for (const auto& r : snapshot.Renderables) { if (r.HostRootId == hostRootId && r.ObjectId != hostRootId) { snapParentToChildren[r.ParentId].push_back(r); } } // 3. 递归 DFS 进行精确层级树结构匹配,穿透所有过渡节点 std::function matchRecursive = [&](MetaCoreId currentId, utils::Entity currentEntity) { ObjectToFilamentEntity_[currentId] = { asset, currentEntity }; // 穿透过渡节点获取当前实体的有效 Filament 子实体列表 std::vector fChildren; GetEffectiveFilamentChildren(currentEntity, asset, parentToChildren, fChildren); auto& sChildren = snapParentToChildren[currentId]; for (utils::Entity fChild : fChildren) { const char* nodeName = asset->getName(fChild); std::string name = nodeName ? nodeName : ""; MetaCoreId matchedChildId = 0; for (auto it = sChildren.begin(); it != sChildren.end(); ++it) { if (it->Name == name) { matchedChildId = it->ObjectId; sChildren.erase(it); break; } } // 兜底匹配 if (matchedChildId == 0 && name.empty() && !sChildren.empty()) { matchedChildId = sChildren.front().ObjectId; sChildren.erase(sChildren.begin()); } if (matchedChildId != 0) { matchRecursive(matchedChildId, fChild); } } }; // 从根开始执行递归层级树匹配 matchRecursive(hostRootId, asset->getRoot()); } // 创建中间 Pivot 实体用于坐标系转换 (glTF Y-up -> MetaCore Z-up) utils::Entity pivotEntity = utils::EntityManager::get().create(); auto& tm = Engine_->getTransformManager(); tm.create(pivotEntity); // 将资产根节点挂载到 Pivot 下 utils::Entity assetRoot = asset->getRoot(); tm.setParent(tm.getInstance(assetRoot), tm.getInstance(pivotEntity)); // 获取原始局部变换并叠加旋转补偿 filament::math::mat4f originalLocalTransform = tm.getTransform(tm.getInstance(assetRoot)); glm::mat4 originalMatrix; std::memcpy(glm::value_ptr(originalMatrix), &originalLocalTransform[0][0], sizeof(float) * 16); glm::mat4 compensationMatrix = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); glm::mat4 finalRootMatrix = compensationMatrix * originalMatrix; tm.setTransform(tm.getInstance(assetRoot), *reinterpret_cast(glm::value_ptr(finalRootMatrix))); // 【关键一步】:注册 hostRootId(顶级根宿主)的 ID 与 pivotEntity,彻底打通顶级宿主的 Gizmo 坐标变换! ObjectToFilamentEntity_[hostRootId] = { asset, pivotEntity }; Scene_->addEntity(pivotEntity); EntitiesInScene_.insert(pivotEntity); // 更新初始位置 glm::mat4 initLocalMat{1.0f}; auto itMat = snapshot.LocalMatrices.find(renderable.ObjectId); if (itMat != snapshot.LocalMatrices.end()) { initLocalMat = itMat->second; } glm::mat4 initWorldMat = initLocalMat; auto itWorldMat = snapshot.WorldMatrices.find(renderable.ObjectId); if (itWorldMat != snapshot.WorldMatrices.end()) { initWorldMat = itWorldMat->second; } UpdateTransform(renderable.ObjectId, initLocalMat, initWorldMat); } // 1. 构建反向查找映射,方便反查 entity 对应的 objectId std::unordered_map entityToObjectId; for (const auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { entityToObjectId[assetEntity.second] = objectId; } // 2. 控制所有已加载 glTF 资产内部所有实体(包括无直接映射的内部渲染实体)的可见性 auto& tm = Engine_->getTransformManager(); for (const auto& [hostRootId, asset] : LoadedAssets_) { if (!asset) continue; const utils::Entity* entities = asset->getEntities(); size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { utils::Entity entity = entities[i]; if (!entity) continue; bool shouldBeInScene = false; auto it = entityToObjectId.find(entity); if (it != entityToObjectId.end()) { // 若是直接映射节点,其可见性由快照决定 shouldBeInScene = isObjectVisible(it->second); } else { // 若是没有直接映射关系的内部渲染/辅助实体,上溯其在 Filament 层级树中的父节点链, // 找到最近的具有映射关系的祖先节点,并继承其可见性状态。 utils::Entity current = entity; utils::Entity mappedAncestor; while (current) { auto instance = tm.getInstance(current); if (!instance) break; utils::Entity parent = tm.getParent(instance); if (!parent) break; if (entityToObjectId.contains(parent)) { mappedAncestor = parent; break; } current = parent; } if (mappedAncestor) { shouldBeInScene = isObjectVisible(entityToObjectId[mappedAncestor]); entityToObjectId[entity] = entityToObjectId[mappedAncestor]; } else { // 回退保护:如果没有找到,则与该资产对应的顶级根宿主保持状态一致 shouldBeInScene = isObjectVisible(hostRootId); entityToObjectId[entity] = hostRootId; } } // 同步可见性状态到 Filament Scene if (shouldBeInScene) { if (Scene_ && !EntitiesInScene_.contains(entity)) { Scene_->addEntity(entity); EntitiesInScene_.insert(entity); } } else { if (Scene_ && EntitiesInScene_.contains(entity)) { Scene_->remove(entity); EntitiesInScene_.erase(entity); } } } } EntityToObjectId_ = std::move(entityToObjectId); // 3. 控制 ObjectToFilamentEntity_ 中所有非资产实体(如 pivotEntity 辅助实体)的可见性 for (const auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { utils::Entity entity = assetEntity.second; if (!entity) continue; // 检查该实体是否为资产内部实体,如果不是(例如 pivotEntity),则直接控制可见性 bool isAssetEntity = false; if (assetEntity.first) { const utils::Entity* entities = assetEntity.first->getEntities(); size_t entityCount = assetEntity.first->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { if (entities[i] == entity) { isAssetEntity = true; break; } } } if (!isAssetEntity) { bool shouldBeInScene = isObjectVisible(objectId); if (shouldBeInScene) { if (Scene_ && !EntitiesInScene_.contains(entity)) { Scene_->addEntity(entity); EntitiesInScene_.insert(entity); } } else { if (Scene_ && EntitiesInScene_.contains(entity)) { Scene_->remove(entity); EntitiesInScene_.erase(entity); } } } } // 4. 同步所有存活实体的本地和全局变换矩阵 for (const auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { auto itWorld = snapshot.WorldMatrices.find(objectId); if (itWorld != snapshot.WorldMatrices.end()) { auto itLocal = snapshot.LocalMatrices.find(objectId); if (itLocal != snapshot.LocalMatrices.end()) { UpdateTransform(objectId, itLocal->second, itWorld->second); } } } const auto setFloatParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, float value) -> bool { const filament::Material* material = materialInstance.getMaterial(); if (material != nullptr && material->hasParameter(name)) { materialInstance.setParameter(name, value); return true; } return false; }; const auto setFloat3ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float3 value) -> bool { const filament::Material* material = materialInstance.getMaterial(); if (material != nullptr && material->hasParameter(name)) { materialInstance.setParameter(name, value); return true; } return false; }; const auto setFloat4ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float4 value) -> bool { const filament::Material* material = materialInstance.getMaterial(); if (material != nullptr && material->hasParameter(name)) { materialInstance.setParameter(name, value); return true; } return false; }; const auto setTextureParameterIfPresent = []( filament::MaterialInstance& materialInstance, const char* name, filament::Texture* texture, const filament::TextureSampler& sampler ) -> bool { if (texture == nullptr) { return false; } const filament::Material* material = materialInstance.getMaterial(); if (material == nullptr || !material->hasParameter(name)) { return false; } materialInstance.setParameter(name, texture, sampler); return true; }; // 5. 同步所有存活网格的材质预览属性。MaterialInstance 参数必须在渲染前更新。 auto& rm = Engine_->getRenderableManager(); std::unordered_map assetChildMapCache; std::unordered_map, utils::Entity::Hasher> materialTargetCache; const filament::TextureSampler materialSampler( filament::TextureSampler::MinFilter::LINEAR, filament::TextureSampler::MagFilter::LINEAR, filament::TextureSampler::WrapMode::REPEAT ); for (const auto& renderable : snapshot.Renderables) { auto itEntity = ObjectToFilamentEntity_.find(renderable.ObjectId); if (itEntity == ObjectToFilamentEntity_.end()) { if (materialDebugLogging) { const std::string signature = "missing|" + std::to_string(snapshot.SceneRevision); if (LastMaterialSyncDebugByObject_[renderable.ObjectId] != signature) { LastMaterialSyncDebugByObject_[renderable.ObjectId] = signature; MetaCoreFilamentMaterialDebugLog( "Material sync skipped mapping missing objectId=" + std::to_string(renderable.ObjectId) + " name=\"" + renderable.Name + "\"" + " hostRootId=" + std::to_string(renderable.HostRootId) + " materialGuids=" + MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) ); } } continue; } utils::Entity materialRootEntity = itEntity->second.second; const utils::Entity originalMappedEntity = materialRootEntity; const bool originalMappedEntityBelongsToAsset = itEntity->second.first != nullptr && EntityBelongsToAsset(itEntity->second.first, originalMappedEntity); if (itEntity->second.first != nullptr && renderable.ObjectId == renderable.HostRootId && !originalMappedEntityBelongsToAsset) { materialRootEntity = itEntity->second.first->getRoot(); } const auto materialTargetCacheIterator = materialTargetCache.find(materialRootEntity); if (materialTargetCacheIterator == materialTargetCache.end()) { std::vector materialTargetEntities; if (itEntity->second.first != nullptr) { auto childMapIterator = assetChildMapCache.find(itEntity->second.first); if (childMapIterator == assetChildMapCache.end()) { childMapIterator = assetChildMapCache.emplace( itEntity->second.first, BuildDirectParentToChildrenMap(itEntity->second.first) ).first; } CollectRenderableDescendantsFromChildMap( materialRootEntity, childMapIterator->second, materialTargetEntities ); if (materialTargetEntities.empty() && renderable.ObjectId == renderable.HostRootId) { CollectAllRenderableAssetEntities(itEntity->second.first, materialTargetEntities); if (materialDebugLogging && !materialTargetEntities.empty()) { MetaCoreFilamentMaterialDebugLog( "Material sync fallback all asset renderables objectId=" + std::to_string(renderable.ObjectId) + " hostRootId=" + std::to_string(renderable.HostRootId) + " count=" + std::to_string(materialTargetEntities.size()) ); } } } else { CollectRenderableDescendants(itEntity->second.first, materialRootEntity, materialTargetEntities); } materialTargetCache.emplace(materialRootEntity, std::move(materialTargetEntities)); } const std::vector& materialTargetEntities = materialTargetCache.find(materialRootEntity)->second; if (materialTargetEntities.empty()) { if (materialDebugLogging) { std::ostringstream signatureStream; signatureStream << "empty|" << snapshot.SceneRevision << "|" << renderable.ObjectId << "|" << materialRootEntity.getId() << "|" << MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) << "|" << MetaCoreDebugVec3String(renderable.BaseColor) << "|" << renderable.Metallic << "|" << renderable.Roughness; const std::string signature = signatureStream.str(); if (LastMaterialSyncDebugByObject_[renderable.ObjectId] != signature) { LastMaterialSyncDebugByObject_[renderable.ObjectId] = signature; MetaCoreFilamentMaterialDebugLog( "Material sync target empty objectId=" + std::to_string(renderable.ObjectId) + " name=\"" + renderable.Name + "\"" + " hostRootId=" + std::to_string(renderable.HostRootId) + " mappedEntity=" + std::to_string(originalMappedEntity.getId()) + " mappedBelongsToAsset=" + (originalMappedEntityBelongsToAsset ? std::string("true") : std::string("false")) + " materialRootEntity=" + std::to_string(materialRootEntity.getId()) + " materialGuids=" + MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) + " baseColor=" + MetaCoreDebugVec3String(renderable.BaseColor) ); } } continue; } filament::Texture* baseColorTexture = LoadMaterialTexture( renderable.BaseColorTexturePath, renderable.SourceModelPath, true ); filament::Texture* metallicRoughnessTexture = LoadMaterialTexture( renderable.MetallicRoughnessTexturePath, renderable.SourceModelPath, false ); filament::Texture* normalTexture = LoadMaterialTexture( renderable.NormalTexturePath, renderable.SourceModelPath, false ); filament::Texture* emissiveTexture = LoadMaterialTexture( renderable.EmissiveTexturePath, renderable.SourceModelPath, true ); filament::Texture* aoTexture = LoadMaterialTexture( renderable.AoTexturePath, renderable.SourceModelPath, false ); std::size_t primitiveCount = 0; std::size_t materialInstanceCount = 0; std::size_t baseColorWrites = 0; std::size_t metallicWrites = 0; std::size_t roughnessWrites = 0; std::size_t emissiveWrites = 0; std::size_t alphaCutoffWrites = 0; std::size_t textureWrites = 0; for (utils::Entity materialTargetEntity : materialTargetEntities) { const auto instance = rm.getInstance(materialTargetEntity); if (!instance) { continue; } // 遍历所有 primitive,获取材质实例,并设置材质预览属性。 const size_t primCount = rm.getPrimitiveCount(instance); for (size_t primIndex = 0; primIndex < primCount; ++primIndex) { ++primitiveCount; filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex); if (matInst) { ++materialInstanceCount; filament::math::float4 baseColorVec{ renderable.BaseColor.x, renderable.BaseColor.y, renderable.BaseColor.z, 1.0f }; if (setFloat4ParameterIfPresent(*matInst, "baseColorFactor", baseColorVec)) { ++baseColorWrites; } if (setFloatParameterIfPresent(*matInst, "metallicFactor", renderable.Metallic)) { ++metallicWrites; } if (setFloatParameterIfPresent(*matInst, "roughnessFactor", renderable.Roughness)) { ++roughnessWrites; } if (setFloat3ParameterIfPresent( *matInst, "emissiveFactor", filament::math::float3{ renderable.EmissiveColor.x, renderable.EmissiveColor.y, renderable.EmissiveColor.z } )) { ++emissiveWrites; } if (renderable.AlphaMode == MetaCoreMeshAlphaMode::Mask || renderable.AlphaMode == MetaCoreMeshAlphaMode::Blend) { if (setFloatParameterIfPresent(*matInst, "alphaCutoff", renderable.AlphaCutoff)) { ++alphaCutoffWrites; } } if (setTextureParameterIfPresent(*matInst, "baseColorMap", baseColorTexture, materialSampler)) { ++textureWrites; } if (setTextureParameterIfPresent(*matInst, "metallicRoughnessMap", metallicRoughnessTexture, materialSampler)) { ++textureWrites; } if (setTextureParameterIfPresent(*matInst, "normalMap", normalTexture, materialSampler)) { ++textureWrites; } if (setTextureParameterIfPresent(*matInst, "emissiveMap", emissiveTexture, materialSampler)) { ++textureWrites; } if (!setTextureParameterIfPresent(*matInst, "occlusionMap", aoTexture, materialSampler)) { if (!setTextureParameterIfPresent(*matInst, "aoMap", aoTexture, materialSampler)) { if (setTextureParameterIfPresent(*matInst, "ambientOcclusionMap", aoTexture, materialSampler)) { ++textureWrites; } } else { ++textureWrites; } } else { ++textureWrites; } } } } if (materialDebugLogging) { std::ostringstream signatureStream; signatureStream << "sync|" << snapshot.SceneRevision << "|" << renderable.ObjectId << "|" << renderable.HostRootId << "|" << materialRootEntity.getId() << "|" << MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) << "|" << MetaCoreDebugVec3String(renderable.BaseColor) << "|" << renderable.Metallic << "|" << renderable.Roughness << "|" << materialTargetEntities.size() << "|" << primitiveCount << "|" << materialInstanceCount << "|" << baseColorWrites << "|" << metallicWrites << "|" << roughnessWrites << "|" << textureWrites; const std::string signature = signatureStream.str(); if (LastMaterialSyncDebugByObject_[renderable.ObjectId] != signature) { LastMaterialSyncDebugByObject_[renderable.ObjectId] = signature; std::ostringstream stream; stream << "Material sync objectId=" << renderable.ObjectId << " name=\"" << renderable.Name << "\"" << " hostRootId=" << renderable.HostRootId << " mappedEntity=" << originalMappedEntity.getId() << " mappedBelongsToAsset=" << (originalMappedEntityBelongsToAsset ? "true" : "false") << " materialRootEntity=" << materialRootEntity.getId() << " targets=" << materialTargetEntities.size() << " primitives=" << primitiveCount << " matInstances=" << materialInstanceCount << " writes={baseColor:" << baseColorWrites << ", metallic:" << metallicWrites << ", roughness:" << roughnessWrites << ", emissive:" << emissiveWrites << ", alphaCutoff:" << alphaCutoffWrites << ", textures:" << textureWrites << "}" << " materialGuids=" << MetaCoreDebugGuidListString(renderable.MaterialAssetGuids) << " baseColor=" << MetaCoreDebugVec3String(renderable.BaseColor) << " metallic=" << renderable.Metallic << " roughness=" << renderable.Roughness << " textures={baseColor:\"" << renderable.BaseColorTexturePath << "\", mr:\"" << renderable.MetallicRoughnessTexturePath << "\", normal:\"" << renderable.NormalTexturePath << "\"}"; MetaCoreFilamentMaterialDebugLog(stream.str()); } } } ApplyRenderDebugView(snapshot, renderableMap); ApplyAnimationStates(snapshot); RenderingStatistics_.CpuFrameMilliseconds = std::chrono::duration(std::chrono::steady_clock::now() - syncStart).count(); } void ApplyAnimationStates(const MetaCoreSceneRenderSyncSnapshot& snapshot) { for (const MetaCoreRenderSyncAnimationState& animationState : snapshot.Animations) { if (animationState.HostRootId == 0) { continue; } const auto assetIterator = LoadedAssets_.find(animationState.HostRootId); if (assetIterator == LoadedAssets_.end() || assetIterator->second == nullptr) { continue; } filament::gltfio::FilamentAsset* asset = assetIterator->second; filament::gltfio::FilamentInstance* instance = asset->getInstance(); if (instance == nullptr) { continue; } filament::gltfio::Animator* animator = instance->getAnimator(); if (animator == nullptr) { continue; } const std::size_t animationCount = animator->getAnimationCount(); const bool validClip = animationState.ClipIndex >= 0 && static_cast(animationState.ClipIndex) < animationCount; if (!animationState.Enabled || !validClip) { animator->resetBoneMatrices(); continue; } const std::size_t clipIndex = static_cast(animationState.ClipIndex); const float durationSeconds = animator->getAnimationDuration(clipIndex); float sampleTimeSeconds = std::isfinite(animationState.TimeSeconds) ? std::max(0.0F, animationState.TimeSeconds) : 0.0F; if (durationSeconds > 0.0F) { sampleTimeSeconds = animationState.Loop ? std::fmod(sampleTimeSeconds, durationSeconds) : std::min(sampleTimeSeconds, durationSeconds); } animator->applyAnimation(clipIndex, sampleTimeSeconds); animator->updateBoneMatrices(); } } void ApplySceneView(const MetaCoreSceneView& sceneView) { if (!Camera_ || !View_) return; CurrentSceneView_ = sceneView; UpdateEditorGridTransform(sceneView); UpdateEnvironmentBackgroundVisibility(); // 1. 视图矩阵同步 const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp); CameraModelMatrix_ = glm::inverse(viewMatrix); Camera_->setModelMatrix(*reinterpret_cast(glm::value_ptr(CameraModelMatrix_))); // 2. 投影矩阵:回归 Filament 原生设置 const auto& viewport = View_->getViewport(); const float aspect = (viewport.height > 0) ? (static_cast(viewport.width) / static_cast(viewport.height)) : 1.0f; const double nearClip = std::max(0.0001, static_cast(sceneView.NearClip)); const double farClip = std::max(nearClip + 0.0001, static_cast(sceneView.FarClip)); if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) { const double halfHeight = std::max(0.0001, static_cast(sceneView.OrthographicSize)); const double halfWidth = halfHeight * std::max(0.0001, static_cast(aspect)); Camera_->setProjection( filament::Camera::Projection::ORTHO, -halfWidth, halfWidth, -halfHeight, halfHeight, nearClip, farClip ); } else { Camera_->setProjection( std::clamp(static_cast(sceneView.VerticalFieldOfViewDegrees), 1.0, 179.0), std::max(0.0001, static_cast(aspect)), nearClip, farClip, filament::Camera::Fov::VERTICAL ); } } [[nodiscard]] filament::Renderer::ClearOptions BuildSceneClearOptions() const { filament::Renderer::ClearOptions options; options.clearColor = { CurrentSceneView_.BackgroundColor.x, CurrentSceneView_.BackgroundColor.y, CurrentSceneView_.BackgroundColor.z, CurrentSceneView_.BackgroundAlpha }; options.clear = CurrentSceneView_.ClearFlags != MetaCoreCameraClearFlags::DontClear; return options; } void RenderAll() { if (!Renderer_ || !SwapChain_ || !View_) { return; } ++RenderFrameIndex_; UpdateAutoExposure(); if (!PendingRenderTargetResources_.empty()) { DebugRenderTargetState("RenderAll before beginFrame with pending destroy"); } // ImGui draw data -> Filament renderable 的同步会修改 Renderable、VertexBuffer、 // MaterialInstance 参数和 descriptor 绑定。必须在 beginFrame/render pass 之前完成, // 否则 Filament 会警告 descriptor set 在 begin/endRenderPass 中被修改,并可能在 // 后续句柄校验中触发 FTexture use-after-free/Postcondition 崩溃。 const bool hasUiPass = UIView_ != nullptr && ImGuiHelper_ != nullptr; if (hasUiPass) { ImGuiIO& io = ImGui::GetIO(); ImGuiHelper_->setDisplaySize(io.DisplaySize.x, io.DisplaySize.y); UIView_->setViewport({0, 0, static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y)}); ImGuiHelper_->processImGuiCommands(ImGui::GetDrawData(), io); } if (Renderer_->beginFrame(SwapChain_)) { const bool renderEditorGrid = GridView_ != nullptr && (EditorGridEntityInScene_ || EditorGridAxisEntityInScene_); const auto renderScenePass = [&](bool outputToRenderTarget) { filament::Renderer::ClearOptions options = BuildSceneClearOptions(); Renderer_->setClearOptions(options); Renderer_->render(View_); if (renderEditorGrid) { options.clear = false; Renderer_->setClearOptions(options); Renderer_->render(GridView_); } (void)outputToRenderTarget; }; if (RenderTarget_) { // 1. 渲染 3D 离屏视口 (输出到 RenderTarget) renderScenePass(true); if (hasUiPass) { // 2. 准备并渲染 UI 视口 (输出到 SwapChain) filament::Renderer::ClearOptions options; options.clearColor = { 0.11f, 0.12f, 0.14f, 1.0f }; // 灰色 options.clear = true; Renderer_->setClearOptions(options); Renderer_->render(UIView_); } } else { // 3. 直接上屏渲染 renderScenePass(false); if (RuntimeOverlayRenderCallback_) { const auto viewport = View_->getViewport(); RuntimeOverlayRenderCallback_(*Engine_, *Renderer_, viewport.width, viewport.height); } } Renderer_->endFrame(); const auto history = Renderer_->getFrameInfoHistory(1U); if (!history.empty() && history.back().gpuFrameDuration >= 0) RenderingStatistics_.GpuFrameMilliseconds = static_cast(history.back().gpuFrameDuration) / 1000000.0F; DestroyDeferredRenderTargetResources(true); } else if (!PendingRenderTargetResources_.empty()) { DebugRenderTargetState("RenderAll beginFrame failed with pending destroy"); } } bool RequestPick(std::uint32_t x, std::uint32_t y, MetaCoreFilamentSceneBridge::PickCallback callback) { if (View_ == nullptr || Camera_ == nullptr || !callback) return false; const auto viewport = View_->getViewport(); if (x >= viewport.width || y >= viewport.height) return false; View_->setTransparentPickingEnabled(true); const std::uint64_t callbackId = NextPickCallbackId_++; PickCallbacks_.emplace(callbackId, PendingPick{ std::move(callback), Camera_->getProjectionMatrix(), Camera_->getModelMatrix(), viewport.width, viewport.height }); View_->pick(x, y, [this, callbackId](const filament::View::PickingQueryResult& result) { const auto callbackIterator = PickCallbacks_.find(callbackId); if (callbackIterator == PickCallbacks_.end()) return; auto pending = std::move(callbackIterator->second); PickCallbacks_.erase(callbackIterator); MetaCoreFilamentSceneBridge::PickResult converted; const auto iterator = EntityToObjectId_.find(result.renderable); if (iterator != EntityToObjectId_.end()) converted.ObjectId = iterator->second; converted.Depth = result.depth; converted.FragmentCoordinates = {result.fragCoords.x, result.fragCoords.y, result.fragCoords.z}; const filament::math::double4 clip{ (static_cast(result.fragCoords.x) / static_cast(pending.ViewportWidth)) * 2.0 - 1.0, (static_cast(result.fragCoords.y) / static_cast(pending.ViewportHeight)) * 2.0 - 1.0, static_cast(result.fragCoords.z) * 2.0 - 1.0, 1.0 }; filament::math::double4 viewPosition = inverse(pending.Projection) * clip; if (std::abs(viewPosition.w) > std::numeric_limits::epsilon()) viewPosition /= viewPosition.w; filament::math::double4 worldPosition = pending.Model * viewPosition; if (std::abs(worldPosition.w) > std::numeric_limits::epsilon()) worldPosition /= worldPosition.w; converted.WorldPosition = { static_cast(worldPosition.x), static_cast(worldPosition.y), static_cast(worldPosition.z) }; pending.Callback(converted); }); return true; } uint32_t GetGLTextureId() const { return GLTextureId_; } void Resize(int width, int height) { if (width <= 0 || height <= 0) return; if (!RenderTarget_) { MetaCoreFilamentDebugLog( "Resize without render target width=" + std::to_string(width) + " height=" + std::to_string(height) ); View_->setViewport({0, 0, static_cast(width), static_cast(height)}); if (EnvironmentView_ != nullptr) { EnvironmentView_->setViewport({0, 0, static_cast(width), static_cast(height)}); } if (GridView_ != nullptr) { GridView_->setViewport({0, 0, static_cast(width), static_cast(height)}); } return; } // 【关键修复】:如果大小没有改变,不要重新创建纹理! // 否则会导致上一帧传给 ImGui 的纹理指针在这一帧被提前销毁,引发 Use-after-free 崩溃。 if (FilamentTexture_ && FilamentTexture_->getWidth() == static_cast(width) && FilamentTexture_->getHeight() == static_cast(height)) { return; } MetaCoreFilamentDebugLog( "Resize recreate begin width=" + std::to_string(width) + " height=" + std::to_string(height) + " oldWidth=" + (FilamentTexture_ != nullptr ? std::to_string(FilamentTexture_->getWidth()) : std::string("null")) + " oldHeight=" + (FilamentTexture_ != nullptr ? std::to_string(FilamentTexture_->getHeight()) : std::string("null")) ); DebugRenderTargetState("Resize before queue current resources"); QueueCurrentRenderTargetResourcesForDestroy(); DestroyDeferredRenderTargetResources(true); FilamentTexture_ = filament::Texture::Builder() .width(static_cast(width)) .height(static_cast(height)) .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) .format(filament::Texture::InternalFormat::RGBA8) .build(*Engine_); DepthTexture_ = filament::Texture::Builder() .width(static_cast(width)) .height(static_cast(height)) .usage(filament::Texture::Usage::DEPTH_ATTACHMENT) .format(filament::Texture::InternalFormat::DEPTH24) .build(*Engine_); RenderTarget_ = filament::RenderTarget::Builder() .texture(filament::RenderTarget::AttachmentPoint::COLOR, FilamentTexture_) .texture(filament::RenderTarget::AttachmentPoint::DEPTH, DepthTexture_) .build(*Engine_); ++RenderTargetGeneration_; View_->setRenderTarget(RenderTarget_); View_->setViewport({0, 0, static_cast(width), static_cast(height)}); if (EnvironmentView_ != nullptr) { EnvironmentView_->setRenderTarget(RenderTarget_); EnvironmentView_->setViewport({0, 0, static_cast(width), static_cast(height)}); } if (GridView_ != nullptr) { GridView_->setRenderTarget(RenderTarget_); GridView_->setViewport({0, 0, static_cast(width), static_cast(height)}); } ConfigureSceneViewRenderState(); ConfigureEnvironmentViewRenderState(); ConfigureGridViewRenderState(); DebugRenderTargetState("Resize after recreate resources"); } void* GetFilamentTexturePointer() const { return FilamentTexture_; } void QueueCurrentRenderTargetResourcesForDestroy() { if (RenderTarget_ == nullptr && FilamentTexture_ == nullptr && DepthTexture_ == nullptr) { return; } PendingRenderTargetResources_.push_back(PendingRenderTargetResource{ RenderTarget_, FilamentTexture_, DepthTexture_, RenderTargetGeneration_ }); DebugRenderTargetState("Queue current render target resources for destroy"); RenderTarget_ = nullptr; FilamentTexture_ = nullptr; DepthTexture_ = nullptr; } void DestroyDeferredRenderTargetResources(bool waitForBackend) { if (PendingRenderTargetResources_.empty() || Engine_ == nullptr) { return; } if (waitForBackend) { DebugRenderTargetState("Destroy deferred resources flush begin"); Engine_->flushAndWait(); DebugRenderTargetState("Destroy deferred resources flush end"); } for (PendingRenderTargetResource& resource : PendingRenderTargetResources_) { std::ostringstream stream; stream << "Destroy deferred resource generation=" << resource.Generation << " renderTarget=" << MetaCoreDebugPointer(resource.RenderTarget) << " colorTexture=" << MetaCoreDebugPointer(resource.ColorTexture) << " depthTexture=" << MetaCoreDebugPointer(resource.DepthTexture); MetaCoreFilamentDebugLog(stream.str()); if (resource.RenderTarget != nullptr) { Engine_->destroy(resource.RenderTarget); } if (resource.ColorTexture != nullptr) { Engine_->destroy(resource.ColorTexture); } if (resource.DepthTexture != nullptr) { Engine_->destroy(resource.DepthTexture); } } PendingRenderTargetResources_.clear(); DebugRenderTargetState("Destroy deferred resources end"); } bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const { const auto worldIt = ObjectWorldMatrices_.find(objectId); if (worldIt == ObjectWorldMatrices_.end()) { return false; } worldMatrix = worldIt->second; return true; } bool HasRuntimeSyncFailure() const { return false; } const std::string& GetLastRuntimeSyncFailure() const { return EmptyString_; } MetaCoreVisibilityStatistics GetRenderingStatistics() const { return RenderingStatistics_; } bool VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const { auto it = ObjectToFilamentEntity_.find(objectId); if (it == ObjectToFilamentEntity_.end()) return false; utils::Entity entity = it->second.second; auto& tm = Engine_->getTransformManager(); auto instance = tm.getInstance(entity); if (!instance) return false; filament::math::mat4f currentMat = tm.getTransform(instance); glm::mat4 actualMatrix; std::memcpy(glm::value_ptr(actualMatrix), ¤tMat[0][0], sizeof(float) * 16); glm::mat4 targetMatrix = expectedMatrix; if (LoadedAssets_.count(objectId) == 0) { glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); targetMatrix = R_plus90X * targetMatrix * R_minus90X; } // 浮点数比对,精度 0.0001 for (int c = 0; c < 4; ++c) { for (int r = 0; r < 4; ++r) { if (std::abs(actualMatrix[c][r] - targetMatrix[c][r]) > 0.0001f) { return false; } } } return true; } bool VerifyLightExistsForTesting(MetaCoreId objectId) const { return SceneLightEntities_.contains(objectId); } float GetDefaultLightIntensityForTesting() const { if (!Engine_) return 0.0F; auto& lightManager = Engine_->getLightManager(); const auto defaultLightInstance = lightManager.getInstance(Light_); if (defaultLightInstance) { return lightManager.getIntensity(defaultLightInstance); } return 0.0F; } bool VerifyEntityInSceneForTesting(MetaCoreId objectId) const { auto it = ObjectToFilamentEntity_.find(objectId); if (it == ObjectToFilamentEntity_.end()) { std::cout << "[DEBUG Verify] Cannot find objectId " << objectId << " in ObjectToFilamentEntity_" << std::endl; std::cout << "[DEBUG Verify] ObjectToFilamentEntity_ size: " << ObjectToFilamentEntity_.size() << std::endl; for (const auto& [oid, ent] : ObjectToFilamentEntity_) { std::cout << " - OID: " << oid << ", entity: " << ent.second.getId() << std::endl; } return false; } bool inScene = EntitiesInScene_.contains(it->second.second); if (!inScene) { std::cout << "[DEBUG Verify] Object " << objectId << " maps to entity " << it->second.second.getId() << ", but this entity is NOT in EntitiesInScene_" << std::endl; std::cout << "[DEBUG Verify] EntitiesInScene_ contains: "; for (auto ent : EntitiesInScene_) { std::cout << ent.getId() << " "; } std::cout << std::endl; } return inScene; } private: struct MetaCoreEditorGridVertex { filament::math::float3 Position{}; std::array Color{255, 255, 255, 255}; }; struct MetaCoreEditorGridPrimitiveRange { std::uint32_t Offset = 0; std::uint32_t Count = 0; }; static constexpr int EditorGridExtent_ = 500; static constexpr int EditorGridMajorStep_ = 10; static constexpr int EditorGridAxisExtent_ = 100000; [[nodiscard]] float ResolveEditorGridSpacing(const MetaCoreSceneView& sceneView) const { float viewScale = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget); if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) { viewScale = std::max(viewScale, sceneView.OrthographicSize * 2.0F); } const float desiredSpacing = std::max(1.0F, viewScale / 12.0F); const float magnitude = std::pow(10.0F, std::floor(std::log10(desiredSpacing))); const float normalizedSpacing = desiredSpacing / magnitude; if (normalizedSpacing <= 1.0F) { return magnitude; } if (normalizedSpacing <= 2.0F) { return 2.0F * magnitude; } if (normalizedSpacing <= 5.0F) { return 5.0F * magnitude; } return 10.0F * magnitude; } void UpdateEditorGridTransform(const MetaCoreSceneView& sceneView) { if (!EditorGridBuilt_ || Engine_ == nullptr || !EditorGridEntity_) { return; } const float spacing = ResolveEditorGridSpacing(sceneView); const float majorSpacing = spacing * static_cast(EditorGridMajorStep_); const glm::vec3 snappedCenter{ std::round(sceneView.CameraTarget.x / majorSpacing) * majorSpacing, std::round(sceneView.CameraTarget.y / majorSpacing) * majorSpacing, 0.0F }; const glm::mat4 gridTransform = glm::scale( glm::translate(glm::mat4(1.0F), snappedCenter), glm::vec3(spacing, spacing, 1.0F) ); auto& transformManager = Engine_->getTransformManager(); const auto transformInstance = transformManager.getInstance(EditorGridEntity_); if (transformInstance) { transformManager.setTransform( transformInstance, *reinterpret_cast(glm::value_ptr(gridTransform)) ); } } struct ProductionRenderableResource { utils::Entity Entity{}; filament::VertexBuffer* Vertices = nullptr; filament::IndexBuffer* Indices = nullptr; filament::MaterialInstance* MaterialInstance = nullptr; std::size_t VertexCapacity = 0U; std::size_t IndexCapacity = 0U; }; struct DebugMaterialOverride { utils::Entity Entity{}; std::size_t Primitive = 0U; filament::MaterialInstance* Original = nullptr; filament::MaterialInstance* Debug = nullptr; }; [[nodiscard]] filament::Material* EnsureRenderDebugMaterial() { if(RenderDebugMaterial_!=nullptr)return RenderDebugMaterial_; const std::array candidates{ "render_debug.filamat","Engine/Materials/render_debug.filamat","../render_debug.filamat","build/linux-development/render_debug.filamat"}; for(const auto& path:candidates){std::ifstream input(path,std::ios::binary);if(!input)continue;std::vector bytes((std::istreambuf_iterator(input)),{});if(bytes.empty())continue;RenderDebugMaterial_=filament::Material::Builder().package(bytes.data(),bytes.size()).build(*Engine_);if(RenderDebugMaterial_)break;} return RenderDebugMaterial_; } void RestoreDebugMaterials() { if(Engine_==nullptr){DebugMaterialOverrides_.clear();return;}auto& rm=Engine_->getRenderableManager(); for(auto& value:DebugMaterialOverrides_){const auto instance=rm.getInstance(value.Entity);if(instance&&value.Primitivedestroy(value.Debug);} DebugMaterialOverrides_.clear(); } void ApplyRenderDebugView(const MetaCoreSceneRenderSyncSnapshot& snapshot, const std::unordered_map& renderables) { RestoreDebugMaterials();if(ActiveDebugView_==MetaCoreRenderDebugView::Lit||Engine_==nullptr)return;const filament::Material* material=EnsureRenderDebugMaterial();if(material==nullptr)return; std::vector probes;for(const auto& probe:snapshot.ReflectionProbes)probes.push_back({probe.ObjectId,glm::vec3(probe.WorldMatrix[3]),probe.Component}); auto& rm=Engine_->getRenderableManager();for(const auto& [entity,objectId]:EntityToObjectId_){const auto instance=rm.getInstance(entity);const auto found=renderables.find(objectId);if(!instance||found==renderables.end())continue;const auto& renderable=*found->second; glm::vec4 value{renderable.BaseColor,1.0F};int mode=0; switch(ActiveDebugView_){case MetaCoreRenderDebugView::WorldNormal:mode=1;break;case MetaCoreRenderDebugView::Roughness:value=glm::vec4(renderable.Roughness,renderable.Roughness,renderable.Roughness,1);break;case MetaCoreRenderDebugView::Metallic:value=glm::vec4(renderable.Metallic,renderable.Metallic,renderable.Metallic,1);break;case MetaCoreRenderDebugView::Lod:{const int level=LodSelections_.contains(objectId)?LodSelections_.at(objectId):0;const std::array colors{{{0.2F,0.9F,0.3F},{0.95F,0.8F,0.15F},{1.0F,0.35F,0.1F},{0.65F,0.2F,0.9F}}};value=glm::vec4(colors[static_cast(std::max(0,level))%colors.size()],1);break;}case MetaCoreRenderDebugView::Occlusion:value={0.1F,0.85F,0.25F,1};break;case MetaCoreRenderDebugView::ReflectionProbe:{const auto selected=MetaCoreSelectReflectionProbes(probes,renderable.WorldBounds.Center());value=selected.empty()?glm::vec4(0.1F,0.1F,0.1F,1):glm::vec4(selected.size()>1U?0.2F:0.1F,selected.front().Weight,1.0F-selected.front().Weight,1);break;}case MetaCoreRenderDebugView::ShadowCascades:{const float normalized=glm::distance(CurrentSceneView_.CameraPosition,renderable.WorldBounds.Center())/std::max(1.0F,RenderSettings_.DirectionalShadowDistance);value=normalized<0.08F?glm::vec4(1,0.2F,0.2F,1):(normalized<0.22F?glm::vec4(0.2F,1,0.2F,1):(normalized<0.48F?glm::vec4(0.2F,0.4F,1,1):glm::vec4(1,0.8F,0.2F,1)));break;}case MetaCoreRenderDebugView::Overdraw:value={1.0F,0.08F,0.02F,1};break;case MetaCoreRenderDebugView::Wireframe:value={0.05F,0.9F,1.0F,1};break;case MetaCoreRenderDebugView::Unlit:case MetaCoreRenderDebugView::Albedo:default:break;} for(std::size_t primitive=0;primitivecreateInstance();if(!original||!debug)continue;debug->setParameter("debugMode",mode);debug->setParameter("debugValue",filament::math::float4{value.x,value.y,value.z,value.w});rm.setMaterialInstanceAt(instance,primitive,debug);DebugMaterialOverrides_.push_back({entity,primitive,original,debug});} } } [[nodiscard]] static std::uint64_t ProductionRenderableKey(MetaCoreId objectId, std::uint8_t kind) { return MetaCoreHashCombine(static_cast(objectId), kind); } [[nodiscard]] filament::Material* EnsureProductionMaterial() { if (ProductionMaterial_ != nullptr) return ProductionMaterial_; ProductionMaterial_ = LoadEditorGridMaterial(); return ProductionMaterial_; } void DestroyProductionRenderableResource(ProductionRenderableResource& resource) { if (Engine_ == nullptr) return; // Filament requires the Renderable component to release all of its resource // references before its material instance or geometry buffers are destroyed. // Particle, line, trail, decal, and terrain renderables all pass through this // path, including the particle-capacity growth path that rebuilds the mesh. if (!resource.Entity.isNull()) { if (Scene_ != nullptr && EntitiesInScene_.erase(resource.Entity) != 0U) { Scene_->remove(resource.Entity); } auto& renderableManager = Engine_->getRenderableManager(); if (renderableManager.getInstance(resource.Entity)) { renderableManager.destroy(resource.Entity); } Engine_->destroy(resource.Entity); utils::EntityManager::get().destroy(resource.Entity); resource.Entity = {}; } if (resource.MaterialInstance != nullptr) { Engine_->destroy(resource.MaterialInstance); resource.MaterialInstance = nullptr; } if (resource.Vertices != nullptr) { Engine_->destroy(resource.Vertices); resource.Vertices = nullptr; } if (resource.Indices != nullptr) { Engine_->destroy(resource.Indices); resource.Indices = nullptr; } } void DestroyProductionRenderable(std::uint64_t key) { const auto found = ProductionRenderables_.find(key); if (found == ProductionRenderables_.end() || Engine_ == nullptr) return; DestroyProductionRenderableResource(found->second); ProductionRenderables_.erase(found); } void DestroyProductionRenderables() { while (!ProductionRenderables_.empty()) DestroyProductionRenderable(ProductionRenderables_.begin()->first); if (Engine_ != nullptr && ProductionMaterial_ != nullptr) { Engine_->destroy(ProductionMaterial_); ProductionMaterial_ = nullptr; } ParticleSimulations_.clear(); ParticleSimulationSeeds_.clear(); TrailHistories_.clear(); } void UpdateProductionRenderable(std::uint64_t key, const MetaCoreDynamicGeometry& geometry, const glm::mat4& transform, bool castShadows, bool receiveShadows, bool depthWrite, std::uint8_t priority) { if (geometry.Vertices.empty() || geometry.Indices.empty() || Engine_ == nullptr || Scene_ == nullptr) { DestroyProductionRenderable(key); return; } const filament::Material* material = EnsureProductionMaterial(); if (material == nullptr) return; auto found = ProductionRenderables_.find(key); if (found != ProductionRenderables_.end() && (found->second.VertexCapacity < geometry.Vertices.size() || found->second.IndexCapacity < geometry.Indices.size())) { DestroyProductionRenderable(key); found = ProductionRenderables_.end(); } if (found == ProductionRenderables_.end()) { ProductionRenderableResource resource; resource.VertexCapacity = std::bit_ceil(geometry.Vertices.size()); resource.IndexCapacity = std::bit_ceil(geometry.Indices.size()); resource.Vertices = filament::VertexBuffer::Builder().vertexCount(resource.VertexCapacity).bufferCount(1) .attribute(filament::VertexAttribute::POSITION,0,filament::VertexBuffer::AttributeType::FLOAT3,offsetof(MetaCoreDynamicVertex,Position),sizeof(MetaCoreDynamicVertex)) .attribute(filament::VertexAttribute::COLOR,0,filament::VertexBuffer::AttributeType::FLOAT4,offsetof(MetaCoreDynamicVertex,Color),sizeof(MetaCoreDynamicVertex)).build(*Engine_); resource.Indices = filament::IndexBuffer::Builder().indexCount(resource.IndexCapacity).bufferType(filament::IndexBuffer::IndexType::UINT).build(*Engine_); resource.MaterialInstance = material->createInstance(); resource.Entity = utils::EntityManager::get().create(); if(resource.Vertices==nullptr||resource.Indices==nullptr||resource.MaterialInstance==nullptr){DestroyProductionRenderableResource(resource);return;} resource.MaterialInstance->setParameter("baseColorFactor",filament::RgbaType::LINEAR,filament::math::float4{1,1,1,1}); resource.MaterialInstance->setDepthWrite(depthWrite);resource.MaterialInstance->setDepthCulling(true);resource.MaterialInstance->setCullingMode(filament::backend::CullingMode::NONE); Engine_->getTransformManager().create(resource.Entity); const glm::vec3 center=geometry.Vertices.front().Position;const auto result=filament::RenderableManager::Builder(1) .boundingBox({{center.x,center.y,center.z},{100000.0F,100000.0F,100000.0F}}).culling(false).castShadows(castShadows).receiveShadows(receiveShadows) .geometry(0,filament::RenderableManager::PrimitiveType::TRIANGLES,resource.Vertices,resource.Indices,0,geometry.Indices.size()) .material(0,resource.MaterialInstance).build(*Engine_,resource.Entity); if(static_cast(result)!=0){DestroyProductionRenderableResource(resource);return;} Scene_->addEntity(resource.Entity);EntitiesInScene_.insert(resource.Entity);found=ProductionRenderables_.emplace(key,std::move(resource)).first; } auto& resource=found->second; const std::size_t vertexBytes=geometry.Vertices.size()*sizeof(MetaCoreDynamicVertex),indexBytes=geometry.Indices.size()*sizeof(std::uint32_t); void* vertexCopy=std::malloc(vertexBytes);void* indexCopy=std::malloc(indexBytes);if(vertexCopy==nullptr||indexCopy==nullptr){std::free(vertexCopy);std::free(indexCopy);return;} std::memcpy(vertexCopy,geometry.Vertices.data(),vertexBytes);std::memcpy(indexCopy,geometry.Indices.data(),indexBytes); resource.Vertices->setBufferAt(*Engine_,0,filament::VertexBuffer::BufferDescriptor(vertexCopy,vertexBytes,[](void* p,size_t,void*){std::free(p);},nullptr)); resource.Indices->setBuffer(*Engine_,filament::IndexBuffer::BufferDescriptor(indexCopy,indexBytes,[](void* p,size_t,void*){std::free(p);},nullptr)); auto& rm=Engine_->getRenderableManager();const auto instance=rm.getInstance(resource.Entity);rm.setGeometryAt(instance,0,filament::RenderableManager::PrimitiveType::TRIANGLES,resource.Vertices,resource.Indices,0,geometry.Indices.size());rm.setCastShadows(instance,castShadows);rm.setReceiveShadows(instance,receiveShadows);rm.setPriority(instance,priority); const glm::vec3 minimum=std::accumulate(geometry.Vertices.begin()+1,geometry.Vertices.end(),geometry.Vertices.front().Position,[](glm::vec3 value,const auto& vertex){return glm::min(value,vertex.Position);}); const glm::vec3 maximum=std::accumulate(geometry.Vertices.begin()+1,geometry.Vertices.end(),geometry.Vertices.front().Position,[](glm::vec3 value,const auto& vertex){return glm::max(value,vertex.Position);}); const glm::vec3 center=(minimum+maximum)*0.5F,extents=glm::max((maximum-minimum)*0.5F,glm::vec3(0.001F));rm.setAxisAlignedBoundingBox(instance,{{center.x,center.y,center.z},{extents.x,extents.y,extents.z}}); Engine_->getTransformManager().setTransform(Engine_->getTransformManager().getInstance(resource.Entity),*reinterpret_cast(glm::value_ptr(transform))); } [[nodiscard]] static MetaCoreDynamicGeometry BuildDecalBox(const MetaCoreDecalProjectorComponent& decal) { MetaCoreDynamicGeometry result;const glm::vec3 e=glm::abs(decal.Size)*0.5F;const glm::vec4 color{1.0F,0.45F,0.1F,std::clamp(decal.Opacity,0.0F,1.0F)}; const std::array p{{{-e.x,-e.y,-e.z},{e.x,-e.y,-e.z},{e.x,e.y,-e.z},{-e.x,e.y,-e.z},{-e.x,-e.y,e.z},{e.x,-e.y,e.z},{e.x,e.y,e.z},{-e.x,e.y,e.z}}};for(const auto& point:p)result.Vertices.push_back({point,color,{}}); result.Indices={0,2,1,0,3,2,4,5,6,4,6,7,0,1,5,0,5,4,2,3,7,2,7,6,1,2,6,1,6,5,3,0,4,3,4,7};return result; } [[nodiscard]] std::optional LoadTerrainGeometry(const MetaCoreTerrainComponent& terrain) const { if(!terrain.HeightmapGuid.IsValid())return std::nullopt;const auto path=MetaCoreAssetRegistry::Get().ResolveGuidToPath(terrain.HeightmapGuid);if(path.empty())return std::nullopt; int width=0,height=0,channels=0;if(std::uint16_t* pixels=stbi_load_16(path.string().c_str(),&width,&height,&channels,1)){std::vector values(pixels,pixels+static_cast(width)*height);stbi_image_free(pixels);const float distance=glm::distance(CurrentSceneView_.CameraPosition,glm::vec3(0));const std::uint32_t step=distance>terrain.WorldSize.x?4U:(distance>terrain.WorldSize.x*0.5F?2U:1U);return MetaCoreBuildTerrainGeometry(values,static_cast(width),static_cast(height),terrain.WorldSize,step);}return std::nullopt; } void SynchronizeProductionComponents(const MetaCoreSceneRenderSyncSnapshot& snapshot) { const auto now=std::chrono::steady_clock::now();const float delta=LastProductionSync_.time_since_epoch().count()==0?1.0F/60.0F:std::clamp(std::chrono::duration(now-LastProductionSync_).count(),0.0F,0.1F);LastProductionSync_=now; const glm::vec3 cameraForward=glm::normalize(CurrentSceneView_.CameraTarget-CurrentSceneView_.CameraPosition);glm::vec3 cameraRight=glm::cross(cameraForward,CurrentSceneView_.CameraUp);if(glm::dot(cameraRight,cameraRight)<0.0001F)cameraRight={1,0,0};cameraRight=glm::normalize(cameraRight);const glm::vec3 cameraUp=glm::normalize(glm::cross(cameraRight,cameraForward)); std::unordered_set active;std::uint32_t particleRemaining=RenderSettings_.GlobalParticleBudget; std::vector emitters;for(const auto& emitter:snapshot.ParticleEmitters)emitters.push_back(&emitter);std::sort(emitters.begin(),emitters.end(),[](const auto* a,const auto* b){return a->ObjectIdObjectId;}); for(const auto* value:emitters){const auto key=ProductionRenderableKey(value->ObjectId,1U);active.insert(key);auto& simulation=ParticleSimulations_[value->ObjectId];if(!ParticleSimulationSeeds_.contains(value->ObjectId)||ParticleSimulationSeeds_[value->ObjectId]!=value->Component.RandomSeed){simulation.Reset(value->Component.RandomSeed);ParticleSimulationSeeds_[value->ObjectId]=value->Component.RandomSeed;}simulation.Simulate(value->Component,delta,particleRemaining);particleRemaining-=std::min(particleRemaining,simulation.GetParticles().size());UpdateProductionRenderable(key,MetaCoreBuildParticleBillboards(simulation.GetParticles(),cameraRight,cameraUp,value->WorldMatrix),glm::mat4(1),false,false,false,6U);} std::uint32_t lineRemaining=RenderSettings_.DynamicLinePointBudget; for(const auto& value:snapshot.LineRenderers){const auto key=ProductionRenderableKey(value.ObjectId,2U);active.insert(key);std::vector points;const auto count=std::min(value.Component.Points.size(),lineRemaining);points.reserve(count);for(std::size_t index=0;index(count);UpdateProductionRenderable(key,MetaCoreBuildLineRibbon(points,value.Component.Width,{value.Component.Color,1},{value.Component.Color,1},cameraForward,static_cast(count)),glm::mat4(1),false,false,false,5U);} for(const auto& value:snapshot.TrailRenderers){const auto key=ProductionRenderableKey(value.ObjectId,3U);active.insert(key);auto& trail=TrailHistories_[value.ObjectId];trail.Update(glm::vec3(value.WorldMatrix[3]),delta,value.Component.Lifetime,value.Component.MinVertexDistance,value.Component.MaxPoints);std::vector points;for(const auto& point:trail.GetPoints())points.push_back(point.Position);const auto allowed=std::min(lineRemaining,static_cast(points.size()));lineRemaining-=allowed;UpdateProductionRenderable(key,MetaCoreBuildLineRibbon(points,value.Component.StartWidth,{value.Component.StartColor,1},{value.Component.EndColor,0},cameraForward,allowed),glm::mat4(1),false,false,false,5U);} for(const auto& value:snapshot.DecalProjectors){const auto key=ProductionRenderableKey(value.ObjectId,4U);active.insert(key);if(value.Component.Enabled&&glm::distance(glm::vec3(value.WorldMatrix[3]),CurrentSceneView_.CameraPosition)<=value.Component.MaxDistance)UpdateProductionRenderable(key,BuildDecalBox(value.Component),value.WorldMatrix,false,false,false,4U);else DestroyProductionRenderable(key);} for(const auto& value:snapshot.Terrains){const auto key=ProductionRenderableKey(value.ObjectId,5U);active.insert(key);const auto geometry=value.Component.Enabled?LoadTerrainGeometry(value.Component):std::nullopt;if(geometry)UpdateProductionRenderable(key,*geometry,value.WorldMatrix,value.Component.CastShadows,true,true,2U);else DestroyProductionRenderable(key);} std::vector stale;for(const auto& [key,resource]:ProductionRenderables_)if(!active.contains(key))stale.push_back(key);for(const auto key:stale)DestroyProductionRenderable(key); } [[nodiscard]] filament::Material* LoadEditorGridMaterial() { if (Engine_ == nullptr) { return nullptr; } const std::array candidatePaths{ std::filesystem::path("grid.filamat"), std::filesystem::path("Engine/Materials/grid.filamat"), std::filesystem::path("../grid.filamat"), std::filesystem::path("build/grid.filamat"), std::filesystem::path("build/linux-full-clang-libcxx-debug/grid.filamat"), std::filesystem::path("build_filament/libs/filamentapp/generated/material/transparentColor.filamat"), std::filesystem::path("../build_filament/libs/filamentapp/generated/material/transparentColor.filamat"), std::filesystem::path("../../build_filament/libs/filamentapp/generated/material/transparentColor.filamat"), std::filesystem::path("../../../build_filament/libs/filamentapp/generated/material/transparentColor.filamat") }; for (const std::filesystem::path& candidate : candidatePaths) { std::ifstream file(candidate, std::ios::binary); if (!file.is_open()) { continue; } std::vector buffer( (std::istreambuf_iterator(file)), std::istreambuf_iterator() ); if (buffer.empty()) { continue; } filament::Material* material = filament::Material::Builder() .package(buffer.data(), buffer.size()) .build(*Engine_); if (material != nullptr) { MetaCoreFilamentDebugLog("Editor grid material loaded path=\"" + candidate.generic_string() + "\""); return material; } } MetaCoreFilamentDebugLog("Editor grid material not found; using Filament default material fallback"); return nullptr; } void SetEditorGridMaterialColor( filament::MaterialInstance& materialInstance, filament::math::float4 color ) const { materialInstance.setColorWrite(true); materialInstance.setDepthWrite(false); materialInstance.setDepthCulling(true); materialInstance.setCullingMode(filament::backend::CullingMode::NONE); const filament::Material* material = materialInstance.getMaterial(); if (material == nullptr) { return; } const auto setColorIfPresent = [&](const char* parameterName) -> bool { if (!material->hasParameter(parameterName)) { return false; } materialInstance.setParameter(parameterName, filament::RgbaType::LINEAR, color); return true; }; if (setColorIfPresent("baseColor") || setColorIfPresent("baseColorFactor") || setColorIfPresent("color") || setColorIfPresent("albedo")) { return; } } [[nodiscard]] bool EnsureEditorGridRenderable() { if (EditorGridBuilt_) { return true; } if (Engine_ == nullptr || Scene_ == nullptr) { return false; } EditorGridMaterial_ = LoadEditorGridMaterial(); const filament::Material* material = EditorGridMaterial_ != nullptr ? EditorGridMaterial_ : Engine_->getDefaultMaterial(); if (material == nullptr) { return false; } enum class GridLineKind : std::size_t { Minor = 0, Major = 1, AxisX = 2, AxisY = 3, Count = 4 }; std::array, static_cast(GridLineKind::Count)> groupedVertices; const auto appendLine = [&]( GridLineKind kind, float x0, float y0, float x1, float y1 ) { const bool shouldFade = kind == GridLineKind::Minor || kind == GridLineKind::Major; const auto vertexAlpha = [&](float x, float y) { if (!shouldFade) { return std::uint8_t{255}; } const float normalizedDistance = std::max(std::abs(x), std::abs(y)) / static_cast(EditorGridExtent_); const float fadeProgress = std::clamp((normalizedDistance - 0.58F) / 0.42F, 0.0F, 1.0F); const float smoothFade = fadeProgress * fadeProgress * (3.0F - 2.0F * fadeProgress); return static_cast(std::round((1.0F - smoothFade) * 255.0F)); }; auto& vertices = groupedVertices[static_cast(kind)]; vertices.push_back(MetaCoreEditorGridVertex{ filament::math::float3{x0, y0, 0.0F}, {255, 255, 255, vertexAlpha(x0, y0)} }); vertices.push_back(MetaCoreEditorGridVertex{ filament::math::float3{x1, y1, 0.0F}, {255, 255, 255, vertexAlpha(x1, y1)} }); }; for (int coordinate = -EditorGridExtent_; coordinate <= EditorGridExtent_; ++coordinate) { if (coordinate == 0) { continue; } const GridLineKind lineKind = (coordinate % EditorGridMajorStep_) == 0 ? GridLineKind::Major : GridLineKind::Minor; appendLine( lineKind, static_cast(coordinate), static_cast(-EditorGridExtent_), static_cast(coordinate), static_cast(EditorGridExtent_) ); appendLine( lineKind, static_cast(-EditorGridExtent_), static_cast(coordinate), static_cast(EditorGridExtent_), static_cast(coordinate) ); } appendLine( GridLineKind::AxisX, static_cast(-EditorGridAxisExtent_), 0.0F, static_cast(EditorGridAxisExtent_), 0.0F ); appendLine( GridLineKind::AxisY, 0.0F, static_cast(-EditorGridAxisExtent_), 0.0F, static_cast(EditorGridAxisExtent_) ); std::vector vertices; std::vector indices; vertices.reserve((EditorGridExtent_ * 2 + 1) * 4 + 4); indices.reserve((EditorGridExtent_ * 2 + 1) * 4 + 4); std::array(GridLineKind::Count)> ranges{}; for (std::size_t kindIndex = 0; kindIndex < groupedVertices.size(); ++kindIndex) { ranges[kindIndex].Offset = static_cast(indices.size()); for (const MetaCoreEditorGridVertex& vertex : groupedVertices[kindIndex]) { if (vertices.size() > std::numeric_limits::max()) { return false; } vertices.push_back(vertex); indices.push_back(static_cast(vertices.size() - 1)); } ranges[kindIndex].Count = static_cast(indices.size() - ranges[kindIndex].Offset); } if (vertices.empty() || indices.empty()) { return false; } EditorGridVertexBuffer_ = filament::VertexBuffer::Builder() .vertexCount(vertices.size()) .bufferCount(1) .attribute( filament::VertexAttribute::POSITION, 0, filament::VertexBuffer::AttributeType::FLOAT3, offsetof(MetaCoreEditorGridVertex, Position), sizeof(MetaCoreEditorGridVertex) ) .attribute( filament::VertexAttribute::COLOR, 0, filament::VertexBuffer::AttributeType::UBYTE4, offsetof(MetaCoreEditorGridVertex, Color), sizeof(MetaCoreEditorGridVertex) ) .normalized(filament::VertexAttribute::COLOR) .build(*Engine_); EditorGridIndexBuffer_ = filament::IndexBuffer::Builder() .indexCount(indices.size()) .bufferType(filament::IndexBuffer::IndexType::USHORT) .build(*Engine_); if (EditorGridVertexBuffer_ == nullptr || EditorGridIndexBuffer_ == nullptr) { DestroyEditorGridRenderable(); return false; } const size_t vertexDataSize = vertices.size() * sizeof(MetaCoreEditorGridVertex); void* vertexData = std::malloc(vertexDataSize); if (vertexData == nullptr) { DestroyEditorGridRenderable(); return false; } std::memcpy(vertexData, vertices.data(), vertexDataSize); EditorGridVertexBuffer_->setBufferAt( *Engine_, 0, filament::VertexBuffer::BufferDescriptor( vertexData, vertexDataSize, [](void* buffer, size_t, void*) { std::free(buffer); }, nullptr ) ); const size_t indexDataSize = indices.size() * sizeof(std::uint16_t); void* indexData = std::malloc(indexDataSize); if (indexData == nullptr) { DestroyEditorGridRenderable(); return false; } std::memcpy(indexData, indices.data(), indexDataSize); EditorGridIndexBuffer_->setBuffer( *Engine_, filament::IndexBuffer::BufferDescriptor( indexData, indexDataSize, [](void* buffer, size_t, void*) { std::free(buffer); }, nullptr ) ); const std::array(GridLineKind::Count)> colors{ filament::math::float4{0.48F, 0.52F, 0.60F, 0.22F}, filament::math::float4{0.62F, 0.68F, 0.78F, 0.36F}, filament::math::float4{0.86F, 0.22F, 0.22F, 0.58F}, filament::math::float4{0.42F, 0.82F, 0.26F, 0.58F} }; for (std::size_t kindIndex = 0; kindIndex < EditorGridMaterialInstances_.size(); ++kindIndex) { EditorGridMaterialInstances_[kindIndex] = material->createInstance(); if (EditorGridMaterialInstances_[kindIndex] == nullptr) { DestroyEditorGridRenderable(); return false; } SetEditorGridMaterialColor(*EditorGridMaterialInstances_[kindIndex], colors[kindIndex]); } EditorGridEntity_ = utils::EntityManager::get().create(); auto& transformManager = Engine_->getTransformManager(); transformManager.create(EditorGridEntity_); auto gridBuilder = filament::RenderableManager::Builder(2); gridBuilder .boundingBox({{0.0F, 0.0F, 0.0F}, {static_cast(EditorGridExtent_), static_cast(EditorGridExtent_), 0.05F}}) .culling(false) .castShadows(false) .receiveShadows(false); for (std::size_t kindIndex = 0; kindIndex < 2; ++kindIndex) { gridBuilder .geometry( kindIndex, filament::RenderableManager::PrimitiveType::LINES, EditorGridVertexBuffer_, EditorGridIndexBuffer_, ranges[kindIndex].Offset, ranges[kindIndex].Count ) .material(kindIndex, EditorGridMaterialInstances_[kindIndex]) .blendOrder(kindIndex, static_cast(kindIndex)); } if (static_cast(gridBuilder.build(*Engine_, EditorGridEntity_)) != 0) { DestroyEditorGridRenderable(); return false; } auto& renderableManager = Engine_->getRenderableManager(); renderableManager.setLayerMask( renderableManager.getInstance(EditorGridEntity_), 0xFFU, kEditorGridLayerMask ); EditorGridAxisEntity_ = utils::EntityManager::get().create(); transformManager.create(EditorGridAxisEntity_); auto axisBuilder = filament::RenderableManager::Builder(2); axisBuilder .boundingBox({{0.0F, 0.0F, 0.0F}, {static_cast(EditorGridAxisExtent_), static_cast(EditorGridAxisExtent_), 0.05F}}) .culling(false) .castShadows(false) .receiveShadows(false); for (std::size_t kindIndex = 0; kindIndex < 2; ++kindIndex) { const std::size_t sourceIndex = kindIndex + 2; axisBuilder .geometry( kindIndex, filament::RenderableManager::PrimitiveType::LINES, EditorGridVertexBuffer_, EditorGridIndexBuffer_, ranges[sourceIndex].Offset, ranges[sourceIndex].Count ) .material(kindIndex, EditorGridMaterialInstances_[sourceIndex]) .blendOrder(kindIndex, static_cast(sourceIndex)); } if (static_cast(axisBuilder.build(*Engine_, EditorGridAxisEntity_)) != 0) { DestroyEditorGridRenderable(); return false; } renderableManager.setLayerMask( renderableManager.getInstance(EditorGridAxisEntity_), 0xFFU, kEditorGridLayerMask ); EditorGridBuilt_ = true; return true; } void DestroyEditorGridRenderable() { if (Engine_ == nullptr) { return; } if (EditorGridEntityInScene_ && Scene_ != nullptr) { Scene_->remove(EditorGridEntity_); EntitiesInScene_.erase(EditorGridEntity_); EditorGridEntityInScene_ = false; } if (EditorGridAxisEntityInScene_ && Scene_ != nullptr) { Scene_->remove(EditorGridAxisEntity_); EntitiesInScene_.erase(EditorGridAxisEntity_); EditorGridAxisEntityInScene_ = false; } const auto destroyGridEntity = [&](utils::Entity& entity) { if (!entity) { return; } auto& renderableManager = Engine_->getRenderableManager(); if (renderableManager.getInstance(entity)) { renderableManager.destroy(entity); } Engine_->destroy(entity); utils::EntityManager::get().destroy(entity); entity = {}; }; destroyGridEntity(EditorGridEntity_); destroyGridEntity(EditorGridAxisEntity_); for (filament::MaterialInstance*& materialInstance : EditorGridMaterialInstances_) { if (materialInstance != nullptr) { Engine_->destroy(materialInstance); materialInstance = nullptr; } } if (EditorGridVertexBuffer_ != nullptr) { Engine_->destroy(EditorGridVertexBuffer_); EditorGridVertexBuffer_ = nullptr; } if (EditorGridIndexBuffer_ != nullptr) { Engine_->destroy(EditorGridIndexBuffer_); EditorGridIndexBuffer_ = nullptr; } if (EditorGridMaterial_ != nullptr) { Engine_->destroy(EditorGridMaterial_); EditorGridMaterial_ = nullptr; } EditorGridBuilt_ = false; } bool EntityBelongsToAsset(filament::gltfio::FilamentAsset* asset, utils::Entity entity) const { if (asset == nullptr || entity.isNull()) { return false; } if (entity == asset->getRoot()) { return true; } const utils::Entity* entities = asset->getEntities(); const size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { if (entities[i] == entity) { return true; } } return false; } void CollectAllRenderableAssetEntities( filament::gltfio::FilamentAsset* asset, std::vector& outEntities ) const { if (asset == nullptr || Engine_ == nullptr) { return; } auto& rm = Engine_->getRenderableManager(); const utils::Entity* entities = asset->getEntities(); const size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { const utils::Entity entity = entities[i]; if (entity.isNull() || !rm.getInstance(entity)) { continue; } if (std::find(outEntities.begin(), outEntities.end(), entity) == outEntities.end()) { outEntities.push_back(entity); } } } // 判断当前实体是否为过渡节点(即无名且无 Mesh 渲染组件的节点) bool IsTransitionNode(utils::Entity entity, filament::gltfio::FilamentAsset* asset) const { if (!entity) return false; if (entity == asset->getRoot()) return false; // 1. 判断是否有名字 const char* name = asset->getName(entity); if (name && strlen(name) > 0) { return false; } // 2. 判断是否是渲染实体 auto& rm = Engine_->getRenderableManager(); if (rm.getInstance(entity)) { return false; } return true; } // 穿透子节点链中的所有过渡节点,提取出所有具备有效名称或为渲染实体的第一代有效子节点 void GetEffectiveFilamentChildren( utils::Entity parent, filament::gltfio::FilamentAsset* asset, const std::unordered_map, utils::Entity::Hasher>& parentToChildren, std::vector& outEffectiveChildren ) const { auto it = parentToChildren.find(parent); if (it == parentToChildren.end()) return; for (utils::Entity child : it->second) { if (IsTransitionNode(child, asset)) { GetEffectiveFilamentChildren(child, asset, parentToChildren, outEffectiveChildren); } else { outEffectiveChildren.push_back(child); } } } // 自底向上溯源生成覆盖所有中间隐藏过渡节点的完整父子映射表 std::unordered_map, utils::Entity::Hasher> BuildFullParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const { std::unordered_map, utils::Entity::Hasher> parentToChildren; if (!asset) return parentToChildren; const utils::Entity* entities = asset->getEntities(); size_t entityCount = asset->getEntityCount(); auto& tm = Engine_->getTransformManager(); for (size_t i = 0; i < entityCount; ++i) { utils::Entity current = entities[i]; if (current == asset->getRoot()) continue; while (current && current != asset->getRoot()) { auto instance = tm.getInstance(current); if (!instance) break; utils::Entity parent = tm.getParent(instance); if (parent.isNull()) break; auto& children = parentToChildren[parent]; if (std::find(children.begin(), children.end(), current) == children.end()) { children.push_back(current); } current = parent; } } return parentToChildren; } EntityChildrenMap BuildDirectParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const { EntityChildrenMap parentToChildren; if (!asset || Engine_ == nullptr) { return parentToChildren; } auto& tm = Engine_->getTransformManager(); const utils::Entity* entities = asset->getEntities(); const size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { const utils::Entity entity = entities[i]; if (entity.isNull()) { continue; } const auto instance = tm.getInstance(entity); if (!instance) { continue; } const utils::Entity parent = tm.getParent(instance); if (!parent.isNull()) { parentToChildren[parent].push_back(entity); } } return parentToChildren; } void CollectRenderableDescendantsFromChildMap( utils::Entity rootEntity, const EntityChildrenMap& parentToChildren, std::vector& outEntities ) const { if (!Engine_ || rootEntity.isNull()) { return; } auto& rm = Engine_->getRenderableManager(); std::vector pending{rootEntity}; std::unordered_set visited; while (!pending.empty()) { const utils::Entity entity = pending.back(); pending.pop_back(); if (entity.isNull() || !visited.insert(entity).second) { continue; } if (rm.getInstance(entity)) { outEntities.push_back(entity); } const auto childrenIterator = parentToChildren.find(entity); if (childrenIterator == parentToChildren.end()) { continue; } pending.insert(pending.end(), childrenIterator->second.begin(), childrenIterator->second.end()); } } void CollectRenderableDescendants( filament::gltfio::FilamentAsset* asset, utils::Entity rootEntity, std::vector& outEntities ) const { if (!Engine_ || rootEntity.isNull()) { return; } auto& rm = Engine_->getRenderableManager(); auto& tm = Engine_->getTransformManager(); const auto addRenderable = [&](utils::Entity entity) { if (entity.isNull() || !rm.getInstance(entity)) { return; } if (std::find(outEntities.begin(), outEntities.end(), entity) == outEntities.end()) { outEntities.push_back(entity); } }; addRenderable(rootEntity); if (asset == nullptr) { return; } const utils::Entity* entities = asset->getEntities(); const size_t entityCount = asset->getEntityCount(); for (size_t i = 0; i < entityCount; ++i) { utils::Entity candidate = entities[i]; if (candidate.isNull() || candidate == rootEntity) { continue; } utils::Entity current = candidate; bool isDescendant = false; while (!current.isNull()) { if (current == rootEntity) { isDescendant = true; break; } const auto transformInstance = tm.getInstance(current); if (!transformInstance) { break; } current = tm.getParent(transformInstance); } if (isDescendant) { addRenderable(candidate); } } } static filament::math::float3 ToFilamentFloat3(const glm::vec3& value) { return filament::math::float3{ value.x, value.y, value.z }; } static filament::math::float3 BuildFilamentLightDirection(const glm::mat4& worldMatrix) { glm::vec3 direction = glm::vec3(worldMatrix * glm::vec4(0.0F, 0.0F, -1.0F, 0.0F)); if (glm::length(direction) <= 0.0001F) { direction = glm::vec3(0.5F, 0.5F, -1.0F); } direction = glm::normalize(direction); return ToFilamentFloat3(direction); } static filament::LinearColor BuildFilamentLightColor(const glm::vec3& color) { return filament::Color::toLinear({ color.x, color.y, color.z }); } static filament::math::float3 BuildFilamentLightPosition(const glm::mat4& worldMatrix) { return ToFilamentFloat3(glm::vec3(worldMatrix[3])); } static filament::LightManager::Type BuildFilamentLightType(MetaCoreLightType type) { if (type == MetaCoreLightType::Point) return filament::LightManager::Type::POINT; if (type == MetaCoreLightType::Spot) return filament::LightManager::Type::SPOT; return filament::LightManager::Type::DIRECTIONAL; } filament::LightManager::ShadowOptions BuildShadowOptions(const MetaCoreRenderSyncLight& light) const { filament::LightManager::ShadowOptions options; options.mapSize = RenderSettings_.ShadowAtlasSize; options.constantBias = std::max(0.0F, light.ShadowBias); options.normalBias = std::max(0.0F, light.ShadowNormalBias); if (light.Type == MetaCoreLightType::Directional) { options.shadowCascades = static_cast(RenderSettings_.DirectionalCascadeCount); for (std::uint32_t index = 0; index + 1U < RenderSettings_.DirectionalCascadeCount && index < 3U; ++index) { options.cascadeSplitPositions[index] = RenderSettings_.DirectionalCascadeSplits[index]; } options.shadowFar = RenderSettings_.DirectionalShadowDistance; options.shadowFarHint = RenderSettings_.DirectionalShadowDistance; options.stable = true; } return options; } static float BuildFilamentLightIntensity(float intensity) { return intensity > 0.0F ? intensity : 0.0F; } void SyncSceneLights(const MetaCoreSceneRenderSyncSnapshot& snapshot) { if (!Engine_ || !Scene_) { return; } std::unordered_set activeLightIds; auto& lightManager = Engine_->getLightManager(); const auto defaultLightInstance = lightManager.getInstance(Light_); // 统计当前是否有处于启用状态的光源 bool hasActiveLight = false; for (const auto& light : snapshot.Lights) { if (light.Enabled) { hasActiveLight = true; break; } } if (defaultLightInstance) { // 如果场景中没有任何起作用的灯光,则自动亮起默认灯光作为兜底 lightManager.setIntensity(defaultLightInstance, hasActiveLight ? 0.0F : 100000.0F); } std::vector shadowCandidates; shadowCandidates.reserve(snapshot.Lights.size()); for (const auto& light : snapshot.Lights) { const glm::vec3 position(light.WorldMatrix[3]); shadowCandidates.push_back({light.ObjectId, light.Type, light.ShadowPriority, glm::distance(position, CurrentSceneView_.CameraPosition), light.Enabled, light.CastShadows}); } const auto shadowIds = MetaCoreAllocateShadowBudget(shadowCandidates, RenderSettings_); const std::unordered_set shadowSet(shadowIds.begin(), shadowIds.end()); for (const MetaCoreRenderSyncLight& light : snapshot.Lights) { if (!light.Enabled) { continue; } activeLightIds.insert(light.ObjectId); auto lightIt = SceneLightEntities_.find(light.ObjectId); if (lightIt != SceneLightEntities_.end()) { const auto instance = lightManager.getInstance(lightIt->second); if (!instance || lightManager.getType(instance) != BuildFilamentLightType(light.Type)) { Scene_->remove(lightIt->second); EntitiesInScene_.erase(lightIt->second); Engine_->destroy(lightIt->second); utils::EntityManager::get().destroy(lightIt->second); SceneLightEntities_.erase(lightIt); lightIt = SceneLightEntities_.end(); } } if (lightIt == SceneLightEntities_.end()) { utils::Entity lightEntity = utils::EntityManager::get().create(); const filament::LinearColor color = light.UseColorTemperature ? filament::Color::cct(std::clamp(light.ColorTemperatureKelvin, 1000.0F, 15000.0F)) : BuildFilamentLightColor(light.Color); filament::LightManager::Builder builder(BuildFilamentLightType(light.Type)); builder.color(color) .intensity(BuildFilamentLightIntensity(light.Intensity)) .position(BuildFilamentLightPosition(light.WorldMatrix)) .direction(BuildFilamentLightDirection(light.WorldMatrix)) .falloff(std::max(0.001F, light.Range)) .spotLightCone(glm::radians(std::clamp(light.InnerConeDegrees, 0.5F, 89.0F)), glm::radians(std::clamp(light.OuterConeDegrees, light.InnerConeDegrees, 89.5F))) .castShadows(shadowSet.contains(light.ObjectId)) .shadowOptions(BuildShadowOptions(light)); for (unsigned int channel=0; channel<8U; ++channel) builder.lightChannel(channel, (light.LightingMask & (1U<addEntity(lightEntity); EntitiesInScene_.insert(lightEntity); lightIt = SceneLightEntities_.emplace(light.ObjectId, lightEntity).first; } const auto lightInstance = lightManager.getInstance(lightIt->second); if (lightInstance) { const filament::LinearColor color = light.UseColorTemperature ? filament::Color::cct(std::clamp(light.ColorTemperatureKelvin,1000.0F,15000.0F)) : BuildFilamentLightColor(light.Color); lightManager.setColor(lightInstance, color); lightManager.setIntensity(lightInstance, BuildFilamentLightIntensity(light.Intensity)); lightManager.setPosition(lightInstance, BuildFilamentLightPosition(light.WorldMatrix)); lightManager.setDirection(lightInstance, BuildFilamentLightDirection(light.WorldMatrix)); lightManager.setFalloff(lightInstance, std::max(0.001F, light.Range)); lightManager.setSpotLightCone(lightInstance, glm::radians(std::clamp(light.InnerConeDegrees,0.5F,89.0F)), glm::radians(std::clamp(light.OuterConeDegrees,light.InnerConeDegrees,89.5F))); lightManager.setShadowCaster(lightInstance, shadowSet.contains(light.ObjectId)); lightManager.setShadowOptions(lightInstance, BuildShadowOptions(light)); for(unsigned int channel=0;channel<8U;++channel)lightManager.setLightChannel(lightInstance,channel,(light.LightingMask&(1U<first)) { ++it; continue; } Scene_->remove(it->second); EntitiesInScene_.erase(it->second); Engine_->destroy(it->second); utils::EntityManager::get().destroy(it->second); it = SceneLightEntities_.erase(it); } } void UpdateTransform(MetaCoreId objectId, const glm::mat4& localMatrix, const glm::mat4& worldMatrix) { auto it = ObjectToFilamentEntity_.find(objectId); if (it == ObjectToFilamentEntity_.end()) return; utils::Entity entity = it->second.second; auto& tm = Engine_->getTransformManager(); auto instance = tm.getInstance(entity); if (instance) { auto parent = tm.getParent(instance); glm::mat4 matrix = parent ? localMatrix : worldMatrix; // 【关键修复】:如果当前 entity 是子节点(即不是顶级映射的 pivotEntity) // 由于它的父级(assetRoot)带有了 -90X 旋转,我们必须对子节点应用基变换来抵消这个旋转。 // 公式:L_filament = R(90X) * L_ecs * R(-90X) if (LoadedAssets_.count(objectId) == 0) { glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); matrix = R_plus90X * matrix * R_minus90X; } tm.setTransform(instance, *reinterpret_cast(glm::value_ptr(matrix))); } } void ConfigureSceneViewRenderState() { if (View_ == nullptr) { return; } View_->setBlendMode(filament::View::BlendMode::OPAQUE); View_->setVisibleLayers(0xFFU, kSceneContentLayerMask); for (uint8_t channel = 0; channel < 8; ++channel) { View_->setChannelDepthClearEnabled(channel, true); } } void ConfigureEnvironmentViewRenderState() { if (EnvironmentView_ == nullptr) { return; } // This view is rendered after the main scene, so its skybox composites // only into pixels not covered by scene geometry. EnvironmentView_->setBlendMode(filament::View::BlendMode::TRANSLUCENT); EnvironmentView_->setVisibleLayers(0xFFU, kEnvironmentSkyboxLayerMask); for (uint8_t channel = 0; channel < 8; ++channel) { EnvironmentView_->setChannelDepthClearEnabled(channel, false); } } void ConfigureGridViewRenderState() { if (GridView_ == nullptr) { return; } GridView_->setBlendMode(filament::View::BlendMode::TRANSLUCENT); GridView_->setVisibleLayers(0xFFU, kEditorGridLayerMask); for (uint8_t channel = 0; channel < 8; ++channel) { GridView_->setChannelDepthClearEnabled(channel, false); } } void DebugRenderTargetState(const char* event) const { std::ostringstream stream; stream << event << " frame=" << RenderFrameIndex_ << " generation=" << RenderTargetGeneration_ << " renderTarget=" << MetaCoreDebugPointer(RenderTarget_) << " colorTexture=" << MetaCoreDebugPointer(FilamentTexture_) << " depthTexture=" << MetaCoreDebugPointer(DepthTexture_) << " pending=" << PendingRenderTargetResources_.size() << " loadedAssets=" << LoadedAssets_.size() << " objectMappings=" << ObjectToFilamentEntity_.size(); MetaCoreFilamentDebugLog(stream.str()); } filament::Engine* Engine_ = nullptr; filament::SwapChain* SwapChain_ = nullptr; filament::Renderer* Renderer_ = nullptr; filament::Scene* Scene_ = nullptr; filament::Scene* EnvironmentScene_ = nullptr; filament::Camera* Camera_ = nullptr; filament::ColorGrading* ColorGrading_ = nullptr; filament::View* View_ = nullptr; filament::View* EnvironmentView_ = nullptr; filament::View* GridView_ = nullptr; filament::Texture* EnvironmentIblTexture_ = nullptr; filament::Texture* EnvironmentSkyboxTexture_ = nullptr; filament::IndirectLight* EnvironmentIndirectLight_ = nullptr; filament::Skybox* EnvironmentSkybox_ = nullptr; filament::Skybox* SolidColorSkybox_ = nullptr; filament::math::float4 SolidColorSkyboxColor_{-1.0f, -1.0f, -1.0f, -1.0f}; bool EnvironmentLoaded_ = false; bool EnvironmentBackgroundVisible_ = false; MetaCoreSceneEnvironmentSettings ActiveEnvironmentSettings_{}; std::unordered_map EnvironmentProfiles_{}; std::unordered_map PostProcessProfiles_{}; std::unordered_set MissingEnvironmentProfileDiagnostics_{}; std::unordered_set MissingPostProcessProfileDiagnostics_{}; MetaCoreAssetGuid ActiveScenePostProcessGuid_{}; MetaCoreAssetGuid ActiveCameraPostProcessGuid_{}; MetaCorePostProcessProfileDocument ActivePostProcessProfile_{}; float AutoExposureEv100_ = 15.0F; std::chrono::steady_clock::time_point LastAutoExposureUpdate_{}; std::chrono::steady_clock::time_point LastRenderConfigurationPoll_{}; std::uint64_t RenderConfigurationFingerprint_ = 0U; std::filesystem::path LoadedEnvironmentIblPath_{}; std::filesystem::path LoadedEnvironmentSkyboxPath_{}; std::vector EnvironmentIblKtxBytes_{}; std::vector EnvironmentSkyboxKtxBytes_{}; std::string LastEnvironmentValidationFailure_{}; glm::mat4 CameraModelMatrix_{1.0F}; filament::gltfio::AssetLoader* AssetLoader_ = nullptr; filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr; filament::gltfio::TextureProvider* StbTextureProvider_ = nullptr; utils::NameComponentManager* NameManager_ = nullptr; MetaCoreWindow* Window_ = nullptr; MetaCoreSceneRenderSync RenderSync_{}; MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{}; MetaCoreSceneView CurrentSceneView_{}; std::unordered_map LodSelections_{}; MetaCoreStaticVisibilityBvh VisibilityBvh_{}; MetaCoreConservativeHzb PreviousHzb_{}; MetaCoreOcclusionHistory OcclusionHistory_{}; std::unordered_set LastFrameVisibleObjects_{}; glm::vec3 PreviousVisibilityCameraPosition_{}; glm::vec3 PreviousVisibilityCameraDirection_{0.0F,0.0F,-1.0F}; bool HasPreviousVisibilityCamera_ = false; MetaCoreVisibilityStatistics RenderingStatistics_{}; bool HasLoggedSnapshotDebug_ = false; std::uint64_t LastDebugSnapshotRevision_ = 0; std::size_t LastDebugRenderableCount_ = 0; std::unordered_map LastMaterialSyncDebugByObject_{}; std::unordered_map ObjectWorldMatrices_{}; std::unordered_map> ObjectToFilamentEntity_; std::unordered_map EntityToObjectId_; struct PendingPick { MetaCoreFilamentSceneBridge::PickCallback Callback{}; filament::math::mat4 Projection{}; filament::math::mat4 Model{}; std::uint32_t ViewportWidth = 1; std::uint32_t ViewportHeight = 1; }; std::unordered_map PickCallbacks_; std::uint64_t NextPickCallbackId_ = 1; std::unordered_map LoadedAssets_; std::unordered_map PendingResourceLoads_; std::unordered_map HostRootSourceModelGuids_; std::unordered_set InvalidatedModelGuids_; std::unordered_map> AssetDuplicatedMaterials_; std::unordered_map MaterialTextureCache_; std::unordered_map SceneLightEntities_; std::unordered_set EntitiesInScene_; bool EditorGridBuilt_ = false; bool EditorGridEntityInScene_ = false; bool EditorGridAxisEntityInScene_ = false; utils::Entity EditorGridEntity_{}; utils::Entity EditorGridAxisEntity_{}; filament::VertexBuffer* EditorGridVertexBuffer_ = nullptr; filament::IndexBuffer* EditorGridIndexBuffer_ = nullptr; filament::Material* EditorGridMaterial_ = nullptr; std::array EditorGridMaterialInstances_{}; filament::Material* ProductionMaterial_ = nullptr; filament::Material* RenderDebugMaterial_ = nullptr; std::vector DebugMaterialOverrides_{}; MetaCoreRenderDebugView ActiveDebugView_ = MetaCoreRenderDebugView::Lit; std::unordered_map ProductionRenderables_{}; std::unordered_map ParticleSimulations_{}; std::unordered_map ParticleSimulationSeeds_{}; std::unordered_map TrailHistories_{}; std::chrono::steady_clock::time_point LastProductionSync_{}; filament::View* UIView_ = nullptr; MetaCoreImGuiHelper* ImGuiHelper_ = nullptr; MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback RuntimeOverlayRenderCallback_{}; GLuint GLTextureId_ = 0; struct PendingRenderTargetResource { filament::RenderTarget* RenderTarget = nullptr; filament::Texture* ColorTexture = nullptr; filament::Texture* DepthTexture = nullptr; std::uint64_t Generation = 0; }; filament::Texture* FilamentTexture_ = nullptr; filament::Texture* DepthTexture_ = nullptr; filament::RenderTarget* RenderTarget_ = nullptr; std::vector PendingRenderTargetResources_; std::uint64_t RenderFrameIndex_ = 0; std::uint64_t RenderTargetGeneration_ = 0; utils::Entity Light_; std::filesystem::path ProjectRootPath_{}; MetaCoreRenderSettingsDocument RenderSettings_{}; std::string EmptyString_{}; MetaCoreRuntimeAssetRegistry::SubscriptionId RuntimeAssetSubscriptionId_ = 0; }; MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridge() : Impl_(std::make_unique()) {} MetaCoreFilamentSceneBridge::~MetaCoreFilamentSceneBridge() = default; bool MetaCoreFilamentSceneBridge::Initialize(MetaCoreWindow& window, bool offscreen) { return Impl_->Initialize(window, offscreen); } void MetaCoreFilamentSceneBridge::Shutdown() { Impl_->Shutdown(); } void MetaCoreFilamentSceneBridge::SetProjectRootPath(const std::filesystem::path& projectRootPath) { Impl_->SetProjectRootPath(projectRootPath); } void MetaCoreFilamentSceneBridge::SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { auto snapshot = Impl_->BuildSnapshot(scene); Impl_->SyncScene(snapshot, &scene, compatibilityMeshOnly, useScenePrimaryCamera); } void MetaCoreFilamentSceneBridge::SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { Impl_->SyncScene(snapshot, scene, compatibilityMeshOnly, useScenePrimaryCamera); } void MetaCoreFilamentSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneView) { Impl_->ApplySceneView(sceneView); } void MetaCoreFilamentSceneBridge::SetEditorGridVisible(bool visible) { Impl_->SetEditorGridVisible(visible); } void MetaCoreFilamentSceneBridge::SetRenderDebugView(MetaCoreRenderDebugView debugView) { Impl_->SetRenderDebugView(debugView); } MetaCoreRenderDebugView MetaCoreFilamentSceneBridge::GetRenderDebugView() const { return Impl_->GetRenderDebugView(); } void MetaCoreFilamentSceneBridge::SetRuntimeOverlayRenderCallback(RuntimeOverlayRenderCallback callback) { Impl_->SetRuntimeOverlayRenderCallback(std::move(callback)); } void MetaCoreFilamentSceneBridge::RenderAll() { Impl_->RenderAll(); } bool MetaCoreFilamentSceneBridge::RequestPick(std::uint32_t x, std::uint32_t y, PickCallback callback) { return Impl_->RequestPick(x, y, std::move(callback)); } uint32_t MetaCoreFilamentSceneBridge::GetGLTextureId() const { return Impl_->GetGLTextureId(); } void MetaCoreFilamentSceneBridge::Resize(int width, int height) { Impl_->Resize(width, height); } void* MetaCoreFilamentSceneBridge::GetFilamentTexturePointer() const { return Impl_->GetFilamentTexturePointer(); } bool MetaCoreFilamentSceneBridge::TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const { return Impl_->TryGetObjectWorldMatrix(objectId, worldMatrix); } bool MetaCoreFilamentSceneBridge::HasRuntimeSyncFailure() const { return Impl_->HasRuntimeSyncFailure(); } const std::string& MetaCoreFilamentSceneBridge::GetLastRuntimeSyncFailure() const { return Impl_->GetLastRuntimeSyncFailure(); } MetaCoreVisibilityStatistics MetaCoreFilamentSceneBridge::GetRenderingStatistics() const { return Impl_->GetRenderingStatistics(); } bool MetaCoreFilamentSceneBridge::VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const { return Impl_->VerifyTransformForTesting(objectId, expectedMatrix); } bool MetaCoreFilamentSceneBridge::VerifyLightExistsForTesting(MetaCoreId objectId) const { return Impl_->VerifyLightExistsForTesting(objectId); } float MetaCoreFilamentSceneBridge::GetDefaultLightIntensityForTesting() const { return Impl_->GetDefaultLightIntensityForTesting(); } bool MetaCoreFilamentSceneBridge::VerifyEntityInSceneForTesting(MetaCoreId objectId) const { return Impl_->VerifyEntityInSceneForTesting(objectId); } bool MetaCoreFilamentSceneBridge::VerifyEditorGridVisibleForTesting() const { return Impl_->VerifyEditorGridVisibleForTesting(); } bool MetaCoreFilamentSceneBridge::VerifyEnvironmentLoadedForTesting() const { return Impl_->VerifyEnvironmentLoadedForTesting(); } bool MetaCoreFilamentSceneBridge::VerifyEnvironmentBackgroundVisibleForTesting() const { return Impl_->VerifyEnvironmentBackgroundVisibleForTesting(); } float MetaCoreFilamentSceneBridge::GetEnvironmentIndirectLightIntensityForTesting() const { return Impl_->GetEnvironmentIndirectLightIntensityForTesting(); } } // namespace MetaCore