3508 lines
149 KiB
C++
3508 lines
149 KiB
C++
#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 <imgui.h>
|
||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||
|
||
#include <filament/Engine.h>
|
||
#include <filament/Scene.h>
|
||
#include <filament/View.h>
|
||
#include <filament/Camera.h>
|
||
#include <filament/SwapChain.h>
|
||
#include <filament/Renderer.h>
|
||
#include <filament/Viewport.h>
|
||
#include <filament/TransformManager.h>
|
||
#include <filament/Texture.h>
|
||
#include <filament/RenderTarget.h>
|
||
#include <filament/LightManager.h>
|
||
#include <filament/RenderableManager.h>
|
||
#include <filament/Material.h>
|
||
#include <filament/MaterialInstance.h>
|
||
#include <filament/TextureSampler.h>
|
||
#include <filament/Skybox.h>
|
||
#include <filament/IndirectLight.h>
|
||
#include <filament/VertexBuffer.h>
|
||
#include <filament/IndexBuffer.h>
|
||
#include <filament/Color.h>
|
||
|
||
#include <gltfio/AssetLoader.h>
|
||
#include <gltfio/FilamentAsset.h>
|
||
#include <gltfio/ResourceLoader.h>
|
||
#include <gltfio/MaterialProvider.h>
|
||
#include <gltfio/TextureProvider.h>
|
||
|
||
#include <ktxreader/Ktx1Reader.h>
|
||
|
||
#include <utils/Entity.h>
|
||
#include <utils/EntityManager.h>
|
||
#include <utils/NameComponentManager.h>
|
||
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
||
|
||
#define GLM_ENABLE_EXPERIMENTAL
|
||
#include <glm/gtc/type_ptr.hpp>
|
||
#include <glm/geometric.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtx/matrix_decompose.hpp>
|
||
#include <glm/gtc/quaternion.hpp>
|
||
|
||
#include <iostream>
|
||
#include <fstream>
|
||
#include <algorithm>
|
||
#include <array>
|
||
#include <cctype>
|
||
#include <cstddef>
|
||
#include <cstdlib>
|
||
#include <cstdint>
|
||
#include <cstring>
|
||
#include <cmath>
|
||
#include <memory>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <map>
|
||
#include <string_view>
|
||
#include <sstream>
|
||
#include <vector>
|
||
#include <functional>
|
||
#include <iterator>
|
||
#include <limits>
|
||
|
||
// 引入平台和 OpenGL 头文件以创建纹理
|
||
#if defined(_WIN32)
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <windows.h>
|
||
#endif
|
||
#include <GL/gl.h>
|
||
|
||
#if !defined(_WIN32)
|
||
#define GLFW_EXPOSE_NATIVE_X11
|
||
#include <GLFW/glfw3.h>
|
||
#include <GLFW/glfw3native.h>
|
||
#endif
|
||
|
||
namespace MetaCore {
|
||
|
||
namespace {
|
||
|
||
constexpr std::uint8_t kSceneContentLayerMask = 0x01U;
|
||
constexpr std::uint8_t kEnvironmentSkyboxLayerMask = 0x02U;
|
||
constexpr std::uint8_t kEditorGridLayerMask = 0x04U;
|
||
|
||
void* MetaCoreGetFilamentNativeWindowHandle(MetaCoreWindow& window) {
|
||
#if defined(_WIN32)
|
||
return window.GetNativeWindowHandle();
|
||
#else
|
||
auto* glfwWindow = static_cast<GLFWwindow*>(window.GetNativeWindowHandle());
|
||
if (glfwWindow == nullptr) {
|
||
return nullptr;
|
||
}
|
||
|
||
const Window x11Window = glfwGetX11Window(glfwWindow);
|
||
return reinterpret_cast<void*>(static_cast<std::uintptr_t>(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<typename T>
|
||
[[nodiscard]] std::string MetaCoreDebugPointer(T* pointer) {
|
||
std::ostringstream stream;
|
||
stream << static_cast<const void*>(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<MetaCoreAssetGuid>& 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("<none>"));
|
||
}
|
||
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, std::vector<utils::Entity>, utils::Entity::Hasher>;
|
||
|
||
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;
|
||
|
||
// 创建 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<filament::ACCURATE>({ 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<uint32_t>(width))
|
||
.height(static_cast<uint32_t>(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<uint32_t>(width))
|
||
.height(static_cast<uint32_t>(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<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
EnvironmentView_->setRenderTarget(RenderTarget_);
|
||
EnvironmentView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
GridView_->setRenderTarget(RenderTarget_);
|
||
GridView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
ConfigureSceneViewRenderState();
|
||
ConfigureEnvironmentViewRenderState();
|
||
ConfigureGridViewRenderState();
|
||
} else {
|
||
auto [width, height] = window.GetFramebufferSize();
|
||
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
EnvironmentView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
GridView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(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 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");
|
||
|
||
DestroyEditorGridRenderable();
|
||
|
||
// 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();
|
||
|
||
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_->destroy(View_);
|
||
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;
|
||
}
|
||
}
|
||
|
||
void SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||
if (ProjectRootPath_ != projectRootPath) {
|
||
DestroyMaterialTextureCache();
|
||
DestroyEnvironmentLighting();
|
||
}
|
||
ProjectRootPath_ = projectRootPath;
|
||
SynchronizeEnvironment(ActiveEnvironmentSettings_);
|
||
}
|
||
|
||
void DestroyMaterialTextureCache() {
|
||
if (Engine_) {
|
||
Engine_->flushAndWait();
|
||
for (auto& [_, texture] : MaterialTextureCache_) {
|
||
if (texture != nullptr) {
|
||
Engine_->destroy(texture);
|
||
}
|
||
}
|
||
}
|
||
MaterialTextureCache_.clear();
|
||
}
|
||
|
||
[[nodiscard]] static std::optional<std::vector<std::uint8_t>> 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<std::uint8_t> bytes(static_cast<std::size_t>(fileSize));
|
||
input.seekg(0, std::ios::beg);
|
||
if (!input.read(reinterpret_cast<char*>(bytes.data()), fileSize)) {
|
||
return std::nullopt;
|
||
}
|
||
return bytes;
|
||
}
|
||
|
||
[[nodiscard]] std::filesystem::path ResolveDefaultEnvironmentPath(std::string_view filename) const {
|
||
const std::array<std::filesystem::path, 4> 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<std::filesystem::path> 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<std::uint8_t>* 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<std::uint32_t>(bytes->size());
|
||
if (retainedBytes != nullptr) {
|
||
*retainedBytes = std::move(*bytes);
|
||
data = retainedBytes->data();
|
||
size = static_cast<std::uint32_t>(retainedBytes->size());
|
||
}
|
||
|
||
auto bundle = std::make_unique<ktxreader::Ktx1Bundle>(
|
||
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<char>(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<int>(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<const filament::math::mat3f*>(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<std::size_t>(width) * static_cast<std::size_t>(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<std::uint32_t>(width))
|
||
.height(static_cast<std::uint32_t>(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, std::vector<utils::Entity>, utils::Entity::Hasher> parentToChildren =
|
||
BuildFullParentToChildrenMap(asset);
|
||
|
||
// 2. 建立 ECS 的父子关系映射
|
||
std::unordered_map<MetaCoreId, std::vector<MetaCoreGameObject>> ecsParentToChildren;
|
||
std::vector<MetaCoreId> 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<void(MetaCoreGameObject, utils::Entity)> 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<MetaCoreTransformComponent>();
|
||
transform.Position = translation;
|
||
transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation));
|
||
transform.Scale = scale;
|
||
}
|
||
}
|
||
|
||
// 穿透过渡节点获取当前实体的有效 Filament 子实体列表
|
||
std::vector<utils::Entity> 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;
|
||
}
|
||
|
||
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) {
|
||
if (!AssetLoader_) return;
|
||
(void)compatibilityMeshOnly;
|
||
|
||
LastSyncSnapshot_ = snapshot;
|
||
SynchronizeEnvironment(LastSyncSnapshot_.Environment);
|
||
ObjectWorldMatrices_ = LastSyncSnapshot_.WorldMatrices;
|
||
const bool materialDebugLogging = MetaCoreMaterialDebugLogEnabled();
|
||
|
||
// 构建物体ID到网格快照的快速映射,方便后续查询可见性及材质属性
|
||
std::unordered_map<MetaCoreId, const MetaCoreRenderSyncRenderable*> renderableMap;
|
||
for (const auto& r : snapshot.Renderables) {
|
||
renderableMap[r.ObjectId] = &r;
|
||
}
|
||
|
||
// 定义用于检测场景中某个对象及其所有祖先节点当前在快照里是否应为可见状态的辅助方法
|
||
std::unordered_map<MetaCoreId, bool> visibilityCache;
|
||
std::function<bool(MetaCoreId)> 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_);
|
||
if (useScenePrimaryCamera) {
|
||
MetaCoreSceneView primarySceneView;
|
||
if (MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(LastSyncSnapshot_, primarySceneView)) {
|
||
ApplySceneView(primarySceneView);
|
||
}
|
||
}
|
||
|
||
// 1. 收集当前 snapshot 中所有存在的模型 Root ID
|
||
std::unordered_set<MetaCoreId> 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();
|
||
}
|
||
|
||
// 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。
|
||
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())
|
||
);
|
||
|
||
std::ifstream file(absolutePath, std::ios::binary);
|
||
if (!file.is_open()) {
|
||
std::cerr << "Failed to open file: " << absolutePath << std::endl;
|
||
continue;
|
||
}
|
||
|
||
std::vector<char> buffer((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||
|
||
filament::gltfio::FilamentAsset* asset = AssetLoader_->createAsset(
|
||
reinterpret_cast<const uint8_t*>(buffer.data()),
|
||
static_cast<uint32_t>(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())
|
||
);
|
||
|
||
// 加载贴图等资源。gltfio 不会默认解码 PNG/JPEG,必须注册 TextureProvider。
|
||
const std::string gltfPath = absolutePath.string();
|
||
filament::gltfio::ResourceConfiguration resourceConfig{};
|
||
resourceConfig.engine = Engine_;
|
||
resourceConfig.gltfPath = gltfPath.c_str();
|
||
resourceConfig.normalizeSkinningWeights = true;
|
||
filament::gltfio::ResourceLoader resourceLoader(resourceConfig);
|
||
if (StbTextureProvider_) {
|
||
resourceLoader.addTextureProvider("image/png", StbTextureProvider_);
|
||
resourceLoader.addTextureProvider("image/jpeg", StbTextureProvider_);
|
||
}
|
||
if (!resourceLoader.loadResources(asset)) {
|
||
std::cerr << "Warning: GLTF resources were not fully loaded 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<filament::MaterialInstance*>& 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<MetaCoreModelRootTag>()) {
|
||
nonConstHostRoot.AddComponent<MetaCoreModelRootTag>(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, std::vector<utils::Entity>, utils::Entity::Hasher> parentToChildren =
|
||
BuildFullParentToChildrenMap(asset);
|
||
|
||
// 2. 建立快照中的父子关系映射
|
||
std::unordered_map<MetaCoreId, std::vector<MetaCoreRenderSyncRenderable>> snapParentToChildren;
|
||
for (const auto& r : snapshot.Renderables) {
|
||
if (r.HostRootId == hostRootId && r.ObjectId != hostRootId) {
|
||
snapParentToChildren[r.ParentId].push_back(r);
|
||
}
|
||
}
|
||
|
||
// 3. 递归 DFS 进行精确层级树结构匹配,穿透所有过渡节点
|
||
std::function<void(MetaCoreId, utils::Entity)> matchRecursive =
|
||
[&](MetaCoreId currentId, utils::Entity currentEntity) {
|
||
ObjectToFilamentEntity_[currentId] = { asset, currentEntity };
|
||
|
||
// 穿透过渡节点获取当前实体的有效 Filament 子实体列表
|
||
std::vector<utils::Entity> 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<const filament::math::mat4f*>(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<utils::Entity, MetaCoreId, utils::Entity::Hasher> 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]);
|
||
} else {
|
||
// 回退保护:如果没有找到,则与该资产对应的顶级根宿主保持状态一致
|
||
shouldBeInScene = isObjectVisible(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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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<filament::gltfio::FilamentAsset*, EntityChildrenMap> assetChildMapCache;
|
||
std::unordered_map<utils::Entity, std::vector<utils::Entity>, 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<utils::Entity> 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<utils::Entity>& 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());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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<const filament::math::mat4f*>(glm::value_ptr(CameraModelMatrix_)));
|
||
|
||
// 2. 投影矩阵:回归 Filament 原生设置
|
||
const auto& viewport = View_->getViewport();
|
||
const float aspect = (viewport.height > 0) ? (static_cast<float>(viewport.width) / static_cast<float>(viewport.height)) : 1.0f;
|
||
const double nearClip = std::max(0.0001, static_cast<double>(sceneView.NearClip));
|
||
const double farClip = std::max(nearClip + 0.0001, static_cast<double>(sceneView.FarClip));
|
||
|
||
if (sceneView.ProjectionMode == MetaCoreCameraProjectionMode::Orthographic) {
|
||
const double halfHeight = std::max(0.0001, static_cast<double>(sceneView.OrthographicSize));
|
||
const double halfWidth = halfHeight * std::max(0.0001, static_cast<double>(aspect));
|
||
Camera_->setProjection(
|
||
filament::Camera::Projection::ORTHO,
|
||
-halfWidth,
|
||
halfWidth,
|
||
-halfHeight,
|
||
halfHeight,
|
||
nearClip,
|
||
farClip
|
||
);
|
||
} else {
|
||
Camera_->setProjection(
|
||
std::clamp(static_cast<double>(sceneView.VerticalFieldOfViewDegrees), 1.0, 179.0),
|
||
std::max(0.0001, static_cast<double>(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_;
|
||
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<uint32_t>(io.DisplaySize.x), static_cast<uint32_t>(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();
|
||
|
||
DestroyDeferredRenderTargetResources(true);
|
||
} else if (!PendingRenderTargetResources_.empty()) {
|
||
DebugRenderTargetState("RenderAll beginFrame failed with pending destroy");
|
||
}
|
||
}
|
||
|
||
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<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
if (EnvironmentView_ != nullptr) {
|
||
EnvironmentView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
}
|
||
if (GridView_ != nullptr) {
|
||
GridView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 【关键修复】:如果大小没有改变,不要重新创建纹理!
|
||
// 否则会导致上一帧传给 ImGui 的纹理指针在这一帧被提前销毁,引发 Use-after-free 崩溃。
|
||
if (FilamentTexture_ &&
|
||
FilamentTexture_->getWidth() == static_cast<uint32_t>(width) &&
|
||
FilamentTexture_->getHeight() == static_cast<uint32_t>(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<uint32_t>(width))
|
||
.height(static_cast<uint32_t>(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<uint32_t>(width))
|
||
.height(static_cast<uint32_t>(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<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
if (EnvironmentView_ != nullptr) {
|
||
EnvironmentView_->setRenderTarget(RenderTarget_);
|
||
EnvironmentView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||
}
|
||
if (GridView_ != nullptr) {
|
||
GridView_->setRenderTarget(RenderTarget_);
|
||
GridView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(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_; }
|
||
|
||
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<std::uint8_t, 4> 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<float>(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<const filament::math::mat4f*>(glm::value_ptr(gridTransform))
|
||
);
|
||
}
|
||
}
|
||
|
||
[[nodiscard]] filament::Material* LoadEditorGridMaterial() {
|
||
if (Engine_ == nullptr) {
|
||
return nullptr;
|
||
}
|
||
|
||
const std::array<std::filesystem::path, 8> candidatePaths{
|
||
std::filesystem::path("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<char> buffer(
|
||
(std::istreambuf_iterator<char>(file)),
|
||
std::istreambuf_iterator<char>()
|
||
);
|
||
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<std::vector<MetaCoreEditorGridVertex>, static_cast<std::size_t>(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<float>(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::uint8_t>(std::round((1.0F - smoothFade) * 255.0F));
|
||
};
|
||
|
||
auto& vertices = groupedVertices[static_cast<std::size_t>(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<float>(coordinate),
|
||
static_cast<float>(-EditorGridExtent_),
|
||
static_cast<float>(coordinate),
|
||
static_cast<float>(EditorGridExtent_)
|
||
);
|
||
appendLine(
|
||
lineKind,
|
||
static_cast<float>(-EditorGridExtent_),
|
||
static_cast<float>(coordinate),
|
||
static_cast<float>(EditorGridExtent_),
|
||
static_cast<float>(coordinate)
|
||
);
|
||
}
|
||
appendLine(
|
||
GridLineKind::AxisX,
|
||
static_cast<float>(-EditorGridAxisExtent_),
|
||
0.0F,
|
||
static_cast<float>(EditorGridAxisExtent_),
|
||
0.0F
|
||
);
|
||
appendLine(
|
||
GridLineKind::AxisY,
|
||
0.0F,
|
||
static_cast<float>(-EditorGridAxisExtent_),
|
||
0.0F,
|
||
static_cast<float>(EditorGridAxisExtent_)
|
||
);
|
||
|
||
std::vector<MetaCoreEditorGridVertex> vertices;
|
||
std::vector<std::uint16_t> indices;
|
||
vertices.reserve((EditorGridExtent_ * 2 + 1) * 4 + 4);
|
||
indices.reserve((EditorGridExtent_ * 2 + 1) * 4 + 4);
|
||
|
||
std::array<MetaCoreEditorGridPrimitiveRange, static_cast<std::size_t>(GridLineKind::Count)> ranges{};
|
||
for (std::size_t kindIndex = 0; kindIndex < groupedVertices.size(); ++kindIndex) {
|
||
ranges[kindIndex].Offset = static_cast<std::uint32_t>(indices.size());
|
||
for (const MetaCoreEditorGridVertex& vertex : groupedVertices[kindIndex]) {
|
||
if (vertices.size() > std::numeric_limits<std::uint16_t>::max()) {
|
||
return false;
|
||
}
|
||
vertices.push_back(vertex);
|
||
indices.push_back(static_cast<std::uint16_t>(vertices.size() - 1));
|
||
}
|
||
ranges[kindIndex].Count = static_cast<std::uint32_t>(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<filament::math::float4, static_cast<std::size_t>(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<float>(EditorGridExtent_), static_cast<float>(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<uint16_t>(kindIndex));
|
||
}
|
||
|
||
if (static_cast<int>(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<float>(EditorGridAxisExtent_), static_cast<float>(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<uint16_t>(sourceIndex));
|
||
}
|
||
if (static_cast<int>(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<utils::Entity>& 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, std::vector<utils::Entity>, utils::Entity::Hasher>& parentToChildren,
|
||
std::vector<utils::Entity>& 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, std::vector<utils::Entity>, utils::Entity::Hasher>
|
||
BuildFullParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const {
|
||
std::unordered_map<utils::Entity, std::vector<utils::Entity>, 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<utils::Entity>& outEntities
|
||
) const {
|
||
if (!Engine_ || rootEntity.isNull()) {
|
||
return;
|
||
}
|
||
|
||
auto& rm = Engine_->getRenderableManager();
|
||
std::vector<utils::Entity> pending{rootEntity};
|
||
std::unordered_set<utils::Entity, utils::Entity::Hasher> 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<utils::Entity>& 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<filament::ACCURATE>({ color.x, color.y, color.z });
|
||
}
|
||
|
||
static float BuildFilamentLightIntensity(float intensity) {
|
||
return (intensity > 0.0F ? intensity : 0.0F) * 100000.0F;
|
||
}
|
||
|
||
void SyncSceneLights(const MetaCoreSceneRenderSyncSnapshot& snapshot) {
|
||
if (!Engine_ || !Scene_) {
|
||
return;
|
||
}
|
||
|
||
std::unordered_set<MetaCoreId> 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);
|
||
}
|
||
|
||
for (const MetaCoreRenderSyncLight& light : snapshot.Lights) {
|
||
if (!light.Enabled) {
|
||
continue;
|
||
}
|
||
activeLightIds.insert(light.ObjectId);
|
||
auto lightIt = SceneLightEntities_.find(light.ObjectId);
|
||
if (lightIt == SceneLightEntities_.end()) {
|
||
utils::Entity lightEntity = utils::EntityManager::get().create();
|
||
filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL)
|
||
.color(BuildFilamentLightColor(light.Color))
|
||
.intensity(BuildFilamentLightIntensity(light.Intensity))
|
||
.direction(BuildFilamentLightDirection(light.WorldMatrix))
|
||
.castShadows(true)
|
||
.build(*Engine_, lightEntity);
|
||
Scene_->addEntity(lightEntity);
|
||
EntitiesInScene_.insert(lightEntity);
|
||
lightIt = SceneLightEntities_.emplace(light.ObjectId, lightEntity).first;
|
||
}
|
||
|
||
const auto lightInstance = lightManager.getInstance(lightIt->second);
|
||
if (lightInstance) {
|
||
lightManager.setColor(lightInstance, BuildFilamentLightColor(light.Color));
|
||
lightManager.setIntensity(lightInstance, BuildFilamentLightIntensity(light.Intensity));
|
||
lightManager.setDirection(lightInstance, BuildFilamentLightDirection(light.WorldMatrix));
|
||
}
|
||
}
|
||
|
||
for (auto it = SceneLightEntities_.begin(); it != SceneLightEntities_.end();) {
|
||
if (activeLightIds.contains(it->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<const filament::math::mat4f*>(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::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::filesystem::path LoadedEnvironmentIblPath_{};
|
||
std::filesystem::path LoadedEnvironmentSkyboxPath_{};
|
||
std::vector<std::uint8_t> EnvironmentIblKtxBytes_{};
|
||
std::vector<std::uint8_t> 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;
|
||
|
||
MetaCoreSceneRenderSync RenderSync_{};
|
||
MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{};
|
||
MetaCoreSceneView CurrentSceneView_{};
|
||
bool HasLoggedSnapshotDebug_ = false;
|
||
std::uint64_t LastDebugSnapshotRevision_ = 0;
|
||
std::size_t LastDebugRenderableCount_ = 0;
|
||
std::unordered_map<MetaCoreId, std::string> LastMaterialSyncDebugByObject_{};
|
||
std::unordered_map<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
|
||
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
|
||
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
|
||
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> HostRootSourceModelGuids_;
|
||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> InvalidatedModelGuids_;
|
||
std::unordered_map<MetaCoreId, std::vector<filament::MaterialInstance*>> AssetDuplicatedMaterials_;
|
||
std::unordered_map<std::string, filament::Texture*> MaterialTextureCache_;
|
||
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
|
||
std::unordered_set<utils::Entity, utils::Entity::Hasher> 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<filament::MaterialInstance*, 4> EditorGridMaterialInstances_{};
|
||
|
||
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<PendingRenderTargetResource> PendingRenderTargetResources_;
|
||
std::uint64_t RenderFrameIndex_ = 0;
|
||
std::uint64_t RenderTargetGeneration_ = 0;
|
||
utils::Entity Light_;
|
||
|
||
std::filesystem::path ProjectRootPath_{};
|
||
std::string EmptyString_{};
|
||
MetaCoreRuntimeAssetRegistry::SubscriptionId RuntimeAssetSubscriptionId_ = 0;
|
||
};
|
||
|
||
MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridge()
|
||
: Impl_(std::make_unique<MetaCoreFilamentSceneBridgeImpl>()) {}
|
||
|
||
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::SetRuntimeOverlayRenderCallback(RuntimeOverlayRenderCallback callback) {
|
||
Impl_->SetRuntimeOverlayRenderCallback(std::move(callback));
|
||
}
|
||
|
||
void MetaCoreFilamentSceneBridge::RenderAll() {
|
||
Impl_->RenderAll();
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
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
|