天空盒更新
This commit is contained in:
parent
74416a6b60
commit
6c39655c8f
@ -6781,6 +6781,7 @@ public:
|
||||
}
|
||||
|
||||
MetaCoreEditorStateSnapshot editorStateSnapshot;
|
||||
editorStateSnapshot.SceneSnapshot.Environment = sceneDocument->Environment;
|
||||
editorStateSnapshot.SceneSnapshot.GameObjects = sceneDocument->GameObjects;
|
||||
editorStateSnapshot.SelectionSnapshot = sceneDocument->Selection;
|
||||
editorContext.RestoreStateSnapshot(editorStateSnapshot);
|
||||
@ -6853,7 +6854,9 @@ public:
|
||||
|
||||
MetaCoreSceneDocument sceneDocument;
|
||||
sceneDocument.Name = CurrentSceneName_.empty() ? MetaCoreSceneDisplayNameFromPath(normalizedScenePath) : CurrentSceneName_;
|
||||
sceneDocument.GameObjects = editorContext.GetScene().CaptureSnapshot().GameObjects;
|
||||
const MetaCoreSceneSnapshot sceneSnapshot = editorContext.GetScene().CaptureSnapshot();
|
||||
sceneDocument.Environment = sceneSnapshot.Environment;
|
||||
sceneDocument.GameObjects = sceneSnapshot.GameObjects;
|
||||
sceneDocument.Selection.SelectedObjectIds = editorContext.GetSelectedObjectIds();
|
||||
sceneDocument.Selection.ActiveObjectId = editorContext.GetActiveObjectId();
|
||||
sceneDocument.Selection.SelectionAnchorId = editorContext.GetSelectionAnchorId();
|
||||
|
||||
@ -2610,11 +2610,131 @@ private:
|
||||
|
||||
class MetaCoreScenePanelProvider final : public MetaCoreIEditorPanelProvider {
|
||||
public:
|
||||
std::string GetPanelId() const override { return "Scene"; }
|
||||
std::string GetPanelId() const override { return "SceneSettings"; }
|
||||
std::string GetPanelTitle() const override { return "场景"; }
|
||||
bool IsOpenByDefault() const override { return true; }
|
||||
|
||||
void DrawPanel(MetaCoreEditorContext&) override {
|
||||
void DrawPanel(MetaCoreEditorContext& editorContext) override {
|
||||
MetaCoreScene& scene = editorContext.GetScene();
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
|
||||
std::vector<std::filesystem::path> ktxPaths;
|
||||
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
|
||||
for (const MetaCoreAssetRecord& asset : assetDatabaseService->GetAssetRegistry()) {
|
||||
std::string extension = asset.RelativePath.extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) {
|
||||
return static_cast<char>(std::tolower(value));
|
||||
});
|
||||
const std::filesystem::path normalizedPath = asset.RelativePath.lexically_normal();
|
||||
const auto firstPathComponent = normalizedPath.begin();
|
||||
if (extension == ".ktx" &&
|
||||
!normalizedPath.is_absolute() &&
|
||||
firstPathComponent != normalizedPath.end() &&
|
||||
*firstPathComponent == "Assets") {
|
||||
ktxPaths.push_back(normalizedPath);
|
||||
}
|
||||
}
|
||||
std::sort(ktxPaths.begin(), ktxPaths.end());
|
||||
ktxPaths.erase(std::unique(ktxPaths.begin(), ktxPaths.end()), ktxPaths.end());
|
||||
}
|
||||
|
||||
const auto applyDiscreteChange = [&](const char* label, const std::function<void(MetaCoreSceneEnvironmentSettings&)>& mutator) {
|
||||
(void)editorContext.ExecuteSnapshotCommand(label, [&]() {
|
||||
MetaCoreSceneEnvironmentSettings settings = scene.GetEnvironmentSettings();
|
||||
mutator(settings);
|
||||
return scene.SetEnvironmentSettings(settings);
|
||||
});
|
||||
};
|
||||
|
||||
ImGui::BeginChild("SceneEnvironmentPanel", ImVec2(0.0F, 0.0F), ImGuiChildFlags_Borders);
|
||||
ImGui::TextUnformatted("环境 / 天空盒");
|
||||
ImGui::Separator();
|
||||
|
||||
const MetaCoreSceneEnvironmentSettings& environment = scene.GetEnvironmentSettings();
|
||||
int sourceIndex = environment.Source == MetaCoreSceneEnvironmentSource::ProjectKtxPair ? 1 : 0;
|
||||
const char* sourceLabels[] = {"内置环境", "项目 KTX 对"};
|
||||
if (ImGui::Combo("来源", &sourceIndex, sourceLabels, IM_ARRAYSIZE(sourceLabels))) {
|
||||
applyDiscreteChange("修改场景环境来源", [sourceIndex](MetaCoreSceneEnvironmentSettings& settings) {
|
||||
settings.Source = sourceIndex == 1
|
||||
? MetaCoreSceneEnvironmentSource::ProjectKtxPair
|
||||
: MetaCoreSceneEnvironmentSource::Builtin;
|
||||
});
|
||||
}
|
||||
|
||||
bool skyboxVisible = environment.SkyboxVisible;
|
||||
if (ImGui::Checkbox("显示天空盒", &skyboxVisible)) {
|
||||
applyDiscreteChange("切换天空盒显示", [skyboxVisible](MetaCoreSceneEnvironmentSettings& settings) {
|
||||
settings.SkyboxVisible = skyboxVisible;
|
||||
});
|
||||
}
|
||||
|
||||
if (environment.Source == MetaCoreSceneEnvironmentSource::ProjectKtxPair) {
|
||||
const auto drawKtxSelector = [&](const char* label, const std::filesystem::path& currentPath, bool ibl) {
|
||||
const std::string preview = currentPath.empty() ? "未选择" : currentPath.generic_string();
|
||||
if (!ImGui::BeginCombo(label, preview.c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Selectable("清除", currentPath.empty())) {
|
||||
applyDiscreteChange("修改天空盒资源", [ibl](MetaCoreSceneEnvironmentSettings& settings) {
|
||||
if (ibl) settings.IblKtxPath.clear();
|
||||
else settings.SkyboxKtxPath.clear();
|
||||
});
|
||||
}
|
||||
ImGui::Separator();
|
||||
for (const std::filesystem::path& path : ktxPaths) {
|
||||
const std::string pathLabel = path.generic_string();
|
||||
const bool selected = path == currentPath;
|
||||
if (ImGui::Selectable(pathLabel.c_str(), selected)) {
|
||||
applyDiscreteChange("修改天空盒资源", [ibl, path](MetaCoreSceneEnvironmentSettings& settings) {
|
||||
if (ibl) settings.IblKtxPath = path;
|
||||
else settings.SkyboxKtxPath = path;
|
||||
});
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
};
|
||||
|
||||
drawKtxSelector("IBL KTX", environment.IblKtxPath, true);
|
||||
drawKtxSelector("天空盒 KTX", environment.SkyboxKtxPath, false);
|
||||
if (ktxPaths.empty()) {
|
||||
ImGui::TextDisabled("Assets 下没有可用 KTX 资源。");
|
||||
}
|
||||
}
|
||||
|
||||
float rotationDegrees = environment.RotationDegrees;
|
||||
if (ImGui::DragFloat("水平旋转", &rotationDegrees, 1.0F, 0.0F, 360.0F, "%.1f 度")) {
|
||||
MetaCoreSceneEnvironmentSettings settings = environment;
|
||||
settings.RotationDegrees = rotationDegrees;
|
||||
(void)scene.SetEnvironmentSettings(settings);
|
||||
}
|
||||
MetaCoreTrackInspectorEdit(editorContext, "调整天空盒旋转", true);
|
||||
|
||||
float indirectIntensity = environment.IndirectLightIntensity;
|
||||
if (ImGui::DragFloat("环境光强度", &indirectIntensity, 100.0F, 0.0F, 100000.0F, "%.0f")) {
|
||||
MetaCoreSceneEnvironmentSettings settings = environment;
|
||||
settings.IndirectLightIntensity = indirectIntensity;
|
||||
(void)scene.SetEnvironmentSettings(settings);
|
||||
}
|
||||
MetaCoreTrackInspectorEdit(editorContext, "调整环境光强度", true);
|
||||
|
||||
float skyboxIntensity = environment.SkyboxIntensity;
|
||||
if (ImGui::DragFloat("天空盒亮度", &skyboxIntensity, 100.0F, 0.0F, 100000.0F, "%.0f")) {
|
||||
MetaCoreSceneEnvironmentSettings settings = environment;
|
||||
settings.SkyboxIntensity = skyboxIntensity;
|
||||
(void)scene.SetEnvironmentSettings(settings);
|
||||
}
|
||||
MetaCoreTrackInspectorEdit(editorContext, "调整天空盒亮度", true);
|
||||
|
||||
if (ImGui::Button("恢复内置环境")) {
|
||||
applyDiscreteChange("恢复内置环境", [](MetaCoreSceneEnvironmentSettings& settings) {
|
||||
settings = MetaCoreSceneEnvironmentSettings{};
|
||||
});
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
};
|
||||
|
||||
@ -6324,6 +6444,9 @@ public:
|
||||
moduleRegistry.RegisterMenuProvider(std::make_unique<MetaCoreDefaultMenuProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreHierarchyPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreScenePanelProvider>());
|
||||
// The central Scene / Game viewport is not a dockable provider, but it keeps
|
||||
// its existing visibility state separate from the scene settings panel.
|
||||
moduleRegistry.AccessPanelOpenState("Scene") = true;
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreInspectorPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreProjectPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreConsolePanelProvider>());
|
||||
|
||||
@ -1432,10 +1432,6 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
MetaCoreDrawInfernuxStatusBar(*EditorContext_, statusPosition, statusSize);
|
||||
|
||||
for (const auto& panelProvider : ModuleRegistry_.GetPanelProviders()) {
|
||||
if (panelProvider->GetPanelId() == "Scene") {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool& panelOpen = ModuleRegistry_.AccessPanelOpenState(panelProvider->GetPanelId());
|
||||
if (!panelOpen) {
|
||||
continue;
|
||||
@ -1754,6 +1750,7 @@ void MetaCoreEditorApp::EnsureDefaultDockLayout(unsigned int dockSpaceId, const
|
||||
|
||||
ImGui::DockBuilderDockWindow("层级", leftNodeId);
|
||||
ImGui::DockBuilderDockWindow("检查器", rightNodeId);
|
||||
ImGui::DockBuilderDockWindow("场景", rightNodeId);
|
||||
ImGui::DockBuilderDockWindow("项目", bottomNodeId);
|
||||
ImGui::DockBuilderDockWindow("控制台", bottomNodeId);
|
||||
ImGui::DockBuilderFinish(dockSpaceId);
|
||||
|
||||
@ -175,6 +175,18 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreStateSnapshotEqual(const MetaCoreEditorStateSnapshot& lhs, const MetaCoreEditorStateSnapshot& rhs) {
|
||||
const MetaCoreSceneEnvironmentSettings& lhsEnvironment = lhs.SceneSnapshot.Environment;
|
||||
const MetaCoreSceneEnvironmentSettings& rhsEnvironment = rhs.SceneSnapshot.Environment;
|
||||
if (lhsEnvironment.Source != rhsEnvironment.Source ||
|
||||
lhsEnvironment.IblKtxPath != rhsEnvironment.IblKtxPath ||
|
||||
lhsEnvironment.SkyboxKtxPath != rhsEnvironment.SkyboxKtxPath ||
|
||||
lhsEnvironment.SkyboxVisible != rhsEnvironment.SkyboxVisible ||
|
||||
!MetaCoreNearlyEqual(lhsEnvironment.RotationDegrees, rhsEnvironment.RotationDegrees) ||
|
||||
!MetaCoreNearlyEqual(lhsEnvironment.IndirectLightIntensity, rhsEnvironment.IndirectLightIntensity) ||
|
||||
!MetaCoreNearlyEqual(lhsEnvironment.SkyboxIntensity, rhsEnvironment.SkyboxIntensity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lhs.SceneSnapshot.GameObjects.size() != rhs.SceneSnapshot.GameObjects.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ material {
|
||||
depthWrite : false,
|
||||
depthCulling : true,
|
||||
doubleSided : true,
|
||||
requires : [ color ],
|
||||
parameters : [
|
||||
{ type : float4, name : baseColorFactor }
|
||||
],
|
||||
@ -14,7 +15,7 @@ material {
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.baseColor = materialParams.baseColorFactor;
|
||||
material.baseColor = materialParams.baseColorFactor * getColor();
|
||||
material.baseColor.rgb *= material.baseColor.a;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
@ -88,6 +89,10 @@ 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();
|
||||
@ -292,6 +297,7 @@ public:
|
||||
|
||||
// 创建场景
|
||||
Scene_ = Engine_->createScene();
|
||||
EnvironmentScene_ = Engine_->createScene();
|
||||
LoadDefaultEnvironmentLighting();
|
||||
|
||||
// 创建默认灯光
|
||||
@ -315,6 +321,16 @@ public:
|
||||
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();
|
||||
@ -352,15 +368,27 @@ public:
|
||||
// 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(
|
||||
@ -385,7 +413,6 @@ public:
|
||||
}
|
||||
|
||||
void SetEditorGridVisible(bool visible) {
|
||||
EditorGridVisibleRequested_ = visible;
|
||||
if (Engine_ == nullptr || Scene_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
@ -399,6 +426,11 @@ public:
|
||||
EntitiesInScene_.insert(EditorGridEntity_);
|
||||
EditorGridEntityInScene_ = true;
|
||||
}
|
||||
if (!EditorGridAxisEntityInScene_) {
|
||||
Scene_->addEntity(EditorGridAxisEntity_);
|
||||
EntitiesInScene_.insert(EditorGridAxisEntity_);
|
||||
EditorGridAxisEntityInScene_ = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -407,10 +439,33 @@ public:
|
||||
EntitiesInScene_.erase(EditorGridEntity_);
|
||||
EditorGridEntityInScene_ = false;
|
||||
}
|
||||
if (EditorGridAxisEntityInScene_) {
|
||||
Scene_->remove(EditorGridAxisEntity_);
|
||||
EntitiesInScene_.erase(EditorGridAxisEntity_);
|
||||
EditorGridAxisEntityInScene_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool VerifyEditorGridVisibleForTesting() const {
|
||||
return EditorGridEntityInScene_;
|
||||
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() {
|
||||
@ -548,6 +603,14 @@ public:
|
||||
}
|
||||
// 销毁 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;
|
||||
@ -565,6 +628,10 @@ public:
|
||||
}
|
||||
|
||||
// 销毁其他资源
|
||||
if (EnvironmentScene_) {
|
||||
Engine_->destroy(EnvironmentScene_);
|
||||
EnvironmentScene_ = nullptr;
|
||||
}
|
||||
Engine_->destroy(Scene_);
|
||||
Engine_->destroy(Renderer_);
|
||||
Engine_->destroy(SwapChain_);
|
||||
@ -581,9 +648,10 @@ public:
|
||||
void SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||||
if (ProjectRootPath_ != projectRootPath) {
|
||||
DestroyMaterialTextureCache();
|
||||
DestroyEnvironmentLighting();
|
||||
}
|
||||
ProjectRootPath_ = projectRootPath;
|
||||
LoadDefaultEnvironmentLighting();
|
||||
SynchronizeEnvironment(ActiveEnvironmentSettings_);
|
||||
}
|
||||
|
||||
void DestroyMaterialTextureCache() {
|
||||
@ -625,18 +693,27 @@ public:
|
||||
std::filesystem::path("build_filament") / "libs" / "ktxreader" / filename
|
||||
};
|
||||
|
||||
const std::filesystem::path cwd = std::filesystem::current_path();
|
||||
for (const std::filesystem::path& relativePath : candidates) {
|
||||
const std::filesystem::path absolutePath = (cwd / relativePath).lexically_normal();
|
||||
if (std::filesystem::exists(absolutePath)) {
|
||||
return absolutePath;
|
||||
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());
|
||||
}
|
||||
|
||||
const std::filesystem::path fromProjectRoot = (ProjectRootPath_.empty()
|
||||
? std::filesystem::path{}
|
||||
: ProjectRootPath_ / ".." / relativePath).lexically_normal();
|
||||
if (!fromProjectRoot.empty() && std::filesystem::exists(fromProjectRoot)) {
|
||||
return fromProjectRoot;
|
||||
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 {};
|
||||
@ -669,50 +746,242 @@ public:
|
||||
return ktxreader::Ktx1Reader::createTexture(Engine_, bundle.release(), srgb);
|
||||
}
|
||||
|
||||
void LoadDefaultEnvironmentLighting() {
|
||||
if (Engine_ == nullptr || Scene_ == nullptr || EnvironmentLoaded_) {
|
||||
return;
|
||||
[[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;
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path iblPath = ResolveDefaultEnvironmentPath("lightroom_14b_ibl.ktx");
|
||||
const std::filesystem::path skyboxPath = ResolveDefaultEnvironmentPath("lightroom_14b_skybox.ktx");
|
||||
if (iblPath.empty() || skyboxPath.empty()) {
|
||||
MetaCoreFilamentDebugLog("Default IBL assets not found; falling back to solid clear color");
|
||||
return;
|
||||
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 &&
|
||||
EnvironmentScene_ != 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 (EnvironmentScene_ != nullptr) {
|
||||
if (shouldShow && EnvironmentScene_->getSkybox() != EnvironmentSkybox_) {
|
||||
EnvironmentScene_->setSkybox(EnvironmentSkybox_);
|
||||
} else if (!shouldShow && EnvironmentScene_->getSkybox() == EnvironmentSkybox_) {
|
||||
EnvironmentScene_->setSkybox(nullptr);
|
||||
}
|
||||
}
|
||||
EnvironmentBackgroundVisible_ = shouldShow;
|
||||
}
|
||||
|
||||
bool RebuildEnvironmentSkybox() {
|
||||
if (Engine_ == nullptr || EnvironmentSkyboxTexture_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (EnvironmentScene_ != nullptr && EnvironmentScene_->getSkybox() == EnvironmentSkybox_) {
|
||||
EnvironmentScene_->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, kEnvironmentSkyboxLayerMask);
|
||||
UpdateEnvironmentBackgroundVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadEnvironmentResources(
|
||||
const std::filesystem::path& iblPath,
|
||||
const std::filesystem::path& skyboxPath
|
||||
) {
|
||||
filament::math::float3 sphericalHarmonics[9]{};
|
||||
EnvironmentIblTexture_ = LoadKtx1Texture(iblPath, false, sphericalHarmonics);
|
||||
EnvironmentSkyboxTexture_ = LoadKtx1Texture(skyboxPath, false);
|
||||
if (EnvironmentIblTexture_ == nullptr || EnvironmentSkyboxTexture_ == nullptr) {
|
||||
MetaCoreFilamentDebugLog("Default IBL KTX load failed");
|
||||
DestroyEnvironmentLighting();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnvironmentIndirectLight_ = filament::IndirectLight::Builder()
|
||||
.reflections(EnvironmentIblTexture_)
|
||||
.irradiance(3, sphericalHarmonics)
|
||||
.intensity(EnvironmentIntensity_)
|
||||
.intensity(ActiveEnvironmentSettings_.IndirectLightIntensity)
|
||||
.build(*Engine_);
|
||||
EnvironmentSkybox_ = filament::Skybox::Builder()
|
||||
.environment(EnvironmentSkyboxTexture_)
|
||||
.intensity(EnvironmentIntensity_)
|
||||
.build(*Engine_);
|
||||
|
||||
if (EnvironmentIndirectLight_ == nullptr || EnvironmentSkybox_ == nullptr) {
|
||||
MetaCoreFilamentDebugLog("Default IBL object build failed");
|
||||
DestroyEnvironmentLighting();
|
||||
return;
|
||||
if (EnvironmentIndirectLight_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Scene_->setIndirectLight(EnvironmentIndirectLight_);
|
||||
Scene_->setSkybox(EnvironmentSkybox_);
|
||||
EnvironmentLoaded_ = true;
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Default IBL loaded ibl=\"" + iblPath.generic_string() +
|
||||
"\" skybox=\"" + skyboxPath.generic_string() + "\""
|
||||
);
|
||||
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() {
|
||||
@ -720,9 +989,9 @@ public:
|
||||
if (Scene_->getIndirectLight() == EnvironmentIndirectLight_) {
|
||||
Scene_->setIndirectLight(nullptr);
|
||||
}
|
||||
if (Scene_->getSkybox() == EnvironmentSkybox_) {
|
||||
Scene_->setSkybox(nullptr);
|
||||
}
|
||||
}
|
||||
if (EnvironmentScene_ != nullptr && EnvironmentScene_->getSkybox() == EnvironmentSkybox_) {
|
||||
EnvironmentScene_->setSkybox(nullptr);
|
||||
}
|
||||
|
||||
if (Engine_ != nullptr) {
|
||||
@ -745,6 +1014,9 @@ public:
|
||||
}
|
||||
}
|
||||
EnvironmentLoaded_ = false;
|
||||
EnvironmentBackgroundVisible_ = false;
|
||||
LoadedEnvironmentIblPath_.clear();
|
||||
LoadedEnvironmentSkyboxPath_.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]] filament::Skybox* GetOrCreateSolidColorSkybox(const MetaCoreSceneView& sceneView) {
|
||||
@ -984,6 +1256,7 @@ public:
|
||||
(void)compatibilityMeshOnly;
|
||||
|
||||
LastSyncSnapshot_ = snapshot;
|
||||
SynchronizeEnvironment(LastSyncSnapshot_.Environment);
|
||||
ObjectWorldMatrices_ = LastSyncSnapshot_.WorldMatrices;
|
||||
const bool materialDebugLogging = MetaCoreMaterialDebugLogEnabled();
|
||||
|
||||
@ -1853,20 +2126,16 @@ public:
|
||||
void ApplySceneView(const MetaCoreSceneView& sceneView) {
|
||||
if (!Camera_ || !View_) return;
|
||||
CurrentSceneView_ = sceneView;
|
||||
UpdateEditorGridTransform(sceneView);
|
||||
if (Scene_ != nullptr) {
|
||||
filament::Skybox* skybox = nullptr;
|
||||
if (sceneView.ClearFlags == MetaCoreCameraClearFlags::Skybox) {
|
||||
skybox = EnvironmentSkybox_ != nullptr ? EnvironmentSkybox_ : GetOrCreateSolidColorSkybox(sceneView);
|
||||
} else if (sceneView.ClearFlags == MetaCoreCameraClearFlags::SolidColor) {
|
||||
skybox = GetOrCreateSolidColorSkybox(sceneView);
|
||||
}
|
||||
Scene_->setSkybox(skybox);
|
||||
Scene_->setSkybox(nullptr);
|
||||
}
|
||||
UpdateEnvironmentBackgroundVisibility();
|
||||
|
||||
// 1. 视图矩阵同步
|
||||
const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
|
||||
const glm::mat4 cameraModelMatrix = glm::inverse(viewMatrix);
|
||||
Camera_->setModelMatrix(*reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(cameraModelMatrix)));
|
||||
CameraModelMatrix_ = glm::inverse(viewMatrix);
|
||||
Camera_->setModelMatrix(*reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(CameraModelMatrix_)));
|
||||
|
||||
// 2. 投影矩阵:回归 Filament 原生设置
|
||||
const auto& viewport = View_->getViewport();
|
||||
@ -1932,15 +2201,40 @@ public:
|
||||
}
|
||||
|
||||
if (Renderer_->beginFrame(SwapChain_)) {
|
||||
if (RenderTarget_) {
|
||||
// 1. 渲染 3D 离屏视口 (输出到 RenderTarget)
|
||||
const bool renderEnvironment = EnvironmentBackgroundVisible_ && EnvironmentView_ != nullptr;
|
||||
const bool renderEditorGrid = GridView_ != nullptr &&
|
||||
(EditorGridEntityInScene_ || EditorGridAxisEntityInScene_);
|
||||
const auto renderScenePass = [&](bool outputToRenderTarget) {
|
||||
filament::Renderer::ClearOptions options = BuildSceneClearOptions();
|
||||
Renderer_->setClearOptions(options);
|
||||
|
||||
Renderer_->render(View_);
|
||||
|
||||
if (renderEnvironment) {
|
||||
options.clear = false;
|
||||
Renderer_->setClearOptions(options);
|
||||
const glm::mat4 environmentCameraMatrix = glm::rotate(
|
||||
glm::mat4(1.0F),
|
||||
glm::radians(-ActiveEnvironmentSettings_.RotationDegrees),
|
||||
glm::vec3(0.0F, 0.0F, 1.0F)
|
||||
) * CameraModelMatrix_;
|
||||
Camera_->setModelMatrix(*reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(environmentCameraMatrix)));
|
||||
Renderer_->render(EnvironmentView_);
|
||||
Camera_->setModelMatrix(*reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(CameraModelMatrix_)));
|
||||
}
|
||||
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);
|
||||
@ -1948,10 +2242,7 @@ public:
|
||||
}
|
||||
} else {
|
||||
// 3. 直接上屏渲染
|
||||
filament::Renderer::ClearOptions options = BuildSceneClearOptions();
|
||||
Renderer_->setClearOptions(options);
|
||||
|
||||
Renderer_->render(View_);
|
||||
renderScenePass(false);
|
||||
}
|
||||
|
||||
Renderer_->endFrame();
|
||||
@ -1975,6 +2266,12 @@ public:
|
||||
" 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;
|
||||
}
|
||||
|
||||
@ -2018,7 +2315,17 @@ public:
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@ -2160,6 +2467,7 @@ public:
|
||||
private:
|
||||
struct MetaCoreEditorGridVertex {
|
||||
filament::math::float3 Position{};
|
||||
std::array<std::uint8_t, 4> Color{255, 255, 255, 255};
|
||||
};
|
||||
|
||||
struct MetaCoreEditorGridPrimitiveRange {
|
||||
@ -2167,6 +2475,58 @@ private:
|
||||
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;
|
||||
@ -2255,8 +2615,6 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int gridExtent = 100;
|
||||
constexpr int majorStep = 10;
|
||||
enum class GridLineKind : std::size_t {
|
||||
Minor = 0,
|
||||
Major = 1,
|
||||
@ -2273,40 +2631,72 @@ private:
|
||||
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}});
|
||||
vertices.push_back(MetaCoreEditorGridVertex{filament::math::float3{x1, y1, 0.0F}});
|
||||
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 = -gridExtent; coordinate <= gridExtent; ++coordinate) {
|
||||
const bool isMajor = coordinate != 0 && (coordinate % majorStep) == 0;
|
||||
const GridLineKind verticalKind = coordinate == 0
|
||||
? GridLineKind::AxisY
|
||||
: (isMajor ? GridLineKind::Major : GridLineKind::Minor);
|
||||
appendLine(
|
||||
verticalKind,
|
||||
static_cast<float>(coordinate),
|
||||
static_cast<float>(-gridExtent),
|
||||
static_cast<float>(coordinate),
|
||||
static_cast<float>(gridExtent)
|
||||
);
|
||||
for (int coordinate = -EditorGridExtent_; coordinate <= EditorGridExtent_; ++coordinate) {
|
||||
if (coordinate == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GridLineKind horizontalKind = coordinate == 0
|
||||
? GridLineKind::AxisX
|
||||
: (isMajor ? GridLineKind::Major : GridLineKind::Minor);
|
||||
const GridLineKind lineKind = (coordinate % EditorGridMajorStep_) == 0
|
||||
? GridLineKind::Major
|
||||
: GridLineKind::Minor;
|
||||
appendLine(
|
||||
horizontalKind,
|
||||
static_cast<float>(-gridExtent),
|
||||
lineKind,
|
||||
static_cast<float>(coordinate),
|
||||
static_cast<float>(gridExtent),
|
||||
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((gridExtent * 2 + 1) * 4);
|
||||
indices.reserve((gridExtent * 2 + 1) * 4);
|
||||
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) {
|
||||
@ -2335,6 +2725,14 @@ private:
|
||||
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())
|
||||
@ -2400,15 +2798,16 @@ private:
|
||||
}
|
||||
|
||||
EditorGridEntity_ = utils::EntityManager::get().create();
|
||||
auto builder = filament::RenderableManager::Builder(static_cast<std::size_t>(GridLineKind::Count));
|
||||
builder
|
||||
.boundingBox({{0.0F, 0.0F, 0.0F}, {static_cast<float>(gridExtent), static_cast<float>(gridExtent), 0.05F}})
|
||||
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 < ranges.size(); ++kindIndex) {
|
||||
builder
|
||||
for (std::size_t kindIndex = 0; kindIndex < 2; ++kindIndex) {
|
||||
gridBuilder
|
||||
.geometry(
|
||||
kindIndex,
|
||||
filament::RenderableManager::PrimitiveType::LINES,
|
||||
@ -2421,10 +2820,48 @@ private:
|
||||
.blendOrder(kindIndex, static_cast<uint16_t>(kindIndex));
|
||||
}
|
||||
|
||||
if (static_cast<int>(builder.build(*Engine_, EditorGridEntity_)) != 0) {
|
||||
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;
|
||||
@ -2440,17 +2877,27 @@ private:
|
||||
EntitiesInScene_.erase(EditorGridEntity_);
|
||||
EditorGridEntityInScene_ = false;
|
||||
}
|
||||
|
||||
if (EditorGridEntity_) {
|
||||
auto& renderableManager = Engine_->getRenderableManager();
|
||||
if (renderableManager.getInstance(EditorGridEntity_)) {
|
||||
renderableManager.destroy(EditorGridEntity_);
|
||||
}
|
||||
Engine_->destroy(EditorGridEntity_);
|
||||
utils::EntityManager::get().destroy(EditorGridEntity_);
|
||||
EditorGridEntity_ = {};
|
||||
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);
|
||||
@ -2814,11 +3261,38 @@ private:
|
||||
}
|
||||
|
||||
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
|
||||
@ -2838,16 +3312,24 @@ private:
|
||||
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};
|
||||
float EnvironmentIntensity_ = 30000.0f;
|
||||
bool EnvironmentLoaded_ = false;
|
||||
bool EnvironmentBackgroundVisible_ = false;
|
||||
MetaCoreSceneEnvironmentSettings ActiveEnvironmentSettings_{};
|
||||
std::filesystem::path LoadedEnvironmentIblPath_{};
|
||||
std::filesystem::path LoadedEnvironmentSkyboxPath_{};
|
||||
std::string LastEnvironmentValidationFailure_{};
|
||||
glm::mat4 CameraModelMatrix_{1.0F};
|
||||
|
||||
filament::gltfio::AssetLoader* AssetLoader_ = nullptr;
|
||||
filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr;
|
||||
@ -2871,10 +3353,11 @@ private:
|
||||
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
|
||||
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;
|
||||
|
||||
bool EditorGridVisibleRequested_ = false;
|
||||
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;
|
||||
@ -2985,4 +3468,16 @@ 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
|
||||
|
||||
@ -77,6 +77,7 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
|
||||
MetaCoreSceneRenderSyncSnapshot snapshot;
|
||||
snapshot.FrameId = ++FrameId_;
|
||||
snapshot.SceneRevision = scene.GetRevision();
|
||||
snapshot.Environment = scene.GetEnvironmentSettings();
|
||||
|
||||
// 预处理:构建子树模型网格节点计数表,支撑在没有 Tag 时的 Fallback 回溯
|
||||
std::unordered_map<MetaCoreId, std::unordered_map<std::string, int>> subtreeModelCounts;
|
||||
|
||||
@ -45,6 +45,9 @@ public:
|
||||
[[nodiscard]] float GetDefaultLightIntensityForTesting() const;
|
||||
[[nodiscard]] bool VerifyEntityInSceneForTesting(MetaCoreId objectId) const;
|
||||
[[nodiscard]] bool VerifyEditorGridVisibleForTesting() const;
|
||||
[[nodiscard]] bool VerifyEnvironmentLoadedForTesting() const;
|
||||
[[nodiscard]] bool VerifyEnvironmentBackgroundVisibleForTesting() const;
|
||||
[[nodiscard]] float GetEnvironmentIndirectLightIntensityForTesting() const;
|
||||
|
||||
private:
|
||||
class MetaCoreFilamentSceneBridgeImpl;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
#include "MetaCoreScene/MetaCoreComponents.h"
|
||||
#include "MetaCoreScene/MetaCoreSceneDocument.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
@ -76,6 +77,7 @@ struct MetaCoreRenderSyncLight {
|
||||
};
|
||||
|
||||
struct MetaCoreSceneRenderSyncSnapshot {
|
||||
MetaCoreSceneEnvironmentSettings Environment{};
|
||||
std::unordered_map<MetaCoreId, glm::mat4> WorldMatrices{};
|
||||
std::unordered_map<MetaCoreId, glm::mat4> LocalMatrices{};
|
||||
std::vector<MetaCoreRenderSyncRenderable> Renderables{};
|
||||
|
||||
@ -23,6 +23,19 @@ std::string MetaCoreBuildCopyName(const std::string& name) {
|
||||
return name + " Copy";
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreEnvironmentSettingsEqual(
|
||||
const MetaCoreSceneEnvironmentSettings& lhs,
|
||||
const MetaCoreSceneEnvironmentSettings& rhs
|
||||
) {
|
||||
return lhs.Source == rhs.Source &&
|
||||
lhs.IblKtxPath == rhs.IblKtxPath &&
|
||||
lhs.SkyboxKtxPath == rhs.SkyboxKtxPath &&
|
||||
lhs.SkyboxVisible == rhs.SkyboxVisible &&
|
||||
lhs.RotationDegrees == rhs.RotationDegrees &&
|
||||
lhs.IndirectLightIntensity == rhs.IndirectLightIntensity &&
|
||||
lhs.SkyboxIntensity == rhs.SkyboxIntensity;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MetaCoreGameObject MetaCoreScene::CreateGameObject(const std::string& name, MetaCoreId parentId) {
|
||||
@ -405,8 +418,18 @@ bool MetaCoreScene::ReparentGameObjects(const std::vector<MetaCoreId>& objectIds
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetaCoreScene::SetEnvironmentSettings(const MetaCoreSceneEnvironmentSettings& settings) {
|
||||
if (MetaCoreEnvironmentSettingsEqual(EnvironmentSettings_, settings)) {
|
||||
return false;
|
||||
}
|
||||
EnvironmentSettings_ = settings;
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
||||
MetaCoreSceneSnapshot snapshot;
|
||||
snapshot.Environment = EnvironmentSettings_;
|
||||
auto objects = GetGameObjects();
|
||||
snapshot.GameObjects.reserve(objects.size());
|
||||
for (auto obj : objects) {
|
||||
@ -439,6 +462,7 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
||||
void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
||||
Registry_.clear();
|
||||
IdToEntity_.clear();
|
||||
EnvironmentSettings_ = snapshot.Environment;
|
||||
|
||||
MetaCoreId maxId = 0;
|
||||
for (const MetaCoreGameObjectData& data : snapshot.GameObjects) {
|
||||
|
||||
@ -32,6 +32,33 @@ static MetaCoreAssetGuid StringToGuid(const std::string& str) {
|
||||
return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{});
|
||||
}
|
||||
|
||||
static json SceneEnvironmentToJson(const MetaCoreSceneEnvironmentSettings& environment) {
|
||||
json environmentJson = json::object();
|
||||
environmentJson["Source"] = static_cast<int>(environment.Source);
|
||||
environmentJson["IblKtxPath"] = environment.IblKtxPath.generic_string();
|
||||
environmentJson["SkyboxKtxPath"] = environment.SkyboxKtxPath.generic_string();
|
||||
environmentJson["SkyboxVisible"] = environment.SkyboxVisible;
|
||||
environmentJson["RotationDegrees"] = environment.RotationDegrees;
|
||||
environmentJson["IndirectLightIntensity"] = environment.IndirectLightIntensity;
|
||||
environmentJson["SkyboxIntensity"] = environment.SkyboxIntensity;
|
||||
return environmentJson;
|
||||
}
|
||||
|
||||
static MetaCoreSceneEnvironmentSettings JsonToSceneEnvironment(const json& environmentJson) {
|
||||
MetaCoreSceneEnvironmentSettings environment;
|
||||
if (!environmentJson.is_object()) {
|
||||
return environment;
|
||||
}
|
||||
if (environmentJson.contains("Source")) environment.Source = static_cast<MetaCoreSceneEnvironmentSource>(environmentJson["Source"].get<int>());
|
||||
if (environmentJson.contains("IblKtxPath")) environment.IblKtxPath = environmentJson["IblKtxPath"].get<std::string>();
|
||||
if (environmentJson.contains("SkyboxKtxPath")) environment.SkyboxKtxPath = environmentJson["SkyboxKtxPath"].get<std::string>();
|
||||
if (environmentJson.contains("SkyboxVisible")) environment.SkyboxVisible = environmentJson["SkyboxVisible"].get<bool>();
|
||||
if (environmentJson.contains("RotationDegrees")) environment.RotationDegrees = environmentJson["RotationDegrees"].get<float>();
|
||||
if (environmentJson.contains("IndirectLightIntensity")) environment.IndirectLightIntensity = environmentJson["IndirectLightIntensity"].get<float>();
|
||||
if (environmentJson.contains("SkyboxIntensity")) environment.SkyboxIntensity = environmentJson["SkyboxIntensity"].get<float>();
|
||||
return environment;
|
||||
}
|
||||
|
||||
// 辅助方法:将单个 MetaCoreGameObjectData 转换为 json 格式
|
||||
static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCoreTypeRegistry& registry) {
|
||||
json objJson = json::object();
|
||||
@ -340,6 +367,8 @@ bool MetaCoreSceneSerializer::SaveSceneToJson(
|
||||
for (const auto& field : sceneDesc->Fields) {
|
||||
if (field.Name == "Name") {
|
||||
sceneJson["SceneName"] = sceneDocument.Name;
|
||||
} else if (field.Name == "Environment") {
|
||||
sceneJson["Environment"] = SceneEnvironmentToJson(sceneDocument.Environment);
|
||||
} else if (field.Name == "Selection") {
|
||||
const MetaCoreStructDescriptor* selDesc = registry.FindStruct<MetaCoreEditorSelectionSnapshot>();
|
||||
if (selDesc) {
|
||||
@ -400,6 +429,8 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
|
||||
for (const auto& field : sceneDesc->Fields) {
|
||||
if (field.Name == "Name" && sceneJson.contains("SceneName")) {
|
||||
sceneDocument.Name = sceneJson["SceneName"].get<std::string>();
|
||||
} else if (field.Name == "Environment" && sceneJson.contains("Environment")) {
|
||||
sceneDocument.Environment = JsonToSceneEnvironment(sceneJson["Environment"]);
|
||||
} else if (field.Name == "Selection" && sceneJson.contains("Selection")) {
|
||||
const auto& selectionJson = sceneJson["Selection"];
|
||||
const MetaCoreStructDescriptor* selDesc = registry.FindStruct<MetaCoreEditorSelectionSnapshot>();
|
||||
|
||||
@ -14,6 +14,9 @@ MC_STRUCT()
|
||||
struct MetaCoreSceneSnapshot {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
MetaCoreSceneEnvironmentSettings Environment{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreGameObjectData> GameObjects;
|
||||
};
|
||||
@ -40,6 +43,9 @@ public:
|
||||
[[nodiscard]] std::vector<MetaCoreId> DuplicateGameObjects(const std::vector<MetaCoreId>& objectIds);
|
||||
bool ReparentGameObjects(const std::vector<MetaCoreId>& objectIds, MetaCoreId newParentId, bool keepWorldTransform);
|
||||
|
||||
[[nodiscard]] const MetaCoreSceneEnvironmentSettings& GetEnvironmentSettings() const { return EnvironmentSettings_; }
|
||||
[[nodiscard]] bool SetEnvironmentSettings(const MetaCoreSceneEnvironmentSettings& settings);
|
||||
|
||||
[[nodiscard]] MetaCoreSceneSnapshot CaptureSnapshot() const;
|
||||
void RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot);
|
||||
|
||||
@ -54,6 +60,7 @@ private:
|
||||
|
||||
entt::registry Registry_;
|
||||
std::unordered_map<MetaCoreId, entt::entity> IdToEntity_;
|
||||
MetaCoreSceneEnvironmentSettings EnvironmentSettings_{};
|
||||
std::uint64_t Revision_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "MetaCoreScene/MetaCoreGameObject.h"
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -26,6 +27,38 @@ struct MetaCoreEditorSelectionSnapshot {
|
||||
MetaCoreId SelectionAnchorId = 0;
|
||||
};
|
||||
|
||||
MC_ENUM()
|
||||
enum class MetaCoreSceneEnvironmentSource {
|
||||
Builtin = 0,
|
||||
ProjectKtxPair
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreSceneEnvironmentSettings {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
MetaCoreSceneEnvironmentSource Source = MetaCoreSceneEnvironmentSource::Builtin;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path IblKtxPath{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path SkyboxKtxPath{};
|
||||
|
||||
MC_PROPERTY()
|
||||
bool SkyboxVisible = true;
|
||||
|
||||
MC_PROPERTY()
|
||||
float RotationDegrees = 0.0F;
|
||||
|
||||
MC_PROPERTY()
|
||||
float IndirectLightIntensity = 30000.0F;
|
||||
|
||||
MC_PROPERTY()
|
||||
float SkyboxIntensity = 30000.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreSceneDocument {
|
||||
MC_GENERATED_BODY()
|
||||
@ -33,6 +66,9 @@ struct MetaCoreSceneDocument {
|
||||
MC_PROPERTY()
|
||||
std::string Name{};
|
||||
|
||||
MC_PROPERTY()
|
||||
MetaCoreSceneEnvironmentSettings Environment{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreGameObjectData> GameObjects{};
|
||||
|
||||
|
||||
@ -4537,6 +4537,133 @@ void MetaCoreTestHotReloadQueuesExternalMoveUntilTick() {
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestSceneEnvironmentSettings() {
|
||||
const std::filesystem::path tempRoot = std::filesystem::temp_directory_path() / "MetaCoreSceneEnvironmentSmoke";
|
||||
std::filesystem::remove_all(tempRoot);
|
||||
std::filesystem::create_directories(tempRoot / "Assets" / "Environments");
|
||||
|
||||
const std::filesystem::path sourceEnvironmentDirectory =
|
||||
MetaCoreTestRepositoryRoot() / "third_party" / "filament_installed" / "bin" / "assets" / "ibl" / "lightroom_14b";
|
||||
const std::filesystem::path sourceIbl = sourceEnvironmentDirectory / "lightroom_14b_ibl.ktx";
|
||||
const std::filesystem::path sourceSkybox = sourceEnvironmentDirectory / "lightroom_14b_skybox.ktx";
|
||||
MetaCoreExpect(std::filesystem::exists(sourceIbl) && std::filesystem::exists(sourceSkybox), "应提供默认 KTX 环境资源");
|
||||
const std::filesystem::path projectIbl = tempRoot / "Assets" / "Environments" / "studio_ibl.ktx";
|
||||
const std::filesystem::path projectSkybox = tempRoot / "Assets" / "Environments" / "studio_skybox.ktx";
|
||||
std::filesystem::copy_file(sourceIbl, projectIbl, std::filesystem::copy_options::overwrite_existing);
|
||||
std::filesystem::copy_file(sourceSkybox, projectSkybox, std::filesystem::copy_options::overwrite_existing);
|
||||
|
||||
MetaCore::MetaCoreSceneEnvironmentSettings environment;
|
||||
environment.Source = MetaCore::MetaCoreSceneEnvironmentSource::ProjectKtxPair;
|
||||
environment.IblKtxPath = std::filesystem::path("Assets") / "Environments" / "studio_ibl.ktx";
|
||||
environment.SkyboxKtxPath = std::filesystem::path("Assets") / "Environments" / "studio_skybox.ktx";
|
||||
environment.SkyboxVisible = true;
|
||||
environment.RotationDegrees = 137.5F;
|
||||
environment.IndirectLightIntensity = 25000.0F;
|
||||
environment.SkyboxIntensity = 7500.0F;
|
||||
|
||||
MetaCore::MetaCoreScene scene;
|
||||
MetaCoreExpect(scene.SetEnvironmentSettings(environment), "应能设置场景环境");
|
||||
const MetaCore::MetaCoreSceneSnapshot environmentSnapshot = scene.CaptureSnapshot();
|
||||
(void)scene.SetEnvironmentSettings(MetaCore::MetaCoreSceneEnvironmentSettings{});
|
||||
scene.RestoreSnapshot(environmentSnapshot);
|
||||
MetaCoreExpect(scene.GetEnvironmentSettings().Source == MetaCore::MetaCoreSceneEnvironmentSource::ProjectKtxPair, "场景快照应恢复环境来源");
|
||||
MetaCoreExpect(scene.GetEnvironmentSettings().IblKtxPath == environment.IblKtxPath, "场景快照应恢复 IBL 路径");
|
||||
MetaCoreExpect(std::abs(scene.GetEnvironmentSettings().SkyboxIntensity - environment.SkyboxIntensity) <= 0.0001F, "场景快照应恢复天空盒亮度");
|
||||
|
||||
MetaCore::MetaCoreWindow commandWindow;
|
||||
MetaCore::MetaCoreRenderDevice commandRenderDevice;
|
||||
MetaCore::MetaCoreEditorViewportRenderer commandViewportRenderer;
|
||||
MetaCore::MetaCoreLogService commandLogService;
|
||||
MetaCore::MetaCoreEditorModuleRegistry commandModuleRegistry;
|
||||
MetaCore::MetaCoreEditorContext commandContext(
|
||||
commandWindow,
|
||||
commandRenderDevice,
|
||||
commandViewportRenderer,
|
||||
scene,
|
||||
commandLogService,
|
||||
commandModuleRegistry
|
||||
);
|
||||
MetaCoreExpect(commandContext.ExecuteSnapshotCommand("修改天空盒显示", [&]() {
|
||||
MetaCore::MetaCoreSceneEnvironmentSettings changed = scene.GetEnvironmentSettings();
|
||||
changed.SkyboxVisible = false;
|
||||
return scene.SetEnvironmentSettings(changed);
|
||||
}), "环境设置修改应进入撤销栈");
|
||||
MetaCoreExpect(!scene.GetEnvironmentSettings().SkyboxVisible, "执行环境设置命令后应更新天空盒显示");
|
||||
MetaCoreExpect(commandContext.UndoCommand(), "环境设置应支持撤销");
|
||||
MetaCoreExpect(scene.GetEnvironmentSettings().SkyboxVisible, "撤销后应恢复天空盒显示");
|
||||
MetaCoreExpect(commandContext.RedoCommand(), "环境设置应支持重做");
|
||||
MetaCoreExpect(!scene.GetEnvironmentSettings().SkyboxVisible, "重做后应再次关闭天空盒显示");
|
||||
(void)scene.SetEnvironmentSettings(environment);
|
||||
|
||||
MetaCore::MetaCoreSceneRenderSync renderSync;
|
||||
const MetaCore::MetaCoreSceneRenderSyncSnapshot renderSnapshot = renderSync.BuildSnapshot(scene);
|
||||
MetaCoreExpect(renderSnapshot.Environment.IblKtxPath == environment.IblKtxPath, "渲染快照应包含环境路径");
|
||||
MetaCoreExpect(renderSnapshot.Renderables.empty(), "环境设置不应生成可渲染场景对象");
|
||||
|
||||
MetaCore::MetaCoreTypeRegistry registry;
|
||||
MetaCore::MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
MetaCore::MetaCoreRegisterSceneGeneratedTypes(registry);
|
||||
MetaCore::MetaCoreSceneDocument document;
|
||||
document.Name = "EnvironmentScene";
|
||||
document.Environment = environment;
|
||||
const std::filesystem::path jsonPath = tempRoot / "Environment.mcscene.json";
|
||||
MetaCoreExpect(MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(jsonPath, document, registry), "环境场景 JSON 应可保存");
|
||||
const auto loadedDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(jsonPath, registry);
|
||||
MetaCoreExpect(loadedDocument.has_value(), "环境场景 JSON 应可读取");
|
||||
MetaCoreExpect(loadedDocument->Environment.SkyboxKtxPath == environment.SkyboxKtxPath, "环境场景 JSON 应保留天空盒路径");
|
||||
MetaCoreExpect(std::abs(loadedDocument->Environment.RotationDegrees - environment.RotationDegrees) <= 0.0001F, "环境场景 JSON 应保留旋转");
|
||||
|
||||
const std::filesystem::path packagePath = tempRoot / "Environment.mcscene";
|
||||
MetaCoreExpect(MetaCore::MetaCoreWriteScenePackage(packagePath, document), "环境场景包应可保存");
|
||||
const auto loadedPackage = MetaCore::MetaCoreReadScenePackage(packagePath);
|
||||
MetaCoreExpect(loadedPackage.has_value(), "环境场景包应可读取");
|
||||
MetaCoreExpect(loadedPackage->Environment.Source == MetaCore::MetaCoreSceneEnvironmentSource::ProjectKtxPair, "环境场景包应保留来源");
|
||||
|
||||
const std::filesystem::path legacyJsonPath = tempRoot / "Legacy.mcscene.json";
|
||||
{
|
||||
std::ofstream legacyOutput(legacyJsonPath, std::ios::trunc);
|
||||
legacyOutput << "{\"SceneName\":\"Legacy\",\"GameObjects\":[]}";
|
||||
}
|
||||
const auto legacyDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(legacyJsonPath, registry);
|
||||
MetaCoreExpect(legacyDocument.has_value(), "旧场景缺少环境字段时仍应可读取");
|
||||
MetaCoreExpect(legacyDocument->Environment.Source == MetaCore::MetaCoreSceneEnvironmentSource::Builtin, "旧场景应使用内置环境默认值");
|
||||
|
||||
MetaCore::MetaCoreWindow window;
|
||||
if (!window.Initialize(640, 480, "SceneEnvironmentSmoke")) {
|
||||
std::cout << "[SKIP] Scene environment Filament smoke requires an available display." << std::endl;
|
||||
std::filesystem::remove_all(tempRoot);
|
||||
return;
|
||||
}
|
||||
MetaCore::MetaCoreFilamentSceneBridge bridge;
|
||||
bridge.SetProjectRootPath(tempRoot);
|
||||
MetaCoreExpect(bridge.Initialize(window), "环境测试桥接器应初始化成功");
|
||||
bridge.SyncScene(renderSnapshot, nullptr, false, false);
|
||||
bridge.ApplySceneView(MetaCore::MetaCoreSceneView{});
|
||||
MetaCoreExpect(bridge.VerifyEnvironmentLoadedForTesting(), "自定义 KTX 对应加载为环境资源");
|
||||
MetaCoreExpect(bridge.VerifyEnvironmentBackgroundVisibleForTesting(), "天空盒应在 Skybox 清屏模式下显示");
|
||||
MetaCoreExpect(
|
||||
std::abs(bridge.GetEnvironmentIndirectLightIntensityForTesting() - environment.IndirectLightIntensity) <= 0.0001F,
|
||||
"环境光强度应写入 Filament"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreSceneEnvironmentSettings hiddenEnvironment = environment;
|
||||
hiddenEnvironment.SkyboxVisible = false;
|
||||
(void)scene.SetEnvironmentSettings(hiddenEnvironment);
|
||||
bridge.SyncScene(renderSync.BuildSnapshot(scene), nullptr, false, false);
|
||||
MetaCoreExpect(!bridge.VerifyEnvironmentBackgroundVisibleForTesting(), "关闭天空盒后背景不应显示");
|
||||
MetaCoreExpect(bridge.VerifyEnvironmentLoadedForTesting(), "关闭天空盒不应销毁环境光");
|
||||
|
||||
MetaCore::MetaCoreSceneEnvironmentSettings invalidEnvironment = environment;
|
||||
invalidEnvironment.IblKtxPath = std::filesystem::path("..") / "unsafe_ibl.ktx";
|
||||
(void)scene.SetEnvironmentSettings(invalidEnvironment);
|
||||
bridge.SyncScene(renderSync.BuildSnapshot(scene), nullptr, false, false);
|
||||
MetaCoreExpect(bridge.VerifyEnvironmentLoadedForTesting(), "无效自定义路径应回退到内置环境");
|
||||
|
||||
bridge.Shutdown();
|
||||
window.Shutdown();
|
||||
std::filesystem::remove_all(tempRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
@ -4618,6 +4745,8 @@ int main() {
|
||||
MetaCoreTestNativeScriptBehavioursRestorePrePlaySnapshotOnStop();
|
||||
std::cout << "[RUN] MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot..." << std::endl;
|
||||
MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot();
|
||||
std::cout << "[RUN] MetaCoreTestSceneEnvironmentSettings..." << std::endl;
|
||||
MetaCoreTestSceneEnvironmentSettings();
|
||||
std::cout << "[RUN] MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults..." << std::endl;
|
||||
MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults();
|
||||
std::cout << "[RUN] MetaCoreTestCameraSelectionAndProjectionUtilities..." << std::endl;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user