Environment settings become scene entities (UE Skybox/SkyLight semantics)

Skybox/SkyLight are now Actor-carried components instead of resident
global settings, matching UE where SkyAtmosphere/SkyLight/fog are
Actors added from Place Actors and configured in Details.

- New SkyboxComponent (active + envPath asset slot) and SkyLightComponent
  (active + ambientColor); first-active entity wins per frame
- SceneRenderer collects env from components into FrameSceneData
  (DrawSkybox/envPath/ambient); Renderer performs delta sync - env
  reloads only when the resolved path changes, neutral fallback when
  empty or unloadable
- App-side env plumbing removed: kEnvPaths table and getEnvIndex/
  setEnvIndex callbacks deleted; StatsPanel keeps ClearColor + debug
  view, env params move to Details
- Serializer: scene-level Settings stores ClearColor only; env
  components serialize per entity; legacy v1 scenes (Environment header
  + Settings.AmbientColor/DrawSkybox) auto-migrate into an Environment
  entity on load for visual parity
- Inspector: Skybox/Sky Light sections with .hdr asset slot dropdown
  and Add Component entries; Outliner "+ Create" popup offers
  Empty/Light/Camera/Environment
This commit is contained in:
2026-10-05 01:02:21 +08:00
parent b7d6f53591
commit 432089747f
14 changed files with 317 additions and 144 deletions
+16
View File
@@ -377,6 +377,22 @@ namespace Engine::ECS {
bool operator==(const ScriptComponent&) const = default;
};
// 天空盒/环境背景(UE Skybox Actor 语义:环境是场景里的 Actor +
// 组件,不是常驻全局设置)。envPath = 环境贴图资产(.hdr,经
// AssetManager 加载、Renderer 预积分后进 IBL/Skybox pass);空 =
// 只清屏(ClearColor 露出)。多实例取第一个 active(entt 视图序 =
// 组件创建序,首个命中即稳定)
struct SkyboxComponent {
bool active = true; // 禁用 = 不画天空(清屏色露出)
std::string envPath;
};
// 环境光(UE SkyLight 语义:全局漫射环境照明)。多实例取第一个 active
struct SkyLightComponent {
bool active = true;
glm::vec4 ambientColor{0.07f, 0.07f, 0.09f, 0.0f}; // 旧 Settings 默认
};
} // namespace Engine::ECS
#endif //HELLOVULKAN_COMPONENTS_H
+2 -2
View File
@@ -40,10 +40,10 @@ namespace Engine::ECS {
Scene& operator=(const Scene&) = delete;
// ---- 场景级设置 ----
// 环境类设置已迁为环境实体组件(SkyboxComponent/SkyLightComponent,
// UE Skybox/SkyLight Actor 语义);仅保留渲Target清屏色
struct Settings {
glm::vec4 ClearColor = {0.1, 0.1, 0.1, 1.0};
glm::vec4 AmbientColor{0.07f, 0.07f, 0.09f, 0.0f};
bool DrawSkybox = true;
};
Settings& GetSettings() { return m_Settings; }
const Settings& GetSettings() const { return m_Settings; } // 序列化只读
@@ -211,6 +211,8 @@ namespace Engine {
// 中性兜底环境(IBL 四件套):Application 未加载/加载失败时保底;
// 声明在 m_Passes 之前 → 析构晚于 pass(引用安全)
Scope<Cubemap> m_Environment;
// 当前绑定环境的资产路径(帧数据 envPath 差量同步基准;空 = 兜底)
std::string m_ActiveEnvPath;
Scope<Swapchain> m_Swapchain;
Scope<SyncManager> m_SyncManager;
Scope<CommandManager> m_CommandManager;
@@ -140,6 +140,10 @@ namespace Engine {
glm::vec4 ClearColor{0.1, 0.1, 0.1, 1.0};
bool DrawSkybox = true;
// 本帧生效的环境贴图资产路径(来自第一个 active 的
// SkyboxComponent;空 = 无环境 → Renderer 回中性兜底 cubemap)。
// Renderer 据此与当前已绑定环境做差量同步(路径变化才重载)
std::string envPath;
// 天空盒反投影矩阵:inverse(projNoJitter × mat4(mat3(view)))
//(视图去平移只留旋转 → 反投影结果即世界方向;Skybox pass push)
@@ -8,18 +8,18 @@
// 场景内资产引用双写 {Guid, Path}:GUID 是主键(重命名/移动资产不断
// 引用),Path 是人工可读提示 + 注册表缺失时的解析兜底。
//
// 引擎层身份:本类不感知编辑器(kEnvPaths 表)与渲染器——
// environmentPath 以普通字符串经参数进出,消费方(编辑器/游戏运行时)
// 自行解释。一套 API 服务两个消费者:编辑器 File 菜单 与
// 游戏运行时动态加载,不存在"另一套序列化"。
// 引擎层身份:本类不感知编辑器与渲染器。环境 = 场景实体组件
//(SkyboxComponent/SkyLightComponent,UE Skybox/SkyLight Actor 语义),
// 随实体序列化,场景级仅存清屏色。
//
// 前向兼容(Prefab 路线预留):
// - 场景 = 实体节点列表,Prefab 复用同一套实体读写函数
// - Version 闸:高版本文件拒载并 WARN,低版本宽容
// - 未知组件名跳过(Unity 语义:新版场景在旧版引擎打开可玩)
// - UUID 重复 → 重新生成(即 Prefab 实例化的 ID 重映射)
// - 旧档 v1 环境语义(文档头 Environment + Settings.AmbientColor/
// DrawSkybox)→ 自动补建 Environment 实体,视觉等价迁移
//
#ifndef HELLOVULKAN_SCENESERIALIZER_H
#define HELLOVULKAN_SCENESERIALIZER_H
@@ -37,42 +37,35 @@ namespace Engine::Serialization {
class SceneSerializer {
public:
// ---- 保存 ----
// scene 序列化到 outPath(YAML)。environmentPath 可空(不写
// Environment 字段)。失败(无法写文件)返回 false。
// scene 序列化到 outPath(YAML)。失败(无法写文件)返回 false。
static bool Serialize(const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& outPath,
const std::string& environmentPath = {});
const std::string& outPath);
// ---- 加载 ----
// 原子换血:先解析校验到中间结构,全部成功才 scene.Clear() 重建
//(解析失败不动现场)。成功时 outEnvironmentPath 带回场景记录的
// 环境路径(空 = 场景未带环境,消费方保持当前环境即可)。
//(解析失败不动现场)。
// 健壮性:单实体损坏跳过继续、缺字段取默认值、未知组件跳过、
// UUID 重复重新生成、YAML 异常优雅报错。
// 注意:调用方持有的 Entity/EntityHandle 在成功后全部悬空,
// 必须重解析(Scene::GetEntityHandle / 按 UUID 查询)。
static bool Deserialize(ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& inPath,
std::string& outEnvironmentPath);
const std::string& inPath);
// ---- 内存快照(同一套实体读写,不同载体)----
// 消费者:编辑器 Play-In-Editor 快照恢复、Prefab、运行时动态
// 拼装场景。documentName 仅写入文档头 "Scene" 字段(注释性),
// 快照可传实体名或留空;environmentPath 语义同文件版。
// 拼装场景。documentName 仅写入文档头 "Scene" 字段(注释性)。
static std::string SerializeToString(
const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& documentName = {},
const std::string& environmentPath = {});
const std::string& documentName = {});
// 失败(YAML 异常/版本过高)返回 false 且不动现场。
// 实体悬空语义同文件版 Deserialize。
static bool DeserializeFromString(ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& yamlText,
std::string& outEnvironmentPath);
const std::string& yamlText);
};
} // namespace Engine::Serialization